Skip to content

Instantly share code, notes, and snippets.

@watzon
Created July 14, 2025 19:11
Show Gist options
  • Save watzon/94b7d5eb8c5ba4a7dc9aa3e667594d4c to your computer and use it in GitHub Desktop.
Save watzon/94b7d5eb8c5ba4a7dc9aa3e667594d4c to your computer and use it in GitHub Desktop.
Elysium Living Specification

Elysium Language Specification

Version: 0.1 (Initial Draft) Status: Under active development

1. Introduction

Elysium is a modern programming language that emphasizes readability, safety, and performance. This specification defines the syntax, semantics, and behavior of the Elysium language.

1.1 Design Principles

  • 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

1.2 Lexical Conventions

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

2. Lexical Structure

2.1 Tokens

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)

2.2 Keywords

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

2.3 Identifiers

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

2.4 Literals

2.4.1 Integer Literals

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

2.4.2 Floating Point Literals

3.14        # Default float (f64)
2.5f32      # 32-bit float
1.0e10      # Scientific notation

2.4.3 String Literals

"Hello, world!"              # Basic string
"Line 1\nLine 2"            # With escape sequences
r"Raw string\n"             # Raw string (no escapes)
"Interpolated: {variable}"   # String interpolation

2.4.4 Boolean and Nil Literals

true
false
nil

2.4.5 List and Map Literals

# 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"]

2.4.6 Regex Literals (Planned)

r/[a-z]+/i                  # Case-insensitive regex
r/\d{3}-\d{4}/             # Phone number pattern

2.4.7 Byte String Literals (Planned)

b"Hello"                    # Byte string
b"\x48\x65\x6C\x6C\x6F"   # Hex bytes

2.5 Comments

# Single-line comment

/* 
   Multi-line comment
   can span multiple lines
*/

3. Grammar and Syntax

3.1 Program Structure

An Elysium program consists of one or more facets:

program = facet*

facet = "facet" identifier ["impl" type_list] block

block = ":" newline indent statement* dedent

3.1.1 Entry Point

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)

3.1.2 Top-Level Scripts

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

3.1.3 Custom Entry Points

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:

  1. facet Main with fn main() in the root file
  2. Top-level statements (script mode)
  3. Error if neither is found

3.2 Indentation Rules

Elysium uses indentation to define code blocks, similar to Python:

  1. Colons: Required after statements that introduce a new block
  2. Indentation: Must be consistent within the same block
  3. Dedentation: Returning to a previous indentation level closes blocks
# Correct indentation
fn example()
    if condition
        statement1()
        statement2()
    else
        statement3()

3.3 Statements

3.3.1 Expression Statements

Any expression can be used as a statement:

function_call()
variable = value
array[index] = new_value

3.3.2 Let Declarations

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

3.3.3 Function Declarations

fn function_name(param1: Type1, param2: Type2) -> ReturnType
    # Function body
    return expression

3.3.4 Control Flow Statements

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

3.3.5 Flow Pipelines

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)))

3.4 Expressions

3.4.1 Literals

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

3.4.2 Binary Operations

# 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

3.4.3 Function Calls

function_name(arg1, arg2)
object.method(arg)

3.4.4 Lambda Expressions

# 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())

3.5 Types

3.5.1 Basic Types

  • 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)

3.5.2 Composite Types

# 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?

3.5.3 Function Types

fn(Int, String) -> Bool     # Function taking Int and String, returning Bool
fn() -> ()                  # Function with no parameters or return value

3.5.4 Adaptive Dynamic Types

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")

3.5.5 Algebraic Data Types

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

3.6 Facets and Weaving

3.6.1 Facet Definition

Facets are Elysium's module system:

facet Logger
    fn log(message: String)
        print("[LOG] {message}")
    
    fn error(message: String)  
        print("[ERROR] {message}")

3.6.2 Abstract Facets

facet Database
    fn connect(url: String) -> Connection    # Abstract method
    fn query(sql: String) -> Result<Rows, Error>  # Abstract method

3.6.3 Facet Implementation

facet PostgresDB impl Database
    fn connect(url: String) -> Connection
        # Implementation
    
    fn query(sql: String) -> Result<Rows, Error>
        # Implementation

3.6.4 Weaving (Composition)

facet WebApp
    weave with Logger, PostgresDB
    
    fn start()
        let conn = connect("postgres://localhost/mydb")
        log("Application started")

3.6.5 Dynamic Weaving (Runtime Plugins)

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")

3.7 Error Handling

3.7.1 Result Types

# 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)

3.7.2 Try Operator

fn calculate() -> Result<Int, String>
    let a = try some_fallible_function()?
    let b = try another_fallible_function()?
    return Ok(a + b)

3.8 Module System

3.8.1 Automatic Module Resolution

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")

3.8.2 External Dependencies

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)

3.8.3 Visibility and Exports

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

3.8.4 Package Manifest

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 point

3.9 Visibility Modifiers

Elysium 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.

3.10 Memory Management

3.10.1 Ownership

By default, Elysium uses ownership-based memory management:

fn process_data(data: List<Int>)    # Takes ownership
    # data is automatically freed at end of function

3.10.2 References

fn read_data(data: &List<Int>)      # Borrows data (immutable)
fn modify_data(data: &mut List<Int>) # Borrows data (mutable)

3.10.3 Reference Counting

let shared_data = Rc::new(expensive_data)
let reference1 = shared_data.clone()
let reference2 = shared_data.clone()

3.10.4 Thread-Safe Reference Counting

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()
)

3.10.5 Lifetime Annotations (Future)

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

3.10.6 Garbage Collection

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"

3.10.7 RAII and Drop Semantics

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

4. Standard Library

4.1 Core Types

  • Int, Float, Bool, String, Char
  • List<T>, Map<K, V>, Set<T>
  • Option<T>, Result<T, E>

4.2 I/O and System

# 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>

4.3 Collections

# 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>

5. Advanced Features

5.1 Generics

5.1.1 Basic Generics

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

5.1.2 Traits and Generic Constraints

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

5.1.3 Associated Types

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)

5.2 Async/Await

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)

5.2.1 Structured Concurrency

# 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

5.3 Pattern Matching

match value
    case Some(x) if x > 0
        print("Positive: {x}")
    case Some(x)
        print("Non-positive: {x}")
    case None
        print("No value")

5.4 Foreign Function Interface (FFI)

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.

5.4.1 Syntax

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.ely to generate an extern facet from headers (maps C types to Elysium, e.g., int -> Int, char* -> &String for 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 @Dynamic annotation for runtime loading (dlopen-style).

5.4.2 C++ Bindings (Planned)

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

5.4.3 Safety Features

  • 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.

5.5 Annotations

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.

5.5.1 Syntax

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.

5.5.2 Built-In Annotations

  • @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).

5.5.3 User-Defined Annotations (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).

6. Language Semantics

6.1 Evaluation Order

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()

6.2 Type Inference Rules

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

6.3 Ownership Transfer Semantics

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

6.4 Name Resolution

Names are resolved in the following order:

  1. Local variables in current scope
  2. Parameters of enclosing function
  3. Members of current facet
  4. Woven facet members (see 6.6 for conflict resolution)
  5. Package-level facets
  6. External dependencies via use

6.5 Dynamic Type Performance

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.

6.6 Weaving Resolution

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:

  1. Last woven facet wins by default (DebugLogger.log in above example)
  2. Explicit qualification always works (Logger.log)
  3. Local definitions shadow woven members
  4. Compiler warns on conflicts unless explicitly resolved

6.7 Concurrency Model

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.

6.7.1 Actor Model (Planned)

# 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

6.7.2 Select Statement (Planned)

# Select from multiple channels
select
    case msg = <-channel1
        handle_channel1(msg)
    case msg = <-channel2
        handle_channel2(msg)
    case <-timeout(5.seconds)
        handle_timeout()

7. Parser Implementation Notes

7.1 Indentation Handling

The parser maintains an indentation stack to track block nesting:

  1. INDENT tokens are generated when indentation increases
  2. DEDENT tokens are generated when indentation decreases
  3. Multiple DEDENT tokens may be generated for deeply nested structures
  4. Annotations are tokenized as '@' followed by identifier and optional parentheses

7.2 Error Recovery

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

7.3 Grammar Ambiguity Resolution

Operator precedence from highest to lowest:

  1. Function calls, array access: f(), a[i]
  2. Unary operators: not, -, +
  3. Exponentiation: **
  4. Multiplication, division, modulo: *, /, %
  5. Addition, subtraction: +, -
  6. Comparisons: ==, !=, <, <=, >, >=
  7. Logical AND: and
  8. Logical OR: or
  9. Assignment: =, +=, etc.

8. Examples

8.1 Complete Program

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}")

8.2 Web Server Example

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...

8.3 Simple Script Example

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.

9. Implementation Status

9.1 Fully Implemented Features

  • 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

9.2 Partially Implemented Features

  • Generic types: Parser code exists but not fully tested
  • Pattern matching: Basic patterns work, advanced patterns need work
  • Error handling: ? operator works, try expressions not implemented

9.3 Not Yet 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 &mut not implemented
  • Reference counting: Rc types not supported
  • Garbage collection: enable gc directive not implemented
  • Dynamic weaving: weave_dynamic for 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: flow keyword 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 await syntax (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 facet and auto-binding tool (planned feature, see 5.4)
  • Annotations: Core syntax and built-in (@Link, etc.); extensibility planned (see 5.5)

9.4 Features Needing Specification

  • 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.

10. Future Directions

10.1 Planned Features

  • 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

10.2 Ecosystem Goals

  • 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

10.3 Open Design Questions

  • 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment