Version: 0.1 (Initial Draft) Status: Under active development
Elysium is a modern programming language that emphasizes readability, safety, and performance. This specification defines the syntax, semantics, and behavior of the Elysium language.
- Indentation-based syntax for clean, readable code structure
 - Static typing with type inference and optional dynamic typing
 - Memory safety through ownership and optional garbage collection
 - Modular composition via the novel facets system
 - Unified execution model supporting both compilation and interpretation
 
Elysium source code is UTF-8 encoded text. The language is case-sensitive and uses indentation to define code blocks.
Key Conventions:
- No semicolons: Newlines end statements
 - Indentation-based: Blocks are defined by indentation levels (braces are not used for block delimitation)
 - Braces for Expressions: Braces 
{}are used sparingly for inline literals (e.g., maps like{"key": "value"}) and expressions, similar to Python - Whitespace-aware: Consistent indentation within blocks is mandatory
 
The lexical elements of Elysium include:
- Keywords: Reserved words with special meaning
 - Identifiers: Names for variables, functions, types, etc.
 - Literals: Constant values (numbers, strings, booleans)
 - Operators: Symbols that perform operations
 - Annotations: Metadata prefixes like 
@Name(key = value) - Punctuation: Delimiters and separators
 - Whitespace: Spaces, tabs, and newlines (significant for indentation)
 
and     annotation  as      async   await   break   case    continue  
dyn     else        enum    extern  facet   false   flow    fn      
for     if          impl    in      let     match   mut     nil     
not     or          pub     private return  self    struct  true    
try     type        use     weave   while
Identifiers start with a letter or underscore, followed by any combination of letters, digits, or underscores.
identifier = (letter | '_') (letter | digit | '_')*
letter = 'a'...'z' | 'A'...'Z'
digit = '0'...'9'
Examples:
myVariable
_private_function
ClassName
MAX_SIZE
factorial2
Elysium supports arbitrary precision integers with optional type suffixes:
42          # Default integer (i32)
42i8        # 8-bit signed integer
255u8       # 8-bit unsigned integer  
1000i256    # 256-bit signed integer
0xFF_u64    # Hexadecimal with underscores for readability
0b1010_i16  # Binary literal
0o755_u32   # Octal literal
3.14        # Default float (f64)
2.5f32      # 32-bit float
1.0e10      # Scientific notation
"Hello, world!"              # Basic string
"Line 1\nLine 2"            # With escape sequences
r"Raw string\n"             # Raw string (no escapes)
"Interpolated: {variable}"   # String interpolation
true
false
nil
# List literals
[1, 2, 3]                    # Basic list
[]                          # Empty list
# Map literals - traditional style
{"key": "value", "num": 42}  # Basic map
{}                          # Empty map
# Map literals - indentation style (syntax under design)
map
    "name": "Elysium"
    "version": 1.0
    "features": ["safety", "performance"]
r/[a-z]+/i                  # Case-insensitive regex
r/\d{3}-\d{4}/             # Phone number pattern
b"Hello"                    # Byte string
b"\x48\x65\x6C\x6C\x6F"   # Hex bytes
# Single-line comment
/* 
   Multi-line comment
   can span multiple lines
*/
An Elysium program consists of one or more facets:
program = facet*
facet = "facet" identifier ["impl" type_list] block
block = ":" newline indent statement* dedent
Elysium programs begin execution at the main function within a Main facet, providing flexibility for both scripts and applications:
Standard Entry Point:
facet Main
    fn main()
        print("Hello, Elysium!")
With Error Handling:
facet Main
    fn main() -> Result<(), Error>
        let config = load_config()?
        run_application(config)
Async Entry Point:
facet Main
    async fn main()
        let data = await fetch_data()
        process(data)
For simple scripts, top-level statements are automatically wrapped in an implicit main:
# script.ely - no boilerplate needed
print("Hello, Elysium!")
let x = 42
print("The answer is {x}")
# Implicitly wrapped as:
# facet _ScriptMain
#     fn main()
#         <top-level statements>
Limitations:
- Top-level code cannot define facets (but can define functions)
 - Only supported in single-file scripts
 - Multi-file projects require explicit 
facet Main 
For larger projects, the entry point can be configured in elysium.toml:
[project]
name = "my-app"
entry = "src/app.ely:AppFacet:start"  # file:facet:function
# For testing
[test]
entry = "tests/main.ely:TestRunner:run_all"If no configuration is provided, the compiler searches for:
facet Mainwithfn main()in the root file- Top-level statements (script mode)
 - Error if neither is found
 
Elysium uses indentation to define code blocks, similar to Python:
- Colons: Required after statements that introduce a new block
 - Indentation: Must be consistent within the same block
 - Dedentation: Returning to a previous indentation level closes blocks
 
# Correct indentation
fn example()
    if condition
        statement1()
        statement2()
    else
        statement3()
Any expression can be used as a statement:
function_call()
variable = value
array[index] = new_value
Variable declarations use the let keyword:
let variable_name: Type = initial_value
let inferred_variable = value  # Type inferred
let mut mutable_var = 0        # Mutable variable
fn function_name(param1: Type1, param2: Type2) -> ReturnType
    # Function body
    return expression
If Statements:
if condition
    # statements
elif other_condition
    # statements  
else
    # statements
Loops:
# While loop
while condition
    # statements
# For loop
for item in iterable
    # statements
# For loop with range
for i in 0..10
    # statements
Match Expressions:
match expression
    case pattern1
        # statements
    case pattern2 if guard
        # statements
    else
        # statements
The flow keyword introduces functional-style data pipelines with automatic parallelization:
# Basic flow pipeline
flow [1, 2, 3, 4, 5]
    | filter(x => x % 2 == 0)
    | map(x => x * x)
    | reduce((a, b) => a + b)
# Flow with error handling
flow read_file("data.csv")
    | parse_csv()
    | filter(row => row.valid)
    | transform(process_row)
    | write_file("output.json")
# Parallel processing flow
flow large_dataset
    | parallel_map(expensive_computation)
    | collect()
The pipeline operator |> can also be used outside of flow for simple function chaining:
# Standard pipeline
result = data |> process() |> validate() |> format()
# Equivalent to:
result = format(validate(process(data)))
42          # Integer
3.14        # Float
"hello"     # String
true        # Boolean
[1, 2, 3]   # Array
# Map literals (syntax under design)
# Traditional: {"key": "value"}
# Proposed indentation-based:
# map
#     "key": "value"
#     "another": 42
# Arithmetic
a + b
a - b
a * b
a / b
a % b
a ** b      # Exponentiation
# Comparison
a == b
a != b
a < b
a <= b
a > b
a >= b
# Logical
a and b
a or b
not a
# Bitwise
a & b       # Bitwise AND
a | b       # Bitwise OR
a ^ b       # Bitwise XOR
a << b      # Left shift
a >> b      # Right shift
# Special Operators
a |> b      # Pipeline operator
a ?? b      # Null coalesce operator
a?.b        # Optional chaining
# Assignment
a = b
a += b
a -= b
a *= b
a /= b
a %= b
a &= b
a |= b
a ^= b
a <<= b
a >>= b
function_name(arg1, arg2)
object.method(arg)
# Simple lambda
(x) => x * 2
# Multi-parameter lambda
(x, y) => x + y
# Multi-line lambda
(items) =>
    items
        .filter(item => item.is_valid())
        .map(item => item.process())
- Integers: 
i8,i16,i32,i64,i128,u8,u16,u32,u64,u128 - Arbitrary precision: 
i256,u2048,i8388607, etc. - Floating point: 
f32,f64 - Boolean: 
Bool - String: 
String - Unit: 
()(empty tuple) 
# Arrays
[Int; 5]        # Fixed-size array
List<Int>       # Dynamic array
# Maps
Map<String, Int>
# Tuples
(Int, String, Bool)
# Optional types
Int?            # Can be Int or nil
String?
fn(Int, String) -> Bool     # Function taking Int and String, returning Bool
fn() -> ()                  # Function with no parameters or return value
The dyn keyword enables runtime typing with automatic type checking:
# Dynamic variable
let data: dyn = parse_json(json_string)
# Runtime type checking on access
let name = data.name        # Runtime field access
let items = data["items"]   # Runtime indexing
# Type guards for safety
if data is String
    print("String value: {data}")
elif data is Map<String, dyn>
    for key, value in data
        print("{key}: {value}")
# Mixing static and dynamic
fn process_data(static_id: Int, dynamic_payload: dyn) -> Result<String, Error>
    # static_id has compile-time type checking
    # dynamic_payload has runtime type checking
    match dynamic_payload
        case is String => Ok("String: {dynamic_payload}")
        case is Int => Ok("Number: {dynamic_payload}")
        else => Err("Unsupported type")
Elysium supports sum types through enums and product types through structs:
# Sum type (enum)
enum Result<T, E>
    Ok(T)
    Err(E)
enum JsonValue
    Null
    Bool(Bool)
    Number(Float)
    String(String)
    Array(List<JsonValue>)
    Object(Map<String, JsonValue>)
# Product type (struct)
struct Point
    x: Float
    y: Float
    z: Float?  # Optional field
# Pattern matching on algebraic types
match json_value
    case JsonValue::Number(n) => n * 2
    case JsonValue::String(s) => s.len()
    case JsonValue::Array(items) => items.len()
    else => 0
Facets are Elysium's module system:
facet Logger
    fn log(message: String)
        print("[LOG] {message}")
    
    fn error(message: String)  
        print("[ERROR] {message}")
facet Database
    fn connect(url: String) -> Connection    # Abstract method
    fn query(sql: String) -> Result<Rows, Error>  # Abstract method
facet PostgresDB impl Database
    fn connect(url: String) -> Connection
        # Implementation
    
    fn query(sql: String) -> Result<Rows, Error>
        # Implementation
facet WebApp
    weave with Logger, PostgresDB
    
    fn start()
        let conn = connect("postgres://localhost/mydb")
        log("Application started")
For plugin systems and dynamic module loading:
# Load facets at runtime
let plugin_facet = weave_dynamic("plugins/auth_provider.ely")
# Dynamic weaving with type checking
facet PluginHost
    let mut plugins: List<dyn Facet> = []
    
    fn load_plugin(path: String) -> Result<(), Error>
        let facet = weave_dynamic(path)?
        
        # Verify the facet implements required interface
        if facet implements AuthProvider
            plugins.push(facet)
            Ok(())
        else
            Err("Plugin must implement AuthProvider")
    
    fn authenticate(credentials: Credentials) -> Result<User, Error>
        for plugin in plugins
            if let Ok(user) = plugin.authenticate(credentials)
                return Ok(user)
        Err("Authentication failed")
# Function that can fail
fn divide(a: Int, b: Int) -> Result<Int, String>
    if b == 0
        return Err("Division by zero")
    return Ok(a / b)
fn calculate() -> Result<Int, String>
    let a = try some_fallible_function()?
    let b = try another_fallible_function()?
    return Ok(a + b)
Elysium uses automatic module resolution based on file paths and manifest configuration. No explicit imports are needed for internal modules:
# In src/utils/logger.ely
facet Logger
    fn log(message: String)
        print("[LOG] {message}")
# In src/main.ely - Logger is automatically available
facet Main
    weave with Logger  # No import needed
    
    fn main()
        log("Application started")
The use keyword is for external packages and aliasing:
# External package with version
use "pkg:std/[email protected]" as web
use "pkg:aws/[email protected]" as s3
# Convenience aliasing for long paths
use MyVeryLongModuleName as Short
# Using external packages
facet WebServer
    weave with web.HttpServer
    
    fn start()
        web.listen(8080)
All facets and public members are automatically available within the same package. Use visibility modifiers to control access:
facet PublicAPI
    pub fn external_function()    # Available to other packages
        # implementation
    
    private fn internal_helper()  # Only available within this facet
        # implementation
    fn package_function()         # Available within this package (default)
        # implementation
Project configuration and dependencies are declared in elysium.toml:
[package]
name = "my-app"
version = "1.0.0"
entry = "src/main.ely"  # Optional: override default entry point
[dependencies]
std = "1.2"
aws = { version = "2.0", features = ["s3", "dynamodb"] }
[dev-dependencies]
test = "0.5"
[test]
entry = "tests/runner.ely:TestSuite:run_all"  # Custom test entry pointElysium uses a simplified visibility model focused on facets and packages:
pub fn public_function()         # Public to all packages
private fn internal_function()   # Private to current facet
fn default_visibility()          # Package-private (default)
Note: The keywords protected and crate are reserved for potential future use but are not part of the current language design, as Elysium uses composition (facets) rather than inheritance.
By default, Elysium uses ownership-based memory management:
fn process_data(data: List<Int>)    # Takes ownership
    # data is automatically freed at end of function
fn read_data(data: &List<Int>)      # Borrows data (immutable)
fn modify_data(data: &mut List<Int>) # Borrows data (mutable)
let shared_data = Rc::new(expensive_data)
let reference1 = shared_data.clone()
let reference2 = shared_data.clone()
For concurrent access across threads:
# Arc for thread-safe shared ownership
let shared_state = Arc::new(GameState::new())
let state1 = shared_state.clone()
let state2 = shared_state.clone()
# Spawn threads with shared state
spawn_thread(() =>
    state1.update()
)
For advanced memory safety scenarios:
# Planned syntax for explicit lifetimes
fn longest<'a>(x: &'a String, y: &'a String) -> &'a String
    if x.len() > y.len() then x else y
# Lifetime elision rules will handle most cases automatically
fn first_word(s: &String) -> &String  # Lifetimes inferred
Optional per-facet or globally:
# Per-facet GC
facet CyclicStructure
    enable gc
    
    struct Node
        value: Int
        next: Option<Node>  # Can create cycles safely
# Global GC (in elysium.toml)
# [runtime]
# gc = "enabled"
Resources are automatically cleaned up when they go out of scope:
fn process_file(path: String) -> Result<String, Error>
    let file = open_file(path)?  # File opened
    let contents = file.read_all()?
    # File automatically closed here via RAII
    return Ok(contents)
# Custom drop behavior
struct TempFile impl Drop
    path: String
    
    fn drop(&mut self)
        delete_file(self.path)  # Cleanup on scope exit
Int,Float,Bool,String,CharList<T>,Map<K, V>,Set<T>Option<T>,Result<T, E>
# Console I/O
print(message: String)
println(message: String)
read_line() -> String
# File I/O
read_file(path: String) -> Result<String, Error>
write_file(path: String, content: String) -> Result<(), Error>
# List operations
list.push(item)
list.pop() -> Option<T>
list.filter(predicate) -> List<T>
list.map(function) -> List<U>
# Map operations
map.get(key) -> Option<V>
map.insert(key, value)
map.remove(key) -> Option<V>
fn identity<T>(x: T) -> T
    return x
facet Container<T>
    items: List<T>
    
    fn add(item: T)
        items.push(item)
    
    fn get(index: Int) -> Option<T>
        if index < items.len()
            Some(items[index])
        else
            None
Traits define shared behavior that types can implement:
# Trait definition
trait Comparable
    fn compare(&self, other: &Self) -> Ordering
# Implementing a trait for a type
facet String impl Comparable
    fn compare(&self, other: &Self) -> Ordering
        # String comparison implementation
# Traits can have default implementations
trait Display
    fn format(&self) -> String
    
    fn print(&self)
        println(self.format())  # Default implementation
# Generic with trait bound
fn find_max<T: Comparable>(items: List<T>) -> Option<T>
    if items.is_empty()
        return None
    
    let mut max = &items[0]
    for item in items[1..]
        if item.compare(max) == Ordering::Greater
            max = item
    return Some(*max)
# Multiple constraints
fn process<T: Serializable + Clone>(data: T) -> String
    let backup = data.clone()
    return data.serialize()
# Where clauses for complex constraints
fn complex_function<T, U>(x: T, y: U) -> Result<String, Error>
    where T: Display + Debug,
          U: Iterator<Item = T>
    # implementation
trait Collection
    type Item  # Associated type
    
    fn add(&mut self, item: Self::Item)
    fn get(&self, index: Int) -> Option<Self::Item>
facet StringList impl Collection
    type Item = String  # Concrete associated type
    
    items: List<String>
    
    fn add(&mut self, item: String)
        items.push(item)
    
    fn get(&self, index: Int) -> Option<String>
        items.get(index)
Elysium provides first-class support for asynchronous programming:
# Async function declaration
async fn fetch_data(url: String) -> Result<String, Error>
    let response = await http_get(url)?
    return Ok(response.body)
# Async main function
async fn main()
    let data = await fetch_data("https://api.example.com/data")
    print(data)
# Concurrent async operations
async fn fetch_multiple(urls: List<String>) -> List<Result<String, Error>>
    # Launch all requests concurrently
    let futures = urls.map(url => fetch_data(url))
    
    # Await all results
    return await_all(futures)
# Async streams
async fn process_stream(source: AsyncIterator<Data>) 
    for await item in source
        process_item(item)
# Task groups ensure all tasks complete before continuing
async fn parallel_processing()
    async with task_group() as group
        group.spawn(async_task1())
        group.spawn(async_task2())
        group.spawn(async_task3())
    # All tasks completed here
match value
    case Some(x) if x > 0
        print("Positive: {x}")
    case Some(x)
        print("Non-positive: {x}")
    case None
        print("No value")
Elysium provides seamless integration with C/C++ code through extern facets, enabling easy bindings to existing libraries with minimal boilerplate. The compiler can auto-generate bindings from header files via the elysium bind tool.
Use extern facet to declare bindings, with annotations for metadata:
@Link("c")  # Built-in annotation for linking (see 5.5)
extern facet LibC
    fn printf(format: &String, ...) -> Int  # Function binding (varargs supported)
    
    struct TimeVal  # Struct binding
        tv_sec: Int
        tv_usec: Int
    
    const EINVAL: Int = 22  # Constant
# Usage (weave like any facet)
facet Main
    weave with LibC
    
    fn main() -> Result<(), Error>
        try printf("Hello: %d\n", 42)?
- Auto-Generation: Run 
elysium bind mylib.h --output mylib.elyto generate an extern facet from headers (maps C types to Elysium, e.g.,int->Int,char*->&Stringfor safety). - Calling Semantics: Invocations are zero-overhead but wrapped in safe defaults (e.g., null checks, error codes to 
Result). Ownership applies (pointers as borrows). - Dynamic Loading: Use 
@Dynamicannotation for runtime loading (dlopen-style). 
For C++ classes/templates, use @CXX annotation:
@Link("mycpp") @CXX
extern facet MyCpp
    struct MyClass
        fn new() -> MyClass
        fn method(&self, arg: Int) -> Int
        fn drop(&mut self)  # Integrates with RAII
- Automatic wrappers for errors (e.g., errno to 
Result), null checks, and string conversions. - Opt-in unsafe for raw calls: 
unsafe { raw_c_call(ptr) }. - Type mappings ensure memory safety (e.g., C arrays to 
&[T]borrows). 
FFI integrates with facets for composable wrappers and our ownership model.
Annotations attach compile-time metadata to declarations (facets, functions, types, etc.), enabling features like FFI linking and metaprogramming. They are optional and have no runtime overhead by default.
Annotations use @Name(key = value, ...) or @Name (no params), applied before the target:
@Deprecated("Use new_fn instead")
fn old_fn() -> Int
    # Compiler warns on usage
- Can be multi-line with indentation for complex args.
 - Applied to: facets, fn, struct, enum, let, etc.
 
@Link(lib: String, pkgconfig: String?, framework: String?): For FFI linking (see 5.4).@Deprecated(message: String?): Warns on deprecated usage.@Inline: Optimization hint (e.g., force inlining).@Dynamic: For runtime loading (e.g., in FFI or weaving).@CXX: Enables C++-specific handling in FFI (planned).
Define custom annotations in facets for extensibility, tied to metaprogramming:
facet JsonLib
    annotation Serializable(ignore: List<String> = [])  # Defines with params
        # Compile-time macro logic (generates code, e.g., to_json method)
# Usage
@Serializable(ignore = ["password"])
struct User
    name: String
    password: String
- Processed at compile-time (e.g., via hygienic macros).
 - Weavable like facets for library sharing.
 - Enables custom tools (e.g., @Benchmark, @Route for web, or AI hooks like @GenerateDoc).
 
Elysium follows strict left-to-right evaluation order for all expressions:
# Function arguments evaluated left-to-right
result = f(a(), b(), c())  # a() called first, then b(), then c()
# Binary operators evaluate left operand first
x = expensive() + cheap()  # expensive() evaluated before cheap()
Elysium uses Hindley-Milner type inference with bidirectional type checking:
# Type flows from initialization
let x = 42              # x: Int inferred
let y = [1, 2, 3]      # y: List<Int> inferred
# Type flows from usage context
let numbers = []       # Type unknown
numbers.push(1)        # Now inferred as List<Int>
# Function return types can be inferred
fn double(x: Int)      # Return type Int inferred
    return x * 2
Values are moved by default, with explicit cloning required for duplication:
let original = [1, 2, 3]
let moved = original         # Ownership transferred
# original no longer accessible
let data = [1, 2, 3]
let cloned = data.clone()   # Explicit copy
# Both data and cloned are accessible
Names are resolved in the following order:
- Local variables in current scope
 - Parameters of enclosing function
 - Members of current facet
 - Woven facet members (see 6.6 for conflict resolution)
 - Package-level facets
 - External dependencies via 
use 
The dyn type incurs runtime overhead for type checking:
# Static typing - zero overhead
let x: Int = 42
let y = x + 1  # Compiled to direct addition
# Dynamic typing - runtime checks
let d: dyn = get_dynamic_value()
let result = d + 1  # Runtime type check, then dispatch
Performance characteristics:
- Field access: O(1) hash lookup for properties
 - Type checks: O(1) for primitive types, O(n) for union types
 - Method dispatch: Virtual table lookup similar to interface calls
 - Memory: Additional 8-16 bytes per value for type information
 
Use dyn judiciously for flexibility where static typing is impractical.
When multiple woven facets provide the same name, conflicts are resolved as follows:
facet Logger
    fn log(msg: String)
        print("[LOG] {msg}")
facet DebugLogger
    fn log(msg: String)
        print("[DEBUG] {msg}")
facet App
    weave with Logger, DebugLogger  # Conflict!
    
    # Resolution strategies:
    
    # 1. Explicit disambiguation
    fn test1()
        Logger.log("Using Logger")
        DebugLogger.log("Using DebugLogger")
    
    # 2. Alias on weave (planned)
    # weave with Logger, DebugLogger as Debug
    
    # 3. Override in current facet (shadows both)
    fn log(msg: String)
        print("[APP] {msg}")
Conflict rules:
- Last woven facet wins by default (DebugLogger.log in above example)
 - Explicit qualification always works (Logger.log)
 - Local definitions shadow woven members
 - Compiler warns on conflicts unless explicitly resolved
 
Elysium provides structured concurrency with ownership-based safety:
# Spawn returns a handle for joining
let handle = spawn(() =>
    expensive_computation()
)
# Await the result
let result = handle.await()
# Channels for communication
let (sender, receiver) = channel<String>()
spawn(() =>
    sender.send("Hello from thread")
)
let message = receiver.receive()
Memory Safety in Concurrent Code:
Elysium prevents data races at compile time through marker traits:
# Send trait - safe to transfer ownership between threads
trait Send
    # Automatically implemented for most types
# Sync trait - safe to share references between threads  
trait Sync
    # Automatically implemented for immutable types
# Example: Compile-time safety
struct NotThreadSafe
    data: *mut Int  # Raw pointer - not Send or Sync
fn spawn_worker<T: Send>(data: T)
    spawn(() =>
        process(data)  # OK - T is Send
    )
# This won't compile:
# let unsafe_data = NotThreadSafe { ... }
# spawn_worker(unsafe_data)  # Error: NotThreadSafe is not Send
# Thread-safe sharing via Arc
let shared = Arc::new(GameState::new())  # Arc<T> is Send+Sync if T is Send+Sync
This zero-cost abstraction ensures memory safety without runtime overhead.
# Define an actor
actor Counter
    mut count: Int = 0
    
    fn receive(msg: Message)
        match msg
            case Increment => count += 1
            case Decrement => count -= 1
            case GetCount(reply_to) => reply_to.send(count)
# Use the actor
let counter = spawn_actor(Counter)
counter.send(Increment)
counter.send(Increment)
let count = counter.ask(GetCount)  # Returns 2
# Select from multiple channels
select
    case msg = <-channel1
        handle_channel1(msg)
    case msg = <-channel2
        handle_channel2(msg)
    case <-timeout(5.seconds)
        handle_timeout()
The parser maintains an indentation stack to track block nesting:
- INDENT tokens are generated when indentation increases
 - DEDENT tokens are generated when indentation decreases
 - Multiple DEDENT tokens may be generated for deeply nested structures
 - Annotations are tokenized as '@' followed by identifier and optional parentheses
 
The parser provides helpful error messages for common indentation mistakes:
- Missing colons after block-introducing statements
 - Inconsistent indentation levels
 - Mixing tabs and spaces
 - Unexpected indentation changes
 
Operator precedence from highest to lowest:
- Function calls, array access: 
f(),a[i] - Unary operators: 
not,-,+ - Exponentiation: 
** - Multiplication, division, modulo: 
*,/,% - Addition, subtraction: 
+,- - Comparisons: 
==,!=,<,<=,>,>= - Logical AND: 
and - Logical OR: 
or - Assignment: 
=,+=, etc. 
facet MathUtils
    fn fibonacci(n: Int) -> Int
        if n <= 1
            return n
        return fibonacci(n - 1) + fibonacci(n - 2)
    
    fn factorial(n: Int) -> Int
        if n <= 1
            return 1
        return n * factorial(n - 1)
facet Main
    weave with MathUtils
    
    fn main()
        let n = 10
        let fib = fibonacci(n)
        let fact = factorial(n)
        
        print("Fibonacci({n}) = {fib}")
        print("Factorial({n}) = {fact}")
facet Logger
    fn log(message: String)
        print("[{now()}] {message}")
facet WebServer  
    weave with Logger
    
    fn handle_request(path: String) -> String
        log("Handling request for {path}")
        
        match path
            case "/"
                return "Welcome to Elysium!"
            case "/about"
                return "Elysium programming language"
            else
                return "Page not found"
    
    fn start(port: Int)
        log("Starting server on port {port}")
        # Server implementation...
For quick scripts and experiments, Elysium supports top-level execution without boilerplate:
# calculator.ely - a simple calculator script
# No facet or main needed for scripts
print("Simple Calculator")
print("================")
let a = 10
let b = 5
print("{a} + {b} = {a + b}")
print("{a} - {b} = {a - b}")
print("{a} * {b} = {a * b}")
print("{a} / {b} = {a / b}")
# Can still use functions at top level
fn is_prime(n: Int) -> Bool
    if n < 2
        return false
    for i in 2..n
        if n % i == 0
            return false
    return true
# Use the function
for num in [2, 3, 4, 5, 6, 7, 8, 9, 10]
    if is_prime(num)
        print("{num} is prime")
This script can be run directly with elysium calculator.ely without any additional setup.
- Basic lexing and tokenization
 - Indentation-based block structure
 - Function declarations and calls
 - Control flow (if/else, while, for, match)
 - Basic types and literals (integers, floats, strings, booleans)
 - Mutable variables (
let mut) - Binary operators (arithmetic, comparison, logical, bitwise)
 - Pipeline operator (
|>) and null coalesce (??) - Optional chaining (
?.) - Facet declarations and weaving
 
- Generic types: Parser code exists but not fully tested
 - Pattern matching: Basic patterns work, advanced patterns need work
 - Error handling: 
?operator works,tryexpressions not implemented 
- Map literals: Need indentation-friendly syntax design (syntax under design)
 - Selective imports/exports: Stub implementations exist
 - Hex/binary/octal literals: Only decimal numbers supported
 - Raw strings: Not supported in lexer
 - Multi-line comments: Only single-line 
#comments work - Reference types: 
&and&mutnot implemented - Reference counting: 
Rctypes not supported - Garbage collection: 
enable gcdirective not implemented - Dynamic weaving: 
weave_dynamicfor runtime plugins (planned feature, see 3.6.5) - Regex literals: Syntax defined but not implemented
 - Byte string literals: Syntax defined but not implemented
 - Flow pipelines: 
flowkeyword and parallel processing (planned feature, see 3.3.5) - Traits: Core trait system for generic constraints (planned feature, see 5.1.2)
 - Async streams: 
for awaitsyntax (planned feature, see 5.2) - Actor model: Actor-based concurrency (planned feature, see 6.7.1)
 - Select statement: Multi-channel selection (planned feature, see 6.7.2)
 - Foreign Function Interface (FFI): 
extern facetand auto-binding tool (planned feature, see 5.4) - Annotations: Core syntax and built-in (@Link, etc.); extensibility planned (see 5.5)
 
- Map literal syntax for indentation-based approach
 - Selective import/export syntax without braces
 - Spread operator syntax and semantics
 - Postfix increment/decrement operators
 
This specification defines the core syntax and semantics of Elysium, focusing on the indentation-based approach that makes the language clean and readable while maintaining the flexibility needed for modern software development. The implementation is actively evolving to match this specification.
- AI Integration Hooks: Native support for LLM-based code generation and analysis
 - Unified Backend: Single VM supporting both interpretation and compilation
 - Effect System: Track side effects at the type level for better optimization
 - Dependent Types: Support for theorem proving and verification
 - Metaprogramming: Compile-time code generation via hygienic macros, integrated with extensible annotations
 
- Self-Hosting Compiler: Rewrite compiler in Elysium itself
 - Package Manager (ElyPkg): Decentralized package registry with semantic versioning
 - IDE Support: Language server protocol implementation
 - Documentation Generator: Extract docs from code with rich type information
 - Interactive REPL: With hot-reloading and time-travel debugging
 
- Optimal syntax for map literals in indentation style
 - Macro system design that preserves readability
 - Integration with existing ecosystems (npm, cargo, etc.)
 - Mobile and embedded platform support strategy
 - Balancing static vs. dynamic modes - when to guide users toward each
 - Facet versioning and compatibility - how to evolve facets over time
 - Performance guarantees for 
dyn- can we optimize common patterns? - Module boundaries vs. facet composition - finding the right granularity
 - Parser evolution for optional braces - future tool compatibility considerations
 - Annotations extensibility - balancing power with readability in user-defined cases
 
This is version 0.1 of the Elysium Language Specification. As the language evolves, this document will be updated to reflect new features and refinements.