diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..8a40280 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,49 @@ +name: Deploy documentation + +on: + push: + branches: + - master + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Install documentation dependencies + run: python -m pip install --upgrade pip && python -m pip install -r requirements-docs.txt + + - name: Build documentation + run: python -m mkdocs build --strict + + - name: Configure GitHub Pages + uses: actions/configure-pages@v5 + + - name: Upload site artifact + uses: actions/upload-pages-artifact@v3 + with: + path: site + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 7ed331c..1cac9f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,89 +1,36 @@ -# ═══════════════════════════════════════════════════════════════════════════════ -# AETHER Project Gitignore -# ═══════════════════════════════════════════════════════════════════════════════ - # Rust -/target/ +/target +/debug +/release **/*.rs.bk Cargo.lock -**/*.profraw -**/*.profdata - -# Python -__pycache__/ -*.py[cod] -*$py.class -*.so -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST -# Virtual Environment -venv/ -.venv/ -env/ -.env/ +# Build artifacts +build_err*.txt +build_log*.txt +check_output*.txt +test_output.txt +*.log +*.out +/site/ -# IDE/Editor -.idea/ -.vscode/ +# Editors +.vscode +.idea *.swp *.swo -*.sublime-project -*.sublime-workspace -# OS Generated Files +# OS .DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db Thumbs.db -# Logs & Diagnostics -*.log -*.txt -*.tmp -build_err*.txt -build_log*.txt -check_*.log -error.log -test_*.log - -# Build Artifacts -*.bin -*.img -*.iso -*.iso.bin -*.iso.lock - -# Agents / AI -agents/ -.gemini/ -.antigravity/ - -# Archives -*.zip -*.tar -*.tar.gz -*.rar - -# Docker -.dockerignore - +# Python +__pycache__ +*.pyc +.venv +venv +test_perf/ + +# Agent scratch +.jules/ +jules/ diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..d9daaea --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,12 @@ +## 2026-07-06 - Tensor metadata cloning in autograd +**Learning:** In reverse-mode autograd passes, cloning `Option` or passing references triggers unnecessary heap allocations for tensor metadata (shape/strides), even though the underlying data is reference-counted. +**Action:** Use `Option::take()` to acquire ownership of gradients during the backward pass, and pass tensors by value to `accumulate_grad` to eliminate metadata clones. +## 2026-07-14 - Optimizing Tensor allocations in linear algebra +**Learning:** High-level tensor operations like `.sub()` and `.map()` during gradient calculations trigger costly intermediate heap allocations for both data and metadata. +**Action:** Use single-pass iterators (`.iter().zip().map().collect()`) directly over the borrowed data arrays and consume the resulting vector with `Tensor::from_vec()` to avoid redundant O(N) slice allocations. +## 2026-07-25 - Tensor metadata cloning in MLP forward passes +**Learning:** In `aether-core::ml::neural`, cloning the `input` tensor in `MLP::forward` before passing its reference to the first layer's `forward` method triggers an unnecessary heap allocation for tensor metadata and an `Rc` increment. +**Action:** Extract the first layer using `self.layers.iter_mut()` to pass the initial `input` as a `&Tensor` reference directly, as subsequent layers naturally consume the output of the previous layer. +## 2026-08-03 - [Optimize DenseLayer backward pass] +**Learning:** In aether-core, DenseLayer::backward is optimized by using Option::take() on last_z and last_input instead of cloning them. This takes ownership, avoiding unnecessary heap allocations for shape and stride metadata while preventing mutable borrow conflicts during backpropagation. +**Action:** When working with cached tensor state during backpropagation, use Option::take() to transfer ownership rather than cloning, provided the state isn't needed again before the next forward pass. diff --git a/Aether.lean b/Aether.lean new file mode 100644 index 0000000..dc4d5a8 --- /dev/null +++ b/Aether.lean @@ -0,0 +1,6 @@ +import Aether.Lexer +import Aether.Core +import Aether.Static +import Aether.Parser +import Aether.VM +import Aether.Pipeline diff --git a/Aether/Core.lean b/Aether/Core.lean new file mode 100644 index 0000000..2404e0f --- /dev/null +++ b/Aether/Core.lean @@ -0,0 +1,3356 @@ +namespace Aether + +abbrev Ident := String + +inductive BinOp where + | add + | sub + | mul + | div + | mod + | eq + | neq + | lt + | gt + | le + | ge + | and + | or + deriving Repr, BEq, DecidableEq + +inductive UnOp where + | neg + | not + deriving Repr, BEq, DecidableEq + +inductive AnnTy where + | num + | bool + | str + | unit + | list : AnnTy -> AnnTy + deriving Repr, BEq, DecidableEq + +mutual + inductive Expr where + | num : Int -> Expr + | float : Int -> Int -> Expr + | bool : Bool -> Expr + | str : String -> Expr + | unit : Expr + | list : List Expr -> Expr + | index : Expr -> Expr -> Expr + | field : Expr -> Ident -> Expr + | method : Expr -> Ident -> List Arg -> Expr + | var : Ident -> Expr + | unary : UnOp -> Expr -> Expr + | binary : Expr -> BinOp -> Expr -> Expr + | call : Ident -> List Arg -> Expr + deriving Repr, BEq + + inductive Arg where + | positional : Expr -> Arg + | named : Ident -> Expr -> Arg + deriving Repr, BEq + + inductive Stmt where + | letDecl : Ident -> Expr -> Stmt + | letDeclTyped : Ident -> AnnTy -> Expr -> Stmt + | assign : Ident -> Expr -> Stmt + | ifThenElse : Expr -> List Stmt -> Option (List Stmt) -> Stmt + | while : Expr -> List Stmt -> Stmt + | forRange : Ident -> Int -> Int -> List Stmt -> Stmt + | seal : Option Expr -> List Stmt -> Stmt + | fnDecl : Ident -> List Ident -> List Stmt -> Stmt + | fnDeclReturn : Ident -> List Ident -> AnnTy -> List Stmt -> Stmt + | fnDeclTyped : Ident -> List (Ident × AnnTy) -> List Stmt -> Stmt + | fnDeclTypedReturn : Ident -> List (Ident × AnnTy) -> AnnTy -> List Stmt -> Stmt + | ret : Option Expr -> Stmt + | break : Stmt + | continue : Stmt + | expr : Expr -> Stmt + deriving Repr, BEq +end + +instance : Coe Expr Arg where + coe := Arg.positional + +inductive Value where + | num : Int -> Value + | float : Int -> Int -> Value + | bool : Bool -> Value + | str : String -> Value + | list : List Value -> Value + | unit : Value + deriving Repr, BEq + +mutual + def Value.decEq : (left right : Value) -> Decidable (left = right) + | Value.num left, Value.num right => + if h : left = right then isTrue (by cases h; rfl) + else isFalse (by intro eq; cases eq; exact h rfl) + | Value.float leftInt leftFrac, Value.float rightInt rightFrac => + if hInt : leftInt = rightInt then + if hFrac : leftFrac = rightFrac then + isTrue (by cases hInt; cases hFrac; rfl) + else + isFalse (by intro eq; cases eq; exact hFrac rfl) + else + isFalse (by intro eq; cases eq; exact hInt rfl) + | Value.bool left, Value.bool right => + if h : left = right then isTrue (by cases h; rfl) + else isFalse (by intro eq; cases eq; exact h rfl) + | Value.str left, Value.str right => + if h : left = right then isTrue (by cases h; rfl) + else isFalse (by intro eq; cases eq; exact h rfl) + | Value.list left, Value.list right => + match Value.decEqList left right with + | isTrue h => isTrue (by cases h; rfl) + | isFalse h => isFalse (by intro eq; cases eq; exact h rfl) + | Value.unit, Value.unit => isTrue rfl + | Value.num _, Value.float _ _ => isFalse (by intro h; cases h) + | Value.num _, Value.bool _ => isFalse (by intro h; cases h) + | Value.num _, Value.str _ => isFalse (by intro h; cases h) + | Value.num _, Value.list _ => isFalse (by intro h; cases h) + | Value.num _, Value.unit => isFalse (by intro h; cases h) + | Value.float _ _, Value.num _ => isFalse (by intro h; cases h) + | Value.float _ _, Value.bool _ => isFalse (by intro h; cases h) + | Value.float _ _, Value.str _ => isFalse (by intro h; cases h) + | Value.float _ _, Value.list _ => isFalse (by intro h; cases h) + | Value.float _ _, Value.unit => isFalse (by intro h; cases h) + | Value.bool _, Value.num _ => isFalse (by intro h; cases h) + | Value.bool _, Value.float _ _ => isFalse (by intro h; cases h) + | Value.bool _, Value.str _ => isFalse (by intro h; cases h) + | Value.bool _, Value.list _ => isFalse (by intro h; cases h) + | Value.bool _, Value.unit => isFalse (by intro h; cases h) + | Value.str _, Value.num _ => isFalse (by intro h; cases h) + | Value.str _, Value.float _ _ => isFalse (by intro h; cases h) + | Value.str _, Value.bool _ => isFalse (by intro h; cases h) + | Value.str _, Value.list _ => isFalse (by intro h; cases h) + | Value.str _, Value.unit => isFalse (by intro h; cases h) + | Value.list _, Value.num _ => isFalse (by intro h; cases h) + | Value.list _, Value.float _ _ => isFalse (by intro h; cases h) + | Value.list _, Value.bool _ => isFalse (by intro h; cases h) + | Value.list _, Value.str _ => isFalse (by intro h; cases h) + | Value.list _, Value.unit => isFalse (by intro h; cases h) + | Value.unit, Value.num _ => isFalse (by intro h; cases h) + | Value.unit, Value.float _ _ => isFalse (by intro h; cases h) + | Value.unit, Value.bool _ => isFalse (by intro h; cases h) + | Value.unit, Value.str _ => isFalse (by intro h; cases h) + | Value.unit, Value.list _ => isFalse (by intro h; cases h) + + def Value.decEqList : (left right : List Value) -> Decidable (left = right) + | [], [] => isTrue rfl + | [], _ :: _ => isFalse (by intro h; cases h) + | _ :: _, [] => isFalse (by intro h; cases h) + | left :: leftRest, right :: rightRest => + match Value.decEq left right, Value.decEqList leftRest rightRest with + | isTrue headEq, isTrue tailEq => + isTrue (by cases headEq; cases tailEq; rfl) + | isFalse headNe, _ => + isFalse (by intro h; cases h; exact headNe rfl) + | _, isFalse tailNe => + isFalse (by intro h; cases h; exact tailNe rfl) +end + +instance : DecidableEq Value := Value.decEq + +inductive Flow where + | value : Value -> Flow + | return : Value -> Flow + | break : Flow + | continue : Flow + deriving Repr, BEq, DecidableEq + +abbrev Env := List (Ident × Value) + +structure Function where + params : List Ident + body : List Stmt + deriving Repr, BEq + +abbrev FnEnv := List (Ident × Function) + +def Env.lookup (env : Env) (name : Ident) : Option Value := + match env with + | [] => none + | (key, value) :: rest => + if key == name then some value else Env.lookup rest name + +def Env.bind (env : Env) (name : Ident) (value : Value) : Env := + (name, value) :: env + +def Env.assign (env : Env) (name : Ident) (value : Value) : Option Env := + match env with + | [] => none + | (key, old) :: rest => + if key == name then + some ((key, value) :: rest) + else + match Env.assign rest name value with + | some updated => some ((key, old) :: updated) + | none => none + +def FnEnv.lookup (fns : FnEnv) (name : Ident) : Option Function := + match fns with + | [] => none + | (key, fn) :: rest => + if key == name then some fn else FnEnv.lookup rest name + +def FnEnv.bind (fns : FnEnv) (name : Ident) (fn : Function) : FnEnv := + (name, fn) :: fns + +def truthy : Value -> Bool + | Value.bool b => b + | Value.num n => n != 0 + | Value.float intPart fracMicros => intPart != 0 || fracMicros != 0 + | Value.str value => value != "" + | Value.list values => values != [] + | Value.unit => false + +def microsPerUnit : Int := 1000000 + +def numericMicros? : Value -> Option Int + | Value.num n => some (n * microsPerUnit) + | Value.float intPart fracMicros => some (intPart * microsPerUnit + fracMicros) + | _ => none + +def floatFromMicros (micros : Int) : Value := + Value.float (micros / microsPerUnit) (Int.emod micros microsPerUnit) + +def numericResult (hasFloat : Bool) (micros : Int) : Value := + if hasFloat then floatFromMicros micros else Value.num (micros / microsPerUnit) + +def hasFloat : Value -> Bool + | Value.float _ _ => true + | _ => false + +def valuesEq : Value -> Value -> Bool + | Value.num a, Value.num b => a == b + | Value.num a, Value.float bInt bFrac => + numericMicros? (Value.num a) == numericMicros? (Value.float bInt bFrac) + | Value.float aInt aFrac, Value.num b => + numericMicros? (Value.float aInt aFrac) == numericMicros? (Value.num b) + | Value.float aInt aFrac, Value.float bInt bFrac => + numericMicros? (Value.float aInt aFrac) == numericMicros? (Value.float bInt bFrac) + | Value.bool a, Value.bool b => a == b + | Value.str a, Value.str b => a == b + | Value.list a, Value.list b => a == b + | Value.unit, Value.unit => true + | _, _ => false + +def evalBinOp (op : BinOp) (left right : Value) : Option Value := + match op, left, right with + | BinOp.add, Value.num a, Value.num b => some (Value.num (a + b)) + | BinOp.add, _, _ => do + let leftMicros <- numericMicros? left + let rightMicros <- numericMicros? right + some (numericResult (hasFloat left || hasFloat right) (leftMicros + rightMicros)) + | BinOp.sub, Value.num a, Value.num b => some (Value.num (a - b)) + | BinOp.sub, _, _ => do + let leftMicros <- numericMicros? left + let rightMicros <- numericMicros? right + some (numericResult (hasFloat left || hasFloat right) (leftMicros - rightMicros)) + | BinOp.mul, Value.num a, Value.num b => some (Value.num (a * b)) + | BinOp.mul, _, _ => do + let leftMicros <- numericMicros? left + let rightMicros <- numericMicros? right + some (numericResult (hasFloat left || hasFloat right) ((leftMicros * rightMicros) / microsPerUnit)) + | BinOp.div, Value.num _, Value.num 0 => none + | BinOp.div, Value.num a, Value.num b => some (Value.num (a / b)) + | BinOp.div, _, _ => do + let leftMicros <- numericMicros? left + let rightMicros <- numericMicros? right + if rightMicros == 0 then none else some (floatFromMicros ((leftMicros * microsPerUnit) / rightMicros)) + | BinOp.mod, Value.num _, Value.num 0 => none + | BinOp.mod, Value.num a, Value.num b => some (Value.num (Int.emod a b)) + | BinOp.eq, _, _ => some (Value.bool (valuesEq left right)) + | BinOp.neq, _, _ => some (Value.bool (!valuesEq left right)) + | BinOp.lt, Value.num a, Value.num b => some (Value.bool (a < b)) + | BinOp.lt, _, _ => do + let leftMicros <- numericMicros? left + let rightMicros <- numericMicros? right + some (Value.bool (leftMicros < rightMicros)) + | BinOp.gt, Value.num a, Value.num b => some (Value.bool (a > b)) + | BinOp.gt, _, _ => do + let leftMicros <- numericMicros? left + let rightMicros <- numericMicros? right + some (Value.bool (leftMicros > rightMicros)) + | BinOp.le, Value.num a, Value.num b => some (Value.bool (a <= b)) + | BinOp.le, _, _ => do + let leftMicros <- numericMicros? left + let rightMicros <- numericMicros? right + some (Value.bool (leftMicros <= rightMicros)) + | BinOp.ge, Value.num a, Value.num b => some (Value.bool (a >= b)) + | BinOp.ge, _, _ => do + let leftMicros <- numericMicros? left + let rightMicros <- numericMicros? right + some (Value.bool (leftMicros >= rightMicros)) + | BinOp.and, _, _ => some (Value.bool (truthy left && truthy right)) + | BinOp.or, _, _ => some (Value.bool (truthy left || truthy right)) + | _, _, _ => none + +def evalUnOp (op : UnOp) (value : Value) : Option Value := + match op, value with + | UnOp.neg, Value.num n => some (Value.num (-n)) + | UnOp.neg, Value.float intPart fracMicros => do + let micros <- numericMicros? (Value.float intPart fracMicros) + some (floatFromMicros (-micros)) + | UnOp.not, _ => some (Value.bool (!truthy value)) + | _, _ => none + +def evalIndex : Value -> Value -> Option Value + | Value.list values, Value.num idx => + if idx < 0 then none else values[idx.toNat]? + | Value.str value, Value.num idx => + if idx < 0 then none else do + let char <- value.toList[idx.toNat]? + some (Value.str (String.ofList [char])) + | _, _ => none + +def evalField : Value -> Ident -> Option Value + | Value.list values, "length" => some (Value.num values.length) + | Value.str value, "length" => some (Value.num value.length) + | _, _ => none + +def valueInList (needle : Value) : List Value -> Bool + | [] => false + | value :: rest => if value == needle then true else valueInList needle rest + +def charPrefix : List Char -> List Char -> Bool + | [], _ => true + | _ :: _, [] => false + | needle :: needleRest, value :: valueRest => + needle == value && charPrefix needleRest valueRest + +def charListContains (needle : List Char) : List Char -> Bool + | [] => needle == [] + | value :: rest => + charPrefix needle (value :: rest) || charListContains needle rest + +def takeValues : Nat -> List Value -> List Value + | 0, _ => [] + | _ + 1, [] => [] + | n + 1, value :: rest => value :: takeValues n rest + +def dropValues : Nat -> List Value -> List Value + | 0, values => values + | _ + 1, [] => [] + | n + 1, _ :: rest => dropValues n rest + +def takeChars : Nat -> List Char -> List Char + | 0, _ => [] + | _ + 1, [] => [] + | n + 1, value :: rest => value :: takeChars n rest + +def dropChars : Nat -> List Char -> List Char + | 0, values => values + | _ + 1, [] => [] + | n + 1, _ :: rest => dropChars n rest + +def joinStringValues : List Value -> String -> Option String + | [], _ => some "" + | [Value.str value], _ => some value + | Value.str value :: rest, sep => do + let joinedRest <- joinStringValues rest sep + some (value ++ sep ++ joinedRest) + | _ :: _, _ => none + +def evalMethod : Value -> Ident -> List Value -> Option Value + | Value.list values, "len", [] => some (Value.num values.length) + | Value.list values, "is_empty", [] => some (Value.bool values.isEmpty) + | Value.list (value :: _), "first", [] => some value + | Value.list (_ :: rest), "tail", [] => some (Value.list rest) + | Value.list values, "last", [] => + match values.length with + | 0 => none + | n + 1 => values[n]? + | Value.list values, "at", [Value.num idx] => evalIndex (Value.list values) (Value.num idx) + | Value.list values, "take", [Value.num idx] => + if idx < 0 then none else some (Value.list (takeValues idx.toNat values)) + | Value.list values, "drop", [Value.num idx] => + if idx < 0 then none else some (Value.list (dropValues idx.toNat values)) + | Value.list values, "reverse", [] => some (Value.list values.reverse) + | Value.list values, "append", [value] => some (Value.list (values ++ [value])) + | Value.list values, "prepend", [value] => some (Value.list (value :: values)) + | Value.list values, "concat", [Value.list suffix] => some (Value.list (values ++ suffix)) + | Value.list values, "join", [Value.str sep] => do + let joined <- joinStringValues values sep + some (Value.str joined) + | Value.list values, "contains", [needle] => some (Value.bool (valueInList needle values)) + | Value.str value, "len", [] => some (Value.num value.length) + | Value.str value, "is_empty", [] => some (Value.bool value.isEmpty) + | Value.str value, "first", [] => evalIndex (Value.str value) (Value.num 0) + | Value.str value, "last", [] => + match value.toList.reverse with + | [] => none + | char :: _ => some (Value.str (String.ofList [char])) + | Value.str value, "tail", [] => + match value.toList with + | [] => none + | _ :: rest => some (Value.str (String.ofList rest)) + | Value.str value, "take", [Value.num idx] => + if idx < 0 then none else some (Value.str (String.ofList (takeChars idx.toNat value.toList))) + | Value.str value, "drop", [Value.num idx] => + if idx < 0 then none else some (Value.str (String.ofList (dropChars idx.toNat value.toList))) + | Value.str value, "at", [Value.num idx] => evalIndex (Value.str value) (Value.num idx) + | Value.str value, "contains", [Value.str needle] => + some (Value.bool (charListContains needle.toList value.toList)) + | Value.str value, "starts_with", [Value.str needle] => + some (Value.bool (charPrefix needle.toList value.toList)) + | Value.str value, "ends_with", [Value.str needle] => + some (Value.bool (charPrefix needle.toList.reverse value.toList.reverse)) + | Value.str value, "reverse", [] => + some (Value.str (String.ofList value.toList.reverse)) + | _, _, _ => none + +mutual + def evalExpr (env : Env) : Expr -> Option Value + | Expr.num n => some (Value.num n) + | Expr.float intPart fracMicros => some (Value.float intPart fracMicros) + | Expr.bool b => some (Value.bool b) + | Expr.str value => some (Value.str value) + | Expr.unit => some Value.unit + | Expr.list exprs => do + let values <- evalExprs env exprs + some (Value.list values) + | Expr.var name => Env.lookup env name + | Expr.unary op expr => do + let value <- evalExpr env expr + evalUnOp op value + | Expr.binary left op right => do + let leftValue <- evalExpr env left + let rightValue <- evalExpr env right + evalBinOp op leftValue rightValue + | Expr.index target index => do + let targetValue <- evalExpr env target + let indexValue <- evalExpr env index + evalIndex targetValue indexValue + | Expr.field target field => do + let targetValue <- evalExpr env target + evalField targetValue field + | Expr.method target method args => do + let targetValue <- evalExpr env target + let argValues <- evalArgs env args + evalMethod targetValue method argValues + | Expr.call _ _ => none + + def evalExprs (env : Env) : List Expr -> Option (List Value) + | [] => some [] + | expr :: rest => do + let value <- evalExpr env expr + let values <- evalExprs env rest + some (value :: values) + + def evalArg (env : Env) : Arg -> Option Value + | Arg.positional expr => evalExpr env expr + | Arg.named _ expr => evalExpr env expr + + def evalArgs (env : Env) : List Arg -> Option (List Value) + | [] => some [] + | arg :: rest => do + let value <- evalArg env arg + let values <- evalArgs env rest + some (value :: values) +end + +def bindParams : List Ident -> List Value -> Env -> Option Env + | [], [], env => some env + | name :: names, value :: values, env => bindParams names values (Env.bind env name value) + | _, _, _ => none + +def identInList (name : Ident) : List Ident -> Bool + | [] => false + | first :: rest => first == name || identInList name rest + +def argBindingsLookup (bindings : List (Ident × Value)) (name : Ident) : Option Value := + match bindings with + | [] => none + | (key, value) :: rest => + if key == name then some value else argBindingsLookup rest name + +def bindArgName + (params : List Ident) + (name : Ident) + (value : Value) + (bindings : List (Ident × Value)) : + Option (List (Ident × Value)) := + if identInList name params then + match argBindingsLookup bindings name with + | none => some ((name, value) :: bindings) + | some _ => none + else + none + +def firstUnboundParam (params : List Ident) (bindings : List (Ident × Value)) : Option Ident := + match params with + | [] => none + | name :: rest => + match argBindingsLookup bindings name with + | none => some name + | some _ => firstUnboundParam rest bindings + +def bindArgValues + (params : List Ident) + (args : List Arg) + (values : List Value) + (bindings : List (Ident × Value)) : + Option (List (Ident × Value)) := + match args, values with + | [], [] => some bindings + | Arg.positional _ :: restArgs, value :: restValues => do + let name <- firstUnboundParam params bindings + let updated <- bindArgName params name value bindings + bindArgValues params restArgs restValues updated + | Arg.named name _ :: restArgs, value :: restValues => do + let updated <- bindArgName params name value bindings + bindArgValues params restArgs restValues updated + | _, _ => none + +def bindBoundParams + (params : List Ident) + (bindings : List (Ident × Value)) + (env : Env) : + Option Env := + match params with + | [] => some env + | name :: rest => do + let value <- argBindingsLookup bindings name + bindBoundParams rest bindings (Env.bind env name value) + +def argsAllPositional : List Arg -> Bool + | [] => true + | Arg.positional _ :: rest => argsAllPositional rest + | Arg.named _ _ :: _ => false + +def bindCallArgs (params : List Ident) (args : List Arg) (values : List Value) (env : Env) : + Option Env := + if argsAllPositional args then + bindParams params values env + else do + let bindings <- bindArgValues params args values [] + bindBoundParams params bindings env + +mutual + partial def evalExprsWithFns (fuel : Nat) (env : Env) (fns : FnEnv) : List Expr -> Option (List Value) + | [] => some [] + | expr :: rest => do + let value <- evalExprWithFns fuel env fns expr + let values <- evalExprsWithFns fuel env fns rest + some (value :: values) + + partial def evalArgWithFns (fuel : Nat) (env : Env) (fns : FnEnv) : Arg -> Option Value + | Arg.positional expr => evalExprWithFns fuel env fns expr + | Arg.named _ expr => evalExprWithFns fuel env fns expr + + partial def evalArgsWithFns (fuel : Nat) (env : Env) (fns : FnEnv) : List Arg -> Option (List Value) + | [] => some [] + | arg :: rest => do + let value <- evalArgWithFns fuel env fns arg + let values <- evalArgsWithFns fuel env fns rest + some (value :: values) + + partial def evalExprWithFns (fuel : Nat) (env : Env) (fns : FnEnv) : Expr -> Option Value := + match fuel with + | 0 => fun _ => none + | fuel' + 1 => + fun + | Expr.num n => some (Value.num n) + | Expr.float intPart fracMicros => some (Value.float intPart fracMicros) + | Expr.bool b => some (Value.bool b) + | Expr.str value => some (Value.str value) + | Expr.unit => some Value.unit + | Expr.list exprs => do + let values <- evalExprsWithFns fuel' env fns exprs + some (Value.list values) + | Expr.var name => Env.lookup env name + | Expr.unary op expr => do + let value <- evalExprWithFns fuel' env fns expr + evalUnOp op value + | Expr.binary left op right => do + let leftValue <- evalExprWithFns fuel' env fns left + let rightValue <- evalExprWithFns fuel' env fns right + evalBinOp op leftValue rightValue + | Expr.index target index => do + let targetValue <- evalExprWithFns fuel' env fns target + let indexValue <- evalExprWithFns fuel' env fns index + evalIndex targetValue indexValue + | Expr.field target field => do + let targetValue <- evalExprWithFns fuel' env fns target + evalField targetValue field + | Expr.method target method args => do + let targetValue <- evalExprWithFns fuel' env fns target + let argValues <- evalArgsWithFns fuel' env fns args + evalMethod targetValue method argValues + | Expr.call name args => do + let fn <- FnEnv.lookup fns name + let values <- evalArgsWithFns fuel' env fns args + let frame <- bindCallArgs fn.params args values env + let (_, _, flow) <- execBlockWithFns fuel' frame fns fn.body + match flow with + | Flow.value value => some value + | Flow.return value => some value + | Flow.break => none + | Flow.continue => none + + partial def execStmtWithFns (fuel : Nat) (env : Env) (fns : FnEnv) : Stmt -> Option (Env × FnEnv × Flow) := + match fuel with + | 0 => fun _ => none + | fuel' + 1 => + fun + | Stmt.letDecl name expr => do + let value <- evalExprWithFns fuel' env fns expr + some (Env.bind env name value, fns, Flow.value value) + | Stmt.letDeclTyped name _ expr => do + let value <- evalExprWithFns fuel' env fns expr + some (Env.bind env name value, fns, Flow.value value) + | Stmt.assign name expr => do + let value <- evalExprWithFns fuel' env fns expr + let updated <- Env.assign env name value + some (updated, fns, Flow.value value) + | Stmt.ifThenElse condition thenBranch elseBranch => do + let value <- evalExprWithFns fuel' env fns condition + if truthy value then + execBlockWithFns fuel' env fns thenBranch + else + match elseBranch with + | some branch => execBlockWithFns fuel' env fns branch + | none => some (env, fns, Flow.value Value.unit) + | Stmt.while condition body => do + let value <- evalExprWithFns fuel' env fns condition + if truthy value then + let (env1, fns1, flow) <- execBlockWithFns fuel' env fns body + match flow with + | Flow.value _ => execStmtWithFns fuel' env1 fns1 (Stmt.while condition body) + | Flow.return value => some (env1, fns1, Flow.return value) + | Flow.break => some (env1, fns1, Flow.value Value.unit) + | Flow.continue => execStmtWithFns fuel' env1 fns1 (Stmt.while condition body) + else + some (env, fns, Flow.value Value.unit) + | Stmt.forRange iterator start stop body => do + if start < stop then + let loopEnv := Env.bind env iterator (Value.num start) + let (env1, fns1, flow) <- execBlockWithFns fuel' loopEnv fns body + match flow with + | Flow.value _ => + execStmtWithFns fuel' env1 fns1 (Stmt.forRange iterator (start + 1) stop body) + | Flow.return value => some (env1, fns1, Flow.return value) + | Flow.break => some (env1, fns1, Flow.value Value.unit) + | Flow.continue => + execStmtWithFns fuel' env1 fns1 (Stmt.forRange iterator (start + 1) stop body) + else + let finalEnv := Env.bind env iterator (Value.num start) + some (finalEnv, fns, Flow.value Value.unit) + | Stmt.seal condition body => do + match condition with + | some conditionExpr => do + let value <- evalExprWithFns fuel' env fns conditionExpr + if truthy value then + some (env, fns, Flow.value Value.unit) + else + let (env1, fns1, flow) <- execBlockWithFns fuel' env fns body + match flow with + | Flow.value _ => execStmtWithFns fuel' env1 fns1 (Stmt.seal condition body) + | Flow.return value => some (env1, fns1, Flow.return value) + | Flow.break => some (env1, fns1, Flow.value Value.unit) + | Flow.continue => execStmtWithFns fuel' env1 fns1 (Stmt.seal condition body) + | none => do + let (env1, fns1, flow) <- execBlockWithFns fuel' env fns body + match flow with + | Flow.value _ => execStmtWithFns fuel' env1 fns1 (Stmt.seal none body) + | Flow.return value => some (env1, fns1, Flow.return value) + | Flow.break => some (env1, fns1, Flow.value Value.unit) + | Flow.continue => execStmtWithFns fuel' env1 fns1 (Stmt.seal none body) + | Stmt.fnDecl name params body => + some (env, FnEnv.bind fns name { params := params, body := body }, Flow.value Value.unit) + | Stmt.fnDeclReturn name params _ body => + some (env, FnEnv.bind fns name { params := params, body := body }, Flow.value Value.unit) + | Stmt.fnDeclTyped name params body => + some (env, FnEnv.bind fns name { params := params.map Prod.fst, body := body }, Flow.value Value.unit) + | Stmt.fnDeclTypedReturn name params _ body => + some (env, FnEnv.bind fns name { params := params.map Prod.fst, body := body }, Flow.value Value.unit) + | Stmt.ret (some expr) => do + let value <- evalExprWithFns fuel' env fns expr + some (env, fns, Flow.return value) + | Stmt.ret none => + some (env, fns, Flow.return Value.unit) + | Stmt.expr expr => do + let value <- evalExprWithFns fuel' env fns expr + some (env, fns, Flow.value value) + | Stmt.break => some (env, fns, Flow.break) + | Stmt.continue => some (env, fns, Flow.continue) + + partial def execBlockWithFns (fuel : Nat) (env : Env) (fns : FnEnv) : List Stmt -> Option (Env × FnEnv × Flow) + | [] => some (env, fns, Flow.value Value.unit) + | [stmt] => execStmtWithFns fuel env fns stmt + | stmt :: next :: rest => do + let (env1, fns1, flow) <- execStmtWithFns fuel env fns stmt + match flow with + | Flow.value _ => execBlockWithFns fuel env1 fns1 (next :: rest) + | Flow.return value => some (env1, fns1, Flow.return value) + | Flow.break => some (env1, fns1, Flow.break) + | Flow.continue => some (env1, fns1, Flow.continue) +end + +mutual + inductive StepStmt : Env -> Stmt -> Env -> Flow -> Prop where + | letDecl {env name expr value} : + evalExpr env expr = some value -> + StepStmt env (Stmt.letDecl name expr) (Env.bind env name value) (Flow.value value) + | letDeclTyped {env name ty expr value} : + evalExpr env expr = some value -> + StepStmt env (Stmt.letDeclTyped name ty expr) (Env.bind env name value) (Flow.value value) + | assign {env name expr value updated} : + evalExpr env expr = some value -> + Env.assign env name value = some updated -> + StepStmt env (Stmt.assign name expr) updated (Flow.value value) + | ifTrue {env env' condition thenBranch elseBranch value flow} : + evalExpr env condition = some value -> + truthy value = true -> + StepBlock env thenBranch env' flow -> + StepStmt env (Stmt.ifThenElse condition thenBranch elseBranch) env' flow + | ifFalseSome {env env' condition thenBranch elseBranch value flow} : + evalExpr env condition = some value -> + truthy value = false -> + StepBlock env elseBranch env' flow -> + StepStmt env (Stmt.ifThenElse condition thenBranch (some elseBranch)) env' flow + | ifFalseNone {env condition thenBranch value} : + evalExpr env condition = some value -> + truthy value = false -> + StepStmt env (Stmt.ifThenElse condition thenBranch none) env (Flow.value Value.unit) + | whileFalse {env condition body value} : + evalExpr env condition = some value -> + truthy value = false -> + StepStmt env (Stmt.while condition body) env (Flow.value Value.unit) + | whileValue {env env1 env2 condition body value bodyValue flow} : + evalExpr env condition = some value -> + truthy value = true -> + StepBlock env body env1 (Flow.value bodyValue) -> + StepStmt env1 (Stmt.while condition body) env2 flow -> + StepStmt env (Stmt.while condition body) env2 flow + | whileReturn {env env1 condition body conditionValue returnValue} : + evalExpr env condition = some conditionValue -> + truthy conditionValue = true -> + StepBlock env body env1 (Flow.return returnValue) -> + StepStmt env (Stmt.while condition body) env1 (Flow.return returnValue) + | whileBreak {env env1 condition body value} : + evalExpr env condition = some value -> + truthy value = true -> + StepBlock env body env1 Flow.break -> + StepStmt env (Stmt.while condition body) env1 (Flow.value Value.unit) + | whileContinue {env env1 env2 condition body value flow} : + evalExpr env condition = some value -> + truthy value = true -> + StepBlock env body env1 Flow.continue -> + StepStmt env1 (Stmt.while condition body) env2 flow -> + StepStmt env (Stmt.while condition body) env2 flow + | forDone {env iterator start stop body} : + ¬ start < stop -> + StepStmt env (Stmt.forRange iterator start stop body) + (Env.bind env iterator (Value.num start)) + (Flow.value Value.unit) + | forValue {env env1 env2 iterator start stop body bodyValue flow} : + start < stop -> + StepBlock (Env.bind env iterator (Value.num start)) body env1 (Flow.value bodyValue) -> + StepStmt env1 (Stmt.forRange iterator (start + 1) stop body) env2 flow -> + StepStmt env (Stmt.forRange iterator start stop body) env2 flow + | forReturn {env env1 iterator start stop body returnValue} : + start < stop -> + StepBlock (Env.bind env iterator (Value.num start)) body env1 (Flow.return returnValue) -> + StepStmt env (Stmt.forRange iterator start stop body) env1 (Flow.return returnValue) + | forBreak {env env1 iterator start stop body} : + start < stop -> + StepBlock (Env.bind env iterator (Value.num start)) body env1 Flow.break -> + StepStmt env (Stmt.forRange iterator start stop body) env1 (Flow.value Value.unit) + | forContinue {env env1 env2 iterator start stop body flow} : + start < stop -> + StepBlock (Env.bind env iterator (Value.num start)) body env1 Flow.continue -> + StepStmt env1 (Stmt.forRange iterator (start + 1) stop body) env2 flow -> + StepStmt env (Stmt.forRange iterator start stop body) env2 flow + | sealUntilDone {env condition body value} : + evalExpr env condition = some value -> + truthy value = true -> + StepStmt env (Stmt.seal (some condition) body) env (Flow.value Value.unit) + | sealUntilValue {env env1 env2 condition body value bodyValue flow} : + evalExpr env condition = some value -> + truthy value = false -> + StepBlock env body env1 (Flow.value bodyValue) -> + StepStmt env1 (Stmt.seal (some condition) body) env2 flow -> + StepStmt env (Stmt.seal (some condition) body) env2 flow + | sealUntilReturn {env env1 condition body conditionValue returnValue} : + evalExpr env condition = some conditionValue -> + truthy conditionValue = false -> + StepBlock env body env1 (Flow.return returnValue) -> + StepStmt env (Stmt.seal (some condition) body) env1 (Flow.return returnValue) + | sealUntilBreak {env env1 condition body value} : + evalExpr env condition = some value -> + truthy value = false -> + StepBlock env body env1 Flow.break -> + StepStmt env (Stmt.seal (some condition) body) env1 (Flow.value Value.unit) + | sealUntilContinue {env env1 env2 condition body value flow} : + evalExpr env condition = some value -> + truthy value = false -> + StepBlock env body env1 Flow.continue -> + StepStmt env1 (Stmt.seal (some condition) body) env2 flow -> + StepStmt env (Stmt.seal (some condition) body) env2 flow + | sealValue {env env1 env2 body bodyValue flow} : + StepBlock env body env1 (Flow.value bodyValue) -> + StepStmt env1 (Stmt.seal none body) env2 flow -> + StepStmt env (Stmt.seal none body) env2 flow + | sealReturn {env env1 body returnValue} : + StepBlock env body env1 (Flow.return returnValue) -> + StepStmt env (Stmt.seal none body) env1 (Flow.return returnValue) + | sealBreak {env env1 body} : + StepBlock env body env1 Flow.break -> + StepStmt env (Stmt.seal none body) env1 (Flow.value Value.unit) + | sealContinue {env env1 env2 body flow} : + StepBlock env body env1 Flow.continue -> + StepStmt env1 (Stmt.seal none body) env2 flow -> + StepStmt env (Stmt.seal none body) env2 flow + | fnDecl {env name params body} : + StepStmt env (Stmt.fnDecl name params body) env (Flow.value Value.unit) + | fnDeclReturn {env name params returnTy body} : + StepStmt env (Stmt.fnDeclReturn name params returnTy body) env (Flow.value Value.unit) + | fnDeclTyped {env name params body} : + StepStmt env (Stmt.fnDeclTyped name params body) env (Flow.value Value.unit) + | fnDeclTypedReturn {env name params returnTy body} : + StepStmt env (Stmt.fnDeclTypedReturn name params returnTy body) env (Flow.value Value.unit) + | expr {env expr value} : + evalExpr env expr = some value -> + StepStmt env (Stmt.expr expr) env (Flow.value value) + | retSome {env expr value} : + evalExpr env expr = some value -> + StepStmt env (Stmt.ret (some expr)) env (Flow.return value) + | retNone {env} : + StepStmt env (Stmt.ret none) env (Flow.return Value.unit) + | break {env} : + StepStmt env Stmt.break env Flow.break + | continue {env} : + StepStmt env Stmt.continue env Flow.continue + + inductive StepBlock : Env -> List Stmt -> Env -> Flow -> Prop where + | nil {env} : + StepBlock env [] env (Flow.value Value.unit) + | single {env env' stmt flow} : + StepStmt env stmt env' flow -> + StepBlock env [stmt] env' flow + | consValue {env env1 env2 stmt next rest value flow} : + StepStmt env stmt env1 (Flow.value value) -> + StepBlock env1 (next :: rest) env2 flow -> + StepBlock env (stmt :: next :: rest) env2 flow + | consReturn {env env1 stmt next rest value} : + StepStmt env stmt env1 (Flow.return value) -> + StepBlock env (stmt :: next :: rest) env1 (Flow.return value) + | consBreak {env env1 stmt next rest} : + StepStmt env stmt env1 Flow.break -> + StepBlock env (stmt :: next :: rest) env1 Flow.break + | consContinue {env env1 stmt next rest} : + StepStmt env stmt env1 Flow.continue -> + StepBlock env (stmt :: next :: rest) env1 Flow.continue +end + +mutual + inductive EvalExprWithFnsRel : Env -> FnEnv -> Expr -> Value -> Prop where + | num {env fns n} : + EvalExprWithFnsRel env fns (Expr.num n) (Value.num n) + | float {env fns intPart fracMicros} : + EvalExprWithFnsRel env fns (Expr.float intPart fracMicros) (Value.float intPart fracMicros) + | bool {env fns b} : + EvalExprWithFnsRel env fns (Expr.bool b) (Value.bool b) + | str {env fns value} : + EvalExprWithFnsRel env fns (Expr.str value) (Value.str value) + | unit {env fns} : + EvalExprWithFnsRel env fns Expr.unit Value.unit + | list {env fns exprs values} : + EvalExprsWithFnsRel env fns exprs values -> + EvalExprWithFnsRel env fns (Expr.list exprs) (Value.list values) + | var {env fns name value} : + Env.lookup env name = some value -> + EvalExprWithFnsRel env fns (Expr.var name) value + | unary {env fns op expr value result} : + EvalExprWithFnsRel env fns expr value -> + evalUnOp op value = some result -> + EvalExprWithFnsRel env fns (Expr.unary op expr) result + | binary {env fns left op right leftValue rightValue result} : + EvalExprWithFnsRel env fns left leftValue -> + EvalExprWithFnsRel env fns right rightValue -> + evalBinOp op leftValue rightValue = some result -> + EvalExprWithFnsRel env fns (Expr.binary left op right) result + | index {env fns target index targetValue indexValue result} : + EvalExprWithFnsRel env fns target targetValue -> + EvalExprWithFnsRel env fns index indexValue -> + evalIndex targetValue indexValue = some result -> + EvalExprWithFnsRel env fns (Expr.index target index) result + | field {env fns target field targetValue result} : + EvalExprWithFnsRel env fns target targetValue -> + evalField targetValue field = some result -> + EvalExprWithFnsRel env fns (Expr.field target field) result + | method {env fns target method args targetValue argValues result} : + EvalExprWithFnsRel env fns target targetValue -> + EvalArgsWithFnsRel env fns args argValues -> + evalMethod targetValue method argValues = some result -> + EvalExprWithFnsRel env fns (Expr.method target method args) result + | callValue {env fns name args fn values frame env' fns' value} : + FnEnv.lookup fns name = some fn -> + EvalArgsWithFnsRel env fns args values -> + bindCallArgs fn.params args values env = some frame -> + StepBlockWithFns frame fns fn.body env' fns' (Flow.value value) -> + EvalExprWithFnsRel env fns (Expr.call name args) value + | callReturn {env fns name args fn values frame env' fns' value} : + FnEnv.lookup fns name = some fn -> + EvalArgsWithFnsRel env fns args values -> + bindCallArgs fn.params args values env = some frame -> + StepBlockWithFns frame fns fn.body env' fns' (Flow.return value) -> + EvalExprWithFnsRel env fns (Expr.call name args) value + + inductive EvalExprsWithFnsRel : Env -> FnEnv -> List Expr -> List Value -> Prop where + | nil {env fns} : + EvalExprsWithFnsRel env fns [] [] + | cons {env fns expr exprs value values} : + EvalExprWithFnsRel env fns expr value -> + EvalExprsWithFnsRel env fns exprs values -> + EvalExprsWithFnsRel env fns (expr :: exprs) (value :: values) + + inductive EvalArgWithFnsRel : Env -> FnEnv -> Arg -> Value -> Prop where + | positional {env fns expr value} : + EvalExprWithFnsRel env fns expr value -> + EvalArgWithFnsRel env fns (Arg.positional expr) value + | named {env fns name expr value} : + EvalExprWithFnsRel env fns expr value -> + EvalArgWithFnsRel env fns (Arg.named name expr) value + + inductive EvalArgsWithFnsRel : Env -> FnEnv -> List Arg -> List Value -> Prop where + | nil {env fns} : + EvalArgsWithFnsRel env fns [] [] + | cons {env fns arg args value values} : + EvalArgWithFnsRel env fns arg value -> + EvalArgsWithFnsRel env fns args values -> + EvalArgsWithFnsRel env fns (arg :: args) (value :: values) + + inductive StepStmtWithFns : Env -> FnEnv -> Stmt -> Env -> FnEnv -> Flow -> Prop where + | letDecl {env fns name expr value} : + EvalExprWithFnsRel env fns expr value -> + StepStmtWithFns env fns (Stmt.letDecl name expr) + (Env.bind env name value) fns (Flow.value value) + | letDeclTyped {env fns name ty expr value} : + EvalExprWithFnsRel env fns expr value -> + StepStmtWithFns env fns (Stmt.letDeclTyped name ty expr) + (Env.bind env name value) fns (Flow.value value) + | assign {env fns name expr value updated} : + EvalExprWithFnsRel env fns expr value -> + Env.assign env name value = some updated -> + StepStmtWithFns env fns (Stmt.assign name expr) updated fns (Flow.value value) + | ifTrue {env fns env' fns' condition thenBranch elseBranch value flow} : + EvalExprWithFnsRel env fns condition value -> + truthy value = true -> + StepBlockWithFns env fns thenBranch env' fns' flow -> + StepStmtWithFns env fns (Stmt.ifThenElse condition thenBranch elseBranch) env' fns' flow + | ifFalseSome {env fns env' fns' condition thenBranch elseBranch value flow} : + EvalExprWithFnsRel env fns condition value -> + truthy value = false -> + StepBlockWithFns env fns elseBranch env' fns' flow -> + StepStmtWithFns env fns (Stmt.ifThenElse condition thenBranch (some elseBranch)) env' fns' flow + | ifFalseNone {env fns condition thenBranch value} : + EvalExprWithFnsRel env fns condition value -> + truthy value = false -> + StepStmtWithFns env fns (Stmt.ifThenElse condition thenBranch none) env fns (Flow.value Value.unit) + | whileFalse {env fns condition body value} : + EvalExprWithFnsRel env fns condition value -> + truthy value = false -> + StepStmtWithFns env fns (Stmt.while condition body) env fns (Flow.value Value.unit) + | whileValue {env fns env1 fns1 env2 fns2 condition body value bodyValue flow} : + EvalExprWithFnsRel env fns condition value -> + truthy value = true -> + StepBlockWithFns env fns body env1 fns1 (Flow.value bodyValue) -> + StepStmtWithFns env1 fns1 (Stmt.while condition body) env2 fns2 flow -> + StepStmtWithFns env fns (Stmt.while condition body) env2 fns2 flow + | whileReturn {env fns env1 fns1 condition body conditionValue returnValue} : + EvalExprWithFnsRel env fns condition conditionValue -> + truthy conditionValue = true -> + StepBlockWithFns env fns body env1 fns1 (Flow.return returnValue) -> + StepStmtWithFns env fns (Stmt.while condition body) env1 fns1 (Flow.return returnValue) + | whileBreak {env fns env1 fns1 condition body value} : + EvalExprWithFnsRel env fns condition value -> + truthy value = true -> + StepBlockWithFns env fns body env1 fns1 Flow.break -> + StepStmtWithFns env fns (Stmt.while condition body) env1 fns1 (Flow.value Value.unit) + | whileContinue {env fns env1 fns1 env2 fns2 condition body value flow} : + EvalExprWithFnsRel env fns condition value -> + truthy value = true -> + StepBlockWithFns env fns body env1 fns1 Flow.continue -> + StepStmtWithFns env1 fns1 (Stmt.while condition body) env2 fns2 flow -> + StepStmtWithFns env fns (Stmt.while condition body) env2 fns2 flow + | forDone {env fns iterator start stop body} : + ¬ start < stop -> + StepStmtWithFns env fns (Stmt.forRange iterator start stop body) + (Env.bind env iterator (Value.num start)) fns + (Flow.value Value.unit) + | forValue {env fns env1 fns1 env2 fns2 iterator start stop body bodyValue flow} : + start < stop -> + StepBlockWithFns (Env.bind env iterator (Value.num start)) fns body env1 fns1 (Flow.value bodyValue) -> + StepStmtWithFns env1 fns1 (Stmt.forRange iterator (start + 1) stop body) env2 fns2 flow -> + StepStmtWithFns env fns (Stmt.forRange iterator start stop body) env2 fns2 flow + | forReturn {env fns env1 fns1 iterator start stop body returnValue} : + start < stop -> + StepBlockWithFns (Env.bind env iterator (Value.num start)) fns body env1 fns1 (Flow.return returnValue) -> + StepStmtWithFns env fns (Stmt.forRange iterator start stop body) env1 fns1 (Flow.return returnValue) + | forBreak {env fns env1 fns1 iterator start stop body} : + start < stop -> + StepBlockWithFns (Env.bind env iterator (Value.num start)) fns body env1 fns1 Flow.break -> + StepStmtWithFns env fns (Stmt.forRange iterator start stop body) env1 fns1 (Flow.value Value.unit) + | forContinue {env fns env1 fns1 env2 fns2 iterator start stop body flow} : + start < stop -> + StepBlockWithFns (Env.bind env iterator (Value.num start)) fns body env1 fns1 Flow.continue -> + StepStmtWithFns env1 fns1 (Stmt.forRange iterator (start + 1) stop body) env2 fns2 flow -> + StepStmtWithFns env fns (Stmt.forRange iterator start stop body) env2 fns2 flow + | sealUntilDone {env fns condition body value} : + EvalExprWithFnsRel env fns condition value -> + truthy value = true -> + StepStmtWithFns env fns (Stmt.seal (some condition) body) env fns (Flow.value Value.unit) + | sealUntilValue {env fns env1 fns1 env2 fns2 condition body value bodyValue flow} : + EvalExprWithFnsRel env fns condition value -> + truthy value = false -> + StepBlockWithFns env fns body env1 fns1 (Flow.value bodyValue) -> + StepStmtWithFns env1 fns1 (Stmt.seal (some condition) body) env2 fns2 flow -> + StepStmtWithFns env fns (Stmt.seal (some condition) body) env2 fns2 flow + | sealUntilReturn {env fns env1 fns1 condition body conditionValue returnValue} : + EvalExprWithFnsRel env fns condition conditionValue -> + truthy conditionValue = false -> + StepBlockWithFns env fns body env1 fns1 (Flow.return returnValue) -> + StepStmtWithFns env fns (Stmt.seal (some condition) body) env1 fns1 (Flow.return returnValue) + | sealUntilBreak {env fns env1 fns1 condition body value} : + EvalExprWithFnsRel env fns condition value -> + truthy value = false -> + StepBlockWithFns env fns body env1 fns1 Flow.break -> + StepStmtWithFns env fns (Stmt.seal (some condition) body) env1 fns1 (Flow.value Value.unit) + | sealUntilContinue {env fns env1 fns1 env2 fns2 condition body value flow} : + EvalExprWithFnsRel env fns condition value -> + truthy value = false -> + StepBlockWithFns env fns body env1 fns1 Flow.continue -> + StepStmtWithFns env1 fns1 (Stmt.seal (some condition) body) env2 fns2 flow -> + StepStmtWithFns env fns (Stmt.seal (some condition) body) env2 fns2 flow + | sealValue {env fns env1 fns1 env2 fns2 body bodyValue flow} : + StepBlockWithFns env fns body env1 fns1 (Flow.value bodyValue) -> + StepStmtWithFns env1 fns1 (Stmt.seal none body) env2 fns2 flow -> + StepStmtWithFns env fns (Stmt.seal none body) env2 fns2 flow + | sealReturn {env fns env1 fns1 body returnValue} : + StepBlockWithFns env fns body env1 fns1 (Flow.return returnValue) -> + StepStmtWithFns env fns (Stmt.seal none body) env1 fns1 (Flow.return returnValue) + | sealBreak {env fns env1 fns1 body} : + StepBlockWithFns env fns body env1 fns1 Flow.break -> + StepStmtWithFns env fns (Stmt.seal none body) env1 fns1 (Flow.value Value.unit) + | sealContinue {env fns env1 fns1 env2 fns2 body flow} : + StepBlockWithFns env fns body env1 fns1 Flow.continue -> + StepStmtWithFns env1 fns1 (Stmt.seal none body) env2 fns2 flow -> + StepStmtWithFns env fns (Stmt.seal none body) env2 fns2 flow + | fnDecl {env fns name params body} : + StepStmtWithFns env fns (Stmt.fnDecl name params body) + env (FnEnv.bind fns name { params := params, body := body }) + (Flow.value Value.unit) + | fnDeclReturn {env fns name params returnTy body} : + StepStmtWithFns env fns (Stmt.fnDeclReturn name params returnTy body) + env (FnEnv.bind fns name { params := params, body := body }) + (Flow.value Value.unit) + | fnDeclTyped {env fns name params body} : + StepStmtWithFns env fns (Stmt.fnDeclTyped name params body) + env (FnEnv.bind fns name { params := params.map Prod.fst, body := body }) + (Flow.value Value.unit) + | fnDeclTypedReturn {env fns name params returnTy body} : + StepStmtWithFns env fns (Stmt.fnDeclTypedReturn name params returnTy body) + env (FnEnv.bind fns name { params := params.map Prod.fst, body := body }) + (Flow.value Value.unit) + | expr {env fns expr value} : + EvalExprWithFnsRel env fns expr value -> + StepStmtWithFns env fns (Stmt.expr expr) env fns (Flow.value value) + | retSome {env fns expr value} : + EvalExprWithFnsRel env fns expr value -> + StepStmtWithFns env fns (Stmt.ret (some expr)) env fns (Flow.return value) + | retNone {env fns} : + StepStmtWithFns env fns (Stmt.ret none) env fns (Flow.return Value.unit) + | break {env fns} : + StepStmtWithFns env fns Stmt.break env fns Flow.break + | continue {env fns} : + StepStmtWithFns env fns Stmt.continue env fns Flow.continue + + inductive StepBlockWithFns : Env -> FnEnv -> List Stmt -> Env -> FnEnv -> Flow -> Prop where + | nil {env fns} : + StepBlockWithFns env fns [] env fns (Flow.value Value.unit) + | single {env fns env' fns' stmt flow} : + StepStmtWithFns env fns stmt env' fns' flow -> + StepBlockWithFns env fns [stmt] env' fns' flow + | consValue {env fns env1 fns1 env2 fns2 stmt next rest value flow} : + StepStmtWithFns env fns stmt env1 fns1 (Flow.value value) -> + StepBlockWithFns env1 fns1 (next :: rest) env2 fns2 flow -> + StepBlockWithFns env fns (stmt :: next :: rest) env2 fns2 flow + | consReturn {env fns env1 fns1 stmt next rest value} : + StepStmtWithFns env fns stmt env1 fns1 (Flow.return value) -> + StepBlockWithFns env fns (stmt :: next :: rest) env1 fns1 (Flow.return value) + | consBreak {env fns env1 fns1 stmt next rest} : + StepStmtWithFns env fns stmt env1 fns1 Flow.break -> + StepBlockWithFns env fns (stmt :: next :: rest) env1 fns1 Flow.break + | consContinue {env fns env1 fns1 stmt next rest} : + StepStmtWithFns env fns stmt env1 fns1 Flow.continue -> + StepBlockWithFns env fns (stmt :: next :: rest) env1 fns1 Flow.continue +end + +theorem lookup_bind_same (env : Env) (name : Ident) (value : Value) : + Env.lookup (Env.bind env name value) name = some value := by + unfold Env.bind Env.lookup + simp + +theorem eval_bound_var (env : Env) (name : Ident) (value : Value) : + evalExpr (Env.bind env name value) (Expr.var name) = some value := by + unfold evalExpr + exact lookup_bind_same env name value + +theorem evalExprWithFnsRel_num_sound : + EvalExprWithFnsRel [] [] (Expr.num 7) (Value.num 7) -> + evalExprWithFns 1 [] [] (Expr.num 7) = some (Value.num 7) := by + intro _ + native_decide + +theorem evalExprWithFnsRel_bool_sound : + EvalExprWithFnsRel [] [] (Expr.bool true) (Value.bool true) -> + evalExprWithFns 1 [] [] (Expr.bool true) = some (Value.bool true) := by + intro _ + native_decide + +theorem evalExprWithFnsRel_var_sound : + EvalExprWithFnsRel [("x", Value.str "open")] [] (Expr.var "x") (Value.str "open") -> + evalExprWithFns 1 [("x", Value.str "open")] [] (Expr.var "x") = some (Value.str "open") := by + intro _ + native_decide + +theorem evalExprWithFnsRel_binary_add_sound : + EvalExprWithFnsRel [] [] (Expr.binary (Expr.num 2) BinOp.add (Expr.num 5)) (Value.num 7) -> + evalExprWithFns 2 [] [] (Expr.binary (Expr.num 2) BinOp.add (Expr.num 5)) = + some (Value.num 7) := by + intro _ + native_decide + +theorem evalExprWithFnsRel_unary_not_sound : + EvalExprWithFnsRel [] [] (Expr.unary UnOp.not (Expr.bool false)) (Value.bool true) -> + evalExprWithFns 2 [] [] (Expr.unary UnOp.not (Expr.bool false)) = + some (Value.bool true) := by + intro _ + native_decide + +theorem evalExprWithFnsRel_list_sound : + EvalExprWithFnsRel [] [] (Expr.list [Expr.num 1, Expr.bool true]) + (Value.list [Value.num 1, Value.bool true]) -> + evalExprWithFns 2 [] [] (Expr.list [Expr.num 1, Expr.bool true]) = + some (Value.list [Value.num 1, Value.bool true]) := by + intro _ + native_decide + +theorem evalExprWithFnsRel_index_sound : + EvalExprWithFnsRel [] [] (Expr.index (Expr.list [Expr.str "a", Expr.str "b"]) (Expr.num 1)) + (Value.str "b") -> + evalExprWithFns 3 [] [] (Expr.index (Expr.list [Expr.str "a", Expr.str "b"]) (Expr.num 1)) = + some (Value.str "b") := by + intro _ + native_decide + +theorem evalExprWithFnsRel_field_length_sound : + EvalExprWithFnsRel [] [] (Expr.field (Expr.list [Expr.num 1, Expr.num 2]) "length") + (Value.num 2) -> + evalExprWithFns 3 [] [] (Expr.field (Expr.list [Expr.num 1, Expr.num 2]) "length") = + some (Value.num 2) := by + intro _ + native_decide + +theorem evalExprWithFnsRel_method_len_sound : + EvalExprWithFnsRel [] [] (Expr.method (Expr.str "aether") "len" []) (Value.num 6) -> + evalExprWithFns 2 [] [] (Expr.method (Expr.str "aether") "len" []) = + some (Value.num 6) := by + intro _ + native_decide + +theorem evalExprWithFnsRel_call_return_sound : + EvalExprWithFnsRel [] + [("id", { params := ["x"], body := [Stmt.ret (some (Expr.var "x"))] })] + (Expr.call "id" [Expr.num 3]) + (Value.num 3) -> + evalExprWithFns 3 [] + [("id", { params := ["x"], body := [Stmt.ret (some (Expr.var "x"))] })] + (Expr.call "id" [Expr.num 3]) = some (Value.num 3) := by + intro _ + native_decide + +theorem evalExprWithFnsRel_call_value_sound : + EvalExprWithFnsRel [] + [("one", { params := [], body := [Stmt.expr (Expr.num 1)] })] + (Expr.call "one" []) + (Value.num 1) -> + evalExprWithFns 3 [] + [("one", { params := [], body := [Stmt.expr (Expr.num 1)] })] + (Expr.call "one" []) = some (Value.num 1) := by + intro _ + native_decide + +theorem stepStmtWithFns_let_num_exec_sound : + StepStmtWithFns [] [] (Stmt.letDecl "x" (Expr.num 7)) + [("x", Value.num 7)] [] (Flow.value (Value.num 7)) -> + (execStmtWithFns 2 [] [] (Stmt.letDecl "x" (Expr.num 7))).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 7)], Flow.value (Value.num 7)) := by + intro _ + native_decide + +theorem stepStmtWithFns_fn_decl_exec_sound : + StepStmtWithFns [] [] (Stmt.fnDecl "id" ["x"] [Stmt.ret (some (Expr.var "x"))]) + [] + [("id", { params := ["x"], body := [Stmt.ret (some (Expr.var "x"))] })] + (Flow.value Value.unit) -> + (execStmtWithFns 1 [] [] (Stmt.fnDecl "id" ["x"] [Stmt.ret (some (Expr.var "x"))])).map + (fun result => + ( result.1 + , result.2.1.length + , result.2.2)) = + some ([], 1, Flow.value Value.unit) := by + intro _ + native_decide + +theorem stepStmtWithFns_return_var_exec_sound : + StepStmtWithFns [("x", Value.num 5)] [] (Stmt.ret (some (Expr.var "x"))) + [("x", Value.num 5)] [] (Flow.return (Value.num 5)) -> + (execStmtWithFns 2 [("x", Value.num 5)] [] (Stmt.ret (some (Expr.var "x")))).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 5)], Flow.return (Value.num 5)) := by + intro _ + native_decide + +theorem stepStmtWithFns_assign_num_exec_sound : + StepStmtWithFns [("x", Value.num 1)] [] (Stmt.assign "x" (Expr.num 9)) + [("x", Value.num 9)] [] (Flow.value (Value.num 9)) -> + (execStmtWithFns 2 [("x", Value.num 1)] [] (Stmt.assign "x" (Expr.num 9))).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 9)], Flow.value (Value.num 9)) := by + intro _ + native_decide + +theorem stepStmtWithFns_expr_bool_exec_sound : + StepStmtWithFns [] [] (Stmt.expr (Expr.bool true)) [] [] (Flow.value (Value.bool true)) -> + (execStmtWithFns 2 [] [] (Stmt.expr (Expr.bool true))).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.value (Value.bool true)) := by + intro _ + native_decide + +theorem stepStmtWithFns_return_none_exec_sound : + StepStmtWithFns [] [] (Stmt.ret none) [] [] (Flow.return Value.unit) -> + (execStmtWithFns 1 [] [] (Stmt.ret none)).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.return Value.unit) := by + intro _ + native_decide + +theorem stepStmtWithFns_break_exec_sound : + StepStmtWithFns [] [] Stmt.break [] [] Flow.break -> + (execStmtWithFns 1 [] [] Stmt.break).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.break) := by + intro _ + native_decide + +theorem stepStmtWithFns_continue_exec_sound : + StepStmtWithFns [] [] Stmt.continue [] [] Flow.continue -> + (execStmtWithFns 1 [] [] Stmt.continue).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.continue) := by + intro _ + native_decide + +theorem stepBlockWithFns_nil_exec_sound : + StepBlockWithFns [] [] [] [] [] (Flow.value Value.unit) -> + (execBlockWithFns 1 [] [] []).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.value Value.unit) := by + intro _ + native_decide + +theorem stepBlockWithFns_single_expr_exec_sound : + StepBlockWithFns [] [] [Stmt.expr (Expr.bool true)] [] [] (Flow.value (Value.bool true)) -> + (execBlockWithFns 2 [] [] [Stmt.expr (Expr.bool true)]).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.value (Value.bool true)) := by + intro _ + native_decide + +theorem stepBlockWithFns_cons_value_exec_sound : + StepBlockWithFns [] [] + [Stmt.letDecl "x" (Expr.num 7), Stmt.expr (Expr.var "x")] + [("x", Value.num 7)] [] (Flow.value (Value.num 7)) -> + (execBlockWithFns 2 [] [] + [Stmt.letDecl "x" (Expr.num 7), Stmt.expr (Expr.var "x")]).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 7)], Flow.value (Value.num 7)) := by + intro _ + native_decide + +theorem stepBlockWithFns_cons_return_exec_sound : + StepBlockWithFns [] [] + [Stmt.ret (some (Expr.num 1)), Stmt.expr (Expr.num 2)] + [] [] (Flow.return (Value.num 1)) -> + (execBlockWithFns 2 [] [] [Stmt.ret (some (Expr.num 1)), Stmt.expr (Expr.num 2)]).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.return (Value.num 1)) := by + intro _ + native_decide + +theorem stepBlockWithFns_cons_break_exec_sound : + StepBlockWithFns [] [] [Stmt.break, Stmt.expr (Expr.num 2)] [] [] Flow.break -> + (execBlockWithFns 1 [] [] [Stmt.break, Stmt.expr (Expr.num 2)]).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.break) := by + intro _ + native_decide + +theorem stepBlockWithFns_cons_continue_exec_sound : + StepBlockWithFns [] [] [Stmt.continue, Stmt.expr (Expr.num 2)] [] [] Flow.continue -> + (execBlockWithFns 1 [] [] [Stmt.continue, Stmt.expr (Expr.num 2)]).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.continue) := by + intro _ + native_decide + +theorem stepStmtWithFns_if_true_exec_sound : + StepStmtWithFns [] [] + (Stmt.ifThenElse + (Expr.bool true) + [Stmt.letDecl "x" (Expr.num 1)] + (some [Stmt.letDecl "x" (Expr.num 2)])) + [("x", Value.num 1)] [] (Flow.value (Value.num 1)) -> + (execStmtWithFns 3 [] [] + (Stmt.ifThenElse + (Expr.bool true) + [Stmt.letDecl "x" (Expr.num 1)] + (some [Stmt.letDecl "x" (Expr.num 2)]))).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 1)], Flow.value (Value.num 1)) := by + intro _ + native_decide + +theorem stepStmtWithFns_if_false_some_exec_sound : + StepStmtWithFns [] [] + (Stmt.ifThenElse + (Expr.bool false) + [Stmt.letDecl "x" (Expr.num 1)] + (some [Stmt.letDecl "x" (Expr.num 2)])) + [("x", Value.num 2)] [] (Flow.value (Value.num 2)) -> + (execStmtWithFns 3 [] [] + (Stmt.ifThenElse + (Expr.bool false) + [Stmt.letDecl "x" (Expr.num 1)] + (some [Stmt.letDecl "x" (Expr.num 2)]))).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 2)], Flow.value (Value.num 2)) := by + intro _ + native_decide + +theorem stepStmtWithFns_if_false_none_exec_sound : + StepStmtWithFns [] [] + (Stmt.ifThenElse + (Expr.bool false) + [Stmt.letDecl "x" (Expr.num 1)] + none) + [] [] (Flow.value Value.unit) -> + (execStmtWithFns 2 [] [] + (Stmt.ifThenElse + (Expr.bool false) + [Stmt.letDecl "x" (Expr.num 1)] + none)).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.value Value.unit) := by + intro _ + native_decide + +theorem stepStmtWithFns_while_false_exec_sound : + StepStmtWithFns [("x", Value.num 0)] [] + (Stmt.while (Expr.bool false) [Stmt.assign "x" (Expr.num 1)]) + [("x", Value.num 0)] [] (Flow.value Value.unit) -> + (execStmtWithFns 2 [("x", Value.num 0)] [] + (Stmt.while (Expr.bool false) [Stmt.assign "x" (Expr.num 1)])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 0)], Flow.value Value.unit) := by + intro _ + native_decide + +theorem stepStmtWithFns_while_return_exec_sound : + StepStmtWithFns [] [] + (Stmt.while (Expr.bool true) [Stmt.ret (some (Expr.num 4))]) + [] [] (Flow.return (Value.num 4)) -> + (execStmtWithFns 3 [] [] + (Stmt.while (Expr.bool true) [Stmt.ret (some (Expr.num 4))])).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.return (Value.num 4)) := by + intro _ + native_decide + +theorem stepStmtWithFns_while_break_exec_sound : + StepStmtWithFns [] [] + (Stmt.while (Expr.bool true) [Stmt.break]) + [] [] (Flow.value Value.unit) -> + (execStmtWithFns 2 [] [] + (Stmt.while (Expr.bool true) [Stmt.break])).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.value Value.unit) := by + intro _ + native_decide + +theorem stepStmtWithFns_for_done_exec_sound : + StepStmtWithFns [] [] + (Stmt.forRange "i" 2 2 [Stmt.expr (Expr.num 9)]) + [("i", Value.num 2)] [] (Flow.value Value.unit) -> + (execStmtWithFns 1 [] [] + (Stmt.forRange "i" 2 2 [Stmt.expr (Expr.num 9)])).map + (fun result => (result.1, result.2.2)) = + some ([("i", Value.num 2)], Flow.value Value.unit) := by + intro _ + native_decide + +theorem stepStmtWithFns_for_return_exec_sound : + StepStmtWithFns [] [] + (Stmt.forRange "i" 0 1 [Stmt.ret (some (Expr.var "i"))]) + [("i", Value.num 0)] [] (Flow.return (Value.num 0)) -> + (execStmtWithFns 3 [] [] + (Stmt.forRange "i" 0 1 [Stmt.ret (some (Expr.var "i"))])).map + (fun result => (result.1, result.2.2)) = + some ([("i", Value.num 0)], Flow.return (Value.num 0)) := by + intro _ + native_decide + +theorem stepStmtWithFns_for_break_exec_sound : + StepStmtWithFns [] [] + (Stmt.forRange "i" 0 1 [Stmt.break]) + [("i", Value.num 0)] [] (Flow.value Value.unit) -> + (execStmtWithFns 2 [] [] + (Stmt.forRange "i" 0 1 [Stmt.break])).map + (fun result => (result.1, result.2.2)) = + some ([("i", Value.num 0)], Flow.value Value.unit) := by + intro _ + native_decide + +theorem stepStmtWithFns_for_value_exec_sound : + StepStmtWithFns [("x", Value.num 9)] [] + (Stmt.forRange "i" 0 1 [Stmt.assign "x" (Expr.var "i")]) + [("i", Value.num 1), ("i", Value.num 0), ("x", Value.num 0)] [] + (Flow.value Value.unit) -> + (execStmtWithFns 3 [("x", Value.num 9)] [] + (Stmt.forRange "i" 0 1 [Stmt.assign "x" (Expr.var "i")])).map + (fun result => (result.1, result.2.2)) = + some ([("i", Value.num 1), ("i", Value.num 0), ("x", Value.num 0)], + Flow.value Value.unit) := by + intro _ + native_decide + +theorem stepStmtWithFns_for_continue_exec_sound : + StepStmtWithFns [] [] + (Stmt.forRange "i" 0 1 [Stmt.continue]) + [("i", Value.num 1), ("i", Value.num 0)] [] (Flow.value Value.unit) -> + (execStmtWithFns 3 [] [] + (Stmt.forRange "i" 0 1 [Stmt.continue])).map + (fun result => (result.1, result.2.2)) = + some ([("i", Value.num 1), ("i", Value.num 0)], Flow.value Value.unit) := by + intro _ + native_decide + +theorem stepStmtWithFns_seal_until_done_exec_sound : + StepStmtWithFns [("x", Value.num 0)] [] + (Stmt.seal (some (Expr.bool true)) [Stmt.assign "x" (Expr.num 1)]) + [("x", Value.num 0)] [] (Flow.value Value.unit) -> + (execStmtWithFns 2 [("x", Value.num 0)] [] + (Stmt.seal (some (Expr.bool true)) [Stmt.assign "x" (Expr.num 1)])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 0)], Flow.value Value.unit) := by + intro _ + native_decide + +theorem stepStmtWithFns_seal_until_value_exec_sound : + StepStmtWithFns [("x", Value.num 0)] [] + (Stmt.seal + (some (Expr.var "x")) + [Stmt.assign "x" (Expr.num 1)]) + [("x", Value.num 1)] [] (Flow.value Value.unit) -> + (execStmtWithFns 3 [("x", Value.num 0)] [] + (Stmt.seal + (some (Expr.var "x")) + [Stmt.assign "x" (Expr.num 1)])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 1)], Flow.value Value.unit) := by + intro _ + native_decide + +theorem stepStmtWithFns_seal_until_break_exec_sound : + StepStmtWithFns [("x", Value.num 0)] [] + (Stmt.seal (some (Expr.bool false)) [Stmt.break]) + [("x", Value.num 0)] [] (Flow.value Value.unit) -> + (execStmtWithFns 2 [("x", Value.num 0)] [] + (Stmt.seal (some (Expr.bool false)) [Stmt.break])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 0)], Flow.value Value.unit) := by + intro _ + native_decide + +theorem stepStmtWithFns_seal_until_return_exec_sound : + StepStmtWithFns [("x", Value.num 0)] [] + (Stmt.seal (some (Expr.bool false)) [Stmt.ret (some (Expr.num 5))]) + [("x", Value.num 0)] [] (Flow.return (Value.num 5)) -> + (execStmtWithFns 3 [("x", Value.num 0)] [] + (Stmt.seal (some (Expr.bool false)) [Stmt.ret (some (Expr.num 5))])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 0)], Flow.return (Value.num 5)) := by + intro _ + native_decide + +theorem stepStmtWithFns_seal_until_continue_exec_sound : + StepStmtWithFns [("x", Value.num 0)] [] + (Stmt.seal + (some (Expr.var "x")) + [Stmt.assign "x" (Expr.num 1), Stmt.continue]) + [("x", Value.num 1)] [] (Flow.value Value.unit) -> + (execStmtWithFns 3 [("x", Value.num 0)] [] + (Stmt.seal + (some (Expr.var "x")) + [Stmt.assign "x" (Expr.num 1), Stmt.continue])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 1)], Flow.value Value.unit) := by + intro _ + native_decide + +theorem stepStmtWithFns_seal_value_exec_sound : + StepStmtWithFns [("x", Value.num 0)] [] + (Stmt.seal none + [ Stmt.ifThenElse + (Expr.var "x") + [Stmt.break] + (some [Stmt.assign "x" (Expr.num 1)])]) + [("x", Value.num 1)] [] (Flow.value Value.unit) -> + (execStmtWithFns 4 [("x", Value.num 0)] [] + (Stmt.seal none + [ Stmt.ifThenElse + (Expr.var "x") + [Stmt.break] + (some [Stmt.assign "x" (Expr.num 1)])])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 1)], Flow.value Value.unit) := by + intro _ + native_decide + +theorem stepStmtWithFns_seal_return_exec_sound : + StepStmtWithFns [("x", Value.num 0)] [] + (Stmt.seal none [Stmt.ret (some (Expr.num 5))]) + [("x", Value.num 0)] [] (Flow.return (Value.num 5)) -> + (execStmtWithFns 3 [("x", Value.num 0)] [] + (Stmt.seal none [Stmt.ret (some (Expr.num 5))])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 0)], Flow.return (Value.num 5)) := by + intro _ + native_decide + +theorem stepStmtWithFns_seal_break_exec_sound : + StepStmtWithFns [("x", Value.num 0)] [] + (Stmt.seal none [Stmt.break]) + [("x", Value.num 0)] [] (Flow.value Value.unit) -> + (execStmtWithFns 2 [("x", Value.num 0)] [] + (Stmt.seal none [Stmt.break])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 0)], Flow.value Value.unit) := by + intro _ + native_decide + +theorem stepStmtWithFns_seal_continue_exec_sound : + StepStmtWithFns [("x", Value.num 0)] [] + (Stmt.seal none + [ Stmt.ifThenElse + (Expr.var "x") + [Stmt.break] + (some [Stmt.assign "x" (Expr.num 1), Stmt.continue])]) + [("x", Value.num 1)] [] (Flow.value Value.unit) -> + (execStmtWithFns 4 [("x", Value.num 0)] [] + (Stmt.seal none + [ Stmt.ifThenElse + (Expr.var "x") + [Stmt.break] + (some [Stmt.assign "x" (Expr.num 1), Stmt.continue])])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 1)], Flow.value Value.unit) := by + intro _ + native_decide + +example : + evalExprWithFns 1 [] [] (Expr.num 7) = some (Value.num 7) := by + apply evalExprWithFnsRel_num_sound + apply EvalExprWithFnsRel.num + +example : + evalExprWithFns 1 [] [] (Expr.bool true) = some (Value.bool true) := by + apply evalExprWithFnsRel_bool_sound + apply EvalExprWithFnsRel.bool + +example : + evalExprWithFns 1 [("x", Value.str "open")] [] (Expr.var "x") = + some (Value.str "open") := by + apply evalExprWithFnsRel_var_sound + apply EvalExprWithFnsRel.var + rfl + +example : + evalExprWithFns 2 [] [] (Expr.binary (Expr.num 2) BinOp.add (Expr.num 5)) = + some (Value.num 7) := by + apply evalExprWithFnsRel_binary_add_sound + apply EvalExprWithFnsRel.binary + · apply EvalExprWithFnsRel.num + · apply EvalExprWithFnsRel.num + · rfl + +example : + evalExprWithFns 2 [] [] (Expr.unary UnOp.not (Expr.bool false)) = + some (Value.bool true) := by + apply evalExprWithFnsRel_unary_not_sound + apply EvalExprWithFnsRel.unary + · apply EvalExprWithFnsRel.bool + · rfl + +example : + evalExprWithFns 2 [] [] (Expr.list [Expr.num 1, Expr.bool true]) = + some (Value.list [Value.num 1, Value.bool true]) := by + apply evalExprWithFnsRel_list_sound + apply EvalExprWithFnsRel.list + · apply EvalExprsWithFnsRel.cons + · apply EvalExprWithFnsRel.num + · apply EvalExprsWithFnsRel.cons + · apply EvalExprWithFnsRel.bool + · apply EvalExprsWithFnsRel.nil + +example : + evalExprWithFns 3 [] [] (Expr.index (Expr.list [Expr.str "a", Expr.str "b"]) (Expr.num 1)) = + some (Value.str "b") := by + apply evalExprWithFnsRel_index_sound + apply EvalExprWithFnsRel.index + · apply EvalExprWithFnsRel.list + apply EvalExprsWithFnsRel.cons + · apply EvalExprWithFnsRel.str + · apply EvalExprsWithFnsRel.cons + · apply EvalExprWithFnsRel.str + · apply EvalExprsWithFnsRel.nil + · apply EvalExprWithFnsRel.num + · rfl + +example : + evalExprWithFns 3 [] [] (Expr.field (Expr.list [Expr.num 1, Expr.num 2]) "length") = + some (Value.num 2) := by + apply evalExprWithFnsRel_field_length_sound + apply EvalExprWithFnsRel.field + · apply EvalExprWithFnsRel.list + apply EvalExprsWithFnsRel.cons + · apply EvalExprWithFnsRel.num + · apply EvalExprsWithFnsRel.cons + · apply EvalExprWithFnsRel.num + · apply EvalExprsWithFnsRel.nil + · rfl + +example : + evalExprWithFns 2 [] [] (Expr.method (Expr.str "aether") "len" []) = + some (Value.num 6) := by + apply evalExprWithFnsRel_method_len_sound + apply EvalExprWithFnsRel.method + · apply EvalExprWithFnsRel.str + · apply EvalArgsWithFnsRel.nil + · rfl + +example : + evalExprWithFns 3 [] + [("id", { params := ["x"], body := [Stmt.ret (some (Expr.var "x"))] })] + (Expr.call "id" [Expr.num 3]) = some (Value.num 3) := by + apply evalExprWithFnsRel_call_return_sound + apply EvalExprWithFnsRel.callReturn + · rfl + · apply EvalArgsWithFnsRel.cons + · apply EvalArgWithFnsRel.positional + apply EvalExprWithFnsRel.num + · apply EvalArgsWithFnsRel.nil + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.retSome + apply EvalExprWithFnsRel.var + rfl + +example : + evalExprWithFns 3 [] + [("one", { params := [], body := [Stmt.expr (Expr.num 1)] })] + (Expr.call "one" []) = some (Value.num 1) := by + apply evalExprWithFnsRel_call_value_sound + apply EvalExprWithFnsRel.callValue + · rfl + · apply EvalArgsWithFnsRel.nil + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.expr + apply EvalExprWithFnsRel.num + +example : + (execStmtWithFns 2 [] [] (Stmt.letDecl "x" (Expr.num 7))).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 7)], Flow.value (Value.num 7)) := by + apply stepStmtWithFns_let_num_exec_sound + apply StepStmtWithFns.letDecl + apply EvalExprWithFnsRel.num + +example : + (execStmtWithFns 1 [] [] (Stmt.fnDecl "id" ["x"] [Stmt.ret (some (Expr.var "x"))])).map + (fun result => + ( result.1 + , result.2.1.length + , result.2.2)) = + some ([], 1, Flow.value Value.unit) := by + apply stepStmtWithFns_fn_decl_exec_sound + apply StepStmtWithFns.fnDecl + +example : + (execStmtWithFns 2 [("x", Value.num 5)] [] (Stmt.ret (some (Expr.var "x")))).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 5)], Flow.return (Value.num 5)) := by + apply stepStmtWithFns_return_var_exec_sound + apply StepStmtWithFns.retSome + apply EvalExprWithFnsRel.var + rfl + +example : + (execStmtWithFns 2 [("x", Value.num 1)] [] (Stmt.assign "x" (Expr.num 9))).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 9)], Flow.value (Value.num 9)) := by + apply stepStmtWithFns_assign_num_exec_sound + apply StepStmtWithFns.assign + · apply EvalExprWithFnsRel.num + · rfl + +example : + (execStmtWithFns 2 [] [] (Stmt.expr (Expr.bool true))).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.value (Value.bool true)) := by + apply stepStmtWithFns_expr_bool_exec_sound + apply StepStmtWithFns.expr + apply EvalExprWithFnsRel.bool + +example : + (execStmtWithFns 1 [] [] (Stmt.ret none)).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.return Value.unit) := by + apply stepStmtWithFns_return_none_exec_sound + apply StepStmtWithFns.retNone + +example : + (execStmtWithFns 1 [] [] Stmt.break).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.break) := by + apply stepStmtWithFns_break_exec_sound + apply StepStmtWithFns.break + +example : + (execStmtWithFns 1 [] [] Stmt.continue).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.continue) := by + apply stepStmtWithFns_continue_exec_sound + apply StepStmtWithFns.continue + +example : + (execBlockWithFns 1 [] [] []).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.value Value.unit) := by + apply stepBlockWithFns_nil_exec_sound + apply StepBlockWithFns.nil + +example : + (execBlockWithFns 2 [] [] [Stmt.expr (Expr.bool true)]).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.value (Value.bool true)) := by + apply stepBlockWithFns_single_expr_exec_sound + apply StepBlockWithFns.single + apply StepStmtWithFns.expr + apply EvalExprWithFnsRel.bool + +example : + (execBlockWithFns 2 [] [] + [Stmt.letDecl "x" (Expr.num 7), Stmt.expr (Expr.var "x")]).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 7)], Flow.value (Value.num 7)) := by + apply stepBlockWithFns_cons_value_exec_sound + apply StepBlockWithFns.consValue + · apply StepStmtWithFns.letDecl + apply EvalExprWithFnsRel.num + · apply StepBlockWithFns.single + apply StepStmtWithFns.expr + apply EvalExprWithFnsRel.var + rfl + +example : + (execBlockWithFns 2 [] [] [Stmt.ret (some (Expr.num 1)), Stmt.expr (Expr.num 2)]).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.return (Value.num 1)) := by + apply stepBlockWithFns_cons_return_exec_sound + apply StepBlockWithFns.consReturn + apply StepStmtWithFns.retSome + apply EvalExprWithFnsRel.num + +example : + (execBlockWithFns 1 [] [] [Stmt.break, Stmt.expr (Expr.num 2)]).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.break) := by + apply stepBlockWithFns_cons_break_exec_sound + apply StepBlockWithFns.consBreak + apply StepStmtWithFns.break + +example : + (execBlockWithFns 1 [] [] [Stmt.continue, Stmt.expr (Expr.num 2)]).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.continue) := by + apply stepBlockWithFns_cons_continue_exec_sound + apply StepBlockWithFns.consContinue + apply StepStmtWithFns.continue + +example : + (execStmtWithFns 3 [] [] + (Stmt.ifThenElse + (Expr.bool true) + [Stmt.letDecl "x" (Expr.num 1)] + (some [Stmt.letDecl "x" (Expr.num 2)]) )).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 1)], Flow.value (Value.num 1)) := by + apply stepStmtWithFns_if_true_exec_sound + apply StepStmtWithFns.ifTrue + · apply EvalExprWithFnsRel.bool + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.letDecl + apply EvalExprWithFnsRel.num + +example : + (execStmtWithFns 3 [] [] + (Stmt.ifThenElse + (Expr.bool false) + [Stmt.letDecl "x" (Expr.num 1)] + (some [Stmt.letDecl "x" (Expr.num 2)]) )).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 2)], Flow.value (Value.num 2)) := by + apply stepStmtWithFns_if_false_some_exec_sound + apply StepStmtWithFns.ifFalseSome + · apply EvalExprWithFnsRel.bool + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.letDecl + apply EvalExprWithFnsRel.num + +example : + (execStmtWithFns 2 [] [] + (Stmt.ifThenElse + (Expr.bool false) + [Stmt.letDecl "x" (Expr.num 1)] + none)).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.value Value.unit) := by + apply stepStmtWithFns_if_false_none_exec_sound + apply StepStmtWithFns.ifFalseNone + · apply EvalExprWithFnsRel.bool + · rfl + +example : + (execStmtWithFns 2 [("x", Value.num 0)] [] + (Stmt.while (Expr.bool false) [Stmt.assign "x" (Expr.num 1)])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 0)], Flow.value Value.unit) := by + apply stepStmtWithFns_while_false_exec_sound + apply StepStmtWithFns.whileFalse + · apply EvalExprWithFnsRel.bool + · rfl + +example : + (execStmtWithFns 3 [] [] + (Stmt.while (Expr.bool true) [Stmt.ret (some (Expr.num 4))])).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.return (Value.num 4)) := by + apply stepStmtWithFns_while_return_exec_sound + apply StepStmtWithFns.whileReturn + · apply EvalExprWithFnsRel.bool + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.retSome + apply EvalExprWithFnsRel.num + +example : + (execStmtWithFns 2 [] [] + (Stmt.while (Expr.bool true) [Stmt.break])).map + (fun result => (result.1, result.2.2)) = + some ([], Flow.value Value.unit) := by + apply stepStmtWithFns_while_break_exec_sound + apply StepStmtWithFns.whileBreak + · apply EvalExprWithFnsRel.bool + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.break + +example : + (execStmtWithFns 1 [] [] + (Stmt.forRange "i" 2 2 [Stmt.expr (Expr.num 9)])).map + (fun result => (result.1, result.2.2)) = + some ([("i", Value.num 2)], Flow.value Value.unit) := by + apply stepStmtWithFns_for_done_exec_sound + apply StepStmtWithFns.forDone + decide + +example : + (execStmtWithFns 3 [] [] + (Stmt.forRange "i" 0 1 [Stmt.ret (some (Expr.var "i"))])).map + (fun result => (result.1, result.2.2)) = + some ([("i", Value.num 0)], Flow.return (Value.num 0)) := by + apply stepStmtWithFns_for_return_exec_sound + apply StepStmtWithFns.forReturn + · decide + · apply StepBlockWithFns.single + apply StepStmtWithFns.retSome + apply EvalExprWithFnsRel.var + rfl + +example : + (execStmtWithFns 2 [] [] + (Stmt.forRange "i" 0 1 [Stmt.break])).map + (fun result => (result.1, result.2.2)) = + some ([("i", Value.num 0)], Flow.value Value.unit) := by + apply stepStmtWithFns_for_break_exec_sound + apply StepStmtWithFns.forBreak + · decide + · apply StepBlockWithFns.single + apply StepStmtWithFns.break + +example : + (execStmtWithFns 3 [("x", Value.num 9)] [] + (Stmt.forRange "i" 0 1 [Stmt.assign "x" (Expr.var "i")])).map + (fun result => (result.1, result.2.2)) = + some ([("i", Value.num 1), ("i", Value.num 0), ("x", Value.num 0)], + Flow.value Value.unit) := by + apply stepStmtWithFns_for_value_exec_sound + apply StepStmtWithFns.forValue + · decide + · apply StepBlockWithFns.single + apply StepStmtWithFns.assign + · apply EvalExprWithFnsRel.var + rfl + · rfl + · apply StepStmtWithFns.forDone + decide + +example : + (execStmtWithFns 3 [] [] + (Stmt.forRange "i" 0 1 [Stmt.continue])).map + (fun result => (result.1, result.2.2)) = + some ([("i", Value.num 1), ("i", Value.num 0)], Flow.value Value.unit) := by + apply stepStmtWithFns_for_continue_exec_sound + apply StepStmtWithFns.forContinue + · decide + · apply StepBlockWithFns.single + apply StepStmtWithFns.continue + · apply StepStmtWithFns.forDone + decide + +example : + (execStmtWithFns 2 [("x", Value.num 0)] [] + (Stmt.seal (some (Expr.bool true)) [Stmt.assign "x" (Expr.num 1)])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 0)], Flow.value Value.unit) := by + apply stepStmtWithFns_seal_until_done_exec_sound + apply StepStmtWithFns.sealUntilDone + · apply EvalExprWithFnsRel.bool + · rfl + +example : + (execStmtWithFns 3 [("x", Value.num 0)] [] + (Stmt.seal + (some (Expr.var "x")) + [Stmt.assign "x" (Expr.num 1)])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 1)], Flow.value Value.unit) := by + apply stepStmtWithFns_seal_until_value_exec_sound + apply StepStmtWithFns.sealUntilValue + · apply EvalExprWithFnsRel.var + rfl + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.assign + · apply EvalExprWithFnsRel.num + · rfl + · apply StepStmtWithFns.sealUntilDone + · apply EvalExprWithFnsRel.var + rfl + · rfl + +example : + (execStmtWithFns 2 [("x", Value.num 0)] [] + (Stmt.seal (some (Expr.bool false)) [Stmt.break])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 0)], Flow.value Value.unit) := by + apply stepStmtWithFns_seal_until_break_exec_sound + apply StepStmtWithFns.sealUntilBreak + · apply EvalExprWithFnsRel.bool + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.break + +example : + (execStmtWithFns 3 [("x", Value.num 0)] [] + (Stmt.seal (some (Expr.bool false)) [Stmt.ret (some (Expr.num 5))])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 0)], Flow.return (Value.num 5)) := by + apply stepStmtWithFns_seal_until_return_exec_sound + apply StepStmtWithFns.sealUntilReturn + · apply EvalExprWithFnsRel.bool + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.retSome + apply EvalExprWithFnsRel.num + +example : + (execStmtWithFns 3 [("x", Value.num 0)] [] + (Stmt.seal + (some (Expr.var "x")) + [Stmt.assign "x" (Expr.num 1), Stmt.continue])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 1)], Flow.value Value.unit) := by + apply stepStmtWithFns_seal_until_continue_exec_sound + apply StepStmtWithFns.sealUntilContinue + · apply EvalExprWithFnsRel.var + rfl + · rfl + · apply StepBlockWithFns.consValue + · apply StepStmtWithFns.assign + · apply EvalExprWithFnsRel.num + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.continue + · apply StepStmtWithFns.sealUntilDone + · apply EvalExprWithFnsRel.var + rfl + · rfl + +example : + (execStmtWithFns 4 [("x", Value.num 0)] [] + (Stmt.seal none + [ Stmt.ifThenElse + (Expr.var "x") + [Stmt.break] + (some [Stmt.assign "x" (Expr.num 1)])])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 1)], Flow.value Value.unit) := by + apply stepStmtWithFns_seal_value_exec_sound + apply StepStmtWithFns.sealValue + · apply StepBlockWithFns.single + apply StepStmtWithFns.ifFalseSome + · apply EvalExprWithFnsRel.var + rfl + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.assign + · apply EvalExprWithFnsRel.num + · rfl + · apply StepStmtWithFns.sealBreak + apply StepBlockWithFns.single + apply StepStmtWithFns.ifTrue + · apply EvalExprWithFnsRel.var + rfl + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.break + +example : + (execStmtWithFns 3 [("x", Value.num 0)] [] + (Stmt.seal none [Stmt.ret (some (Expr.num 5))])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 0)], Flow.return (Value.num 5)) := by + apply stepStmtWithFns_seal_return_exec_sound + apply StepStmtWithFns.sealReturn + apply StepBlockWithFns.single + apply StepStmtWithFns.retSome + apply EvalExprWithFnsRel.num + +example : + (execStmtWithFns 2 [("x", Value.num 0)] [] + (Stmt.seal none [Stmt.break])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 0)], Flow.value Value.unit) := by + apply stepStmtWithFns_seal_break_exec_sound + apply StepStmtWithFns.sealBreak + apply StepBlockWithFns.single + apply StepStmtWithFns.break + +example : + (execStmtWithFns 4 [("x", Value.num 0)] [] + (Stmt.seal none + [ Stmt.ifThenElse + (Expr.var "x") + [Stmt.break] + (some [Stmt.assign "x" (Expr.num 1), Stmt.continue])])).map + (fun result => (result.1, result.2.2)) = + some ([("x", Value.num 1)], Flow.value Value.unit) := by + apply stepStmtWithFns_seal_continue_exec_sound + apply StepStmtWithFns.sealContinue + · apply StepBlockWithFns.single + apply StepStmtWithFns.ifFalseSome + · apply EvalExprWithFnsRel.var + rfl + · rfl + · apply StepBlockWithFns.consValue + · apply StepStmtWithFns.assign + · apply EvalExprWithFnsRel.num + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.continue + · apply StepStmtWithFns.sealBreak + apply StepBlockWithFns.single + apply StepStmtWithFns.ifTrue + · apply EvalExprWithFnsRel.var + rfl + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.break + +example : + evalExpr [] (Expr.binary (Expr.num 10) BinOp.mod (Expr.num 4)) = some (Value.num 2) := by + native_decide + +example : + evalExpr [] (Expr.float 1 500000) = some (Value.float 1 500000) := by + native_decide + +example : + evalExpr [] (Expr.binary (Expr.float 1 500000) BinOp.add (Expr.num 2)) = + some (Value.float 3 500000) := by + native_decide + +example : + evalExpr [] (Expr.binary (Expr.float 1 500000) BinOp.lt (Expr.num 2)) = + some (Value.bool true) := by + native_decide + +example : + evalExpr [] (Expr.binary (Expr.bool true) BinOp.and (Expr.num 1)) = some (Value.bool true) := by + native_decide + +example : + evalExpr [] (Expr.str "open") = some (Value.str "open") := by + native_decide + +example : + evalExpr [] Expr.unit = some Value.unit := by + native_decide + +example : + evalExpr [] (Expr.binary (Expr.str "open") BinOp.eq (Expr.str "open")) = + some (Value.bool true) := by + native_decide + +example : + evalExpr [] (Expr.list [Expr.num 1, Expr.bool true, Expr.str "open"]) = + some (Value.list [Value.num 1, Value.bool true, Value.str "open"]) := by + native_decide + +example : + evalExpr [] (Expr.binary (Expr.list [Expr.num 1]) BinOp.eq (Expr.list [Expr.num 1])) = + some (Value.bool true) := by + native_decide + +example : + evalExpr [] (Expr.index (Expr.list [Expr.str "a", Expr.str "b"]) (Expr.num 1)) = + some (Value.str "b") := by + native_decide + +example : + evalExpr [] (Expr.index (Expr.list [Expr.num 1]) (Expr.num 3)) = none := by + native_decide + +example : + evalExpr [] (Expr.index (Expr.str "open") (Expr.num 1)) = + some (Value.str "p") := by + native_decide + +example : + evalExpr [] (Expr.index (Expr.str "open") (Expr.num 9)) = none := by + native_decide + +example : + evalExpr [] (Expr.field (Expr.list [Expr.num 1, Expr.num 2]) "length") = + some (Value.num 2) := by + native_decide + +example : + evalExpr [] (Expr.field (Expr.str "open") "length") = some (Value.num 4) := by + native_decide + +example : + evalExpr [] (Expr.field (Expr.num 1) "length") = none := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 1, Expr.num 2]) "len" []) = + some (Value.num 2) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "open") "len" []) = some (Value.num 4) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list []) "is_empty" []) = some (Value.bool true) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "open") "is_empty" []) = some (Value.bool false) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "open") "at" [Expr.num 1]) = + some (Value.str "p") := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "open") "at" [Expr.num 9]) = none := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "open") "contains" [Expr.str "pe"]) = + some (Value.bool true) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "open") "contains" [Expr.str "zz"]) = + some (Value.bool false) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "open") "starts_with" [Expr.str "op"]) = + some (Value.bool true) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "open") "starts_with" [Expr.str "pe"]) = + some (Value.bool false) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "open") "ends_with" [Expr.str "en"]) = + some (Value.bool true) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "open") "ends_with" [Expr.str "op"]) = + some (Value.bool false) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "open") "reverse" []) = + some (Value.str "nepo") := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "open") "first" []) = + some (Value.str "o") := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "") "first" []) = none := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "open") "last" []) = + some (Value.str "n") := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "") "last" []) = none := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "open") "tail" []) = + some (Value.str "pen") := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "") "tail" []) = none := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "open") "take" [Expr.num 2]) = + some (Value.str "op") := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "open") "take" [Expr.num (-1)]) = none := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "open") "drop" [Expr.num 2]) = + some (Value.str "en") := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.str "open") "drop" [Expr.num (-1)]) = none := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 7, Expr.num 9]) "first" []) = + some (Value.num 7) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list []) "first" []) = none := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 7, Expr.num 9]) "last" []) = + some (Value.num 9) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list []) "last" []) = none := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 7, Expr.num 9]) "tail" []) = + some (Value.list [Value.num 9]) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list []) "tail" []) = none := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 7, Expr.num 9]) "at" [Expr.num 1]) = + some (Value.num 9) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 7]) "at" [Expr.num 3]) = none := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 7, Expr.num 9]) "take" [Expr.num 1]) = + some (Value.list [Value.num 7]) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 7, Expr.num 9]) "take" [Expr.num 3]) = + some (Value.list [Value.num 7, Value.num 9]) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 7]) "take" [Expr.num (-1)]) = none := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 7, Expr.num 9]) "drop" [Expr.num 1]) = + some (Value.list [Value.num 9]) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 7, Expr.num 9]) "drop" [Expr.num 3]) = + some (Value.list []) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 7]) "drop" [Expr.num (-1)]) = none := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 7, Expr.num 9]) "reverse" []) = + some (Value.list [Value.num 9, Value.num 7]) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list []) "reverse" []) = + some (Value.list []) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 7]) "append" [Expr.num 9]) = + some (Value.list [Value.num 7, Value.num 9]) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list []) "append" [Expr.num 9]) = + some (Value.list [Value.num 9]) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 9]) "prepend" [Expr.num 7]) = + some (Value.list [Value.num 7, Value.num 9]) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list []) "prepend" [Expr.num 7]) = + some (Value.list [Value.num 7]) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.str "a", Expr.str "b"]) "join" [Expr.str ","]) = + some (Value.str "a,b") := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list []) "join" [Expr.str ","]) = + some (Value.str "") := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.str "a", Expr.num 1]) "join" [Expr.str ","]) = + none := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 7]) "concat" [Expr.list [Expr.num 9]]) = + some (Value.list [Value.num 7, Value.num 9]) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list []) "concat" [Expr.list [Expr.num 9]]) = + some (Value.list [Value.num 9]) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 7, Expr.num 9]) "contains" [Expr.num 9]) = + some (Value.bool true) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 7]) "contains" [Expr.num 3]) = + some (Value.bool false) := by + native_decide + +example : + evalExpr [] (Expr.method (Expr.list [Expr.num 1]) "len" [Expr.num 0]) = none := by + native_decide + +example : + evalExprWithFns + 20 + [] + [ ( "add" + , { params := ["a", "b"] + body := [Stmt.ret (some (Expr.binary (Expr.var "a") BinOp.add (Expr.var "b")))] })] + (Expr.call "add" [Expr.num 2, Expr.num 3]) + = + some (Value.num 5) := by + native_decide + +example : + evalExprWithFns + 20 + [] + [ ( "pick" + , { params := ["a", "b"] + body := [Stmt.ret (some (Expr.var "a"))] })] + (Expr.call "pick" [Arg.named "b" (Expr.num 2), Arg.named "a" (Expr.num 7)]) + = + some (Value.num 7) := by + native_decide + +example : + evalExprWithFns + 20 + [] + [ ( "one" + , { params := [] + body := [Stmt.letDecl "x" (Expr.num 1), Stmt.expr (Expr.var "x")] })] + (Expr.call "one" []) + = + some (Value.num 1) := by + native_decide + +example : + evalExprWithFns + 20 + [] + [ ( "id" + , { params := ["x"] + body := [Stmt.ret (some (Expr.var "x"))] })] + (Expr.call "id" [Expr.num 1, Expr.num 2]) + = + none := by + native_decide + +example : + (execBlockWithFns + 30 + [("x", Value.num 10)] + [ ( "id" + , { params := ["x"] + body := [Stmt.ret (some (Expr.var "x"))] })] + [Stmt.letDecl "y" (Expr.call "id" [Expr.num 3])] + == + some + ( [("y", Value.num 3), ("x", Value.num 10)] + , [ ( "id" + , { params := ["x"] + body := [Stmt.ret (some (Expr.var "x"))] })] + , Flow.value (Value.num 3) + )) = true := by + native_decide + +example : + (execBlockWithFns + 20 + [] + [] + [ Stmt.ifThenElse + (Expr.bool true) + [Stmt.letDecl "x" (Expr.num 1)] + (some [Stmt.letDecl "x" (Expr.num 2)])] + == + some ([("x", Value.num 1)], [], Flow.value (Value.num 1))) = true := by + native_decide + +example : + (execBlockWithFns + 20 + [] + [] + [ Stmt.ifThenElse + (Expr.bool false) + [Stmt.letDecl "x" (Expr.num 1)] + (some [Stmt.letDecl "x" (Expr.num 2)])] + == + some ([("x", Value.num 2)], [], Flow.value (Value.num 2))) = true := by + native_decide + +example : + (execBlockWithFns + 20 + [] + [] + [ Stmt.ifThenElse + (Expr.bool false) + [Stmt.letDecl "x" (Expr.num 1)] + none] + == + some ([], [], Flow.value Value.unit)) = true := by + native_decide + +example : + (execBlockWithFns + 20 + [("x", Value.num 0)] + [] + [Stmt.while (Expr.bool false) [Stmt.assign "x" (Expr.num 1)]] + == + some ([("x", Value.num 0)], [], Flow.value Value.unit)) = true := by + native_decide + +example : + (execBlockWithFns + 40 + [("x", Value.num 0)] + [] + [ Stmt.while + (Expr.binary (Expr.var "x") BinOp.lt (Expr.num 3)) + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1))]] + == + some ([("x", Value.num 3)], [], Flow.value Value.unit)) = true := by + native_decide + +example : + (execBlockWithFns + 40 + [("x", Value.num 0)] + [] + [ Stmt.while + (Expr.binary (Expr.var "x") BinOp.lt (Expr.num 3)) + [ Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1)) + , Stmt.ifThenElse (Expr.binary (Expr.var "x") BinOp.eq (Expr.num 1)) [Stmt.break] none + , Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 10))]] + == + some ([("x", Value.num 1)], [], Flow.value Value.unit)) = true := by + native_decide + +example : + (execBlockWithFns + 60 + [("x", Value.num 0), ("sum", Value.num 0)] + [] + [ Stmt.while + (Expr.binary (Expr.var "x") BinOp.lt (Expr.num 4)) + [ Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1)) + , Stmt.ifThenElse (Expr.binary (Expr.var "x") BinOp.eq (Expr.num 2)) [Stmt.continue] none + , Stmt.assign "sum" (Expr.binary (Expr.var "sum") BinOp.add (Expr.var "x"))]] + == + some ([("x", Value.num 4), ("sum", Value.num 8)], [], Flow.value Value.unit)) = true := by + native_decide + +example : + (match execBlockWithFns + 40 + [("sum", Value.num 0)] + [] + [ Stmt.forRange + "i" + 0 + 3 + [Stmt.assign "sum" (Expr.binary (Expr.var "sum") BinOp.add (Expr.var "i"))]] + with + | some (env, _, Flow.value Value.unit) => + Env.lookup env "sum" == some (Value.num 3) + && Env.lookup env "i" == some (Value.num 3) + | _ => false) = true := by + native_decide + +example : + (match execBlockWithFns + 20 + [] + [] + [Stmt.forRange "i" 2 2 [Stmt.expr (Expr.num 9)]] + with + | some (env, _, Flow.value Value.unit) => + Env.lookup env "i" == some (Value.num 2) + | _ => false) = true := by + native_decide + +example : + (match execBlockWithFns + 40 + [("sum", Value.num 0)] + [] + [ Stmt.forRange + "i" + 0 + 5 + [ Stmt.ifThenElse (Expr.binary (Expr.var "i") BinOp.eq (Expr.num 2)) [Stmt.break] none + , Stmt.assign "sum" (Expr.binary (Expr.var "sum") BinOp.add (Expr.var "i"))]] + with + | some (env, _, Flow.value Value.unit) => + Env.lookup env "sum" == some (Value.num 1) + && Env.lookup env "i" == some (Value.num 2) + | _ => false) = true := by + native_decide + +example : + (match execBlockWithFns + 60 + [("sum", Value.num 0)] + [] + [ Stmt.forRange + "i" + 0 + 4 + [ Stmt.ifThenElse (Expr.binary (Expr.var "i") BinOp.eq (Expr.num 1)) [Stmt.continue] none + , Stmt.assign "sum" (Expr.binary (Expr.var "sum") BinOp.add (Expr.var "i"))]] + with + | some (env, _, Flow.value Value.unit) => + Env.lookup env "sum" == some (Value.num 5) + && Env.lookup env "i" == some (Value.num 4) + | _ => false) = true := by + native_decide + +example : + (match execBlockWithFns + 20 + [("x", Value.num 0)] + [] + [Stmt.seal (some (Expr.bool true)) [Stmt.assign "x" (Expr.num 1)]] + with + | some (env, _, Flow.value Value.unit) => + Env.lookup env "x" == some (Value.num 0) + | _ => false) = true := by + native_decide + +example : + (match execBlockWithFns + 60 + [("x", Value.num 0)] + [] + [ Stmt.seal + (some (Expr.binary (Expr.var "x") BinOp.ge (Expr.num 3))) + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1))]] + with + | some (env, _, Flow.value Value.unit) => + Env.lookup env "x" == some (Value.num 3) + | _ => false) = true := by + native_decide + +example : + (match execBlockWithFns + 40 + [("x", Value.num 0)] + [] + [ Stmt.seal + none + [ Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1)) + , Stmt.ifThenElse (Expr.binary (Expr.var "x") BinOp.eq (Expr.num 2)) [Stmt.break] none]] + with + | some (env, _, Flow.value Value.unit) => + Env.lookup env "x" == some (Value.num 2) + | _ => false) = true := by + native_decide + +example : + (match execBlockWithFns + 80 + [("x", Value.num 0), ("sum", Value.num 0)] + [] + [ Stmt.seal + (some (Expr.binary (Expr.var "x") BinOp.ge (Expr.num 4))) + [ Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1)) + , Stmt.ifThenElse (Expr.binary (Expr.var "x") BinOp.eq (Expr.num 2)) [Stmt.continue] none + , Stmt.assign "sum" (Expr.binary (Expr.var "sum") BinOp.add (Expr.var "x"))]] + with + | some (env, _, Flow.value Value.unit) => + Env.lookup env "x" == some (Value.num 4) + && Env.lookup env "sum" == some (Value.num 8) + | _ => false) = true := by + native_decide + +example : + StepBlock [] + [Stmt.letDecl "x" (Expr.num 1), Stmt.expr (Expr.var "x")] + [("x", Value.num 1)] + (Flow.value (Value.num 1)) := by + apply StepBlock.consValue + · apply StepStmt.letDecl + rfl + · apply StepBlock.single + apply StepStmt.expr + rfl + +example : + StepBlock + [("x", Value.num 1)] + [Stmt.assign "x" (Expr.num 2), Stmt.expr (Expr.var "x")] + [("x", Value.num 2)] + (Flow.value (Value.num 2)) := by + apply StepBlock.consValue + · apply StepStmt.assign + · rfl + · rfl + · apply StepBlock.single + apply StepStmt.expr + rfl + +example : + StepBlock [] + [Stmt.ret (some (Expr.num 7)), Stmt.expr (Expr.num 9)] + [] + (Flow.return (Value.num 7)) := by + apply StepBlock.consReturn + apply StepStmt.retSome + rfl + +example : + StepBlock [] + [Stmt.break, Stmt.expr (Expr.num 9)] + [] + Flow.break := by + apply StepBlock.consBreak + apply StepStmt.break + +example : + StepBlock [] + [Stmt.continue, Stmt.expr (Expr.num 9)] + [] + Flow.continue := by + apply StepBlock.consContinue + apply StepStmt.continue + +example : + StepBlock [] + [Stmt.ifThenElse + (Expr.bool true) + [Stmt.letDecl "x" (Expr.num 1)] + (some [Stmt.letDecl "x" (Expr.num 2)])] + [("x", Value.num 1)] + (Flow.value (Value.num 1)) := by + apply StepBlock.single + apply StepStmt.ifTrue + · rfl + · rfl + · apply StepBlock.single + apply StepStmt.letDecl + rfl + +example : + StepBlock [] + [Stmt.ifThenElse + (Expr.bool false) + [Stmt.letDecl "x" (Expr.num 1)] + (some [Stmt.letDecl "x" (Expr.num 2)])] + [("x", Value.num 2)] + (Flow.value (Value.num 2)) := by + apply StepBlock.single + apply StepStmt.ifFalseSome + · rfl + · rfl + · apply StepBlock.single + apply StepStmt.letDecl + rfl + +example : + StepBlock [] + [Stmt.ifThenElse + (Expr.bool false) + [Stmt.letDecl "x" (Expr.num 1)] + none] + [] + (Flow.value Value.unit) := by + apply StepBlock.single + apply StepStmt.ifFalseNone + · rfl + · rfl + +example : + StepBlock + [("x", Value.num 0)] + [Stmt.while (Expr.bool false) [Stmt.assign "x" (Expr.num 1)]] + [("x", Value.num 0)] + (Flow.value Value.unit) := by + apply StepBlock.single + apply StepStmt.whileFalse + · rfl + · rfl + +example : + StepBlock + [("x", Value.num 0)] + [ Stmt.while + (Expr.binary (Expr.var "x") BinOp.lt (Expr.num 1)) + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1))]] + [("x", Value.num 1)] + (Flow.value Value.unit) := by + apply StepBlock.single + apply StepStmt.whileValue + · rfl + · rfl + · apply StepBlock.single + apply StepStmt.assign + · rfl + · rfl + · apply StepStmt.whileFalse + · rfl + · rfl + +example : + StepBlock + [("x", Value.num 0)] + [ Stmt.while + (Expr.bool true) + [ Stmt.assign "x" (Expr.num 1) + , Stmt.break]] + [("x", Value.num 1)] + (Flow.value Value.unit) := by + apply StepBlock.single + apply StepStmt.whileBreak + · rfl + · rfl + · apply StepBlock.consValue + · apply StepStmt.assign + · rfl + · rfl + · apply StepBlock.single + apply StepStmt.break + +example : + StepBlock + [] + [Stmt.forRange "i" 2 2 [Stmt.expr (Expr.num 9)]] + [("i", Value.num 2)] + (Flow.value Value.unit) := by + apply StepBlock.single + apply StepStmt.forDone + decide + +example : + StepBlock + [("x", Value.num 0)] + [Stmt.forRange "i" 0 1 [Stmt.assign "x" (Expr.var "i")]] + [("i", Value.num 1), ("i", Value.num 0), ("x", Value.num 0)] + (Flow.value Value.unit) := by + apply StepBlock.single + apply StepStmt.forValue + · decide + · apply StepBlock.single + apply StepStmt.assign + · rfl + · rfl + · apply StepStmt.forDone + decide + +example : + StepBlock + [] + [Stmt.forRange "i" 0 3 [Stmt.break]] + [("i", Value.num 0)] + (Flow.value Value.unit) := by + apply StepBlock.single + apply StepStmt.forBreak + · decide + · apply StepBlock.single + apply StepStmt.break + +example : + StepBlock + [("x", Value.num 0)] + [Stmt.seal (some (Expr.bool true)) [Stmt.assign "x" (Expr.num 1)]] + [("x", Value.num 0)] + (Flow.value Value.unit) := by + apply StepBlock.single + apply StepStmt.sealUntilDone + · rfl + · rfl + +example : + StepBlock + [("x", Value.num 0)] + [ Stmt.seal + (some (Expr.binary (Expr.var "x") BinOp.ge (Expr.num 1))) + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1))]] + [("x", Value.num 1)] + (Flow.value Value.unit) := by + apply StepBlock.single + apply StepStmt.sealUntilValue + · rfl + · rfl + · apply StepBlock.single + apply StepStmt.assign + · rfl + · rfl + · apply StepStmt.sealUntilDone + · rfl + · rfl + +example : + StepBlock + [("x", Value.num 0)] + [ Stmt.seal + none + [ Stmt.assign "x" (Expr.num 1) + , Stmt.break]] + [("x", Value.num 1)] + (Flow.value Value.unit) := by + apply StepBlock.single + apply StepStmt.sealBreak + apply StepBlock.consValue + · apply StepStmt.assign + · rfl + · rfl + · apply StepBlock.single + apply StepStmt.break + +example : + StepBlock [] + [Stmt.fnDecl "id" ["x"] [Stmt.ret (some (Expr.var "x"))]] + [] + (Flow.value Value.unit) := by + apply StepBlock.single + apply StepStmt.fnDecl + +example : + StepBlockWithFns [] + [] + [ Stmt.fnDecl "id" ["x"] [Stmt.ret (some (Expr.var "x"))] + , Stmt.letDecl "y" (Expr.call "id" [Expr.num 3])] + [("y", Value.num 3)] + [("id", { params := ["x"], body := [Stmt.ret (some (Expr.var "x"))] })] + (Flow.value (Value.num 3)) := by + apply StepBlockWithFns.consValue + · apply StepStmtWithFns.fnDecl + · apply StepBlockWithFns.single + apply StepStmtWithFns.letDecl + apply EvalExprWithFnsRel.callReturn + · rfl + · apply EvalArgsWithFnsRel.cons + · apply EvalArgWithFnsRel.positional + apply EvalExprWithFnsRel.num + · apply EvalArgsWithFnsRel.nil + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.retSome + apply EvalExprWithFnsRel.var + rfl + +example : + StepBlockWithFns [] + [] + [ Stmt.fnDecl "one" [] [Stmt.expr (Expr.num 1)] + , Stmt.letDecl "y" (Expr.call "one" [])] + [("y", Value.num 1)] + [("one", { params := [], body := [Stmt.expr (Expr.num 1)] })] + (Flow.value (Value.num 1)) := by + apply StepBlockWithFns.consValue + · apply StepStmtWithFns.fnDecl + · apply StepBlockWithFns.single + apply StepStmtWithFns.letDecl + apply EvalExprWithFnsRel.callValue + · rfl + · apply EvalArgsWithFnsRel.nil + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.expr + apply EvalExprWithFnsRel.num + +example : + StepBlockWithFns [] + [] + [Stmt.ifThenElse + (Expr.bool true) + [Stmt.letDecl "x" (Expr.num 1)] + (some [Stmt.letDecl "x" (Expr.num 2)])] + [("x", Value.num 1)] + [] + (Flow.value (Value.num 1)) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.ifTrue + · apply EvalExprWithFnsRel.bool + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.letDecl + apply EvalExprWithFnsRel.num + +example : + StepBlockWithFns [] + [] + [Stmt.ifThenElse + (Expr.bool false) + [Stmt.letDecl "x" (Expr.num 1)] + (some [Stmt.letDecl "x" (Expr.num 2)])] + [("x", Value.num 2)] + [] + (Flow.value (Value.num 2)) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.ifFalseSome + · apply EvalExprWithFnsRel.bool + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.letDecl + apply EvalExprWithFnsRel.num + +example : + StepBlockWithFns [] + [] + [Stmt.ifThenElse + (Expr.bool false) + [Stmt.letDecl "x" (Expr.num 1)] + none] + [] + [] + (Flow.value Value.unit) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.ifFalseNone + · apply EvalExprWithFnsRel.bool + · rfl + +example : + StepBlockWithFns + [("x", Value.num 0)] + [] + [Stmt.while (Expr.bool false) [Stmt.letDecl "x" (Expr.num 1)]] + [("x", Value.num 0)] + [] + (Flow.value Value.unit) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.whileFalse + · apply EvalExprWithFnsRel.bool + · rfl + +example : + StepBlockWithFns + [("go", Value.bool true)] + [] + [Stmt.while (Expr.var "go") [Stmt.letDecl "go" (Expr.bool false)]] + [("go", Value.bool false), ("go", Value.bool true)] + [] + (Flow.value Value.unit) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.whileValue + · apply EvalExprWithFnsRel.var + rfl + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.letDecl + apply EvalExprWithFnsRel.bool + · apply StepStmtWithFns.whileFalse + · apply EvalExprWithFnsRel.var + rfl + · rfl + +example : + StepBlockWithFns + [("x", Value.num 0)] + [] + [ Stmt.while + (Expr.bool true) + [ Stmt.letDecl "x" (Expr.num 1) + , Stmt.break]] + [("x", Value.num 1), ("x", Value.num 0)] + [] + (Flow.value Value.unit) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.whileBreak + · apply EvalExprWithFnsRel.bool + · rfl + · apply StepBlockWithFns.consValue + · apply StepStmtWithFns.letDecl + apply EvalExprWithFnsRel.num + · apply StepBlockWithFns.single + apply StepStmtWithFns.break + +example : + StepBlockWithFns + [("x", Value.num 1)] + [] + [Stmt.assign "x" (Expr.num 2), Stmt.expr (Expr.var "x")] + [("x", Value.num 2)] + [] + (Flow.value (Value.num 2)) := by + apply StepBlockWithFns.consValue + · apply StepStmtWithFns.assign + · apply EvalExprWithFnsRel.num + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.expr + apply EvalExprWithFnsRel.var + rfl + +example : + StepBlockWithFns + [("go", Value.bool true)] + [] + [Stmt.while (Expr.var "go") [Stmt.assign "go" (Expr.bool false)]] + [("go", Value.bool false)] + [] + (Flow.value Value.unit) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.whileValue + · apply EvalExprWithFnsRel.var + rfl + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.assign + · apply EvalExprWithFnsRel.bool + · rfl + · apply StepStmtWithFns.whileFalse + · apply EvalExprWithFnsRel.var + rfl + · rfl + +example : + StepBlockWithFns + [] + [] + [Stmt.forRange "i" 2 2 [Stmt.expr (Expr.num 9)]] + [("i", Value.num 2)] + [] + (Flow.value Value.unit) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.forDone + decide + +example : + StepBlockWithFns + [("x", Value.num 0)] + [] + [Stmt.forRange "i" 0 1 [Stmt.assign "x" (Expr.var "i")]] + [("i", Value.num 1), ("i", Value.num 0), ("x", Value.num 0)] + [] + (Flow.value Value.unit) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.forValue + · decide + · apply StepBlockWithFns.single + apply StepStmtWithFns.assign + · apply EvalExprWithFnsRel.var + rfl + · rfl + · apply StepStmtWithFns.forDone + decide + +example : + StepBlockWithFns + [] + [] + [Stmt.forRange "i" 0 3 [Stmt.break]] + [("i", Value.num 0)] + [] + (Flow.value Value.unit) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.forBreak + · decide + · apply StepBlockWithFns.single + apply StepStmtWithFns.break + +example : + StepBlockWithFns + [("x", Value.num 0)] + [] + [Stmt.seal (some (Expr.bool true)) [Stmt.assign "x" (Expr.num 1)]] + [("x", Value.num 0)] + [] + (Flow.value Value.unit) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.sealUntilDone + · apply EvalExprWithFnsRel.bool + · rfl + +example : + StepBlockWithFns + [("x", Value.num 0)] + [] + [ Stmt.seal + (some (Expr.var "x")) + [Stmt.assign "x" (Expr.num 1)]] + [("x", Value.num 1)] + [] + (Flow.value Value.unit) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.sealUntilValue + · apply EvalExprWithFnsRel.var + rfl + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.assign + · apply EvalExprWithFnsRel.num + · rfl + · apply StepStmtWithFns.sealUntilDone + · apply EvalExprWithFnsRel.var + rfl + · rfl + +example : + StepBlockWithFns + [("x", Value.num 0)] + [] + [ Stmt.seal + none + [ Stmt.assign "x" (Expr.num 1) + , Stmt.break]] + [("x", Value.num 1)] + [] + (Flow.value Value.unit) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.sealBreak + apply StepBlockWithFns.consValue + · apply StepStmtWithFns.assign + · apply EvalExprWithFnsRel.num + · rfl + · apply StepBlockWithFns.single + apply StepStmtWithFns.break + +example : + StepBlockWithFns + [] + [] + [Stmt.expr (Expr.binary (Expr.num 2) BinOp.add (Expr.num 3))] + [] + [] + (Flow.value (Value.num 5)) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.expr + apply EvalExprWithFnsRel.binary + · apply EvalExprWithFnsRel.num + · apply EvalExprWithFnsRel.num + · rfl + +example : + StepBlockWithFns + [] + [] + [Stmt.expr (Expr.binary (Expr.num 2) BinOp.lt (Expr.num 3))] + [] + [] + (Flow.value (Value.bool true)) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.expr + apply EvalExprWithFnsRel.binary + · apply EvalExprWithFnsRel.num + · apply EvalExprWithFnsRel.num + · rfl + +example : + StepBlockWithFns + [] + [] + [Stmt.expr (Expr.unary UnOp.not (Expr.bool false))] + [] + [] + (Flow.value (Value.bool true)) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.expr + apply EvalExprWithFnsRel.unary + · apply EvalExprWithFnsRel.bool + · rfl + +example : + StepBlockWithFns + [] + [] + [Stmt.expr (Expr.list [Expr.num 1, Expr.bool true])] + [] + [] + (Flow.value (Value.list [Value.num 1, Value.bool true])) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.expr + apply EvalExprWithFnsRel.list + apply EvalExprsWithFnsRel.cons + · apply EvalExprWithFnsRel.num + · apply EvalExprsWithFnsRel.cons + · apply EvalExprWithFnsRel.bool + · apply EvalExprsWithFnsRel.nil + +example : + StepBlockWithFns + [] + [] + [Stmt.expr (Expr.index (Expr.list [Expr.str "a", Expr.str "b"]) (Expr.num 1))] + [] + [] + (Flow.value (Value.str "b")) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.expr + apply EvalExprWithFnsRel.index + · apply EvalExprWithFnsRel.list + apply EvalExprsWithFnsRel.cons + · apply EvalExprWithFnsRel.str + · apply EvalExprsWithFnsRel.cons + · apply EvalExprWithFnsRel.str + · apply EvalExprsWithFnsRel.nil + · apply EvalExprWithFnsRel.num + · rfl + +example : + StepBlockWithFns + [] + [] + [Stmt.expr (Expr.field (Expr.list [Expr.num 1, Expr.num 2]) "length")] + [] + [] + (Flow.value (Value.num 2)) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.expr + apply EvalExprWithFnsRel.field + · apply EvalExprWithFnsRel.list + apply EvalExprsWithFnsRel.cons + · apply EvalExprWithFnsRel.num + · apply EvalExprsWithFnsRel.cons + · apply EvalExprWithFnsRel.num + · apply EvalExprsWithFnsRel.nil + · rfl + +example : + StepBlockWithFns + [] + [] + [Stmt.expr (Expr.method (Expr.str "open") "len" [])] + [] + [] + (Flow.value (Value.num 4)) := by + apply StepBlockWithFns.single + apply StepStmtWithFns.expr + apply EvalExprWithFnsRel.method + · apply EvalExprWithFnsRel.str + · apply EvalArgsWithFnsRel.nil + · rfl + +end Aether diff --git a/Aether/Lexer.lean b/Aether/Lexer.lean new file mode 100644 index 0000000..1448a60 --- /dev/null +++ b/Aether/Lexer.lean @@ -0,0 +1,853 @@ +namespace Aether +namespace Lexer + +inductive TokenKind where + | manifold + | block + | regress + | render + | embed + | until + | escalate + | convergence + | true + | false + | class + | new + | self + | import + | from + | as + | seal + | for + | while + | if_ + | else_ + | fn + | return + | break + | continue + | in_ + | let_ + | dim + | tau + | model + | color + | axis + | project + | cluster + | center + | spread + | format + | output + | identifier : String -> TokenKind + | number : Int -> TokenKind + | float : Int -> Int -> TokenKind + | stringLit : String -> TokenKind + | equals + | colon + | comma + | dot + | lBrace + | rBrace + | lBracket + | rBracket + | lParen + | rParen + | plus + | minus + | star + | slash + | percent + | less + | greater + | lessEq + | greaterEq + | eqEq + | notEq + | and + | or + | not + | dotDot + | tilde + | semicolon + | newline + | eof + | error : String -> TokenKind + deriving Repr, BEq, DecidableEq + +structure SourcePos where + line : Nat + column : Nat + deriving Repr, BEq, DecidableEq + +structure SourceSpan where + start : SourcePos + stop : SourcePos + deriving Repr, BEq, DecidableEq + +structure LocatedToken where + kind : TokenKind + start : SourcePos + stop : SourcePos + deriving Repr, BEq, DecidableEq + +def initialPos : SourcePos := { line := 1, column := 1 } + +def advanceNewline (pos : SourcePos) : SourcePos := + { line := pos.line + 1, column := 1 } + +def advanceChar (pos : SourcePos) (c : Char) : SourcePos := + if c == '\n' then + advanceNewline pos + else + { pos with column := pos.column + 1 } + +def advanceMany : SourcePos -> List Char -> Nat -> SourcePos + | pos, _, 0 => pos + | pos, [], _ + 1 => pos + | pos, c :: rest, fuel + 1 => advanceMany (advanceChar pos c) rest fuel + +def mkLocated (kind : TokenKind) (start : SourcePos) (chars : List Char) (consumed : Nat) : + LocatedToken := + { kind := kind, start := start, stop := advanceMany start chars consumed } + +def LocatedToken.span (token : LocatedToken) : SourceSpan := + { start := token.start, stop := token.stop } + +def isAsciiDigit (c : Char) : Bool := + '0'.toNat <= c.toNat && c.toNat <= '9'.toNat + +def isAsciiAlpha (c : Char) : Bool := + ('a'.toNat <= c.toNat && c.toNat <= 'z'.toNat) + || ('A'.toNat <= c.toNat && c.toNat <= 'Z'.toNat) + +def isIdentStart (c : Char) : Bool := + isAsciiAlpha c || c == '_' + +def isIdentContinue (c : Char) : Bool := + isIdentStart c || isAsciiDigit c + +def digitValue? (c : Char) : Option Nat := + if isAsciiDigit c then + some (c.toNat - '0'.toNat) + else + none + +def parseNatDigits : List Char -> Nat + | [] => 0 + | c :: rest => + match digitValue? c with + | some digit => digit + 10 * parseNatDigits rest + | none => parseNatDigits rest + +def parseNatDigitsLeft (digits : List Char) : Nat := + digits.foldl + (fun acc c => + match digitValue? c with + | some digit => acc * 10 + digit + | none => acc) + 0 + +def readWhile (p : Char -> Bool) : List Char -> List Char -> String × List Char + | [], acc => (String.ofList acc.reverse, []) + | c :: rest, acc => + if p c then + readWhile p rest (c :: acc) + else + (String.ofList acc.reverse, c :: rest) + +def readDigits : List Char -> List Char -> List Char × List Char + | [], acc => (acc.reverse, []) + | c :: rest, acc => + if isAsciiDigit c then + readDigits rest (c :: acc) + else + (acc.reverse, c :: rest) + +def readFracDigits : Nat -> List Char -> Nat -> Nat -> Nat × Nat × List Char + | 0, chars, acc, digits => (acc, digits, chars) + | _ + 1, [], acc, digits => (acc, digits, []) + | fuel + 1, c :: rest, acc, digits => + match digitValue? c with + | some digit => readFracDigits fuel rest (acc * 10 + digit) (digits + 1) + | none => (acc, digits, c :: rest) + +def padFracToMicros : Nat -> Nat -> Nat + | 0, frac => frac + | fuel + 1, frac => padFracToMicros fuel (frac * 10) + +def keywordOrIdent (name : String) : TokenKind := + match name with + | "manifold" => TokenKind.manifold + | "block" => TokenKind.block + | "regress" => TokenKind.regress + | "render" => TokenKind.render + | "embed" => TokenKind.embed + | "until" => TokenKind.until + | "escalate" => TokenKind.escalate + | "convergence" => TokenKind.convergence + | "true" => TokenKind.true + | "false" => TokenKind.false + | "class" => TokenKind.class + | "new" => TokenKind.new + | "self" => TokenKind.self + | "import" => TokenKind.import + | "from" => TokenKind.from + | "as" => TokenKind.as + | "seal" => TokenKind.seal + | "for" => TokenKind.for + | "while" => TokenKind.while + | "if" => TokenKind.if_ + | "else" => TokenKind.else_ + | "fn" => TokenKind.fn + | "return" => TokenKind.return + | "break" => TokenKind.break + | "continue" => TokenKind.continue + | "in" => TokenKind.in_ + | "let" => TokenKind.let_ + | "dim" => TokenKind.dim + | "tau" => TokenKind.tau + | "model" => TokenKind.model + | "color" => TokenKind.color + | "axis" => TokenKind.axis + | "project" => TokenKind.project + | "cluster" => TokenKind.cluster + | "center" => TokenKind.center + | "spread" => TokenKind.spread + | "format" => TokenKind.format + | "output" => TokenKind.output + | _ => TokenKind.identifier name + +def skipWhitespace : List Char -> List Char + | [] => [] + | c :: rest => + if c == ' ' || c == '\t' then + skipWhitespace rest + else + c :: rest + +def skipWhitespaceLocated : List Char -> SourcePos -> List Char × SourcePos + | [], pos => ([], pos) + | c :: rest, pos => + if c == ' ' || c == '\t' then + skipWhitespaceLocated rest (advanceChar pos c) + else + (c :: rest, pos) + +def skipComment : List Char -> List Char + | [] => [] + | c :: rest => + if c == '\n' || c == '\r' then + c :: rest + else + skipComment rest + +def skipCommentLocated : List Char -> SourcePos -> List Char × SourcePos + | [], pos => ([], pos) + | c :: rest, pos => + if c == '\n' || c == '\r' then + (c :: rest, pos) + else + skipCommentLocated rest (advanceChar pos c) + +partial def skipBlockCommentDepth (depth : Nat) : List Char -> Option (List Char) + | [] => none + | '/' :: '*' :: rest => skipBlockCommentDepth (depth + 1) rest + | '*' :: '/' :: rest => + if depth == 1 then + some rest + else + skipBlockCommentDepth (depth - 1) rest + | _ :: rest => skipBlockCommentDepth depth rest + +def skipBlockComment (chars : List Char) : Option (List Char) := + skipBlockCommentDepth 1 chars + +partial def skipBlockCommentLocatedDepth + (depth : Nat) : List Char -> SourcePos -> Except SourcePos (List Char × SourcePos) + | [], pos => Except.error pos + | '/' :: '*' :: rest, pos => + skipBlockCommentLocatedDepth (depth + 1) rest (advanceChar (advanceChar pos '/') '*') + | '*' :: '/' :: rest, pos => + let nextPos := advanceChar (advanceChar pos '*') '/' + if depth == 1 then + Except.ok (rest, nextPos) + else + skipBlockCommentLocatedDepth (depth - 1) rest nextPos + | '\r' :: '\n' :: rest, pos => skipBlockCommentLocatedDepth depth rest (advanceNewline pos) + | '\r' :: rest, pos => skipBlockCommentLocatedDepth depth rest (advanceNewline pos) + | c :: rest, pos => skipBlockCommentLocatedDepth depth rest (advanceChar pos c) + +def skipBlockCommentLocated (chars : List Char) (pos : SourcePos) : + Except SourcePos (List Char × SourcePos) := + skipBlockCommentLocatedDepth 1 chars pos + +def readString : List Char -> List Char -> TokenKind × List Char + | [], _ => (TokenKind.error "unexpected EOF in string", []) + | '\\' :: escaped :: rest, acc => + match escaped with + | '"' => readString rest ('"' :: acc) + | '\\' => readString rest ('\\' :: acc) + | 'n' => readString rest ('\n' :: acc) + | 'r' => readString rest ('\r' :: acc) + | 't' => readString rest ('\t' :: acc) + | other => (TokenKind.error ("invalid escape: \\" ++ String.ofList [other]), rest) + | ['\\'], _ => (TokenKind.error "unexpected EOF in string", []) + | c :: rest, acc => + if c == '"' then + (TokenKind.stringLit (String.ofList acc.reverse), rest) + else if c == '\n' || c == '\r' then + (TokenKind.error "unterminated string", c :: rest) + else + readString rest (c :: acc) + +def readStringLocated + (openingStart : SourcePos) : + List Char -> SourcePos -> List Char -> LocatedToken × List Char × SourcePos + | [], pos, _ => + ( { kind := TokenKind.error "unexpected EOF in string" + , start := openingStart + , stop := pos } + , [] + , pos ) + | '\\' :: escaped :: rest, pos, acc => + let stop := advanceMany pos ['\\', escaped] 2 + match escaped with + | '"' => readStringLocated openingStart rest stop ('"' :: acc) + | '\\' => readStringLocated openingStart rest stop ('\\' :: acc) + | 'n' => readStringLocated openingStart rest stop ('\n' :: acc) + | 'r' => readStringLocated openingStart rest stop ('\r' :: acc) + | 't' => readStringLocated openingStart rest stop ('\t' :: acc) + | other => + ( { kind := TokenKind.error ("invalid escape: \\" ++ String.ofList [other]) + , start := pos + , stop := stop } + , rest + , stop ) + | ['\\'], pos, _ => + ( { kind := TokenKind.error "unexpected EOF in string" + , start := openingStart + , stop := advanceChar pos '\\' } + , [] + , advanceChar pos '\\' ) + | c :: rest, pos, acc => + if c == '"' then + let stop := advanceChar pos c + ( { kind := TokenKind.stringLit (String.ofList acc.reverse) + , start := openingStart + , stop := stop } + , rest + , stop ) + else if c == '\n' || c == '\r' then + ( { kind := TokenKind.error "unterminated string" + , start := openingStart + , stop := pos } + , c :: rest + , pos ) + else + readStringLocated openingStart rest (advanceChar pos c) (c :: acc) + +def readNumber (first : Char) (rest : List Char) : TokenKind × List Char := + let (digits, afterDigits) := readDigits rest [first] + let intPart := parseNatDigitsLeft digits + match afterDigits with + | '.' :: next :: afterDot => + if isAsciiDigit next then + let (frac, fracDigits, remaining) := readFracDigits 6 (next :: afterDot) 0 0 + let fracMicros := padFracToMicros (6 - fracDigits) frac + (TokenKind.float (Int.ofNat intPart) (Int.ofNat fracMicros), remaining) + else + (TokenKind.number (Int.ofNat intPart), afterDigits) + | _ => (TokenKind.number (Int.ofNat intPart), afterDigits) + +def scanFuel : Nat -> List Char -> List TokenKind + | 0, _ => [TokenKind.error "lexer fuel exhausted", TokenKind.eof] + | fuel + 1, chars => + match skipWhitespace chars with + | [] => [TokenKind.eof] + | '/' :: '*' :: rest => + match skipBlockComment rest with + | some remaining => scanFuel fuel remaining + | none => [TokenKind.error "unterminated block comment", TokenKind.eof] + | '/' :: '/' :: rest => scanFuel fuel (skipComment rest) + | '/' :: rest => TokenKind.slash :: scanFuel fuel rest + | '🦭' :: rest => TokenKind.seal :: scanFuel fuel rest + | '~' :: rest => TokenKind.tilde :: scanFuel fuel rest + | ';' :: rest => TokenKind.semicolon :: scanFuel fuel rest + | '=' :: '=' :: rest => TokenKind.eqEq :: scanFuel fuel rest + | '=' :: rest => TokenKind.equals :: scanFuel fuel rest + | '!' :: '=' :: rest => TokenKind.notEq :: scanFuel fuel rest + | '!' :: rest => TokenKind.not :: scanFuel fuel rest + | '<' :: '=' :: rest => TokenKind.lessEq :: scanFuel fuel rest + | '<' :: rest => TokenKind.less :: scanFuel fuel rest + | '>' :: '=' :: rest => TokenKind.greaterEq :: scanFuel fuel rest + | '>' :: rest => TokenKind.greater :: scanFuel fuel rest + | '&' :: '&' :: rest => TokenKind.and :: scanFuel fuel rest + | '|' :: '|' :: rest => TokenKind.or :: scanFuel fuel rest + | '.' :: '.' :: rest => TokenKind.dotDot :: scanFuel fuel rest + | ':' :: rest => TokenKind.colon :: scanFuel fuel rest + | ',' :: rest => TokenKind.comma :: scanFuel fuel rest + | '.' :: rest => TokenKind.dot :: scanFuel fuel rest + | '{' :: rest => TokenKind.lBrace :: scanFuel fuel rest + | '}' :: rest => TokenKind.rBrace :: scanFuel fuel rest + | '[' :: rest => TokenKind.lBracket :: scanFuel fuel rest + | ']' :: rest => TokenKind.rBracket :: scanFuel fuel rest + | '(' :: rest => TokenKind.lParen :: scanFuel fuel rest + | ')' :: rest => TokenKind.rParen :: scanFuel fuel rest + | '\r' :: '\n' :: rest => TokenKind.newline :: scanFuel fuel rest + | '\r' :: rest => TokenKind.newline :: scanFuel fuel rest + | '\n' :: rest => TokenKind.newline :: scanFuel fuel rest + | '+' :: rest => TokenKind.plus :: scanFuel fuel rest + | '-' :: rest => TokenKind.minus :: scanFuel fuel rest + | '*' :: rest => TokenKind.star :: scanFuel fuel rest + | '%' :: rest => TokenKind.percent :: scanFuel fuel rest + | '"' :: rest => + let (token, remaining) := readString rest [] + token :: scanFuel fuel remaining + | c :: rest => + if isAsciiDigit c then + let (token, remaining) := readNumber c rest + token :: scanFuel fuel remaining + else if isIdentStart c then + let (name, remaining) := readWhile isIdentContinue rest [c] + keywordOrIdent name :: scanFuel fuel remaining + else + TokenKind.error ("unexpected char: " ++ String.ofList [c]) :: scanFuel fuel rest + +def tokenize (source : String) : List TokenKind := + scanFuel (source.toList.length + 1) source.toList + +def scanLocatedFuel : Nat -> List Char -> SourcePos -> List LocatedToken + | 0, _, pos => + [ { kind := TokenKind.error "lexer fuel exhausted", start := pos, stop := pos } + , { kind := TokenKind.eof, start := pos, stop := pos } + ] + | fuel + 1, chars, pos => + let (chars, pos) := skipWhitespaceLocated chars pos + match chars with + | [] => [{ kind := TokenKind.eof, start := pos, stop := pos }] + | '/' :: '*' :: rest => + let commentStart := pos + let bodyStart := advanceChar (advanceChar pos '/') '*' + match skipBlockCommentLocated rest bodyStart with + | Except.ok (remaining, nextPos) => scanLocatedFuel fuel remaining nextPos + | Except.error stop => + [ { kind := TokenKind.error "unterminated block comment", start := commentStart, stop := stop } + , { kind := TokenKind.eof, start := stop, stop := stop } + ] + | '/' :: '/' :: rest => + let (remaining, nextPos) := skipCommentLocated rest (advanceMany pos chars 2) + scanLocatedFuel fuel remaining nextPos + | '/' :: rest => mkLocated TokenKind.slash pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos '/') + | '🦭' :: rest => mkLocated TokenKind.seal pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos '🦭') + | '~' :: rest => mkLocated TokenKind.tilde pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos '~') + | ';' :: rest => mkLocated TokenKind.semicolon pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos ';') + | '=' :: '=' :: rest => mkLocated TokenKind.eqEq pos chars 2 :: scanLocatedFuel fuel rest (advanceMany pos chars 2) + | '=' :: rest => mkLocated TokenKind.equals pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos '=') + | '!' :: '=' :: rest => mkLocated TokenKind.notEq pos chars 2 :: scanLocatedFuel fuel rest (advanceMany pos chars 2) + | '!' :: rest => mkLocated TokenKind.not pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos '!') + | '<' :: '=' :: rest => mkLocated TokenKind.lessEq pos chars 2 :: scanLocatedFuel fuel rest (advanceMany pos chars 2) + | '<' :: rest => mkLocated TokenKind.less pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos '<') + | '>' :: '=' :: rest => mkLocated TokenKind.greaterEq pos chars 2 :: scanLocatedFuel fuel rest (advanceMany pos chars 2) + | '>' :: rest => mkLocated TokenKind.greater pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos '>') + | '&' :: '&' :: rest => mkLocated TokenKind.and pos chars 2 :: scanLocatedFuel fuel rest (advanceMany pos chars 2) + | '|' :: '|' :: rest => mkLocated TokenKind.or pos chars 2 :: scanLocatedFuel fuel rest (advanceMany pos chars 2) + | '.' :: '.' :: rest => mkLocated TokenKind.dotDot pos chars 2 :: scanLocatedFuel fuel rest (advanceMany pos chars 2) + | ':' :: rest => mkLocated TokenKind.colon pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos ':') + | ',' :: rest => mkLocated TokenKind.comma pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos ',') + | '.' :: rest => mkLocated TokenKind.dot pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos '.') + | '{' :: rest => mkLocated TokenKind.lBrace pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos '{') + | '}' :: rest => mkLocated TokenKind.rBrace pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos '}') + | '[' :: rest => mkLocated TokenKind.lBracket pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos '[') + | ']' :: rest => mkLocated TokenKind.rBracket pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos ']') + | '(' :: rest => mkLocated TokenKind.lParen pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos '(') + | ')' :: rest => mkLocated TokenKind.rParen pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos ')') + | '\r' :: '\n' :: rest => + { kind := TokenKind.newline, start := pos, stop := advanceNewline pos } + :: scanLocatedFuel fuel rest (advanceNewline pos) + | '\r' :: rest => + { kind := TokenKind.newline, start := pos, stop := advanceNewline pos } + :: scanLocatedFuel fuel rest (advanceNewline pos) + | '\n' :: rest => mkLocated TokenKind.newline pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos '\n') + | '+' :: rest => mkLocated TokenKind.plus pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos '+') + | '-' :: rest => mkLocated TokenKind.minus pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos '-') + | '*' :: rest => mkLocated TokenKind.star pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos '*') + | '%' :: rest => mkLocated TokenKind.percent pos chars 1 :: scanLocatedFuel fuel rest (advanceChar pos '%') + | '"' :: rest => + let (token, remaining, nextPos) := readStringLocated pos rest (advanceChar pos '"') [] + token :: scanLocatedFuel fuel remaining nextPos + | c :: rest => + if isAsciiDigit c then + let (token, remaining) := readNumber c rest + let consumed := chars.length - remaining.length + mkLocated token pos chars consumed :: scanLocatedFuel fuel remaining (advanceMany pos chars consumed) + else if isIdentStart c then + let (name, remaining) := readWhile isIdentContinue rest [c] + let consumed := chars.length - remaining.length + mkLocated (keywordOrIdent name) pos chars consumed :: scanLocatedFuel fuel remaining (advanceMany pos chars consumed) + else + mkLocated (TokenKind.error ("unexpected char: " ++ String.ofList [c])) pos chars 1 + :: scanLocatedFuel fuel rest (advanceChar pos c) + +def tokenizeLocated (source : String) : List LocatedToken := + scanLocatedFuel (source.toList.length + 1) source.toList initialPos + +def tokenKinds (tokens : List LocatedToken) : List TokenKind := + tokens.map (fun token => token.kind) + +example : + tokenize "let x = 1..10" + = + [ TokenKind.let_ + , TokenKind.identifier "x" + , TokenKind.equals + , TokenKind.number 1 + , TokenKind.dotDot + , TokenKind.number 10 + , TokenKind.eof + ] := by + native_decide + +example : + tokenize "1.5 1..10" + = + [ TokenKind.float 1 500000 + , TokenKind.number 1 + , TokenKind.dotDot + , TokenKind.number 10 + , TokenKind.eof + ] := by + native_decide + +example : + tokenize "if true { x = x + 1 } // done\n~" + = + [ TokenKind.if_ + , TokenKind.true + , TokenKind.lBrace + , TokenKind.identifier "x" + , TokenKind.equals + , TokenKind.identifier "x" + , TokenKind.plus + , TokenKind.number 1 + , TokenKind.rBrace + , TokenKind.newline + , TokenKind.tilde + , TokenKind.eof + ] := by + native_decide + +example : + tokenize "let x = 1\rlet y = 2" + = + [ TokenKind.let_ + , TokenKind.identifier "x" + , TokenKind.equals + , TokenKind.number 1 + , TokenKind.newline + , TokenKind.let_ + , TokenKind.identifier "y" + , TokenKind.equals + , TokenKind.number 2 + , TokenKind.eof + ] := by + native_decide + +example : + tokenize "let x = 1\r\nlet y = 2" + = + [ TokenKind.let_ + , TokenKind.identifier "x" + , TokenKind.equals + , TokenKind.number 1 + , TokenKind.newline + , TokenKind.let_ + , TokenKind.identifier "y" + , TokenKind.equals + , TokenKind.number 2 + , TokenKind.eof + ] := by + native_decide + +example : + tokenize "let x = 1; let y = 2" + = + [ TokenKind.let_ + , TokenKind.identifier "x" + , TokenKind.equals + , TokenKind.number 1 + , TokenKind.semicolon + , TokenKind.let_ + , TokenKind.identifier "y" + , TokenKind.equals + , TokenKind.number 2 + , TokenKind.eof + ] := by + native_decide + +example : + tokenize "let x = 1 /* ignore tokens + true */ ~" + = + [ TokenKind.let_ + , TokenKind.identifier "x" + , TokenKind.equals + , TokenKind.number 1 + , TokenKind.tilde + , TokenKind.eof + ] := by + native_decide + +example : + tokenize "let x = 1 /* outer /* inner */ still comment */ ~" + = + [ TokenKind.let_ + , TokenKind.identifier "x" + , TokenKind.equals + , TokenKind.number 1 + , TokenKind.tilde + , TokenKind.eof + ] := by + native_decide + +example : + tokenize "let x = 1 /* never closes" + = + [ TokenKind.let_ + , TokenKind.identifier "x" + , TokenKind.equals + , TokenKind.number 1 + , TokenKind.error "unterminated block comment" + , TokenKind.eof + ] := by + native_decide + +example : + tokenize "let x = 1 /* outer /* inner */" + = + [ TokenKind.let_ + , TokenKind.identifier "x" + , TokenKind.equals + , TokenKind.number 1 + , TokenKind.error "unterminated block comment" + , TokenKind.eof + ] := by + native_decide + +example : + tokenize "🦭 until x == 3 { break }" + = + [ TokenKind.seal + , TokenKind.until + , TokenKind.identifier "x" + , TokenKind.eqEq + , TokenKind.number 3 + , TokenKind.lBrace + , TokenKind.break + , TokenKind.rBrace + , TokenKind.eof + ] := by + native_decide + +example : + tokenize "\"open\" \"bad\n" + = + [ TokenKind.stringLit "open" + , TokenKind.error "unterminated string" + , TokenKind.newline + , TokenKind.eof + ] := by + native_decide + +example : + tokenize "\"say \\\"hi\\\"\" \"path\\\\to\"" + = + [ TokenKind.stringLit "say \"hi\"" + , TokenKind.stringLit "path\\to" + , TokenKind.eof + ] := by + native_decide + +example : + tokenize "\"line\\nnext\" \"col\\tvalue\"" + = + [ TokenKind.stringLit "line\nnext" + , TokenKind.stringLit "col\tvalue" + , TokenKind.eof + ] := by + native_decide + +example : + tokenize "\"row\\rnext\"" + = + [ TokenKind.stringLit "row\rnext" + , TokenKind.eof + ] := by + native_decide + +example : + tokenize "\"bad\\q\"" + = + [ TokenKind.error "invalid escape: \\q" + , TokenKind.error "unexpected EOF in string" + , TokenKind.eof + ] := by + native_decide + +example : + tokenizeLocated "let x = 1\n @" + = + [ { kind := TokenKind.let_, start := { line := 1, column := 1 }, stop := { line := 1, column := 4 } } + , { kind := TokenKind.identifier "x", start := { line := 1, column := 5 }, stop := { line := 1, column := 6 } } + , { kind := TokenKind.equals, start := { line := 1, column := 7 }, stop := { line := 1, column := 8 } } + , { kind := TokenKind.number 1, start := { line := 1, column := 9 }, stop := { line := 1, column := 10 } } + , { kind := TokenKind.newline, start := { line := 1, column := 10 }, stop := { line := 2, column := 1 } } + , { kind := TokenKind.error "unexpected char: @", start := { line := 2, column := 3 }, stop := { line := 2, column := 4 } } + , { kind := TokenKind.eof, start := { line := 2, column := 4 }, stop := { line := 2, column := 4 } } + ] := by + native_decide + +example : + tokenizeLocated "let x = 1\r @" + = + [ { kind := TokenKind.let_, start := { line := 1, column := 1 }, stop := { line := 1, column := 4 } } + , { kind := TokenKind.identifier "x", start := { line := 1, column := 5 }, stop := { line := 1, column := 6 } } + , { kind := TokenKind.equals, start := { line := 1, column := 7 }, stop := { line := 1, column := 8 } } + , { kind := TokenKind.number 1, start := { line := 1, column := 9 }, stop := { line := 1, column := 10 } } + , { kind := TokenKind.newline, start := { line := 1, column := 10 }, stop := { line := 2, column := 1 } } + , { kind := TokenKind.error "unexpected char: @", start := { line := 2, column := 3 }, stop := { line := 2, column := 4 } } + , { kind := TokenKind.eof, start := { line := 2, column := 4 }, stop := { line := 2, column := 4 } } + ] := by + native_decide + +example : + tokenizeLocated "let x = 1\r\n @" + = + [ { kind := TokenKind.let_, start := { line := 1, column := 1 }, stop := { line := 1, column := 4 } } + , { kind := TokenKind.identifier "x", start := { line := 1, column := 5 }, stop := { line := 1, column := 6 } } + , { kind := TokenKind.equals, start := { line := 1, column := 7 }, stop := { line := 1, column := 8 } } + , { kind := TokenKind.number 1, start := { line := 1, column := 9 }, stop := { line := 1, column := 10 } } + , { kind := TokenKind.newline, start := { line := 1, column := 10 }, stop := { line := 2, column := 1 } } + , { kind := TokenKind.error "unexpected char: @", start := { line := 2, column := 3 }, stop := { line := 2, column := 4 } } + , { kind := TokenKind.eof, start := { line := 2, column := 4 }, stop := { line := 2, column := 4 } } + ] := by + native_decide + +example : + tokenizeLocated "let x = 1 /* one\n two */~" + = + [ { kind := TokenKind.let_, start := { line := 1, column := 1 }, stop := { line := 1, column := 4 } } + , { kind := TokenKind.identifier "x", start := { line := 1, column := 5 }, stop := { line := 1, column := 6 } } + , { kind := TokenKind.equals, start := { line := 1, column := 7 }, stop := { line := 1, column := 8 } } + , { kind := TokenKind.number 1, start := { line := 1, column := 9 }, stop := { line := 1, column := 10 } } + , { kind := TokenKind.tilde, start := { line := 2, column := 8 }, stop := { line := 2, column := 9 } } + , { kind := TokenKind.eof, start := { line := 2, column := 9 }, stop := { line := 2, column := 9 } } + ] := by + native_decide + +example : + tokenizeLocated "let x = 1 /* never closes" + = + [ { kind := TokenKind.let_, start := { line := 1, column := 1 }, stop := { line := 1, column := 4 } } + , { kind := TokenKind.identifier "x", start := { line := 1, column := 5 }, stop := { line := 1, column := 6 } } + , { kind := TokenKind.equals, start := { line := 1, column := 7 }, stop := { line := 1, column := 8 } } + , { kind := TokenKind.number 1, start := { line := 1, column := 9 }, stop := { line := 1, column := 10 } } + , { kind := TokenKind.error "unterminated block comment", start := { line := 1, column := 11 }, stop := { line := 1, column := 26 } } + , { kind := TokenKind.eof, start := { line := 1, column := 26 }, stop := { line := 1, column := 26 } } + ] := by + native_decide + +example : + tokenizeLocated "\"bad\n" + = + [ { kind := TokenKind.error "unterminated string", start := { line := 1, column := 1 }, stop := { line := 1, column := 5 } } + , { kind := TokenKind.newline, start := { line := 1, column := 5 }, stop := { line := 2, column := 1 } } + , { kind := TokenKind.eof, start := { line := 2, column := 1 }, stop := { line := 2, column := 1 } } + ] := by + native_decide + +example : + (tokenizeLocated "1..10").map LocatedToken.span + = + [ { start := { line := 1, column := 1 }, stop := { line := 1, column := 2 } } + , { start := { line := 1, column := 2 }, stop := { line := 1, column := 4 } } + , { start := { line := 1, column := 4 }, stop := { line := 1, column := 6 } } + , { start := { line := 1, column := 6 }, stop := { line := 1, column := 6 } } + ] := by + native_decide + +example : + tokenizeLocated "let x = 1; let y = 2" + = + [ { kind := TokenKind.let_, start := { line := 1, column := 1 }, stop := { line := 1, column := 4 } } + , { kind := TokenKind.identifier "x", start := { line := 1, column := 5 }, stop := { line := 1, column := 6 } } + , { kind := TokenKind.equals, start := { line := 1, column := 7 }, stop := { line := 1, column := 8 } } + , { kind := TokenKind.number 1, start := { line := 1, column := 9 }, stop := { line := 1, column := 10 } } + , { kind := TokenKind.semicolon, start := { line := 1, column := 10 }, stop := { line := 1, column := 11 } } + , { kind := TokenKind.let_, start := { line := 1, column := 12 }, stop := { line := 1, column := 15 } } + , { kind := TokenKind.identifier "y", start := { line := 1, column := 16 }, stop := { line := 1, column := 17 } } + , { kind := TokenKind.equals, start := { line := 1, column := 18 }, stop := { line := 1, column := 19 } } + , { kind := TokenKind.number 2, start := { line := 1, column := 20 }, stop := { line := 1, column := 21 } } + , { kind := TokenKind.eof, start := { line := 1, column := 21 }, stop := { line := 1, column := 21 } } + ] := by + native_decide + +example : + tokenizeLocated "🦭 until" + = + [ { kind := TokenKind.seal, start := { line := 1, column := 1 }, stop := { line := 1, column := 2 } } + , { kind := TokenKind.until, start := { line := 1, column := 3 }, stop := { line := 1, column := 8 } } + , { kind := TokenKind.eof, start := { line := 1, column := 8 }, stop := { line := 1, column := 8 } } + ] := by + native_decide + +example : + tokenKinds (tokenizeLocated "let x = 1..10") + = + tokenize "let x = 1..10" := by + native_decide + +example : + tokenKinds (tokenizeLocated "if true { x = x + 1 } // done\n~") + = + tokenize "if true { x = x + 1 } // done\n~" := by + native_decide + +example : + tokenKinds (tokenizeLocated "let x = 1 /* one\n two */~") + = + tokenize "let x = 1 /* one\n two */~" := by + native_decide + +example : + tokenKinds (tokenizeLocated "let x = 1 /* outer /* inner */ still comment */ ~") + = + tokenize "let x = 1 /* outer /* inner */ still comment */ ~" := by + native_decide + +example : + tokenKinds (tokenizeLocated "\"open\" \"bad\n") + = + tokenize "\"open\" \"bad\n" := by + native_decide + +example : + tokenKinds (tokenizeLocated "\"line\\nnext\" \"bad\\q\"") + = + tokenize "\"line\\nnext\" \"bad\\q\"" := by + native_decide + +example : + tokenKinds (tokenizeLocated "\"row\\rnext\"") + = + tokenize "\"row\\rnext\"" := by + native_decide + +example : + tokenKinds (tokenizeLocated "🦭 until x == 3 { break }") + = + tokenize "🦭 until x == 3 { break }" := by + native_decide + +end Lexer +end Aether diff --git a/Aether/Parser.lean b/Aether/Parser.lean new file mode 100644 index 0000000..e93d515 --- /dev/null +++ b/Aether/Parser.lean @@ -0,0 +1,1569 @@ +import Aether.Core +import Aether.Lexer + +namespace Aether +namespace Parser + +abbrev Tokens := List Lexer.TokenKind + +inductive ParseContext where + | expression + | statement + | ifStatement + | block + | range + | params + | typeAnnotation + | terminator + | programEnd + deriving Repr, BEq, DecidableEq, Inhabited + +inductive ParseError where + | expected : ParseContext -> Option Lexer.TokenKind -> ParseError + deriving Repr, BEq, DecidableEq, Inhabited + +def skipTerminators : Tokens -> Tokens + | Lexer.TokenKind.newline :: rest => skipTerminators rest + | Lexer.TokenKind.tilde :: rest => skipTerminators rest + | Lexer.TokenKind.semicolon :: rest => skipTerminators rest + | tokens => tokens + +def skipBinaryRhsNewlines : Tokens -> Tokens + | Lexer.TokenKind.newline :: rest => skipBinaryRhsNewlines rest + | tokens => tokens + +def skipAssignRhsNewlines : Tokens -> Tokens + | Lexer.TokenKind.newline :: rest => skipAssignRhsNewlines rest + | tokens => tokens + +def skipConditionNewlines : Tokens -> Tokens + | Lexer.TokenKind.newline :: rest => skipConditionNewlines rest + | tokens => tokens + +partial def parseLeftAssoc + (parseAtom : Tokens -> Option (Expr × Tokens)) + (operator : Lexer.TokenKind -> Option BinOp) + (tokens : Tokens) : Option (Expr × Tokens) := do + let (first, remaining) <- parseAtom tokens + let rec loop (left : Expr) : Tokens -> Option (Expr × Tokens) + | token :: rest => + match operator token with + | some op => do + let (right, afterRight) <- parseAtom (skipBinaryRhsNewlines rest) + loop (Expr.binary left op right) afterRight + | none => some (left, token :: rest) + | [] => some (left, []) + loop first remaining + +def mulOperator : Lexer.TokenKind -> Option BinOp + | Lexer.TokenKind.star => some BinOp.mul + | Lexer.TokenKind.slash => some BinOp.div + | Lexer.TokenKind.percent => some BinOp.mod + | _ => none + +def addOperator : Lexer.TokenKind -> Option BinOp + | Lexer.TokenKind.plus => some BinOp.add + | Lexer.TokenKind.minus => some BinOp.sub + | _ => none + +def comparisonOperator : Lexer.TokenKind -> Option BinOp + | Lexer.TokenKind.less => some BinOp.lt + | Lexer.TokenKind.greater => some BinOp.gt + | Lexer.TokenKind.lessEq => some BinOp.le + | Lexer.TokenKind.greaterEq => some BinOp.ge + | _ => none + +def equalityOperator : Lexer.TokenKind -> Option BinOp + | Lexer.TokenKind.eqEq => some BinOp.eq + | Lexer.TokenKind.notEq => some BinOp.neq + | _ => none + +def andOperator : Lexer.TokenKind -> Option BinOp + | Lexer.TokenKind.and => some BinOp.and + | _ => none + +def orOperator : Lexer.TokenKind -> Option BinOp + | Lexer.TokenKind.or => some BinOp.or + | _ => none + +def flexibleIdent? : Lexer.TokenKind -> Option Ident + | Lexer.TokenKind.identifier name => some name + | Lexer.TokenKind.dim => some "dim" + | Lexer.TokenKind.tau => some "tau" + | Lexer.TokenKind.model => some "model" + | Lexer.TokenKind.color => some "color" + | Lexer.TokenKind.axis => some "axis" + | Lexer.TokenKind.project => some "project" + | Lexer.TokenKind.cluster => some "cluster" + | Lexer.TokenKind.center => some "center" + | Lexer.TokenKind.spread => some "spread" + | Lexer.TokenKind.format => some "format" + | Lexer.TokenKind.output => some "output" + | Lexer.TokenKind.escalate => some "escalate" + | Lexer.TokenKind.convergence => some "convergence" + | _ => none + +def skipListNewlines : Tokens -> Tokens + | Lexer.TokenKind.newline :: rest => skipListNewlines rest + | tokens => tokens + +def skipArgNewlines : Tokens -> Tokens + | Lexer.TokenKind.newline :: rest => skipArgNewlines rest + | tokens => tokens + +def skipParenNewlines : Tokens -> Tokens + | Lexer.TokenKind.newline :: rest => skipParenNewlines rest + | tokens => tokens + +def skipIndexNewlines : Tokens -> Tokens + | Lexer.TokenKind.newline :: rest => skipIndexNewlines rest + | tokens => tokens + +def skipMemberNewlines : Tokens -> Tokens + | Lexer.TokenKind.newline :: rest => skipMemberNewlines rest + | tokens => tokens + +def skipCallOpenNewlines : Tokens -> Tokens + | Lexer.TokenKind.newline :: rest => skipCallOpenNewlines rest + | tokens => tokens + +def skipFnParamOpenNewlines : Tokens -> Tokens + | Lexer.TokenKind.newline :: rest => skipFnParamOpenNewlines rest + | tokens => tokens + +def skipFnParamOpenNewlinesWithOffset (offset : Nat) : Tokens -> Nat × Tokens + | Lexer.TokenKind.newline :: rest => skipFnParamOpenNewlinesWithOffset (offset + 1) rest + | tokens => (offset, tokens) + +def skipUnaryNewlines : Tokens -> Tokens + | Lexer.TokenKind.newline :: rest => skipUnaryNewlines rest + | tokens => tokens + +mutual + partial def parseListLiteral : Tokens -> Option (List Expr × Tokens) + | tokens => do + match skipListNewlines tokens with + | Lexer.TokenKind.rBracket :: rest => some ([], rest) + | tokens => do + let (item, afterItem) <- parseExpr tokens + match skipListNewlines afterItem with + | Lexer.TokenKind.comma :: rest => + match skipListNewlines rest with + | Lexer.TokenKind.rBracket :: afterBracket => some ([item], afterBracket) + | afterComma => do + let (items, afterItems) <- parseListLiteral afterComma + some (item :: items, afterItems) + | Lexer.TokenKind.rBracket :: rest => some ([item], rest) + | _ => none + + partial def parseArgList : Tokens -> Option (List Arg × Tokens) + | tokens => do + match skipArgNewlines tokens with + | Lexer.TokenKind.rParen :: rest => some ([], rest) + | nameToken :: Lexer.TokenKind.equals :: rest => do + let name <- flexibleIdent? nameToken + let (value, afterValue) <- parseExpr (skipArgNewlines rest) + match skipArgNewlines afterValue with + | Lexer.TokenKind.comma :: afterComma => + match skipArgNewlines afterComma with + | Lexer.TokenKind.rParen :: afterParen => some ([Arg.named name value], afterParen) + | afterComma => do + let (args, afterArgs) <- parseArgList afterComma + some (Arg.named name value :: args, afterArgs) + | Lexer.TokenKind.rParen :: afterParen => some ([Arg.named name value], afterParen) + | _ => none + | tokens => do + let (arg, afterArg) <- parseExpr tokens + match skipArgNewlines afterArg with + | Lexer.TokenKind.comma :: rest => + match skipArgNewlines rest with + | Lexer.TokenKind.rParen :: afterParen => some ([Arg.positional arg], afterParen) + | afterComma => do + let (args, afterArgs) <- parseArgList afterComma + some (Arg.positional arg :: args, afterArgs) + | Lexer.TokenKind.rParen :: rest => some ([Arg.positional arg], rest) + | _ => none + + partial def parsePrimary : Tokens -> Option (Expr × Tokens) + | Lexer.TokenKind.number n :: rest => some (Expr.num n, rest) + | Lexer.TokenKind.float intPart fracMicros :: rest => some (Expr.float intPart fracMicros, rest) + | Lexer.TokenKind.true :: rest => some (Expr.bool true, rest) + | Lexer.TokenKind.false :: rest => some (Expr.bool false, rest) + | Lexer.TokenKind.stringLit value :: rest => some (Expr.str value, rest) + | Lexer.TokenKind.identifier "unit" :: rest => some (Expr.unit, rest) + | Lexer.TokenKind.lBracket :: rest => do + let (items, afterItems) <- parseListLiteral rest + some (Expr.list items, afterItems) + | Lexer.TokenKind.identifier name :: rest => + match skipCallOpenNewlines rest with + | Lexer.TokenKind.lParen :: afterOpen => do + let (args, afterArgs) <- parseArgList afterOpen + some (Expr.call name args, afterArgs) + | _ => some (Expr.var name, rest) + | Lexer.TokenKind.embed :: rest => + match skipCallOpenNewlines rest with + | Lexer.TokenKind.lParen :: afterOpen => do + let (args, afterArgs) <- parseArgList afterOpen + some (Expr.call "embed" args, afterArgs) + | _ => none + | Lexer.TokenKind.convergence :: rest => + match skipCallOpenNewlines rest with + | Lexer.TokenKind.lParen :: afterOpen => do + let (args, afterArgs) <- parseArgList afterOpen + some (Expr.call "convergence" args, afterArgs) + | _ => none + | Lexer.TokenKind.self :: rest => some (Expr.var "self", rest) + | Lexer.TokenKind.lParen :: rest => do + let (expr, afterExpr) <- parseExpr (skipParenNewlines rest) + match skipParenNewlines afterExpr with + | Lexer.TokenKind.rParen :: afterParen => some (expr, afterParen) + | _ => none + | _ => none + + partial def parsePostfixLoop (left : Expr) : Tokens -> Option (Expr × Tokens) + | Lexer.TokenKind.lBracket :: rest => do + let (index, afterIndex) <- parseExpr (skipIndexNewlines rest) + match skipIndexNewlines afterIndex with + | Lexer.TokenKind.rBracket :: afterBracket => + parsePostfixLoop (Expr.index left index) afterBracket + | _ => none + | Lexer.TokenKind.dot :: rest => + match skipMemberNewlines rest with + | nameToken :: Lexer.TokenKind.lParen :: afterName => do + let method <- flexibleIdent? nameToken + let (args, afterArgs) <- parseArgList afterName + parsePostfixLoop (Expr.method left method args) afterArgs + | nameToken :: afterName => do + let field <- flexibleIdent? nameToken + parsePostfixLoop (Expr.field left field) afterName + | _ => none + | tokens => some (left, tokens) + + partial def parsePostfix (tokens : Tokens) : Option (Expr × Tokens) := do + let (primary, remaining) <- parsePrimary tokens + parsePostfixLoop primary remaining + + partial def parseUnary : Tokens -> Option (Expr × Tokens) + | Lexer.TokenKind.minus :: rest => do + let (expr, remaining) <- parseUnary (skipUnaryNewlines rest) + some (Expr.unary UnOp.neg expr, remaining) + | Lexer.TokenKind.not :: rest => do + let (expr, remaining) <- parseUnary (skipUnaryNewlines rest) + some (Expr.unary UnOp.not expr, remaining) + | tokens => parsePostfix tokens + + partial def parseMul (tokens : Tokens) : Option (Expr × Tokens) := + parseLeftAssoc parseUnary mulOperator tokens + + partial def parseAdd (tokens : Tokens) : Option (Expr × Tokens) := + parseLeftAssoc parseMul addOperator tokens + + partial def parseComparison (tokens : Tokens) : Option (Expr × Tokens) := + parseLeftAssoc parseAdd comparisonOperator tokens + + partial def parseEquality (tokens : Tokens) : Option (Expr × Tokens) := + parseLeftAssoc parseComparison equalityOperator tokens + + partial def parseAnd (tokens : Tokens) : Option (Expr × Tokens) := + parseLeftAssoc parseEquality andOperator tokens + + partial def parseOr (tokens : Tokens) : Option (Expr × Tokens) := + parseLeftAssoc parseAnd orOperator tokens + + partial def parseExpr (tokens : Tokens) : Option (Expr × Tokens) := + parseOr tokens +end + +def expectTerminator : Tokens -> Option Tokens + | Lexer.TokenKind.newline :: rest => some (skipTerminators rest) + | Lexer.TokenKind.tilde :: rest => some (skipTerminators rest) + | Lexer.TokenKind.semicolon :: rest => some (skipTerminators rest) + | tokens@(Lexer.TokenKind.rBrace :: _) => some tokens + | Lexer.TokenKind.eof :: rest => some (Lexer.TokenKind.eof :: rest) + | [] => some [] + | _ => none + +def parseSignedInt : Tokens -> Option (Int × Tokens) + | Lexer.TokenKind.number value :: rest => some (value, rest) + | Lexer.TokenKind.minus :: Lexer.TokenKind.number value :: rest => some (-value, rest) + | _ => none + +def parseIntRange (tokens : Tokens) : Option (Int × Int × Tokens) := do + let (start, afterStart) <- parseSignedInt tokens + match afterStart with + | Lexer.TokenKind.dotDot :: afterDot => do + let (stop, rest) <- parseSignedInt afterDot + some (start, stop, rest) + | _ => none + +def skipParamNewlines : Tokens -> Tokens + | Lexer.TokenKind.newline :: rest => skipParamNewlines rest + | tokens => tokens + +def skipTypeNewlines : Tokens -> Tokens + | Lexer.TokenKind.newline :: rest => skipTypeNewlines rest + | tokens => tokens + +def skipTypeNewlinesWithOffset : Nat -> Tokens -> Nat × Tokens + | offset, Lexer.TokenKind.newline :: rest => skipTypeNewlinesWithOffset (offset + 1) rest + | offset, tokens => (offset, tokens) + +mutual + partial def parseParamList : Tokens -> Option (List Ident × Tokens) + | tokens => + match skipParamNewlines tokens with + | Lexer.TokenKind.rParen :: rest => some ([], rest) + | Lexer.TokenKind.identifier name :: rest => + match skipParamNewlines rest with + | Lexer.TokenKind.comma :: afterComma => + match skipParamNewlines afterComma with + | Lexer.TokenKind.rParen :: afterParen => some ([name], afterParen) + | afterComma => do + let (params, afterParams) <- parseParamList afterComma + some (name :: params, afterParams) + | Lexer.TokenKind.rParen :: afterParen => some ([name], afterParen) + | _ => none + | _ => none + + partial def parseAnnTy : Tokens -> Option (AnnTy × Tokens) + | tokens => + match skipTypeNewlines tokens with + | Lexer.TokenKind.identifier "num" :: rest => some (AnnTy.num, rest) + | Lexer.TokenKind.identifier "bool" :: rest => some (AnnTy.bool, rest) + | Lexer.TokenKind.identifier "str" :: rest => some (AnnTy.str, rest) + | Lexer.TokenKind.identifier "unit" :: rest => some (AnnTy.unit, rest) + | Lexer.TokenKind.identifier "list" :: Lexer.TokenKind.lBracket :: rest => do + let (elementTy, afterElementTy) <- parseAnnTy (skipTypeNewlines rest) + match skipTypeNewlines afterElementTy with + | Lexer.TokenKind.rBracket :: afterBracket => some (AnnTy.list elementTy, afterBracket) + | _ => none + | _ => none + + partial def parseTypedParamList : Tokens -> Option (List (Ident × AnnTy) × Tokens) + | tokens => + match skipParamNewlines tokens with + | Lexer.TokenKind.rParen :: rest => some ([], rest) + | Lexer.TokenKind.identifier name :: Lexer.TokenKind.colon :: rest => do + let (ty, afterTy) <- parseAnnTy (skipParamNewlines rest) + match skipParamNewlines afterTy with + | Lexer.TokenKind.comma :: afterComma => + match skipParamNewlines afterComma with + | Lexer.TokenKind.rParen :: afterParen => some ([(name, ty)], afterParen) + | afterComma => do + let (params, afterParams) <- parseTypedParamList afterComma + some ((name, ty) :: params, afterParams) + | Lexer.TokenKind.rParen :: afterParen => some ([(name, ty)], afterParen) + | _ => none + | _ => none + + partial def parseBlock : Tokens -> Option (List Stmt × Tokens) + | tokens => + match skipTerminators tokens with + | Lexer.TokenKind.lBrace :: rest => parseBlockBody (skipTerminators rest) + | _ => none + + partial def parseBlockBody : Tokens -> Option (List Stmt × Tokens) + | [] => none + | Lexer.TokenKind.eof :: _ => none + | tokens => do + let tokens := skipTerminators tokens + match tokens with + | Lexer.TokenKind.rBrace :: rest => some ([], skipTerminators rest) + | [] => none + | Lexer.TokenKind.eof :: _ => none + | _ => + let (stmt, afterStmt) <- parseStmt tokens + let (rest, afterBlock) <- parseBlockBody afterStmt + some (stmt :: rest, afterBlock) + + partial def parseStmt : Tokens -> Option (Stmt × Tokens) + | Lexer.TokenKind.if_ :: rest => do + let (condition, afterCondition) <- parseExpr (skipConditionNewlines rest) + let (thenBranch, afterThen) <- parseBlock afterCondition + match afterThen with + | Lexer.TokenKind.else_ :: afterElse => do + let (elseBranch, afterElseBlock) <- parseBlock afterElse + some (Stmt.ifThenElse condition thenBranch (some elseBranch), afterElseBlock) + | _ => + some (Stmt.ifThenElse condition thenBranch none, afterThen) + | Lexer.TokenKind.while :: rest => do + let (condition, afterCondition) <- parseExpr (skipConditionNewlines rest) + let (body, afterBody) <- parseBlock afterCondition + some (Stmt.while condition body, afterBody) + | Lexer.TokenKind.for :: Lexer.TokenKind.identifier iterator :: Lexer.TokenKind.in_ :: rest => do + let (start, stop, afterRange) <- parseIntRange rest + let (body, afterBody) <- parseBlock afterRange + some (Stmt.forRange iterator start stop body, afterBody) + | Lexer.TokenKind.seal :: Lexer.TokenKind.until :: rest => do + let (condition, afterCondition) <- parseExpr (skipConditionNewlines rest) + let (body, afterBody) <- parseBlock afterCondition + some (Stmt.seal (some condition) body, afterBody) + | Lexer.TokenKind.seal :: rest => do + let (body, afterBody) <- parseBlock rest + some (Stmt.seal none body, afterBody) + | Lexer.TokenKind.fn :: Lexer.TokenKind.identifier name :: rest => do + match skipFnParamOpenNewlines rest with + | Lexer.TokenKind.lParen :: afterOpen => + match parseTypedParamList afterOpen with + | some (params, afterParams) => + match afterParams with + | Lexer.TokenKind.colon :: afterColon => do + let (returnTy, afterReturnTy) <- parseAnnTy afterColon + let (body, afterBody) <- parseBlock afterReturnTy + some (Stmt.fnDeclTypedReturn name params returnTy body, afterBody) + | _ => do + let (body, afterBody) <- parseBlock afterParams + some (Stmt.fnDeclTyped name params body, afterBody) + | none => do + let (params, afterParams) <- parseParamList afterOpen + match afterParams with + | Lexer.TokenKind.colon :: afterColon => do + let (returnTy, afterReturnTy) <- parseAnnTy afterColon + let (body, afterBody) <- parseBlock afterReturnTy + some (Stmt.fnDeclReturn name params returnTy body, afterBody) + | _ => do + let (body, afterBody) <- parseBlock afterParams + some (Stmt.fnDecl name params body, afterBody) + | _ => none + | Lexer.TokenKind.let_ :: Lexer.TokenKind.identifier name :: Lexer.TokenKind.colon :: rest => do + let (ty, afterTy) <- parseAnnTy rest + match afterTy with + | Lexer.TokenKind.equals :: afterEquals => do + let (value, afterExpr) <- parseExpr (skipAssignRhsNewlines afterEquals) + let remaining <- expectTerminator afterExpr + some (Stmt.letDeclTyped name ty value, remaining) + | _ => none + | Lexer.TokenKind.let_ :: Lexer.TokenKind.identifier name :: Lexer.TokenKind.equals :: rest => do + let (value, afterExpr) <- parseExpr (skipAssignRhsNewlines rest) + let remaining <- expectTerminator afterExpr + some (Stmt.letDecl name value, remaining) + | Lexer.TokenKind.identifier name :: Lexer.TokenKind.equals :: rest => do + let (value, afterExpr) <- parseExpr (skipAssignRhsNewlines rest) + let remaining <- expectTerminator afterExpr + some (Stmt.assign name value, remaining) + | Lexer.TokenKind.return :: rest => do + match rest with + | Lexer.TokenKind.newline :: _ => + let remaining <- expectTerminator rest + some (Stmt.ret none, remaining) + | Lexer.TokenKind.tilde :: _ => + let remaining <- expectTerminator rest + some (Stmt.ret none, remaining) + | Lexer.TokenKind.semicolon :: _ => + let remaining <- expectTerminator rest + some (Stmt.ret none, remaining) + | Lexer.TokenKind.rBrace :: _ => + some (Stmt.ret none, rest) + | Lexer.TokenKind.eof :: _ => + some (Stmt.ret none, rest) + | [] => some (Stmt.ret none, []) + | _ => do + let (value, afterExpr) <- parseExpr rest + let remaining <- expectTerminator afterExpr + some (Stmt.ret (some value), remaining) + | Lexer.TokenKind.break :: rest => do + let remaining <- expectTerminator rest + some (Stmt.break, remaining) + | Lexer.TokenKind.continue :: rest => do + let remaining <- expectTerminator rest + some (Stmt.continue, remaining) + | tokens => do + let (expr, afterExpr) <- parseExpr tokens + let remaining <- expectTerminator afterExpr + some (Stmt.expr expr, remaining) + + partial def parseProgramFromTokens : Tokens -> Option (List Stmt) + | [] => some [] + | Lexer.TokenKind.eof :: _ => some [] + | tokens => do + let tokens := skipTerminators tokens + match tokens with + | [] => some [] + | Lexer.TokenKind.eof :: _ => some [] + | _ => + let (stmt, afterStmt) <- parseStmt tokens + let rest <- parseProgramFromTokens afterStmt + some (stmt :: rest) +end + +def parseProgram (source : String) : Option (List Stmt) := + parseProgramFromTokens (Lexer.tokenize source) + +def firstToken? : Tokens -> Option Lexer.TokenKind + | [] => none + | token :: _ => some token + +def tokenAt? : Tokens -> Nat -> Option Lexer.TokenKind + | [], _ => none + | token :: _, 0 => some token + | _ :: rest, index + 1 => tokenAt? rest index + +def startsExpr : Lexer.TokenKind -> Bool + | Lexer.TokenKind.number _ => true + | Lexer.TokenKind.float _ _ => true + | Lexer.TokenKind.true => true + | Lexer.TokenKind.false => true + | Lexer.TokenKind.stringLit _ => true + | Lexer.TokenKind.lBracket => true + | Lexer.TokenKind.identifier _ => true + | Lexer.TokenKind.self => true + | Lexer.TokenKind.lParen => true + | Lexer.TokenKind.minus => true + | Lexer.TokenKind.not => true + | _ => false + +def startsExprPrefix : Tokens -> Bool + | Lexer.TokenKind.embed :: Lexer.TokenKind.lParen :: _ => true + | Lexer.TokenKind.convergence :: Lexer.TokenKind.lParen :: _ => true + | token :: _ => startsExpr token + | [] => false + +def invalidExprStartOffset? (offset : Nat) : Tokens -> Option Nat + | tokens@(_ :: _) => if startsExprPrefix tokens then none else some offset + | [] => some offset + +def isBinOpToken : Lexer.TokenKind -> Bool + | Lexer.TokenKind.plus => true + | Lexer.TokenKind.minus => true + | Lexer.TokenKind.star => true + | Lexer.TokenKind.slash => true + | Lexer.TokenKind.percent => true + | Lexer.TokenKind.less => true + | Lexer.TokenKind.greater => true + | Lexer.TokenKind.lessEq => true + | Lexer.TokenKind.greaterEq => true + | Lexer.TokenKind.eqEq => true + | Lexer.TokenKind.notEq => true + | Lexer.TokenKind.and => true + | Lexer.TokenKind.or => true + | _ => false + +def isExprTerminator : Lexer.TokenKind -> Bool + | Lexer.TokenKind.newline => true + | Lexer.TokenKind.tilde => true + | Lexer.TokenKind.semicolon => true + | Lexer.TokenKind.rBrace => true + | Lexer.TokenKind.eof => true + | _ => false + +def isConditionTerminator : Lexer.TokenKind -> Bool + | Lexer.TokenKind.lBrace => true + | token => isExprTerminator token + +def trailingBinOpOffset? (offset : Nat) : Tokens -> Option Nat + | token :: next :: _ => + if isBinOpToken token && isExprTerminator next then + some offset + else + none + | [token] => + if isBinOpToken token then some offset else none + | [] => none + +partial def trailingBinOpInExprOffset? (offset : Nat) : Tokens -> Option Nat + | [] => none + | token :: rest => + match trailingBinOpOffset? offset (token :: rest) with + | some found => some found + | none => + if isExprTerminator token then + none + else + trailingBinOpInExprOffset? (offset + 1) rest + +def trailingBinOpInConditionOffset? (offset : Nat) : Tokens -> Option Nat + | token :: next :: rest => + if isBinOpToken token && isConditionTerminator next then + some offset + else if isConditionTerminator token then + none + else + trailingBinOpInConditionOffset? (offset + 1) (next :: rest) + | [token] => + if isBinOpToken token then some offset else none + | [] => none + +def hasParamListPrefix : Tokens -> Bool + | Lexer.TokenKind.rParen :: _ => true + | Lexer.TokenKind.identifier _ :: Lexer.TokenKind.rParen :: _ => true + | Lexer.TokenKind.identifier _ :: Lexer.TokenKind.comma :: rest => hasParamListPrefix rest + | _ => false + +def hasSignedIntPrefix : Tokens -> Option Tokens + | Lexer.TokenKind.number _ :: rest => some rest + | Lexer.TokenKind.minus :: Lexer.TokenKind.number _ :: rest => some rest + | _ => none + +def hasIntRangePrefix (tokens : Tokens) : Bool := + match hasSignedIntPrefix tokens with + | some (Lexer.TokenKind.dotDot :: afterDot) => + match hasSignedIntPrefix afterDot with + | some _ => true + | none => false + | _ => false + +def typedParamListRemainder? (tokens : Tokens) : Option Tokens := + match parseTypedParamList tokens with + | some (_, rest) => some rest + | none => none + +def paramListRemainder? (tokens : Tokens) : Option Tokens := + match typedParamListRemainder? tokens with + | some rest => some rest + | none => + match parseParamList tokens with + | some (_, rest) => some rest + | none => none + +partial def annTyFailureOffset? (offset : Nat) : Tokens -> Option Nat + | tokens => + let (offset, tokens) := skipTypeNewlinesWithOffset offset tokens + match tokens with + | Lexer.TokenKind.identifier "num" :: _ => none + | Lexer.TokenKind.identifier "bool" :: _ => none + | Lexer.TokenKind.identifier "str" :: _ => none + | Lexer.TokenKind.identifier "unit" :: _ => none + | Lexer.TokenKind.identifier "list" :: Lexer.TokenKind.lBracket :: rest => + let (elementOffset, elementTokens) := skipTypeNewlinesWithOffset (offset + 2) rest + match parseAnnTy elementTokens with + | some (_, afterElementTy) => + let afterElementOffset := elementOffset + (elementTokens.length - afterElementTy.length) + let (closeOffset, closeTokens) := skipTypeNewlinesWithOffset afterElementOffset afterElementTy + match closeTokens with + | Lexer.TokenKind.rBracket :: _ => none + | _ => some closeOffset + | none => annTyFailureOffset? elementOffset elementTokens + | _ :: _ => some offset + | [] => some offset + +def malformedReturnTypeAfterParams? (tokens : Tokens) : Bool := + match paramListRemainder? tokens with + | some (Lexer.TokenKind.colon :: afterColon) => + match parseAnnTy afterColon with + | some _ => false + | none => true + | _ => false + +partial def malformedParamTypeOffset? (offset : Nat) : Tokens -> Option Nat + | Lexer.TokenKind.identifier _ :: Lexer.TokenKind.colon :: rest => + match parseAnnTy rest with + | some (_, afterTy@(Lexer.TokenKind.comma :: afterComma)) => + malformedParamTypeOffset? (offset + 2 + (rest.length - afterTy.length) + 1) afterComma + | some (_, Lexer.TokenKind.rParen :: _) => none + | some _ => none + | none => annTyFailureOffset? (offset + 2) rest + | Lexer.TokenKind.identifier _ :: Lexer.TokenKind.comma :: rest => + malformedParamTypeOffset? (offset + 2) rest + | Lexer.TokenKind.identifier _ :: Lexer.TokenKind.rParen :: _ => none + | Lexer.TokenKind.rParen :: _ => none + | _ => none + +def classifyStmtStart : Tokens -> ParseContext + | Lexer.TokenKind.if_ :: Lexer.TokenKind.lBrace :: _ => ParseContext.expression + | Lexer.TokenKind.if_ :: rest => + match trailingBinOpInConditionOffset? 1 rest with + | some _ => ParseContext.expression + | none => ParseContext.block + | Lexer.TokenKind.else_ :: _ => ParseContext.ifStatement + | Lexer.TokenKind.while :: Lexer.TokenKind.lBrace :: _ => ParseContext.expression + | Lexer.TokenKind.while :: rest => + match trailingBinOpInConditionOffset? 1 rest with + | some _ => ParseContext.expression + | none => ParseContext.block + | Lexer.TokenKind.for :: Lexer.TokenKind.identifier _ :: Lexer.TokenKind.in_ :: rest => + if hasIntRangePrefix rest then + ParseContext.block + else + ParseContext.range + | Lexer.TokenKind.for :: _ => ParseContext.range + | Lexer.TokenKind.seal :: Lexer.TokenKind.until :: _ => ParseContext.expression + | Lexer.TokenKind.seal :: _ => ParseContext.block + | Lexer.TokenKind.let_ :: Lexer.TokenKind.identifier _ :: Lexer.TokenKind.colon :: rest => + match parseAnnTy rest with + | some _ => ParseContext.expression + | none => ParseContext.typeAnnotation + | Lexer.TokenKind.fn :: Lexer.TokenKind.identifier _ :: rest => + match skipFnParamOpenNewlines rest with + | Lexer.TokenKind.lParen :: afterOpen => + if malformedReturnTypeAfterParams? afterOpen then + ParseContext.typeAnnotation + else if (malformedParamTypeOffset? 3 afterOpen).isSome then + ParseContext.typeAnnotation + else if hasParamListPrefix afterOpen || (typedParamListRemainder? afterOpen).isSome then + ParseContext.block + else + ParseContext.params + | _ => ParseContext.params + | Lexer.TokenKind.fn :: _ => ParseContext.params + | Lexer.TokenKind.let_ :: _ => ParseContext.expression + | Lexer.TokenKind.identifier _ :: Lexer.TokenKind.equals :: _ => ParseContext.expression + | Lexer.TokenKind.return :: _ => ParseContext.expression + | Lexer.TokenKind.break :: _ => ParseContext.terminator + | Lexer.TokenKind.continue :: _ => ParseContext.terminator + | _ => ParseContext.statement + +def diagnosticOffset : Tokens -> Nat + | Lexer.TokenKind.if_ :: rest => + match trailingBinOpInConditionOffset? 1 rest with + | some offset => offset + | none => + match invalidExprStartOffset? 1 rest with + | some offset => offset + | none => 0 + | Lexer.TokenKind.while :: rest => + match trailingBinOpInConditionOffset? 1 rest with + | some offset => offset + | none => + match invalidExprStartOffset? 1 rest with + | some offset => offset + | none => 0 + | Lexer.TokenKind.seal :: Lexer.TokenKind.until :: rest => + match trailingBinOpInConditionOffset? 2 rest with + | some offset => offset + | none => + match invalidExprStartOffset? 2 rest with + | some offset => offset + | none => 0 + | Lexer.TokenKind.let_ :: Lexer.TokenKind.identifier _ :: Lexer.TokenKind.colon :: rest => + match parseAnnTy rest with + | some _ => 0 + | none => + match annTyFailureOffset? 3 rest with + | some offset => offset + | none => 3 + | Lexer.TokenKind.fn :: Lexer.TokenKind.identifier _ :: rest => + let (openOffset, afterSkipped) := skipFnParamOpenNewlinesWithOffset 2 rest + match afterSkipped with + | Lexer.TokenKind.lParen :: afterOpen => + let paramStartOffset := openOffset + 1 + match malformedParamTypeOffset? paramStartOffset afterOpen with + | some offset => offset + | none => + match paramListRemainder? afterOpen with + | some (Lexer.TokenKind.colon :: afterColon) => + match parseAnnTy afterColon with + | some _ => 0 + | none => + let afterColonOffset := paramStartOffset + (afterOpen.length - (Lexer.TokenKind.colon :: afterColon).length) + 1 + match annTyFailureOffset? afterColonOffset afterColon with + | some offset => offset + | none => afterColonOffset + | _ => 0 + | _ => 0 + | Lexer.TokenKind.let_ :: Lexer.TokenKind.identifier _ :: Lexer.TokenKind.equals :: rest => + match trailingBinOpInExprOffset? 3 rest with + | some offset => offset + | none => + match invalidExprStartOffset? 3 rest with + | some offset => offset + | none => 0 + | Lexer.TokenKind.identifier _ :: Lexer.TokenKind.equals :: rest => + match trailingBinOpInExprOffset? 2 rest with + | some offset => offset + | none => + match invalidExprStartOffset? 2 rest with + | some offset => offset + | none => 0 + | Lexer.TokenKind.return :: rest => + match trailingBinOpInExprOffset? 1 rest with + | some offset => offset + | none => + match invalidExprStartOffset? 1 rest with + | some offset => offset + | none => 0 + | _ => 0 + +def parseProgramFromTokensDetailed (tokens : Tokens) : Except ParseError (List Stmt) := + match parseProgramFromTokens tokens with + | some stmts => Except.ok stmts + | none => + let stripped := skipTerminators tokens + let offset := diagnosticOffset stripped + Except.error (ParseError.expected (classifyStmtStart stripped) (tokenAt? stripped offset)) + +def parseProgramDetailed (source : String) : Except ParseError (List Stmt) := + parseProgramFromTokensDetailed (Lexer.tokenize source) + +def parseFailedAs (result : Except ParseError α) (err : ParseError) : Bool := + match result with + | Except.error found => found == err + | Except.ok _ => false + +example : + (parseExpr (Lexer.tokenize "1 + 2 * 3") + == + some + ( Expr.binary + (Expr.num 1) + BinOp.add + (Expr.binary (Expr.num 2) BinOp.mul (Expr.num 3)) + , [Lexer.TokenKind.eof] + )) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "1 +\n2 * 3") + == + some + ( Expr.binary + (Expr.num 1) + BinOp.add + (Expr.binary (Expr.num 2) BinOp.mul (Expr.num 3)) + , [Lexer.TokenKind.eof] + )) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "!(x == 0) || false") + == + some + ( Expr.binary + (Expr.unary + UnOp.not + (Expr.binary (Expr.var "x") BinOp.eq (Expr.num 0))) + BinOp.or + (Expr.bool false) + , [Lexer.TokenKind.eof] + )) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "true &&\n!false") + == + some + ( Expr.binary + (Expr.bool true) + BinOp.and + (Expr.unary UnOp.not (Expr.bool false)) + , [Lexer.TokenKind.eof] + )) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "!\nfalse") + == + some (Expr.unary UnOp.not (Expr.bool false), [Lexer.TokenKind.eof])) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "-\n3") + == + some (Expr.unary UnOp.neg (Expr.num 3), [Lexer.TokenKind.eof])) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "(\n1 + 2\n)") + == + some + ( Expr.binary (Expr.num 1) BinOp.add (Expr.num 2) + , [Lexer.TokenKind.eof] + )) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "unit") + == + some (Expr.unit, [Lexer.TokenKind.eof])) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "add(1, x * 2)") + == + some + ( Expr.call + "add" + [ Expr.num 1 + , Expr.binary (Expr.var "x") BinOp.mul (Expr.num 2) + ] + , [Lexer.TokenKind.eof] + )) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "add(1,\n2,)") + == + some + ( Expr.call + "add" + [Expr.num 1, Expr.num 2] + , [Lexer.TokenKind.eof] + )) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "embed(1)") + == + some + ( Expr.call + "embed" + [Expr.num 1] + , [Lexer.TokenKind.eof] + )) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "embed(data, dim=3)") + == + some + ( Expr.call + "embed" + [ Arg.positional (Expr.var "data") + , Arg.named "dim" (Expr.num 3) + ] + , [Lexer.TokenKind.eof] + )) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "convergence(0.1)") + == + some + ( Expr.call + "convergence" + [Expr.float 0 100000] + , [Lexer.TokenKind.eof] + )) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "convergence\n(0.1)") + == + some + ( Expr.call + "convergence" + [Expr.float 0 100000] + , [Lexer.TokenKind.eof] + )) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "self.cluster(axis=2)") + == + some + ( Expr.method + (Expr.var "self") + "cluster" + [Arg.named "axis" (Expr.num 2)] + , [Lexer.TokenKind.eof] + )) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "1.5") == + some (Expr.float 1 500000, [Lexer.TokenKind.eof])) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "\"open\"") == + some (Expr.str "open", [Lexer.TokenKind.eof])) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "self") == + some (Expr.var "self", [Lexer.TokenKind.eof])) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "self.length") == + some (Expr.field (Expr.var "self") "length", [Lexer.TokenKind.eof])) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "self.\nlength") == + some (Expr.field (Expr.var "self") "length", [Lexer.TokenKind.eof])) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "[]") == + some (Expr.list [], [Lexer.TokenKind.eof])) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "[1, true, \"open\"]") == + some (Expr.list [Expr.num 1, Expr.bool true, Expr.str "open"], [Lexer.TokenKind.eof])) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "[1, 2][0]") == + some + ( Expr.index + (Expr.list [Expr.num 1, Expr.num 2]) + (Expr.num 0) + , [Lexer.TokenKind.eof] + )) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "[1, 2][\n0\n]") == + some + ( Expr.index + (Expr.list [Expr.num 1, Expr.num 2]) + (Expr.num 0) + , [Lexer.TokenKind.eof] + )) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "[1, 2].length") == + some + ( Expr.field + (Expr.list [Expr.num 1, Expr.num 2]) + "length" + , [Lexer.TokenKind.eof] + )) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "self.dim") == + some (Expr.field (Expr.var "self") "dim", [Lexer.TokenKind.eof])) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "[1, 2].len()") == + some + ( Expr.method + (Expr.list [Expr.num 1, Expr.num 2]) + "len" + [] + , [Lexer.TokenKind.eof] + )) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "[1, 2].\nlen()") == + some + ( Expr.method + (Expr.list [Expr.num 1, Expr.num 2]) + "len" + [] + , [Lexer.TokenKind.eof] + )) = true := by + native_decide + +example : + (parseExpr (Lexer.tokenize "self.cluster()") == + some (Expr.method (Expr.var "self") "cluster" [], [Lexer.TokenKind.eof])) = true := by + native_decide + +example : + (parseProgram "let x = 1 + 2 * 3~x = x + 1~return x" + == + some + [ Stmt.letDecl + "x" + (Expr.binary + (Expr.num 1) + BinOp.add + (Expr.binary (Expr.num 2) BinOp.mul (Expr.num 3))) + , Stmt.assign + "x" + (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1)) + , Stmt.ret (some (Expr.var "x")) + ]) = true := by + native_decide + +example : + (parseProgram "let count: num = 1" + == + some [Stmt.letDeclTyped "count" AnnTy.num (Expr.num 1)]) = true := by + native_decide + +example : + (parseProgram "let total =\n1 + 2" + == + some + [Stmt.letDecl "total" (Expr.binary (Expr.num 1) BinOp.add (Expr.num 2))]) = true := by + native_decide + +example : + (parseProgram "let count: num =\n3" + == + some [Stmt.letDeclTyped "count" AnnTy.num (Expr.num 3)]) = true := by + native_decide + +example : + (parseProgram "let x = 1~x =\nx + 2" + == + some + [ Stmt.letDecl "x" (Expr.num 1) + , Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 2)) + ]) = true := by + native_decide + +example : + (parseProgram "break\ncontinue\nreturn" + == + some + [ Stmt.break + , Stmt.continue + , Stmt.ret none + ]) = true := by + native_decide + +example : + (parseBlock (Lexer.tokenize "{ let x = 1~x = x + 1 }") + == + some + ( [ Stmt.letDecl "x" (Expr.num 1) + , Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1)) + ] + , [Lexer.TokenKind.eof] + )) = true := by + native_decide + +example : + (parseProgram "if x < 3 { x = x + 1 } else { return x }" + == + some + [ Stmt.ifThenElse + (Expr.binary (Expr.var "x") BinOp.lt (Expr.num 3)) + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1))] + (some [Stmt.ret (some (Expr.var "x"))]) + ]) = true := by + native_decide + +example : + (parseProgram "if true\n{ return 1 }" + == + some + [ Stmt.ifThenElse + (Expr.bool true) + [Stmt.ret (some (Expr.num 1))] + none + ]) = true := by + native_decide + +example : + (parseProgram "if\ntrue { return 1 }" + == + some + [ Stmt.ifThenElse + (Expr.bool true) + [Stmt.ret (some (Expr.num 1))] + none + ]) = true := by + native_decide + +example : + (parseProgram "while x < 3 { x = x + 1~continue }" + == + some + [ Stmt.while + (Expr.binary (Expr.var "x") BinOp.lt (Expr.num 3)) + [ Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1)) + , Stmt.continue + ] + ]) = true := by + native_decide + +example : + (parseProgram "while\nx < 3 { x = x + 1~continue }" + == + some + [ Stmt.while + (Expr.binary (Expr.var "x") BinOp.lt (Expr.num 3)) + [ Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1)) + , Stmt.continue + ] + ]) = true := by + native_decide + +example : + (parseProgram "for i in 0..3 { x = x + i }" + == + some + [ Stmt.forRange + "i" + 0 + 3 + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.var "i"))] + ]) = true := by + native_decide + +example : + (parseProgram "for i in -2..2 { x = x + i }" + == + some + [ Stmt.forRange + "i" + (-2) + 2 + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.var "i"))] + ]) = true := by + native_decide + +example : + (parseProgram "seal until x == 3 { x = x + 1 }" + == + some + [ Stmt.seal + (some (Expr.binary (Expr.var "x") BinOp.eq (Expr.num 3))) + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1))] + ]) = true := by + native_decide + +example : + (parseProgram "seal until\nx == 3 { x = x + 1 }" + == + some + [ Stmt.seal + (some (Expr.binary (Expr.var "x") BinOp.eq (Expr.num 3))) + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1))] + ]) = true := by + native_decide + +example : + (parseProgram "seal { break }" + == + some + [ Stmt.seal none [Stmt.break] + ]) = true := by + native_decide + +example : + (parseProgram "fn add(a, b) { return a + b }~let y = add(1, 2)" + == + some + [ Stmt.fnDecl + "add" + ["a", "b"] + [Stmt.ret (some (Expr.binary (Expr.var "a") BinOp.add (Expr.var "b")))] + , Stmt.letDecl + "y" + (Expr.call "add" [Expr.num 1, Expr.num 2]) + ]) = true := by + native_decide + +example : + (parseProgram "fn add\n(a, b) { return a + b }" + == + some + [ Stmt.fnDecl + "add" + ["a", "b"] + [Stmt.ret (some (Expr.binary (Expr.var "a") BinOp.add (Expr.var "b")))] + ]) = true := by + native_decide + +example : + (parseProgram "fn add(a,\nb,) { return a + b }" + == + some + [ Stmt.fnDecl + "add" + ["a", "b"] + [Stmt.ret (some (Expr.binary (Expr.var "a") BinOp.add (Expr.var "b")))] + ]) = true := by + native_decide + +example : + (parseProgram "fn id(x: num) { return x }" + == + some + [ Stmt.fnDeclTyped + "id" + [("x", AnnTy.num)] + [Stmt.ret (some (Expr.var "x"))] + ]) = true := by + native_decide + +example : + (parseProgram "fn id(x: num): num { return x }" + == + some + [ Stmt.fnDeclTypedReturn + "id" + [("x", AnnTy.num)] + AnnTy.num + [Stmt.ret (some (Expr.var "x"))] + ]) = true := by + native_decide + +example : + (parseProgram "fn add(x: num,\ny: num,): num { return x + y }" + == + some + [ Stmt.fnDeclTypedReturn + "add" + [("x", AnnTy.num), ("y", AnnTy.num)] + AnnTy.num + [Stmt.ret (some (Expr.binary (Expr.var "x") BinOp.add (Expr.var "y")))] + ]) = true := by + native_decide + +example : + (parseProgram "fn id(x): num { return x }" + == + some + [ Stmt.fnDeclReturn + "id" + ["x"] + AnnTy.num + [Stmt.ret (some (Expr.var "x"))] + ]) = true := by + native_decide + +example : + (parseProgram "fn id\n(x: num): num { return x }" + == + some + [ Stmt.fnDeclTypedReturn + "id" + [("x", AnnTy.num)] + AnnTy.num + [Stmt.ret (some (Expr.var "x"))] + ]) = true := by + native_decide + +example : + (parseProgram "fn first(xs: list[num]): num { return xs[0] }" + == + some + [ Stmt.fnDeclTypedReturn + "first" + [("xs", AnnTy.list AnnTy.num)] + AnnTy.num + [Stmt.ret (some (Expr.index (Expr.var "xs") (Expr.num 0)))] + ]) = true := by + native_decide + +example : + (parseProgram "let xs: list[\nnum\n] = [1]" + == + some + [ Stmt.letDeclTyped + "xs" + (AnnTy.list AnnTy.num) + (Expr.list [Expr.num 1]) + ]) = true := by + native_decide + +example : + (parseProgram "fn first(xs: list[\nnum\n]): list[\nnum\n] { return xs }" + == + some + [ Stmt.fnDeclTypedReturn + "first" + [("xs", AnnTy.list AnnTy.num)] + (AnnTy.list AnnTy.num) + [Stmt.ret (some (Expr.var "xs"))] + ]) = true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "let x = 1 +") + (ParseError.expected ParseContext.expression (some Lexer.TokenKind.plus)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "let x = self +") + (ParseError.expected ParseContext.expression (some Lexer.TokenKind.plus)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "let x = 1.5 +") + (ParseError.expected ParseContext.expression (some Lexer.TokenKind.plus)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "let x = embed") + (ParseError.expected ParseContext.expression (some Lexer.TokenKind.embed)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "let x = convergence") + (ParseError.expected ParseContext.expression (some Lexer.TokenKind.convergence)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "for i in 0 { break }") + (ParseError.expected ParseContext.range (some Lexer.TokenKind.for)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "for i in 0..3") + (ParseError.expected ParseContext.block (some Lexer.TokenKind.for)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "for i in -2..2") + (ParseError.expected ParseContext.block (some Lexer.TokenKind.for)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "if { break }") + (ParseError.expected ParseContext.expression (some Lexer.TokenKind.lBrace)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "if 1 + { break }") + (ParseError.expected ParseContext.expression (some Lexer.TokenKind.plus)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "else { break }") + (ParseError.expected ParseContext.ifStatement (some Lexer.TokenKind.else_)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "while { break }") + (ParseError.expected ParseContext.expression (some Lexer.TokenKind.lBrace)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "while 1 + { break }") + (ParseError.expected ParseContext.expression (some Lexer.TokenKind.plus)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "seal until { break }") + (ParseError.expected ParseContext.expression (some Lexer.TokenKind.lBrace)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "seal until 1 + { break }") + (ParseError.expected ParseContext.expression (some Lexer.TokenKind.plus)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "fn add(a, { return a }") + (ParseError.expected ParseContext.params (some Lexer.TokenKind.fn)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "fn add(a, b)") + (ParseError.expected ParseContext.block (some Lexer.TokenKind.fn)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "let count: = 1") + (ParseError.expected ParseContext.typeAnnotation (some Lexer.TokenKind.equals)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "let xs: list[ = [1]") + (ParseError.expected ParseContext.typeAnnotation (some Lexer.TokenKind.equals)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "let xs: list[\n = [1]") + (ParseError.expected ParseContext.typeAnnotation (some Lexer.TokenKind.equals)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "fn id(x: num): { return x }") + (ParseError.expected ParseContext.typeAnnotation (some Lexer.TokenKind.lBrace)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "fn id(x): { return x }") + (ParseError.expected ParseContext.typeAnnotation (some Lexer.TokenKind.lBrace)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "fn id(x:) { return x }") + (ParseError.expected ParseContext.typeAnnotation (some Lexer.TokenKind.rParen)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "fn id(x: list[) { return x }") + (ParseError.expected ParseContext.typeAnnotation (some Lexer.TokenKind.rParen)) + = + true := by + native_decide + +example : + parseFailedAs + (parseProgramDetailed "fn id(x): list[ { return x }") + (ParseError.expected ParseContext.typeAnnotation (some Lexer.TokenKind.lBrace)) + = + true := by + native_decide + +example : + (parseProgram "let x = 1; let y = x + 1" + == + some + [ Stmt.letDecl "x" (Expr.num 1) + , Stmt.letDecl "y" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1)) + ]) := by + native_decide + +example : + (parseProgram "let xs = [1,\n2,]" + == + some [Stmt.letDecl "xs" (Expr.list [Expr.num 1, Expr.num 2])]) := by + native_decide + +end Parser +end Aether diff --git a/Aether/Pipeline.lean b/Aether/Pipeline.lean new file mode 100644 index 0000000..cc0fe9b --- /dev/null +++ b/Aether/Pipeline.lean @@ -0,0 +1,1442 @@ +import Aether.Lexer +import Aether.Parser +import Aether.Static +import Aether.VM + +namespace Aether +namespace Pipeline + +inductive Error where + | lex : String -> Lexer.SourceSpan -> Error + | parse : Parser.ParseError -> Lexer.SourceSpan -> Error + | static : Static.CheckError -> Option Lexer.SourceSpan -> Error + | compile : Error + | runtime : Error + deriving Repr, BEq, DecidableEq + +def firstLexError : List Lexer.LocatedToken -> Option (String × Lexer.SourceSpan) + | [] => none + | { kind := Lexer.TokenKind.error message, start := start, stop := stop } :: _ => + some (message, { start := start, stop := stop }) + | _ :: rest => firstLexError rest + +def skipLocatedTerminators : List Lexer.LocatedToken -> List Lexer.LocatedToken + | { kind := Lexer.TokenKind.newline, start := _, stop := _ } :: rest => skipLocatedTerminators rest + | { kind := Lexer.TokenKind.tilde, start := _, stop := _ } :: rest => skipLocatedTerminators rest + | { kind := Lexer.TokenKind.semicolon, start := _, stop := _ } :: rest => skipLocatedTerminators rest + | tokens => tokens + +def tokenKinds (tokens : List Lexer.LocatedToken) : Parser.Tokens := + tokens.map (fun token => token.kind) + +def tokenMatchesIdent (name : Ident) : Lexer.TokenKind -> Bool + | Lexer.TokenKind.identifier found => found == name + | Lexer.TokenKind.self => name == "self" + | Lexer.TokenKind.embed => name == "embed" + | Lexer.TokenKind.convergence => name == "convergence" + | _ => false + +def tokenMatchesBinOp (op : BinOp) (token : Lexer.TokenKind) : Bool := + match op, token with + | BinOp.add, Lexer.TokenKind.plus => true + | BinOp.sub, Lexer.TokenKind.minus => true + | BinOp.mul, Lexer.TokenKind.star => true + | BinOp.div, Lexer.TokenKind.slash => true + | BinOp.mod, Lexer.TokenKind.percent => true + | BinOp.eq, Lexer.TokenKind.eqEq => true + | BinOp.neq, Lexer.TokenKind.notEq => true + | BinOp.lt, Lexer.TokenKind.less => true + | BinOp.gt, Lexer.TokenKind.greater => true + | BinOp.le, Lexer.TokenKind.lessEq => true + | BinOp.ge, Lexer.TokenKind.greaterEq => true + | BinOp.and, Lexer.TokenKind.and => true + | BinOp.or, Lexer.TokenKind.or => true + | _, _ => false + +def tokenMatchesUnOp (op : UnOp) (token : Lexer.TokenKind) : Bool := + match op, token with + | UnOp.neg, Lexer.TokenKind.minus => true + | UnOp.not, Lexer.TokenKind.not => true + | _, _ => false + +def firstTokenSpanWhere + (p : Lexer.TokenKind -> Bool) : List Lexer.LocatedToken -> Option Lexer.SourceSpan + | [] => none + | token :: rest => + if p token.kind then + some token.span + else + firstTokenSpanWhere p rest + +def lastTokenSpanWhere + (p : Lexer.TokenKind -> Bool) : List Lexer.LocatedToken -> Option Lexer.SourceSpan + | [] => none + | token :: rest => + match lastTokenSpanWhere p rest with + | some span => some span + | none => + if p token.kind then + some token.span + else + none + +def secondTokenSpanWhere + (p : Lexer.TokenKind -> Bool) : List Lexer.LocatedToken -> Option Lexer.SourceSpan + | [] => none + | token :: rest => + if p token.kind then + firstTokenSpanWhere p rest + else + secondTokenSpanWhere p rest + +def fnNameSpanWhere + (name : Ident) : List Lexer.LocatedToken -> Option Lexer.SourceSpan + | [] => none + | { kind := Lexer.TokenKind.fn, start := _, stop := _ } + :: { kind := Lexer.TokenKind.identifier found, start := start, stop := stop } + :: rest => + if found == name then + some { start := start, stop := stop } + else + fnNameSpanWhere name rest + | _ :: rest => fnNameSpanWhere name rest + +def secondFnNameSpanWhere + (name : Ident) : List Lexer.LocatedToken -> Option Lexer.SourceSpan + | [] => none + | { kind := Lexer.TokenKind.fn, start := _, stop := _ } + :: { kind := Lexer.TokenKind.identifier found, start := start, stop := stop } + :: rest => + if found == name then + match fnNameSpanWhere name rest with + | some span => some span + | none => some { start := start, stop := stop } + else + secondFnNameSpanWhere name rest + | _ :: rest => secondFnNameSpanWhere name rest + +partial def closingBraceSpanFromDepth (depth : Nat) : List Lexer.LocatedToken -> Option Lexer.SourceSpan + | [] => none + | { kind := Lexer.TokenKind.lBrace, start := _, stop := _ } :: rest => + closingBraceSpanFromDepth (depth + 1) rest + | { kind := Lexer.TokenKind.rBrace, start := start, stop := stop } :: rest => + if depth == 1 then + some { start := start, stop := stop } + else + closingBraceSpanFromDepth (depth - 1) rest + | _ :: rest => closingBraceSpanFromDepth depth rest + +def firstFunctionBodyCloseSpan : List Lexer.LocatedToken -> Option Lexer.SourceSpan + | [] => none + | { kind := Lexer.TokenKind.lBrace, start := _, stop := _ } :: rest => + closingBraceSpanFromDepth 1 rest + | _ :: rest => firstFunctionBodyCloseSpan rest + +def fnBodyCloseSpanWhere + (name : Ident) : List Lexer.LocatedToken -> Option Lexer.SourceSpan + | [] => none + | { kind := Lexer.TokenKind.fn, start := _, stop := _ } + :: { kind := Lexer.TokenKind.identifier found, start := _, stop := _ } + :: rest => + if found == name then + match firstFunctionBodyCloseSpan rest with + | some span => some span + | none => fnBodyCloseSpanWhere name rest + else + fnBodyCloseSpanWhere name rest + | _ :: rest => fnBodyCloseSpanWhere name rest + +def duplicateParamSpanInList? + (name : Ident) (seen : Bool) : List Lexer.LocatedToken -> Option Lexer.SourceSpan + | [] => none + | { kind := Lexer.TokenKind.rParen, start := _, stop := _ } :: _ => none + | { kind := Lexer.TokenKind.identifier found, start := start, stop := stop } :: rest => + if found == name then + if seen then + some { start := start, stop := stop } + else + duplicateParamSpanInList? name true rest + else + duplicateParamSpanInList? name seen rest + | _ :: rest => duplicateParamSpanInList? name seen rest + +def duplicateParamSpanWhere + (name : Ident) : List Lexer.LocatedToken -> Option Lexer.SourceSpan + | [] => none + | { kind := Lexer.TokenKind.fn, start := _, stop := _ } + :: { kind := Lexer.TokenKind.identifier _, start := _, stop := _ } + :: { kind := Lexer.TokenKind.lParen, start := _, stop := _ } + :: rest => + match duplicateParamSpanInList? name false rest with + | some span => some span + | none => duplicateParamSpanWhere name rest + | _ :: rest => duplicateParamSpanWhere name rest + +def tokenMatchesTy : Static.Ty -> Lexer.TokenKind -> Bool + | Static.Ty.num, Lexer.TokenKind.number _ => true + | Static.Ty.num, Lexer.TokenKind.float _ _ => true + | Static.Ty.bool, Lexer.TokenKind.true => true + | Static.Ty.bool, Lexer.TokenKind.false => true + | Static.Ty.str, Lexer.TokenKind.stringLit _ => true + | Static.Ty.unit, Lexer.TokenKind.identifier "unit" => true + | Static.Ty.list _, Lexer.TokenKind.lBracket => true + | _, _ => false + +def conditionTokenSpanWhere + (actual : Static.Ty) : List Lexer.LocatedToken -> Option Lexer.SourceSpan + | [] => none + | { kind := Lexer.TokenKind.if_, start := _, stop := _ } :: token :: rest => + if tokenMatchesTy actual token.kind then + some token.span + else + conditionTokenSpanWhere actual rest + | { kind := Lexer.TokenKind.while, start := _, stop := _ } :: token :: rest => + if tokenMatchesTy actual token.kind then + some token.span + else + conditionTokenSpanWhere actual rest + | { kind := Lexer.TokenKind.seal, start := _, stop := _ } + :: { kind := Lexer.TokenKind.until, start := _, stop := _ } + :: token + :: rest => + if tokenMatchesTy actual token.kind then + some token.span + else + conditionTokenSpanWhere actual rest + | _ :: rest => conditionTokenSpanWhere actual rest + +def memberNameSpanWhere + (name : Ident) : List Lexer.LocatedToken -> Option Lexer.SourceSpan + | [] => none + | { kind := Lexer.TokenKind.dot, start := _, stop := _ } + :: { kind := token, start := start, stop := stop } + :: rest => + if tokenMatchesIdent name token then + some { start := start, stop := stop } + else + memberNameSpanWhere name rest + | _ :: rest => memberNameSpanWhere name rest + +def staticErrorSpan? (tokens : List Lexer.LocatedToken) : Static.CheckError -> Option Lexer.SourceSpan + | Static.CheckError.undeclaredVariable name => + firstTokenSpanWhere (tokenMatchesIdent name) tokens + | Static.CheckError.undeclaredFunction name => + firstTokenSpanWhere (tokenMatchesIdent name) tokens + | Static.CheckError.operandMismatch op _ _ => + firstTokenSpanWhere (tokenMatchesBinOp op) tokens + | Static.CheckError.unaryMismatch op _ => + firstTokenSpanWhere (tokenMatchesUnOp op) tokens + | Static.CheckError.indexMismatch _ _ => + lastTokenSpanWhere (fun token => token == Lexer.TokenKind.lBracket) tokens + | Static.CheckError.fieldMismatch _ field => + match memberNameSpanWhere field tokens with + | some span => some span + | none => lastTokenSpanWhere (fun token => token == Lexer.TokenKind.dot) tokens + | Static.CheckError.methodMismatch _ method _ => + match memberNameSpanWhere method tokens with + | some span => some span + | none => lastTokenSpanWhere (fun token => token == Lexer.TokenKind.dot) tokens + | Static.CheckError.conditionMismatch actual => + match conditionTokenSpanWhere actual tokens with + | some span => some span + | none => + firstTokenSpanWhere + (fun token => + token == Lexer.TokenKind.if_ + || token == Lexer.TokenKind.while + || token == Lexer.TokenKind.seal) + tokens + | Static.CheckError.assignmentMismatch name _ _ => + firstTokenSpanWhere (tokenMatchesIdent name) tokens + | Static.CheckError.arityMismatch name _ _ => + lastTokenSpanWhere (tokenMatchesIdent name) tokens + | Static.CheckError.functionSignatureMismatch name _ _ => + firstTokenSpanWhere (tokenMatchesIdent name) tokens + | Static.CheckError.unknownNamedArgument _ name => + lastTokenSpanWhere (tokenMatchesIdent name) tokens + | Static.CheckError.duplicateNamedArgument _ name => + lastTokenSpanWhere (tokenMatchesIdent name) tokens + | Static.CheckError.argumentMismatch _ _ _ actual => + lastTokenSpanWhere (tokenMatchesTy actual) tokens + | Static.CheckError.returnMismatch name _ actual => + match lastTokenSpanWhere (tokenMatchesTy actual) tokens with + | some span => some span + | none => + if actual == Static.Ty.unit then + match fnBodyCloseSpanWhere name tokens with + | some span => some span + | none => fnNameSpanWhere name tokens + else + fnNameSpanWhere name tokens + | Static.CheckError.duplicateFunction name => + secondFnNameSpanWhere name tokens + | Static.CheckError.duplicateParameter name => + duplicateParamSpanWhere name tokens + | Static.CheckError.nestedFunction name => + fnNameSpanWhere name tokens + | Static.CheckError.returnOutsideFunction => + firstTokenSpanWhere (fun token => token == Lexer.TokenKind.return) tokens + | Static.CheckError.breakOutsideLoop => + firstTokenSpanWhere (fun token => token == Lexer.TokenKind.break) tokens + | Static.CheckError.continueOutsideLoop => + firstTokenSpanWhere (fun token => token == Lexer.TokenKind.continue) tokens + +partial def parseLocatedProgramDetailed : + List Lexer.LocatedToken -> Except (Parser.ParseError × Lexer.SourceSpan) (List Stmt) + | [] => Except.ok [] + | { kind := Lexer.TokenKind.eof, start := _, stop := _ } :: _ => Except.ok [] + | tokens => + let tokens := skipLocatedTerminators tokens + match tokens with + | [] => Except.ok [] + | { kind := Lexer.TokenKind.eof, start := _, stop := _ } :: _ => Except.ok [] + | first :: _ => + let kinds := tokenKinds tokens + match Parser.parseStmt kinds with + | some (stmt, remainingKinds) => + let consumed := kinds.length - remainingKinds.length + match parseLocatedProgramDetailed (tokens.drop consumed) with + | Except.ok rest => Except.ok (stmt :: rest) + | Except.error err => Except.error err + | none => + let offset := Parser.diagnosticOffset kinds + let diagnosticToken := tokens.drop offset + let span := + match diagnosticToken with + | token :: _ => token.span + | [] => first.span + let err := + Parser.ParseError.expected + (Parser.classifyStmtStart kinds) + (Parser.tokenAt? kinds offset) + Except.error (err, span) + +def parseSource (source : String) : Except Error (List Stmt) := + let located := Lexer.tokenizeLocated source + match firstLexError located with + | some (message, span) => Except.error (Error.lex message span) + | none => + match parseLocatedProgramDetailed located with + | Except.ok stmts => Except.ok stmts + | Except.error (err, span) => Except.error (Error.parse err span) + +def checkSource (source : String) : Except Error Static.CheckState := do + let located := Lexer.tokenizeLocated source + let stmts <- parseSource source + match Static.checkProgramDetailed stmts with + | Except.ok state => Except.ok state + | Except.error err => Except.error (Error.static err (staticErrorSpan? located err)) + +def compileSource (source : String) : Except Error (VM.SlotEnv × VM.FrameFnEnv × List VM.FrameOp) := do + let located := Lexer.tokenizeLocated source + let stmts <- parseSource source + match Static.checkProgramDetailed stmts with + | Except.error err => Except.error (Error.static err (staticErrorSpan? located err)) + | Except.ok _ => + match VM.compileFrameProgram stmts with + | some compiled => Except.ok compiled + | none => Except.error Error.compile + +def runSource (fuel : Nat) (source : String) : + Except Error (VM.SlotEnv × VM.FrameFnEnv × VM.FrameState) := do + let (slots, fns, code) <- compileSource source + match VM.runFrame fuel code with + | some state => Except.ok (slots, fns, state) + | none => Except.error Error.runtime + +def sourceLocal? (fuel : Nat) (source : String) (slot : Nat) : Except Error Value := do + let (_, _, state) <- runSource fuel source + match VM.listGet? state.locals slot with + | some value => Except.ok value + | none => Except.error Error.runtime + +def posString (pos : Lexer.SourcePos) : String := + toString pos.line ++ ":" ++ toString pos.column + +def spanString (span : Lexer.SourceSpan) : String := + posString span.start ++ "-" ++ posString span.stop + +def binOpString : BinOp -> String + | BinOp.add => "+" + | BinOp.sub => "-" + | BinOp.mul => "*" + | BinOp.div => "/" + | BinOp.mod => "%" + | BinOp.eq => "==" + | BinOp.neq => "!=" + | BinOp.lt => "<" + | BinOp.gt => ">" + | BinOp.le => "<=" + | BinOp.ge => ">=" + | BinOp.and => "&&" + | BinOp.or => "||" + +def unOpString : UnOp -> String + | UnOp.neg => "-" + | UnOp.not => "!" + +def tyString : Static.Ty -> String + | Static.Ty.num => "num" + | Static.Ty.bool => "bool" + | Static.Ty.str => "str" + | Static.Ty.list elem => "list[" ++ tyString elem ++ "]" + | Static.Ty.unit => "unit" + | Static.Ty.unknown => "unknown" + +def tokenString : Lexer.TokenKind -> String + | Lexer.TokenKind.identifier name => "identifier(" ++ name ++ ")" + | Lexer.TokenKind.number n => "number(" ++ toString n ++ ")" + | Lexer.TokenKind.float int frac => "float(" ++ toString int ++ "," ++ toString frac ++ ")" + | Lexer.TokenKind.stringLit value => "string(" ++ value ++ ")" + | Lexer.TokenKind.error message => "lexer-error(" ++ message ++ ")" + | Lexer.TokenKind.manifold => "manifold" + | Lexer.TokenKind.block => "block" + | Lexer.TokenKind.regress => "regress" + | Lexer.TokenKind.render => "render" + | Lexer.TokenKind.embed => "embed" + | Lexer.TokenKind.escalate => "escalate" + | Lexer.TokenKind.convergence => "convergence" + | Lexer.TokenKind.class => "class" + | Lexer.TokenKind.new => "new" + | Lexer.TokenKind.self => "self" + | Lexer.TokenKind.import => "import" + | Lexer.TokenKind.from => "from" + | Lexer.TokenKind.as => "as" + | Lexer.TokenKind.dim => "dim" + | Lexer.TokenKind.tau => "tau" + | Lexer.TokenKind.model => "model" + | Lexer.TokenKind.color => "color" + | Lexer.TokenKind.axis => "axis" + | Lexer.TokenKind.project => "project" + | Lexer.TokenKind.cluster => "cluster" + | Lexer.TokenKind.center => "center" + | Lexer.TokenKind.spread => "spread" + | Lexer.TokenKind.format => "format" + | Lexer.TokenKind.output => "output" + | Lexer.TokenKind.let_ => "let" + | Lexer.TokenKind.fn => "fn" + | Lexer.TokenKind.for => "for" + | Lexer.TokenKind.while => "while" + | Lexer.TokenKind.if_ => "if" + | Lexer.TokenKind.else_ => "else" + | Lexer.TokenKind.return => "return" + | Lexer.TokenKind.break => "break" + | Lexer.TokenKind.continue => "continue" + | Lexer.TokenKind.in_ => "in" + | Lexer.TokenKind.seal => "seal" + | Lexer.TokenKind.until => "until" + | Lexer.TokenKind.true => "true" + | Lexer.TokenKind.false => "false" + | Lexer.TokenKind.equals => "=" + | Lexer.TokenKind.comma => "," + | Lexer.TokenKind.dot => "." + | Lexer.TokenKind.dotDot => ".." + | Lexer.TokenKind.tilde => "~" + | Lexer.TokenKind.semicolon => ";" + | Lexer.TokenKind.newline => "newline" + | Lexer.TokenKind.eof => "eof" + | Lexer.TokenKind.lBrace => "{" + | Lexer.TokenKind.rBrace => "}" + | Lexer.TokenKind.lParen => "(" + | Lexer.TokenKind.rParen => ")" + | Lexer.TokenKind.lBracket => "[" + | Lexer.TokenKind.rBracket => "]" + | Lexer.TokenKind.plus => "+" + | Lexer.TokenKind.minus => "-" + | Lexer.TokenKind.star => "*" + | Lexer.TokenKind.slash => "/" + | Lexer.TokenKind.percent => "%" + | Lexer.TokenKind.less => "<" + | Lexer.TokenKind.greater => ">" + | Lexer.TokenKind.lessEq => "<=" + | Lexer.TokenKind.greaterEq => ">=" + | Lexer.TokenKind.eqEq => "==" + | Lexer.TokenKind.notEq => "!=" + | Lexer.TokenKind.and => "&&" + | Lexer.TokenKind.or => "||" + | Lexer.TokenKind.not => "!" + | _ => "token" + +def parseContextString : Parser.ParseContext -> String + | Parser.ParseContext.expression => "expression" + | Parser.ParseContext.statement => "statement" + | Parser.ParseContext.ifStatement => "if-statement" + | Parser.ParseContext.block => "block" + | Parser.ParseContext.range => "range" + | Parser.ParseContext.params => "params" + | Parser.ParseContext.typeAnnotation => "type" + | Parser.ParseContext.terminator => "terminator" + | Parser.ParseContext.programEnd => "program-end" + +def optionalTokenString : Option Lexer.TokenKind -> String + | none => "end-of-input" + | some token => tokenString token + +def parseErrorString : Parser.ParseError -> String + | Parser.ParseError.expected context found => + "expected " ++ parseContextString context ++ ", found " ++ optionalTokenString found + +def staticErrorString : Static.CheckError -> String + | Static.CheckError.undeclaredVariable name => "undeclared variable " ++ name + | Static.CheckError.undeclaredFunction name => "undeclared function " ++ name + | Static.CheckError.operandMismatch op left right => + "operator " ++ binOpString op ++ " cannot accept " ++ tyString left ++ " and " ++ tyString right + | Static.CheckError.unaryMismatch op ty => + "operator " ++ unOpString op ++ " cannot accept " ++ tyString ty + | Static.CheckError.indexMismatch target index => + "index expected list and num, found " ++ tyString target ++ " and " ++ tyString index + | Static.CheckError.fieldMismatch target field => + "field " ++ field ++ " is not available on " ++ tyString target + | Static.CheckError.methodMismatch target method arity => + "method " ++ method ++ "/" ++ toString arity ++ " is not available on " ++ tyString target + | Static.CheckError.conditionMismatch ty => + "condition expected bool, found " ++ tyString ty + | Static.CheckError.assignmentMismatch name expected actual => + "assignment to " ++ name ++ " expected " ++ tyString expected ++ ", found " ++ tyString actual + | Static.CheckError.arityMismatch name expected actual => + "function " ++ name ++ " expected " ++ toString expected ++ " args, found " ++ toString actual + | Static.CheckError.functionSignatureMismatch name expected actual => + "function " ++ name ++ " signature expected " ++ toString expected ++ " params, found " ++ toString actual + | Static.CheckError.unknownNamedArgument fnName argName => + "function " ++ fnName ++ " has no parameter " ++ argName + | Static.CheckError.duplicateNamedArgument fnName argName => + "duplicate named argument " ++ argName ++ " for function " ++ fnName + | Static.CheckError.argumentMismatch fnName paramName expected actual => + "argument " ++ paramName ++ " for function " ++ fnName + ++ " expected " ++ tyString expected ++ ", found " ++ tyString actual + | Static.CheckError.returnMismatch fnName expected actual => + "function " ++ fnName ++ " expected return " ++ tyString expected + ++ ", found " ++ tyString actual + | Static.CheckError.duplicateFunction name => "duplicate function " ++ name + | Static.CheckError.duplicateParameter name => "duplicate parameter " ++ name + | Static.CheckError.nestedFunction name => "nested function " ++ name + | Static.CheckError.returnOutsideFunction => "return outside function" + | Static.CheckError.breakOutsideLoop => "break outside loop" + | Static.CheckError.continueOutsideLoop => "continue outside loop" + +def errorString : Error -> String + | Error.lex message span => "lex error at " ++ spanString span ++ ": " ++ message + | Error.parse err span => "parse error at " ++ spanString span ++ ": " ++ parseErrorString err + | Error.static err none => "static error: " ++ staticErrorString err + | Error.static err (some span) => + "static error at " ++ spanString span ++ ": " ++ staticErrorString err + | Error.compile => "compile error" + | Error.runtime => "runtime error" + +def resultErrorString {α : Type} (result : Except Error α) : Option String := + match result with + | Except.ok _ => none + | Except.error err => some (errorString err) + +def parseSourceErrorString (source : String) : Option String := + resultErrorString (parseSource source) + +def checkSourceErrorString (source : String) : Option String := + resultErrorString (checkSource source) + +def compileSourceErrorString (source : String) : Option String := + resultErrorString (compileSource source) + +def runSourceErrorString (fuel : Nat) (source : String) : Option String := + resultErrorString (runSource fuel source) + +def sourceLocalErrorString (fuel : Nat) (source : String) (slot : Nat) : Option String := + resultErrorString (sourceLocal? fuel source slot) + +example : + (sourceLocal? 40 "fn add(a, b) { return a + b }~let y = add(2, 3)" 0 + == Except.ok (Value.num 5)) := by + native_decide + +example : + (sourceLocal? 40 "fn add(a, b) { return a + b }~let y = add\n(2, 3)" 0 + == Except.ok (Value.num 5)) := by + native_decide + +example : + (sourceLocal? 40 "fn add\n(a, b) { return a + b }~let y = add(2, 3)" 0 + == Except.ok (Value.num 5)) := by + native_decide + +example : + (sourceLocal? 40 "fn id(x)\n{ return x }~let y = id(3)" 0 + == Except.ok (Value.num 3)) := by + native_decide + +example : + (sourceLocal? 40 "fn pick(a, b) { return a }~let y = pick(b=2, a=7)" 0 + == Except.ok (Value.num 7)) := by + native_decide + +example : + (parseSource "let x = 0~🦭 until x == 3 { x = x + 1 }" + == Except.ok + [ Stmt.letDecl "x" (Expr.num 0) + , Stmt.seal + (some (Expr.binary (Expr.var "x") BinOp.eq (Expr.num 3))) + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1))] + ]) := by + native_decide + +example : + (sourceLocal? 80 "let x = 0~🦭 until x == 3 { x = x + 1 }" 0 + == Except.ok (Value.num 3)) := by + native_decide + +example : + (sourceLocal? 100 "let x = 0~if\ntrue { x = x + 1 }~while\nx < 2 { x = x + 1 }~seal until\nx == 3 { x = x + 1 }" 0 + == Except.ok (Value.num 3)) := by + native_decide + +example : + (sourceLocal? 80 "let x = 0~for i in -2..2 { x = x + i }" 0 + == Except.ok (Value.num (-2))) := by + native_decide + +example : + (parseSource "let label = \"open\"" + == Except.ok [Stmt.letDecl "label" (Expr.str "open")]) := by + native_decide + +example : + (sourceLocal? 20 "let label = \"open\"" 0 + == Except.ok (Value.str "open")) := by + native_decide + +example : + (sourceLocal? 20 "let label = \"line\\nnext\"" 0 + == Except.ok (Value.str "line\nnext")) := by + native_decide + +example : + (sourceLocal? 20 "let label = \"row\\rnext\"" 0 + == Except.ok (Value.str "row\rnext")) := by + native_decide + +example : + (sourceLocal? 20 "let x = 1 /* ignored\ncomment */~let y = x + 1" 1 + == Except.ok (Value.num 2)) := by + native_decide + +example : + (sourceLocal? 20 "let x = 1; let y = x + 1" 1 + == Except.ok (Value.num 2)) := by + native_decide + +example : + (sourceLocal? 20 "let xs = [1,\n2,]~let count = xs.len()" 1 + == Except.ok (Value.num 2)) := by + native_decide + +example : + (parseSource "let ratio = 1.5" + == Except.ok [Stmt.letDecl "ratio" (Expr.float 1 500000)]) := by + native_decide + +example : + (parseSource "let done: unit = unit" + == Except.ok [Stmt.letDeclTyped "done" AnnTy.unit Expr.unit]) := by + native_decide + +example : + (parseSource "fn id(x): num { return x }" + == Except.ok + [ Stmt.fnDeclReturn + "id" + ["x"] + AnnTy.num + [Stmt.ret (some (Expr.var "x"))] + ]) := by + native_decide + +example : + (sourceLocal? 60 "fn add(a,\nb,) { return a + b }~let y = add(1, 2)" 0 + == Except.ok (Value.num 3)) := by + native_decide + +example : + (sourceLocal? 30 "let ratio = 1.5 + 2" 0 + == Except.ok (Value.float 3 500000)) := by + native_decide + +example : + (sourceLocal? 30 "let total = (\n1 + 2\n)" 0 + == Except.ok (Value.num 3)) := by + native_decide + +example : + (sourceLocal? 30 "let total = 1 +\n2 * 3" 0 + == Except.ok (Value.num 7)) := by + native_decide + +example : + (sourceLocal? 30 "let total =\n1 + 2~total =\ntotal * 3" 0 + == Except.ok (Value.num 9)) := by + native_decide + +example : + (sourceLocal? 30 "let ok = !\nfalse~let n = -\n3" 1 + == Except.ok (Value.num (-3))) := by + native_decide + +example : + (parseSource "let xs = [1, true, \"open\"]" + == Except.ok [Stmt.letDecl "xs" (Expr.list [Expr.num 1, Expr.bool true, Expr.str "open"])]) := by + native_decide + +example : + (sourceLocal? 30 "let xs = [1, true, \"open\"]" 0 + == Except.ok (Value.list [Value.num 1, Value.bool true, Value.str "open"])) := by + native_decide + +example : + (sourceLocal? 40 "let xs = [1, true, \"open\"]~let first = xs[0]" 1 + == Except.ok (Value.num 1)) := by + native_decide + +example : + (sourceLocal? 40 "let xs = [1, 2]~let first = xs[\n0\n]" 1 + == Except.ok (Value.num 1)) := by + native_decide + +example : + (sourceLocal? 40 "let xs: list[\nnum\n] = [1, 2]~let n = xs.len()" 1 + == Except.ok (Value.num 2)) := by + native_decide + +example : + (sourceLocal? 40 "let label = \"open\"~let second = label[1]" 1 + == Except.ok (Value.str "p")) := by + native_decide + +example : + checkSourceErrorString "let bad = [1][true]" + = + some "static error at 1:14-1:15: index expected list and num, found list[num] and bool" := by + native_decide + +example : + checkSourceErrorString "let bad = \"open\"[true]" + = + some "static error at 1:17-1:18: index expected list and num, found str and bool" := by + native_decide + +example : + (sourceLocal? 40 "let xs = [1, true, \"open\"]~let n = xs.length" 1 + == Except.ok (Value.num 3)) := by + native_decide + +example : + (sourceLocal? 40 "let xs = [1, true, \"open\"]~let n = xs.\nlength" 1 + == Except.ok (Value.num 3)) := by + native_decide + +example : + checkSourceErrorString "let bad = 1.length" + = + some "static error at 1:13-1:19: field length is not available on num" := by + native_decide + +example : + (sourceLocal? 40 "let xs = [1, true, \"open\"]~let n = xs.len()" 1 + == Except.ok (Value.num 3)) := by + native_decide + +example : + (sourceLocal? 40 "let xs = [1, 2]~let n = xs.\nlen()" 1 + == Except.ok (Value.num 2)) := by + native_decide + +example : + (sourceLocal? 40 "let xs = []~let empty = xs.is_empty()" 1 + == Except.ok (Value.bool true)) := by + native_decide + +example : + (sourceLocal? 40 "let label = \"open\"~let empty = label.is_empty()" 1 + == Except.ok (Value.bool false)) := by + native_decide + +example : + (sourceLocal? 40 "let label = \"open\"~let ch = label.at(1)" 1 + == Except.ok (Value.str "p")) := by + native_decide + +example : + (sourceLocal? 40 "let label = \"open\"~let ok = label.starts_with(\n\"op\",\n)" 1 + == Except.ok (Value.bool true)) := by + native_decide + +example : + (sourceLocal? 40 "let label = \"open\"~let has = label.contains(\"pe\")" 1 + == Except.ok (Value.bool true)) := by + native_decide + +example : + (sourceLocal? 40 "let label = \"open\"~let has = label.starts_with(\"op\")" 1 + == Except.ok (Value.bool true)) := by + native_decide + +example : + (sourceLocal? 40 "let label = \"open\"~let has = label.ends_with(\"en\")" 1 + == Except.ok (Value.bool true)) := by + native_decide + +example : + (sourceLocal? 40 "let label = \"open\"~let reversed = label.reverse()" 1 + == Except.ok (Value.str "nepo")) := by + native_decide + +example : + (sourceLocal? 40 "let label = \"open\"~let first = label.first()" 1 + == Except.ok (Value.str "o")) := by + native_decide + +example : + (sourceLocal? 40 "let label = \"open\"~let last = label.last()" 1 + == Except.ok (Value.str "n")) := by + native_decide + +example : + (sourceLocal? 40 "let label = \"open\"~let rest = label.tail()" 1 + == Except.ok (Value.str "pen")) := by + native_decide + +example : + (sourceLocal? 40 "let label = \"open\"~let prefix = label.take(2)" 1 + == Except.ok (Value.str "op")) := by + native_decide + +example : + (sourceLocal? 40 "let label = \"open\"~let suffix = label.drop(2)" 1 + == Except.ok (Value.str "en")) := by + native_decide + +example : + (sourceLocal? 40 "let xs = [7, 9]~let first = xs.first()" 1 + == Except.ok (Value.num 7)) := by + native_decide + +example : + (sourceLocal? 40 "let xs = [7, 9]~let last = xs.last()" 1 + == Except.ok (Value.num 9)) := by + native_decide + +example : + (sourceLocal? 40 "let xs = [7, 9]~let picked = xs.at(1)" 1 + == Except.ok (Value.num 9)) := by + native_decide + +example : + (sourceLocal? 40 "let xs = [7, 9]~let has = xs.contains(9)" 1 + == Except.ok (Value.bool true)) := by + native_decide + +example : + (sourceLocal? 40 "let xs = [7, 9]~let rest = xs.tail()" 1 + == Except.ok (Value.list [Value.num 9])) := by + native_decide + +example : + (sourceLocal? 40 "let xs = [7, 9]~let first = xs.take(1)" 1 + == Except.ok (Value.list [Value.num 7])) := by + native_decide + +example : + (sourceLocal? 40 "let xs = [7, 9]~let rest = xs.drop(1)" 1 + == Except.ok (Value.list [Value.num 9])) := by + native_decide + +example : + (sourceLocal? 40 "let xs = [7, 9]~let reversed = xs.reverse()" 1 + == Except.ok (Value.list [Value.num 9, Value.num 7])) := by + native_decide + +example : + (sourceLocal? 40 "let xs = [7]~let more = xs.append(9)" 1 + == Except.ok (Value.list [Value.num 7, Value.num 9])) := by + native_decide + +example : + (sourceLocal? 40 "let xs = [9]~let more = xs.prepend(7)" 1 + == Except.ok (Value.list [Value.num 7, Value.num 9])) := by + native_decide + +example : + (sourceLocal? 40 "let xs = [\"a\", \"b\"]~let text = xs.join(\",\")" 1 + == Except.ok (Value.str "a,b")) := by + native_decide + +example : + (sourceLocal? 40 "let xs = [7]~let more = xs.concat([9])" 1 + == Except.ok (Value.list [Value.num 7, Value.num 9])) := by + native_decide + +example : + checkSourceErrorString "let bad = 1.len()" + = + some "static error at 1:13-1:16: method len/0 is not available on num" := by + native_decide + +example : + checkSourceErrorString "let bad = [1].at(true)" + = + some "static error at 1:15-1:17: method at/1 is not available on list[num]" := by + native_decide + +example : + checkSourceErrorString "let bad = [1].reverse(0)" + = + some "static error at 1:15-1:22: method reverse/1 is not available on list[num]" := by + native_decide + +example : + checkSourceErrorString "let bad = [1].append(true)" + = + some "static error at 1:15-1:21: method append/1 is not available on list[num]" := by + native_decide + +example : + checkSourceErrorString "let bad = [1].prepend(true)" + = + some "static error at 1:15-1:22: method prepend/1 is not available on list[num]" := by + native_decide + +example : + checkSourceErrorString "let bad = [1].join(\",\")" + = + some "static error at 1:15-1:19: method join/1 is not available on list[num]" := by + native_decide + +example : + checkSourceErrorString "let bad = [1].concat([true])" + = + some "static error at 1:15-1:21: method concat/1 is not available on list[num]" := by + native_decide + +example : + checkSourceErrorString "let bad = \"open\".at(true)" + = + some "static error at 1:18-1:20: method at/1 is not available on str" := by + native_decide + +example : + checkSourceErrorString "let bad = [1].contains(true)" + = + some "static error at 1:15-1:23: method contains/1 is not available on list[num]" := by + native_decide + +example : + checkSourceErrorString "let bad = \"open\".contains(1)" + = + some "static error at 1:18-1:26: method contains/1 is not available on str" := by + native_decide + +example : + checkSourceErrorString "let bad = \"open\".starts_with(1)" + = + some "static error at 1:18-1:29: method starts_with/1 is not available on str" := by + native_decide + +example : + checkSourceErrorString "let bad = \"open\".ends_with(1)" + = + some "static error at 1:18-1:27: method ends_with/1 is not available on str" := by + native_decide + +example : + checkSourceErrorString "let bad = \"open\".reverse(1)" + = + some "static error at 1:18-1:25: method reverse/1 is not available on str" := by + native_decide + +example : + checkSourceErrorString "let bad = \"open\".first(0)" + = + some "static error at 1:18-1:23: method first/1 is not available on str" := by + native_decide + +example : + checkSourceErrorString "let bad = \"open\".last(0)" + = + some "static error at 1:18-1:22: method last/1 is not available on str" := by + native_decide + +example : + checkSourceErrorString "let bad = \"open\".tail(0)" + = + some "static error at 1:18-1:22: method tail/1 is not available on str" := by + native_decide + +example : + checkSourceErrorString "let bad = \"open\".take(true)" + = + some "static error at 1:18-1:22: method take/1 is not available on str" := by + native_decide + +example : + checkSourceErrorString "let bad = \"open\".drop(true)" + = + some "static error at 1:18-1:22: method drop/1 is not available on str" := by + native_decide + +example : + checkSourceErrorString "let bad = [1].tail(0)" + = + some "static error at 1:15-1:19: method tail/1 is not available on list[num]" := by + native_decide + +example : + checkSourceErrorString "let bad = [1].take(true)" + = + some "static error at 1:15-1:19: method take/1 is not available on list[num]" := by + native_decide + +example : + checkSourceErrorString "let bad = [1].drop(true)" + = + some "static error at 1:15-1:19: method drop/1 is not available on list[num]" := by + native_decide + +example : + (compileSource "let x = \"unterminated" + == Except.error + (Error.lex + "unexpected EOF in string" + { start := { line := 1, column := 9 }, stop := { line := 1, column := 22 } })) := by + native_decide + +example : + (compileSource "let x = 1 +" + == Except.error + (Error.parse + (Parser.ParseError.expected Parser.ParseContext.expression (some Lexer.TokenKind.plus)) + { start := { line := 1, column := 11 }, stop := { line := 1, column := 12 } })) := by + native_decide + +example : + (compileSource "\n~for i in 0 { break }" + == Except.error + (Error.parse + (Parser.ParseError.expected Parser.ParseContext.range (some Lexer.TokenKind.for)) + { start := { line := 2, column := 2 }, stop := { line := 2, column := 5 } })) := by + native_decide + +example : + (compileSource "let x = 1~\nlet y = 2 +" + == Except.error + (Error.parse + (Parser.ParseError.expected Parser.ParseContext.expression (some Lexer.TokenKind.plus)) + { start := { line := 2, column := 11 }, stop := { line := 2, column := 12 } })) := by + native_decide + +example : + (compileSource "if 1 + { break }" + == Except.error + (Error.parse + (Parser.ParseError.expected Parser.ParseContext.expression (some Lexer.TokenKind.plus)) + { start := { line := 1, column := 6 }, stop := { line := 1, column := 7 } })) := by + native_decide + +example : + (compileSource "while 1 + { break }" + == Except.error + (Error.parse + (Parser.ParseError.expected Parser.ParseContext.expression (some Lexer.TokenKind.plus)) + { start := { line := 1, column := 9 }, stop := { line := 1, column := 10 } })) := by + native_decide + +example : + (compileSource "seal until 1 + { break }" + == Except.error + (Error.parse + (Parser.ParseError.expected Parser.ParseContext.expression (some Lexer.TokenKind.plus)) + { start := { line := 1, column := 14 }, stop := { line := 1, column := 15 } })) := by + native_decide + +example : + (compileSource "let bad = 2 + true" + == Except.error + (Error.static + (Static.CheckError.operandMismatch BinOp.add Static.Ty.num Static.Ty.bool) + (some { start := { line := 1, column := 13 }, stop := { line := 1, column := 14 } }))) := by + native_decide + +example : + (compileSource "fn id(x) { return x }~let bad = id(1, 2)" + == Except.error + (Error.static + (Static.CheckError.arityMismatch "id" 1 2) + (some { start := { line := 1, column := 33 }, stop := { line := 1, column := 35 } }))) := by + native_decide + +example : + (compileSource "fn dup() { return 1 }~fn dup() { return 2 }" + == Except.error + (Error.static + (Static.CheckError.duplicateFunction "dup") + (some { start := { line := 1, column := 26 }, stop := { line := 1, column := 29 } }))) := by + native_decide + +example : + (compileSource "fn dup() { return dup() }~fn dup() { return 2 }" + == Except.error + (Error.static + (Static.CheckError.duplicateFunction "dup") + (some { start := { line := 1, column := 30 }, stop := { line := 1, column := 33 } }))) := by + native_decide + +example : + (compileSource "fn bad(x, x) { return x }" + == Except.error + (Error.static + (Static.CheckError.duplicateParameter "x") + (some { start := { line := 1, column := 11 }, stop := { line := 1, column := 12 } }))) := by + native_decide + +example : + (compileSource "fn x(x, x) { return x }" + == Except.error + (Error.static + (Static.CheckError.duplicateParameter "x") + (some { start := { line := 1, column := 9 }, stop := { line := 1, column := 10 } }))) := by + native_decide + +example : + (runSource 0 "let x = 1" + == + Except.ok + ( [("x", 0)] + , [] + , { ip := 0 + code := [VM.FrameOp.push (Value.num 1), VM.FrameOp.store 0, VM.FrameOp.halt] + stack := [] + locals := [] + frames := [] + halted := false })) := by + native_decide + +example : + compileSourceErrorString "let x = \"unterminated" + = + some "lex error at 1:9-1:22: unexpected EOF in string" := by + native_decide + +example : + compileSourceErrorString "let x = 1 +" + = + some "parse error at 1:11-1:12: expected expression, found +" := by + native_decide + +example : + compileSourceErrorString "let bad = 2 + true" + = + some "static error at 1:13-1:14: operator + cannot accept num and bool" := by + native_decide + +example : + compileSourceErrorString "fn dup() { return 1 }~fn dup() { return 2 }" + = + some "static error at 1:26-1:29: duplicate function dup" := by + native_decide + +example : + parseSourceErrorString "let x = \"unterminated" + = + some "lex error at 1:9-1:22: unexpected EOF in string" := by + native_decide + +example : + parseSourceErrorString "let x = \"bad\\q\"" + = + some "lex error at 1:13-1:15: invalid escape: \\q" := by + native_decide + +example : + parseSourceErrorString "let x = 1 /* never closes" + = + some "lex error at 1:11-1:26: unterminated block comment" := by + native_decide + +example : + parseSourceErrorString "let x = 1 +" + = + some "parse error at 1:11-1:12: expected expression, found +" := by + native_decide + +example : + parseSourceErrorString "let x = 1~\nlet y = 2 +" + = + some "parse error at 2:11-2:12: expected expression, found +" := by + native_decide + +example : + parseSourceErrorString "for i in 0..3" + = + some "parse error at 1:1-1:4: expected block, found for" := by + native_decide + +example : + parseSourceErrorString "for i in -2..2" + = + some "parse error at 1:1-1:4: expected block, found for" := by + native_decide + +example : + parseSourceErrorString "if { break }" + = + some "parse error at 1:4-1:5: expected expression, found {" := by + native_decide + +example : + parseSourceErrorString "if 1 + { break }" + = + some "parse error at 1:6-1:7: expected expression, found +" := by + native_decide + +example : + parseSourceErrorString "else { break }" + = + some "parse error at 1:1-1:5: expected if-statement, found else" := by + native_decide + +example : + parseSourceErrorString "while { break }" + = + some "parse error at 1:7-1:8: expected expression, found {" := by + native_decide + +example : + parseSourceErrorString "while 1 + { break }" + = + some "parse error at 1:9-1:10: expected expression, found +" := by + native_decide + +example : + parseSourceErrorString "seal until { break }" + = + some "parse error at 1:12-1:13: expected expression, found {" := by + native_decide + +example : + parseSourceErrorString "seal until 1 + { break }" + = + some "parse error at 1:14-1:15: expected expression, found +" := by + native_decide + +example : + parseSourceErrorString "fn add(a, b)" + = + some "parse error at 1:1-1:3: expected block, found fn" := by + native_decide + +example : + parseSourceErrorString "let count: = 1" + = + some "parse error at 1:12-1:13: expected type, found =" := by + native_decide + +example : + parseSourceErrorString "let xs: list[ = [1]" + = + some "parse error at 1:15-1:16: expected type, found =" := by + native_decide + +example : + parseSourceErrorString "let xs: list[\n = [1]" + = + some "parse error at 2:2-2:3: expected type, found =" := by + native_decide + +example : + parseSourceErrorString "fn id(x: num): { return x }" + = + some "parse error at 1:16-1:17: expected type, found {" := by + native_decide + +example : + parseSourceErrorString "fn id(x): { return x }" + = + some "parse error at 1:11-1:12: expected type, found {" := by + native_decide + +example : + parseSourceErrorString "fn id(x:) { return x }" + = + some "parse error at 1:9-1:10: expected type, found )" := by + native_decide + +example : + parseSourceErrorString "fn id(x: list[) { return x }" + = + some "parse error at 1:15-1:16: expected type, found )" := by + native_decide + +example : + parseSourceErrorString "fn id(x): list[ { return x }" + = + some "parse error at 1:17-1:18: expected type, found {" := by + native_decide + +example : + parseSourceErrorString "manifold" + = + some "parse error at 1:1-1:9: expected statement, found manifold" := by + native_decide + +example : + parseSourceErrorString "class" + = + some "parse error at 1:1-1:6: expected statement, found class" := by + native_decide + +example : + checkSourceErrorString "let bad = 2 + true" + = + some "static error at 1:13-1:14: operator + cannot accept num and bool" := by + native_decide + +example : + checkSourceErrorString "let bad = 1 && true" + = + some "static error at 1:13-1:15: operator && cannot accept num and bool" := by + native_decide + +example : + checkSourceErrorString "let bad = false || 1" + = + some "static error at 1:17-1:19: operator || cannot accept bool and num" := by + native_decide + +example : + checkSourceErrorString "let bad = 1 == true" + = + some "static error at 1:13-1:15: operator == cannot accept num and bool" := by + native_decide + +example : + checkSourceErrorString "let bad = false != 1" + = + some "static error at 1:17-1:19: operator != cannot accept bool and num" := by + native_decide + +example : + checkSourceErrorString "let bad = !1" + = + some "static error at 1:11-1:12: operator ! cannot accept num" := by + native_decide + +example : + checkSourceErrorString "let count: num = true" + = + some "static error at 1:5-1:10: assignment to count expected num, found bool" := by + native_decide + +example : + checkSourceErrorString "let bad = self" + = + some "static error at 1:11-1:15: undeclared variable self" := by + native_decide + +example : + checkSourceErrorString "let bad = embed(1)" + = + some "static error at 1:11-1:16: undeclared function embed" := by + native_decide + +example : + checkSourceErrorString "fn pick(a, b) { return a }~let bad = pick(c=1, a=2)" + = + some "static error at 1:43-1:44: function pick has no parameter c" := by + native_decide + +example : + checkSourceErrorString "fn pick(a, b) { return a }~let bad = pick(a=1, a=2)" + = + some "static error at 1:48-1:49: duplicate named argument a for function pick" := by + native_decide + +example : + checkSourceErrorString "fn id(x: num) { return x }~let bad = id(true)" + = + some "static error at 1:41-1:45: argument x for function id expected num, found bool" := by + native_decide + +example : + checkSourceErrorString "fn bad(x: num): num { return true }" + = + some "static error at 1:30-1:34: function bad expected return num, found bool" := by + native_decide + +example : + checkSourceErrorString "fn bad(x): num { return true }" + = + some "static error at 1:25-1:29: function bad expected return num, found bool" := by + native_decide + +example : + checkSourceErrorString "fn bad(): num { if true { return 1 } else { return true } }" + = + some "static error at 1:52-1:56: function bad expected return num, found bool" := by + native_decide + +example : + checkSourceErrorString "fn bad(): num { }" + = + some "static error at 1:17-1:18: function bad expected return num, found unit" := by + native_decide + +example : + checkSourceErrorString "fn bad(): num { return unit }" + = + some "static error at 1:24-1:28: function bad expected return num, found unit" := by + native_decide + +example : + checkSourceErrorString "fn len(xs: list[num]): num { return xs.length }~let bad = len([true])" + = + some "static error at 1:63-1:64: argument xs for function len expected list[num], found list[bool]" := by + native_decide + +example : + checkSourceErrorString "break" + = + some "static error at 1:1-1:6: break outside loop" := by + native_decide + +example : + checkSourceErrorString "if 1 { let x = 2 }" + = + some "static error at 1:4-1:5: condition expected bool, found num" := by + native_decide + +example : + checkSourceErrorString "while 1 { break }" + = + some "static error at 1:7-1:8: condition expected bool, found num" := by + native_decide + +example : + checkSourceErrorString "seal until 1 { break }" + = + some "static error at 1:12-1:13: condition expected bool, found num" := by + native_decide + +example : + checkSourceErrorString "if true { fn nested() { return 1 } }" + = + some "static error at 1:14-1:20: nested function nested" := by + native_decide + +example : + runSourceErrorString 0 "let x = 1" = none := by + native_decide + +example : + sourceLocalErrorString 10 "let x = 1" 9 + = + some "runtime error" := by + native_decide + +end Pipeline +end Aether diff --git a/Aether/Static.lean b/Aether/Static.lean new file mode 100644 index 0000000..84411e2 --- /dev/null +++ b/Aether/Static.lean @@ -0,0 +1,1891 @@ +import Aether.Core + +namespace Aether +namespace Static + +inductive Ty where + | num + | bool + | str + | list : Ty -> Ty + | unit + | unknown + deriving Repr, BEq, DecidableEq, Inhabited + +abbrev VarEnv := List (Ident × Ty) + +structure FnSig where + arity : Nat + params : List Ident + paramTys : List Ty + result : Ty + deriving Repr, BEq, DecidableEq + +abbrev FnSigEnv := List (Ident × FnSig) + +structure Scope where + loopDepth : Nat + functionDepth : Nat + topLevel : Bool + deriving Repr, BEq, DecidableEq + +structure CheckState where + vars : VarEnv + deriving Repr, BEq, DecidableEq + +inductive CheckError where + | undeclaredVariable : Ident -> CheckError + | undeclaredFunction : Ident -> CheckError + | operandMismatch : BinOp -> Ty -> Ty -> CheckError + | unaryMismatch : UnOp -> Ty -> CheckError + | indexMismatch : Ty -> Ty -> CheckError + | fieldMismatch : Ty -> Ident -> CheckError + | methodMismatch : Ty -> Ident -> Nat -> CheckError + | conditionMismatch : Ty -> CheckError + | assignmentMismatch : Ident -> Ty -> Ty -> CheckError + | arityMismatch : Ident -> Nat -> Nat -> CheckError + | functionSignatureMismatch : Ident -> Nat -> Nat -> CheckError + | unknownNamedArgument : Ident -> Ident -> CheckError + | duplicateNamedArgument : Ident -> Ident -> CheckError + | argumentMismatch : Ident -> Ident -> Ty -> Ty -> CheckError + | returnMismatch : Ident -> Ty -> Ty -> CheckError + | duplicateFunction : Ident -> CheckError + | duplicateParameter : Ident -> CheckError + | nestedFunction : Ident -> CheckError + | returnOutsideFunction : CheckError + | breakOutsideLoop : CheckError + | continueOutsideLoop : CheckError + deriving Repr, BEq, DecidableEq, Inhabited + +instance [BEq ε] [BEq α] : BEq (Except ε α) where + beq + | Except.ok left, Except.ok right => left == right + | Except.error left, Except.error right => left == right + | _, _ => false + +def VarEnv.lookup (env : VarEnv) (name : Ident) : Option Ty := + match env with + | [] => none + | (key, ty) :: rest => + if key == name then some ty else VarEnv.lookup rest name + +def VarEnv.bind (env : VarEnv) (name : Ident) (ty : Ty) : VarEnv := + (name, ty) :: env + +def VarEnv.assign (env : VarEnv) (name : Ident) (ty : Ty) : Option VarEnv := + match env with + | [] => none + | (key, oldTy) :: rest => + if key == name then + some ((key, ty) :: rest) + else + match VarEnv.assign rest name ty with + | some updated => some ((key, oldTy) :: updated) + | none => none + +def FnSigEnv.lookup (env : FnSigEnv) (name : Ident) : Option FnSig := + match env with + | [] => none + | (key, sig) :: rest => + if key == name then some sig else FnSigEnv.lookup rest name + +def FnSigEnv.bind (env : FnSigEnv) (name : Ident) (sig : FnSig) : FnSigEnv := + (name, sig) :: env + +def bindUnknownParams (env : VarEnv) : List Ident -> VarEnv + | [] => env + | name :: rest => bindUnknownParams (VarEnv.bind env name Ty.unknown) rest + +def annTyToTy : AnnTy -> Ty + | AnnTy.num => Ty.num + | AnnTy.bool => Ty.bool + | AnnTy.str => Ty.str + | AnnTy.unit => Ty.unit + | AnnTy.list elementTy => Ty.list (annTyToTy elementTy) + +def bindTypedParams (env : VarEnv) : List (Ident × AnnTy) -> VarEnv + | [] => env + | (name, ty) :: rest => bindTypedParams (VarEnv.bind env name (annTyToTy ty)) rest + +def typedParamNames : List (Ident × AnnTy) -> List Ident + | [] => [] + | (name, _) :: rest => name :: typedParamNames rest + +def typedParamTys : List (Ident × AnnTy) -> List Ty + | [] => [] + | (_, ty) :: rest => annTyToTy ty :: typedParamTys rest + +def containsIdent (names : List Ident) (name : Ident) : Bool := + match names with + | [] => false + | first :: rest => first == name || containsIdent rest name + +def seenNamedArg (names : List Ident) (name : Ident) : Bool := + containsIdent names name + +def checkNamedArgs (fnName : Ident) (params : List Ident) (seen : List Ident) : + List Arg -> Option Unit + | [] => some () + | Arg.positional _ :: rest => checkNamedArgs fnName params seen rest + | Arg.named name _ :: rest => + if !containsIdent params name then + none + else if seenNamedArg seen name then + none + else + checkNamedArgs fnName params (name :: seen) rest + +def checkNamedArgsDetailed (fnName : Ident) (params : List Ident) (seen : List Ident) : + List Arg -> Except CheckError Unit + | [] => Except.ok () + | Arg.positional _ :: rest => checkNamedArgsDetailed fnName params seen rest + | Arg.named name _ :: rest => + if !containsIdent params name then + Except.error (CheckError.unknownNamedArgument fnName name) + else if seenNamedArg seen name then + Except.error (CheckError.duplicateNamedArgument fnName name) + else + checkNamedArgsDetailed fnName params (name :: seen) rest + +def bindUnknownParamsDetailed (env : VarEnv) : List Ident -> Except CheckError VarEnv + | [] => Except.ok env + | name :: rest => + if containsIdent rest name then + Except.error (CheckError.duplicateParameter name) + else + bindUnknownParamsDetailed (VarEnv.bind env name Ty.unknown) rest + +def bindTypedParamsDetailed (env : VarEnv) : List (Ident × AnnTy) -> Except CheckError VarEnv + | [] => Except.ok env + | (name, ty) :: rest => + if containsIdent (typedParamNames rest) name then + Except.error (CheckError.duplicateParameter name) + else + bindTypedParamsDetailed (VarEnv.bind env name (annTyToTy ty)) rest + +def numericLike : Ty -> Bool + | Ty.num => true + | Ty.unknown => true + | _ => false + +def boolLike : Ty -> Bool + | Ty.bool => true + | Ty.unknown => true + | _ => false + +def listLike : Ty -> Bool + | Ty.list _ => true + | Ty.unknown => true + | _ => false + +def compatibleTy : Ty -> Ty -> Bool + | Ty.unknown, _ => true + | _, Ty.unknown => true + | Ty.list left, Ty.list right => compatibleTy left right + | left, right => left == right + +def checkCallArgTys + (fnName : Ident) + (params : List Ident) + (paramTys : List Ty) + (args : List Arg) + (values : List Ty) : + Option Unit := + match params, paramTys, args, values with + | [], [], [], [] => some () + | _ :: restParams, expected :: restExpected, _ :: restArgs, actual :: restActual => + if compatibleTy expected actual then + checkCallArgTys fnName restParams restExpected restArgs restActual + else + none + | _, _, _, _ => none + +def checkCallArgTysDetailed + (fnName : Ident) + (params : List Ident) + (paramTys : List Ty) + (args : List Arg) + (values : List Ty) : + Except CheckError Unit := + match params, paramTys, args, values with + | [], [], [], [] => Except.ok () + | param :: restParams, expected :: restExpected, _ :: restArgs, actual :: restActual => + if compatibleTy expected actual then + checkCallArgTysDetailed fnName restParams restExpected restArgs restActual + else + Except.error (CheckError.argumentMismatch fnName param expected actual) + | _, _, _, _ => Except.error (CheckError.arityMismatch fnName params.length args.length) + +def assignable (target value : Ty) : Bool := + compatibleTy target value + +def refineAssignedTy : Ty -> Ty -> Ty + | Ty.unknown, value => value + | Ty.list target, Ty.list value => Ty.list (refineAssignedTy target value) + | target, _ => target + +def mergeElementTy (left right : Ty) : Ty := + if left == right then left else Ty.unknown + +def joinTy (left right : Ty) : Option Ty := + if compatibleTy left right then + some (mergeElementTy left right) + else + none + +def joinBranchVar (baseTy thenTy elseTy : Ty) : Ty := + match joinTy thenTy elseTy with + | some joinedTy => refineAssignedTy baseTy joinedTy + | none => baseTy + +def joinNewBranchVars (base thenVars elseVars : VarEnv) : VarEnv := + match thenVars with + | [] => base + | (name, thenTy) :: rest => + let joinedRest := joinNewBranchVars base rest elseVars + match VarEnv.lookup base name, VarEnv.lookup elseVars name with + | none, some elseTy => + match joinTy thenTy elseTy with + | some joinedTy => VarEnv.bind joinedRest name joinedTy + | none => joinedRest + | some baseTy, some elseTy => + match VarEnv.assign joinedRest name (joinBranchVar baseTy thenTy elseTy) with + | some updated => updated + | none => joinedRest + | _, _ => joinedRest + +def conditionCompatible : Ty -> Bool := + boolLike + +def equalityCompatible (left right : Ty) : Bool := + compatibleTy left right + +def compatibleBinOp (op : BinOp) (left right : Ty) : Option Ty := + match op with + | BinOp.add | BinOp.sub | BinOp.mul | BinOp.div | BinOp.mod => + if numericLike left && numericLike right then some Ty.num else none + | BinOp.lt | BinOp.gt | BinOp.le | BinOp.ge => + if numericLike left && numericLike right then some Ty.bool else none + | BinOp.eq | BinOp.neq => + if equalityCompatible left right then some Ty.bool else none + | BinOp.and | BinOp.or => + if boolLike left && boolLike right then some Ty.bool else none + +def compatibleUnOp (op : UnOp) (value : Ty) : Option Ty := + match op with + | UnOp.neg => if numericLike value then some Ty.num else none + | UnOp.not => if boolLike value then some Ty.bool else none + +def compatibleIndex (target index : Ty) : Option Ty := + match target with + | Ty.list elem => if numericLike index then some elem else none + | Ty.str => if numericLike index then some Ty.str else none + | Ty.unknown => if numericLike index then some Ty.unknown else none + | _ => none + +def compatibleField (target : Ty) (field : Ident) : Option Ty := + match target, field with + | Ty.list _, "length" => some Ty.num + | Ty.str, "length" => some Ty.num + | Ty.unknown, _ => some Ty.unknown + | _, _ => none + +def compatibleMethodWithArgs (target : Ty) (method : Ident) (argTys : List Ty) : Option Ty := + match target, method, argTys with + | Ty.list _, "len", [] => some Ty.num + | Ty.list _, "is_empty", [] => some Ty.bool + | Ty.list elem, "first", [] => some elem + | Ty.list elem, "tail", [] => some (Ty.list elem) + | Ty.list elem, "last", [] => some elem + | Ty.list elem, "at", [indexTy] => if numericLike indexTy then some elem else none + | Ty.list elem, "take", [countTy] => if numericLike countTy then some (Ty.list elem) else none + | Ty.list elem, "drop", [countTy] => if numericLike countTy then some (Ty.list elem) else none + | Ty.list elem, "reverse", [] => some (Ty.list elem) + | Ty.list elem, "append", [argTy] => if compatibleTy elem argTy then some (Ty.list elem) else none + | Ty.list elem, "prepend", [argTy] => if compatibleTy elem argTy then some (Ty.list elem) else none + | Ty.list elem, "concat", [Ty.list other] => if compatibleTy elem other then some (Ty.list elem) else none + | Ty.list elem, "join", [argTy] => + if compatibleTy Ty.str elem && compatibleTy Ty.str argTy then some Ty.str else none + | Ty.list elem, "contains", [argTy] => if compatibleTy elem argTy then some Ty.bool else none + | Ty.str, "len", [] => some Ty.num + | Ty.str, "is_empty", [] => some Ty.bool + | Ty.str, "first", [] => some Ty.str + | Ty.str, "last", [] => some Ty.str + | Ty.str, "tail", [] => some Ty.str + | Ty.str, "take", [countTy] => if numericLike countTy then some Ty.str else none + | Ty.str, "drop", [countTy] => if numericLike countTy then some Ty.str else none + | Ty.str, "at", [indexTy] => if numericLike indexTy then some Ty.str else none + | Ty.str, "contains", [argTy] => if compatibleTy Ty.str argTy then some Ty.bool else none + | Ty.str, "starts_with", [argTy] => if compatibleTy Ty.str argTy then some Ty.bool else none + | Ty.str, "ends_with", [argTy] => if compatibleTy Ty.str argTy then some Ty.bool else none + | Ty.str, "reverse", [] => some Ty.str + | Ty.unknown, _, _ => some Ty.unknown + | _, _, _ => none + +def compatibleMethod (target : Ty) (method : Ident) (arity : Nat) : Option Ty := + compatibleMethodWithArgs target method (List.replicate arity Ty.unknown) + +mutual + partial def checkArg (vars : VarEnv) (fns : FnSigEnv) : Arg -> Option Unit + | Arg.positional expr => do + let _ <- checkExpr vars fns expr + some () + | Arg.named _ expr => do + let _ <- checkExpr vars fns expr + some () + + partial def checkArgs (vars : VarEnv) (fns : FnSigEnv) : List Arg -> Option Unit + | [] => some () + | arg :: rest => do + let _ <- checkArg vars fns arg + checkArgs vars fns rest + + partial def inferArgTys (vars : VarEnv) (fns : FnSigEnv) : List Arg -> Option (List Ty) + | [] => some [] + | Arg.positional expr :: rest => do + let ty <- checkExpr vars fns expr + let tys <- inferArgTys vars fns rest + some (ty :: tys) + | Arg.named _ expr :: rest => do + let ty <- checkExpr vars fns expr + let tys <- inferArgTys vars fns rest + some (ty :: tys) + + partial def checkListElems (vars : VarEnv) (fns : FnSigEnv) : List Expr -> Option Ty + | [] => some Ty.unknown + | [expr] => checkExpr vars fns expr + | expr :: rest => do + let elemTy <- checkExpr vars fns expr + let restTy <- checkListElems vars fns rest + some (mergeElementTy elemTy restTy) + + partial def checkExpr (vars : VarEnv) (fns : FnSigEnv) : Expr -> Option Ty + | Expr.num _ => some Ty.num + | Expr.float _ _ => some Ty.num + | Expr.bool _ => some Ty.bool + | Expr.str _ => some Ty.str + | Expr.unit => some Ty.unit + | Expr.list exprs => do + let elemTy <- checkListElems vars fns exprs + some (Ty.list elemTy) + | Expr.var name => VarEnv.lookup vars name + | Expr.unary op expr => do + let valueTy <- checkExpr vars fns expr + compatibleUnOp op valueTy + | Expr.binary left op right => do + let leftTy <- checkExpr vars fns left + let rightTy <- checkExpr vars fns right + compatibleBinOp op leftTy rightTy + | Expr.index target index => do + let targetTy <- checkExpr vars fns target + let indexTy <- checkExpr vars fns index + compatibleIndex targetTy indexTy + | Expr.field target field => do + let targetTy <- checkExpr vars fns target + compatibleField targetTy field + | Expr.method target method args => do + let targetTy <- checkExpr vars fns target + let argTys <- inferArgTys vars fns args + compatibleMethodWithArgs targetTy method argTys + | Expr.call name args => do + let sig <- FnSigEnv.lookup fns name + if sig.arity == args.length then + let _ <- checkNamedArgs name sig.params [] args + let argTys <- inferArgTys vars fns args + let _ <- checkCallArgTys name sig.params sig.paramTys args argTys + some sig.result + else + none + + partial def checkStmt (fns : FnSigEnv) (scope : Scope) (state : CheckState) : + Stmt -> Option CheckState + | Stmt.letDecl name expr => do + let ty <- checkExpr state.vars fns expr + some { state with vars := VarEnv.bind state.vars name ty } + | Stmt.letDeclTyped name annTy expr => do + let expectedTy := annTyToTy annTy + let valueTy <- checkExpr state.vars fns expr + if assignable expectedTy valueTy then + some { state with vars := VarEnv.bind state.vars name (refineAssignedTy expectedTy valueTy) } + else + none + | Stmt.assign name expr => do + let targetTy <- VarEnv.lookup state.vars name + let valueTy <- checkExpr state.vars fns expr + if assignable targetTy valueTy then + let updatedVars <- VarEnv.assign state.vars name (refineAssignedTy targetTy valueTy) + some { state with vars := updatedVars } + else + none + | Stmt.ifThenElse condition thenBranch elseBranch => do + let conditionTy <- checkExpr state.vars fns condition + if !conditionCompatible conditionTy then + none + else + pure () + let blockScope := { scope with topLevel := false } + let thenState <- checkBlock fns blockScope state thenBranch + match elseBranch with + | none => some state + | some statements => do + let elseState <- checkBlock fns blockScope state statements + some { state with vars := joinNewBranchVars state.vars thenState.vars elseState.vars } + | Stmt.while condition body => do + let conditionTy <- checkExpr state.vars fns condition + if !conditionCompatible conditionTy then + none + else + pure () + let loopScope := { scope with loopDepth := scope.loopDepth + 1, topLevel := false } + let _ <- checkBlock fns loopScope state body + some state + | Stmt.forRange iterator _ _ body => do + let loopScope := { scope with loopDepth := scope.loopDepth + 1, topLevel := false } + let bodyState := { state with vars := VarEnv.bind state.vars iterator Ty.num } + let _ <- checkBlock fns loopScope bodyState body + some state + | Stmt.seal condition body => do + match condition with + | none => pure () + | some expr => + let conditionTy <- checkExpr state.vars fns expr + if !conditionCompatible conditionTy then + none + else + pure () + let loopScope := { scope with loopDepth := scope.loopDepth + 1, topLevel := false } + let _ <- checkBlock fns loopScope state body + some state + | Stmt.fnDecl name params body => do + if !scope.topLevel then + none + else + let sig <- FnSigEnv.lookup fns name + if sig.arity == params.length then + let fnScope := { loopDepth := 0, functionDepth := scope.functionDepth + 1, topLevel := false } + let fnState := { vars := bindUnknownParams [] params } + let _ <- checkBlock fns fnScope fnState body + some state + else + none + | Stmt.fnDeclReturn name params _ body => do + if !scope.topLevel then + none + else + let sig <- FnSigEnv.lookup fns name + if sig.arity == params.length then + let fnScope := { loopDepth := 0, functionDepth := scope.functionDepth + 1, topLevel := false } + let fnState := { vars := bindUnknownParams [] params } + let _ <- checkBlock fns fnScope fnState body + some state + else + none + | Stmt.fnDeclTyped name params body => do + if !scope.topLevel then + none + else + let sig <- FnSigEnv.lookup fns name + if sig.arity == params.length then + let fnScope := { loopDepth := 0, functionDepth := scope.functionDepth + 1, topLevel := false } + let fnState := { vars := bindTypedParams [] params } + let _ <- checkBlock fns fnScope fnState body + some state + else + none + | Stmt.fnDeclTypedReturn name params _ body => do + if !scope.topLevel then + none + else + let sig <- FnSigEnv.lookup fns name + if sig.arity == params.length then + let fnScope := { loopDepth := 0, functionDepth := scope.functionDepth + 1, topLevel := false } + let fnState := { vars := bindTypedParams [] params } + let _ <- checkBlock fns fnScope fnState body + some state + else + none + | Stmt.ret returnValue => + if scope.functionDepth == 0 then + none + else + match returnValue with + | none => some state + | some expr => do + let _ <- checkExpr state.vars fns expr + some state + | Stmt.break => + if scope.loopDepth == 0 then none else some state + | Stmt.continue => + if scope.loopDepth == 0 then none else some state + | Stmt.expr expr => do + let _ <- checkExpr state.vars fns expr + some state + + partial def checkBlock (fns : FnSigEnv) (scope : Scope) (state : CheckState) : + List Stmt -> Option CheckState + | [] => some state + | stmt :: rest => do + let next <- checkStmt fns scope state stmt + checkBlock fns scope next rest +end + +def collectFnSigs : List Stmt -> FnSigEnv + | [] => [] + | Stmt.fnDecl name params _ :: rest => + FnSigEnv.bind (collectFnSigs rest) name + { arity := params.length, params := params, paramTys := List.replicate params.length Ty.unknown, result := Ty.unknown } + | Stmt.fnDeclReturn name params returnTy _ :: rest => + FnSigEnv.bind (collectFnSigs rest) name + { arity := params.length, params := params, paramTys := List.replicate params.length Ty.unknown, result := annTyToTy returnTy } + | Stmt.fnDeclTyped name params _ :: rest => + FnSigEnv.bind (collectFnSigs rest) name + { arity := params.length, params := typedParamNames params, paramTys := typedParamTys params, result := Ty.unknown } + | Stmt.fnDeclTypedReturn name params returnTy _ :: rest => + FnSigEnv.bind (collectFnSigs rest) name + { arity := params.length, params := typedParamNames params, paramTys := typedParamTys params, result := annTyToTy returnTy } + | _ :: rest => collectFnSigs rest + +def collectFnSigsDetailed : List Stmt -> FnSigEnv -> Except CheckError FnSigEnv + | [], fns => Except.ok fns + | Stmt.fnDecl name params _ :: rest, fns => + match FnSigEnv.lookup fns name with + | some _ => Except.error (CheckError.duplicateFunction name) + | none => + collectFnSigsDetailed rest + (FnSigEnv.bind fns name + { arity := params.length, params := params, paramTys := List.replicate params.length Ty.unknown, result := Ty.unknown }) + | Stmt.fnDeclReturn name params returnTy _ :: rest, fns => + match FnSigEnv.lookup fns name with + | some _ => Except.error (CheckError.duplicateFunction name) + | none => + collectFnSigsDetailed rest + (FnSigEnv.bind fns name + { arity := params.length, params := params, paramTys := List.replicate params.length Ty.unknown, result := annTyToTy returnTy }) + | Stmt.fnDeclTyped name params _ :: rest, fns => + match FnSigEnv.lookup fns name with + | some _ => Except.error (CheckError.duplicateFunction name) + | none => + collectFnSigsDetailed rest + (FnSigEnv.bind fns name + { arity := params.length, params := typedParamNames params, paramTys := typedParamTys params, result := Ty.unknown }) + | Stmt.fnDeclTypedReturn name params returnTy _ :: rest, fns => + match FnSigEnv.lookup fns name with + | some _ => Except.error (CheckError.duplicateFunction name) + | none => + collectFnSigsDetailed rest + (FnSigEnv.bind fns name + { arity := params.length, params := typedParamNames params, paramTys := typedParamTys params, result := annTyToTy returnTy }) + | _ :: rest, fns => collectFnSigsDetailed rest fns + +def mergeReturnTy? : Option Ty -> Option Ty -> Option Ty + | none, right => right + | left, none => left + | some left, some right => some (mergeElementTy left right) + +mutual + partial def inferStmtReturnTy? (vars : VarEnv) (fns : FnSigEnv) : Stmt -> Option Ty + | Stmt.ret none => some Ty.unit + | Stmt.ret (some expr) => checkExpr vars fns expr + | Stmt.ifThenElse _ thenBranch elseBranch => + mergeReturnTy? + (inferBlockReturnTy? vars fns thenBranch) + (match elseBranch with + | none => none + | some statements => inferBlockReturnTy? vars fns statements) + | Stmt.while _ body => inferBlockReturnTy? vars fns body + | Stmt.forRange iterator _ _ body => + inferBlockReturnTy? (VarEnv.bind vars iterator Ty.num) fns body + | Stmt.seal _ body => inferBlockReturnTy? vars fns body + | _ => none + + partial def inferStmtVars? (vars : VarEnv) (fns : FnSigEnv) : Stmt -> Option VarEnv + | Stmt.letDecl name expr => do + let ty <- checkExpr vars fns expr + some (VarEnv.bind vars name ty) + | Stmt.letDeclTyped name annTy expr => do + let expectedTy := annTyToTy annTy + let valueTy <- checkExpr vars fns expr + if assignable expectedTy valueTy then + some (VarEnv.bind vars name (refineAssignedTy expectedTy valueTy)) + else + none + | Stmt.assign name expr => do + let targetTy <- VarEnv.lookup vars name + let valueTy <- checkExpr vars fns expr + if assignable targetTy valueTy then + VarEnv.assign vars name (refineAssignedTy targetTy valueTy) + else + none + | Stmt.ifThenElse condition thenBranch (some elseBranch) => do + let conditionTy <- checkExpr vars fns condition + if !conditionCompatible conditionTy then + none + else + let thenVars <- inferBlockVars? vars fns thenBranch + let elseVars <- inferBlockVars? vars fns elseBranch + some (joinNewBranchVars vars thenVars elseVars) + | _ => some vars + + partial def inferBlockVars? (vars : VarEnv) (fns : FnSigEnv) : List Stmt -> Option VarEnv + | [] => some vars + | stmt :: rest => do + let nextVars <- inferStmtVars? vars fns stmt + inferBlockVars? nextVars fns rest + + partial def inferBlockReturnTy? (vars : VarEnv) (fns : FnSigEnv) : List Stmt -> Option Ty + | [] => none + | [Stmt.expr expr] => checkExpr vars fns expr + | [stmt] => inferStmtReturnTy? vars fns stmt + | stmt :: rest => + let nextVars := + match inferStmtVars? vars fns stmt with + | some updated => updated + | none => vars + mergeReturnTy? + (inferStmtReturnTy? vars fns stmt) + (inferBlockReturnTy? nextVars fns rest) +end + +def inferFnSigFromVars (fns : FnSigEnv) (params : List Ident) (paramTys : List Ty) (vars : VarEnv) (body : List Stmt) : FnSig := + let result := + match inferBlockReturnTy? vars fns body with + | some ty => ty + | none => Ty.unknown + { arity := params.length, params := params, paramTys := paramTys, result := result } + +def inferFnSig (fns : FnSigEnv) (params : List Ident) (body : List Stmt) : FnSig := + inferFnSigFromVars fns params (List.replicate params.length Ty.unknown) (bindUnknownParams [] params) body + +def inferReturnFnSig (params : List Ident) (returnTy : AnnTy) : FnSig := + { arity := params.length + , params := params + , paramTys := List.replicate params.length Ty.unknown + , result := annTyToTy returnTy + } + +def inferTypedFnSig (fns : FnSigEnv) (params : List (Ident × AnnTy)) (body : List Stmt) : FnSig := + inferFnSigFromVars fns (typedParamNames params) (typedParamTys params) (bindTypedParams [] params) body + +def inferTypedReturnFnSig (params : List (Ident × AnnTy)) (returnTy : AnnTy) : FnSig := + { arity := params.length + , params := typedParamNames params + , paramTys := typedParamTys params + , result := annTyToTy returnTy + } + +def inferFnSigResults (stmts : List Stmt) (fns : FnSigEnv) : FnSigEnv := + match stmts with + | [] => fns + | Stmt.fnDecl name params body :: rest => + FnSigEnv.bind (inferFnSigResults rest fns) name (inferFnSig fns params body) + | Stmt.fnDeclReturn name params returnTy _ :: rest => + FnSigEnv.bind (inferFnSigResults rest fns) name (inferReturnFnSig params returnTy) + | Stmt.fnDeclTyped name params body :: rest => + FnSigEnv.bind (inferFnSigResults rest fns) name (inferTypedFnSig fns params body) + | Stmt.fnDeclTypedReturn name params returnTy _ :: rest => + FnSigEnv.bind (inferFnSigResults rest fns) name (inferTypedReturnFnSig params returnTy) + | _ :: rest => inferFnSigResults rest fns + +def checkProgramWithFns (fns : FnSigEnv) (stmts : List Stmt) : Option CheckState := + checkBlock fns { loopDepth := 0, functionDepth := 0, topLevel := true } { vars := [] } stmts + +def compatibleBinOpDetailed (op : BinOp) (left right : Ty) : Except CheckError Ty := + match compatibleBinOp op left right with + | some ty => Except.ok ty + | none => Except.error (CheckError.operandMismatch op left right) + +def compatibleUnOpDetailed (op : UnOp) (value : Ty) : Except CheckError Ty := + match compatibleUnOp op value with + | some ty => Except.ok ty + | none => Except.error (CheckError.unaryMismatch op value) + +def compatibleIndexDetailed (target index : Ty) : Except CheckError Ty := + match compatibleIndex target index with + | some ty => Except.ok ty + | none => Except.error (CheckError.indexMismatch target index) + +def compatibleFieldDetailed (target : Ty) (field : Ident) : Except CheckError Ty := + match compatibleField target field with + | some ty => Except.ok ty + | none => Except.error (CheckError.fieldMismatch target field) + +def compatibleMethodDetailed (target : Ty) (method : Ident) (arity : Nat) : + Except CheckError Ty := + match compatibleMethod target method arity with + | some ty => Except.ok ty + | none => Except.error (CheckError.methodMismatch target method arity) + +def checkConditionTyDetailed (ty : Ty) : Except CheckError Unit := + if conditionCompatible ty then + Except.ok () + else + Except.error (CheckError.conditionMismatch ty) + +mutual + partial def checkArgDetailed (vars : VarEnv) (fns : FnSigEnv) : + Arg -> Except CheckError Unit + | Arg.positional expr => do + let _ <- checkExprDetailed vars fns expr + Except.ok () + | Arg.named _ expr => do + let _ <- checkExprDetailed vars fns expr + Except.ok () + + partial def checkArgsDetailed (vars : VarEnv) (fns : FnSigEnv) : + List Arg -> Except CheckError Unit + | [] => Except.ok () + | arg :: rest => do + let _ <- checkArgDetailed vars fns arg + checkArgsDetailed vars fns rest + + partial def inferArgTysDetailed (vars : VarEnv) (fns : FnSigEnv) : + List Arg -> Except CheckError (List Ty) + | [] => Except.ok [] + | Arg.positional expr :: rest => do + let ty <- checkExprDetailed vars fns expr + let tys <- inferArgTysDetailed vars fns rest + Except.ok (ty :: tys) + | Arg.named _ expr :: rest => do + let ty <- checkExprDetailed vars fns expr + let tys <- inferArgTysDetailed vars fns rest + Except.ok (ty :: tys) + + partial def checkListElemsDetailed (vars : VarEnv) (fns : FnSigEnv) : + List Expr -> Except CheckError Ty + | [] => Except.ok Ty.unknown + | [expr] => checkExprDetailed vars fns expr + | expr :: rest => do + let elemTy <- checkExprDetailed vars fns expr + let restTy <- checkListElemsDetailed vars fns rest + Except.ok (mergeElementTy elemTy restTy) + + partial def checkExprDetailed (vars : VarEnv) (fns : FnSigEnv) : + Expr -> Except CheckError Ty + | Expr.num _ => Except.ok Ty.num + | Expr.float _ _ => Except.ok Ty.num + | Expr.bool _ => Except.ok Ty.bool + | Expr.str _ => Except.ok Ty.str + | Expr.unit => Except.ok Ty.unit + | Expr.list exprs => do + let elemTy <- checkListElemsDetailed vars fns exprs + Except.ok (Ty.list elemTy) + | Expr.var name => + match VarEnv.lookup vars name with + | some ty => Except.ok ty + | none => Except.error (CheckError.undeclaredVariable name) + | Expr.unary op expr => do + let valueTy <- checkExprDetailed vars fns expr + compatibleUnOpDetailed op valueTy + | Expr.binary left op right => do + let leftTy <- checkExprDetailed vars fns left + let rightTy <- checkExprDetailed vars fns right + compatibleBinOpDetailed op leftTy rightTy + | Expr.index target index => do + let targetTy <- checkExprDetailed vars fns target + let indexTy <- checkExprDetailed vars fns index + compatibleIndexDetailed targetTy indexTy + | Expr.field target field => do + let targetTy <- checkExprDetailed vars fns target + compatibleFieldDetailed targetTy field + | Expr.method target method args => do + let targetTy <- checkExprDetailed vars fns target + let argTys <- inferArgTysDetailed vars fns args + match compatibleMethodWithArgs targetTy method argTys with + | some ty => Except.ok ty + | none => Except.error (CheckError.methodMismatch targetTy method args.length) + | Expr.call name args => + match FnSigEnv.lookup fns name with + | none => Except.error (CheckError.undeclaredFunction name) + | some sig => + if sig.arity == args.length then do + let _ <- checkNamedArgsDetailed name sig.params [] args + let argTys <- inferArgTysDetailed vars fns args + let _ <- checkCallArgTysDetailed name sig.params sig.paramTys args argTys + Except.ok sig.result + else + Except.error (CheckError.arityMismatch name sig.arity args.length) + + partial def checkDeclaredReturnUnitFallthroughDetailed + (fnName : Ident) + (expected : Ty) : + Except CheckError Unit := + if assignable expected Ty.unit then + Except.ok () + else + Except.error (CheckError.returnMismatch fnName expected Ty.unit) + + partial def checkDeclaredReturnStmtPathsDetailed + (fnName : Ident) + (expected : Ty) + (vars : VarEnv) + (fns : FnSigEnv) : + Stmt -> Except CheckError Unit + | Stmt.ret none => + checkDeclaredReturnUnitFallthroughDetailed fnName expected + | Stmt.ret (some expr) => do + let actual <- checkExprDetailed vars fns expr + if assignable expected actual then + Except.ok () + else + Except.error (CheckError.returnMismatch fnName expected actual) + | Stmt.ifThenElse _ thenBranch elseBranch => do + let _ <- checkDeclaredReturnBlockPathsDetailed fnName expected vars fns thenBranch + match elseBranch with + | none => Except.ok () + | some statements => checkDeclaredReturnBlockPathsDetailed fnName expected vars fns statements + | Stmt.while _ body => do + checkDeclaredReturnBlockPathsDetailed fnName expected vars fns body + | Stmt.forRange iterator _ _ body => do + checkDeclaredReturnBlockPathsDetailed fnName expected (VarEnv.bind vars iterator Ty.num) fns body + | Stmt.seal none body => + checkDeclaredReturnBlockPathsDetailed fnName expected vars fns body + | Stmt.seal (some _) body => do + checkDeclaredReturnBlockPathsDetailed fnName expected vars fns body + | _ => Except.ok () + + partial def checkDeclaredReturnBlockPathsDetailed + (fnName : Ident) + (expected : Ty) + (vars : VarEnv) + (fns : FnSigEnv) : + List Stmt -> Except CheckError Unit + | [] => Except.ok () + | stmt :: rest => do + let _ <- checkDeclaredReturnStmtPathsDetailed fnName expected vars fns stmt + let nextVars := + match inferStmtVars? vars fns stmt with + | some updated => updated + | none => vars + checkDeclaredReturnBlockPathsDetailed fnName expected nextVars fns rest + + partial def checkDeclaredReturnBlockDetailed + (fnName : Ident) + (expected : Ty) + (vars : VarEnv) + (fns : FnSigEnv) : + List Stmt -> Except CheckError Unit + | [] => checkDeclaredReturnUnitFallthroughDetailed fnName expected + | [Stmt.expr expr] => do + let actual <- checkExprDetailed vars fns expr + if assignable expected actual then + Except.ok () + else + Except.error (CheckError.returnMismatch fnName expected actual) + | [Stmt.ret returnValue] => + checkDeclaredReturnStmtPathsDetailed fnName expected vars fns (Stmt.ret returnValue) + | [Stmt.ifThenElse _ thenBranch elseBranch] => do + let _ <- checkDeclaredReturnBlockDetailed fnName expected vars fns thenBranch + match elseBranch with + | none => checkDeclaredReturnUnitFallthroughDetailed fnName expected + | some statements => checkDeclaredReturnBlockDetailed fnName expected vars fns statements + | [Stmt.while _ body] => do + let _ <- checkDeclaredReturnBlockPathsDetailed fnName expected vars fns body + checkDeclaredReturnUnitFallthroughDetailed fnName expected + | [Stmt.forRange iterator _ _ body] => do + let _ <- checkDeclaredReturnBlockPathsDetailed fnName expected (VarEnv.bind vars iterator Ty.num) fns body + checkDeclaredReturnUnitFallthroughDetailed fnName expected + | [Stmt.seal none body] => + checkDeclaredReturnBlockDetailed fnName expected vars fns body + | [Stmt.seal (some _) body] => do + let _ <- checkDeclaredReturnBlockPathsDetailed fnName expected vars fns body + checkDeclaredReturnUnitFallthroughDetailed fnName expected + | [stmt] => do + let _ <- checkDeclaredReturnStmtPathsDetailed fnName expected vars fns stmt + checkDeclaredReturnUnitFallthroughDetailed fnName expected + | stmt :: rest => do + let _ <- checkDeclaredReturnStmtPathsDetailed fnName expected vars fns stmt + let nextVars := + match inferStmtVars? vars fns stmt with + | some updated => updated + | none => vars + checkDeclaredReturnBlockDetailed fnName expected nextVars fns rest + + partial def checkStmtDetailed (fns : FnSigEnv) (scope : Scope) (state : CheckState) : + Stmt -> Except CheckError CheckState + | Stmt.letDecl name expr => do + let ty <- checkExprDetailed state.vars fns expr + Except.ok { state with vars := VarEnv.bind state.vars name ty } + | Stmt.letDeclTyped name annTy expr => do + let expectedTy := annTyToTy annTy + let valueTy <- checkExprDetailed state.vars fns expr + if assignable expectedTy valueTy then + Except.ok { state with vars := VarEnv.bind state.vars name (refineAssignedTy expectedTy valueTy) } + else + Except.error (CheckError.assignmentMismatch name expectedTy valueTy) + | Stmt.assign name expr => + match VarEnv.lookup state.vars name with + | none => Except.error (CheckError.undeclaredVariable name) + | some targetTy => do + let valueTy <- checkExprDetailed state.vars fns expr + if assignable targetTy valueTy then + match VarEnv.assign state.vars name (refineAssignedTy targetTy valueTy) with + | some updatedVars => Except.ok { state with vars := updatedVars } + | none => Except.error (CheckError.undeclaredVariable name) + else + Except.error (CheckError.assignmentMismatch name targetTy valueTy) + | Stmt.ifThenElse condition thenBranch elseBranch => do + let conditionTy <- checkExprDetailed state.vars fns condition + let _ <- checkConditionTyDetailed conditionTy + let blockScope := { scope with topLevel := false } + let thenState <- checkBlockDetailed fns blockScope state thenBranch + match elseBranch with + | none => Except.ok state + | some statements => do + let elseState <- checkBlockDetailed fns blockScope state statements + Except.ok { state with vars := joinNewBranchVars state.vars thenState.vars elseState.vars } + | Stmt.while condition body => do + let conditionTy <- checkExprDetailed state.vars fns condition + let _ <- checkConditionTyDetailed conditionTy + let loopScope := { scope with loopDepth := scope.loopDepth + 1, topLevel := false } + let _ <- checkBlockDetailed fns loopScope state body + Except.ok state + | Stmt.forRange iterator _ _ body => do + let loopScope := { scope with loopDepth := scope.loopDepth + 1, topLevel := false } + let bodyState := { state with vars := VarEnv.bind state.vars iterator Ty.num } + let _ <- checkBlockDetailed fns loopScope bodyState body + Except.ok state + | Stmt.seal condition body => do + match condition with + | none => pure () + | some expr => + let conditionTy <- checkExprDetailed state.vars fns expr + let _ <- checkConditionTyDetailed conditionTy + pure () + let loopScope := { scope with loopDepth := scope.loopDepth + 1, topLevel := false } + let _ <- checkBlockDetailed fns loopScope state body + Except.ok state + | Stmt.fnDecl name params body => + if !scope.topLevel then + Except.error (CheckError.nestedFunction name) + else + match FnSigEnv.lookup fns name with + | none => Except.error (CheckError.undeclaredFunction name) + | some sig => + if sig.arity == params.length then do + let fnScope := { loopDepth := 0, functionDepth := scope.functionDepth + 1, topLevel := false } + let paramVars <- bindUnknownParamsDetailed [] params + let fnState := { vars := paramVars } + let _ <- checkBlockDetailed fns fnScope fnState body + Except.ok state + else + Except.error (CheckError.functionSignatureMismatch name sig.arity params.length) + | Stmt.fnDeclReturn name params returnTy body => + if !scope.topLevel then + Except.error (CheckError.nestedFunction name) + else + match FnSigEnv.lookup fns name with + | none => Except.error (CheckError.undeclaredFunction name) + | some sig => + if sig.arity == params.length then do + let fnScope := { loopDepth := 0, functionDepth := scope.functionDepth + 1, topLevel := false } + let paramVars <- bindUnknownParamsDetailed [] params + let fnState := { vars := paramVars } + let _ <- checkBlockDetailed fns fnScope fnState body + let expected := annTyToTy returnTy + let _ <- checkDeclaredReturnBlockDetailed name expected fnState.vars fns body + let actual := + match inferBlockReturnTy? fnState.vars fns body with + | some found => found + | none => Ty.unit + if compatibleTy expected actual then + Except.ok state + else + Except.error (CheckError.returnMismatch name expected actual) + else + Except.error (CheckError.functionSignatureMismatch name sig.arity params.length) + | Stmt.fnDeclTyped name params body => + if !scope.topLevel then + Except.error (CheckError.nestedFunction name) + else + match FnSigEnv.lookup fns name with + | none => Except.error (CheckError.undeclaredFunction name) + | some sig => + if sig.arity == params.length then do + let fnScope := { loopDepth := 0, functionDepth := scope.functionDepth + 1, topLevel := false } + let paramVars <- bindTypedParamsDetailed [] params + let fnState := { vars := paramVars } + let _ <- checkBlockDetailed fns fnScope fnState body + Except.ok state + else + Except.error (CheckError.functionSignatureMismatch name sig.arity params.length) + | Stmt.fnDeclTypedReturn name params returnTy body => + if !scope.topLevel then + Except.error (CheckError.nestedFunction name) + else + match FnSigEnv.lookup fns name with + | none => Except.error (CheckError.undeclaredFunction name) + | some sig => + if sig.arity == params.length then do + let fnScope := { loopDepth := 0, functionDepth := scope.functionDepth + 1, topLevel := false } + let paramVars <- bindTypedParamsDetailed [] params + let fnState := { vars := paramVars } + let _ <- checkBlockDetailed fns fnScope fnState body + let expected := annTyToTy returnTy + let _ <- checkDeclaredReturnBlockDetailed name expected fnState.vars fns body + let actual := + match inferBlockReturnTy? fnState.vars fns body with + | some found => found + | none => Ty.unit + if compatibleTy expected actual then + Except.ok state + else + Except.error (CheckError.returnMismatch name expected actual) + else + Except.error (CheckError.functionSignatureMismatch name sig.arity params.length) + | Stmt.ret returnValue => + if scope.functionDepth == 0 then + Except.error CheckError.returnOutsideFunction + else + match returnValue with + | none => Except.ok state + | some expr => do + let _ <- checkExprDetailed state.vars fns expr + Except.ok state + | Stmt.break => + if scope.loopDepth == 0 then + Except.error CheckError.breakOutsideLoop + else + Except.ok state + | Stmt.continue => + if scope.loopDepth == 0 then + Except.error CheckError.continueOutsideLoop + else + Except.ok state + | Stmt.expr expr => do + let _ <- checkExprDetailed state.vars fns expr + Except.ok state + + partial def checkBlockDetailed (fns : FnSigEnv) (scope : Scope) (state : CheckState) : + List Stmt -> Except CheckError CheckState + | [] => Except.ok state + | stmt :: rest => do + let next <- checkStmtDetailed fns scope state stmt + checkBlockDetailed fns scope next rest +end + +def checkProgramWithFnsDetailed (fns : FnSigEnv) (stmts : List Stmt) : + Except CheckError CheckState := + checkBlockDetailed fns { loopDepth := 0, functionDepth := 0, topLevel := true } { vars := [] } stmts + +def checkProgramDetailed (stmts : List Stmt) : Except CheckError CheckState := do + let fns <- collectFnSigsDetailed stmts [] + checkProgramWithFnsDetailed (inferFnSigResults stmts fns) stmts + +def checkProgram (stmts : List Stmt) : Option CheckState := + match checkProgramDetailed stmts with + | Except.ok state => some state + | Except.error _ => none + +example : + checkExpr [] [] (Expr.binary (Expr.num 2) BinOp.add (Expr.num 3)) = some Ty.num := by + native_decide + +example : + checkExpr [] [] (Expr.binary (Expr.float 1 500000) BinOp.add (Expr.num 2)) = some Ty.num := by + native_decide + +example : + checkExpr [] [] (Expr.binary (Expr.num 2) BinOp.add (Expr.bool true)) = none := by + native_decide + +example : + checkExpr [] [] (Expr.str "open") = some Ty.str := by + native_decide + +example : + checkExpr [] [] Expr.unit = some Ty.unit := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.binary (Expr.str "open") BinOp.add (Expr.str "done")) + == Except.error (CheckError.operandMismatch BinOp.add Ty.str Ty.str)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.binary (Expr.str "open") BinOp.eq (Expr.str "open")) + == Except.ok Ty.bool) := by + native_decide + +example : + checkExpr [] [] (Expr.list [Expr.num 1, Expr.num 2]) = some (Ty.list Ty.num) := by + native_decide + +example : + checkExpr [] [] (Expr.list [Expr.str "open", Expr.str "done"]) = some (Ty.list Ty.str) := by + native_decide + +example : + checkExpr [] [] (Expr.list [Expr.num 1, Expr.bool true]) = some (Ty.list Ty.unknown) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.list [Expr.var "missing"]) + == Except.error (CheckError.undeclaredVariable "missing")) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.binary (Expr.list [Expr.num 1]) BinOp.add (Expr.list [Expr.num 2])) + == Except.error (CheckError.operandMismatch BinOp.add (Ty.list Ty.num) (Ty.list Ty.num))) := by + native_decide + +example : + checkExpr [] [] (Expr.index (Expr.list [Expr.num 1]) (Expr.num 0)) = some Ty.num := by + native_decide + +example : + checkExpr [] [] (Expr.index (Expr.str "open") (Expr.num 1)) = some Ty.str := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.letDecl "xs" (Expr.list [Expr.num 1]) + , Stmt.letDecl "x" (Expr.index (Expr.var "xs") (Expr.num 0)) + , Stmt.assign "x" (Expr.str "bad") + ] + == Except.error (CheckError.assignmentMismatch "x" Ty.num Ty.str)) := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.letDeclTyped "count" AnnTy.num (Expr.bool true) ] + == Except.error (CheckError.assignmentMismatch "count" Ty.num Ty.bool)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.index (Expr.list [Expr.num 1]) (Expr.bool true)) + == Except.error (CheckError.indexMismatch (Ty.list Ty.num) Ty.bool)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.index (Expr.str "open") (Expr.bool true)) + == Except.error (CheckError.indexMismatch Ty.str Ty.bool)) := by + native_decide + +example : + checkExpr [] [] (Expr.field (Expr.list [Expr.num 1]) "length") = some Ty.num := by + native_decide + +example : + checkExpr [] [] (Expr.field (Expr.str "open") "length") = some Ty.num := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.field (Expr.num 1) "length") + == Except.error (CheckError.fieldMismatch Ty.num "length")) := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.list [Expr.num 1]) "len" []) = some Ty.num := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.str "open") "len" []) = some Ty.num := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.list []) "is_empty" []) = some Ty.bool := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.str "open") "is_empty" []) = some Ty.bool := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.list [Expr.num 1]) "first" []) = some Ty.num := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.list [Expr.str "open"]) "first" []) = some Ty.str := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.list [Expr.num 1]) "last" []) = some Ty.num := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.list [Expr.str "open"]) "last" []) = some Ty.str := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.list [Expr.num 1]) "at" [Expr.num 0]) = some Ty.num := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.list [Expr.num 1]) "at" [Expr.bool true]) + == Except.error (CheckError.methodMismatch (Ty.list Ty.num) "at" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.num 1) "len" []) + == Except.error (CheckError.methodMismatch Ty.num "len" 0)) := by + native_decide + +example : + checkProgram [Stmt.assign "x" (Expr.num 1)] = none := by + native_decide + +example : + checkProgram + [ Stmt.fnDecl "id" ["x"] [Stmt.ret (some (Expr.var "x"))] + , Stmt.letDecl "x" (Expr.call "id" [Expr.num 0]) + , Stmt.assign "x" (Expr.num 1) + ] + = + some { vars := [("x", Ty.num)] } := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.fnDecl "id" ["x"] [Stmt.ret (some (Expr.var "x"))] + , Stmt.letDecl "x" (Expr.call "id" [Expr.num 0]) + , Stmt.assign "x" (Expr.num 1) + , Stmt.assign "x" (Expr.str "bad") + ] + == Except.error (CheckError.assignmentMismatch "x" Ty.num Ty.str)) := by + native_decide + +example : + checkProgram [Stmt.break] = none := by + native_decide + +example : + checkProgram + [ Stmt.letDecl "x" (Expr.num 0) + , Stmt.while + (Expr.binary (Expr.var "x") BinOp.lt (Expr.num 10)) + [ Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1)) + , Stmt.break + ] + ] + = + some { vars := [("x", Ty.num)] } := by + native_decide + +example : + checkProgram + [ Stmt.fnDecl + "add" + ["a", "b"] + [Stmt.ret (some (Expr.binary (Expr.var "a") BinOp.add (Expr.var "b")))] + , Stmt.letDecl "x" (Expr.call "add" [Expr.num 2, Expr.num 3]) + ] + = + some { vars := [("x", Ty.num)] } := by + native_decide + +example : + checkExpr [] [("one", { arity := 0, params := [], paramTys := [], result := Ty.num })] (Expr.call "one" []) = some Ty.num := by + native_decide + +example : + checkProgram + [ Stmt.fnDecl "one" [] [Stmt.ret (some (Expr.num 1))] + , Stmt.letDecl "x" (Expr.call "one" []) + ] + = + some { vars := [("x", Ty.num)] } := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.fnDecl "one" [] [Stmt.ret (some (Expr.num 1))] + , Stmt.letDecl "x" (Expr.call "one" []) + , Stmt.assign "x" (Expr.str "bad") + ] + == Except.error (CheckError.assignmentMismatch "x" Ty.num Ty.str)) := by + native_decide + +example : + checkProgram + [ Stmt.fnDecl "implicitOne" [] [Stmt.letDecl "x" (Expr.num 1), Stmt.expr (Expr.var "x")] + , Stmt.letDecl "x" (Expr.call "implicitOne" []) + ] + = + some { vars := [("x", Ty.num)] } := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.fnDecl "implicitLabel" [] [Stmt.expr (Expr.str "ready")] + , Stmt.letDecl "label" (Expr.call "implicitLabel" []) + , Stmt.assign "label" (Expr.num 1) + ] + == Except.error (CheckError.assignmentMismatch "label" Ty.str Ty.num)) := by + native_decide + +example : + checkProgram + [ Stmt.fnDecl + "branchValue" + [] + [ Stmt.ifThenElse + (Expr.bool true) + [Stmt.letDecl "x" (Expr.num 1)] + (some [Stmt.letDecl "x" (Expr.num 2)]) + , Stmt.expr (Expr.var "x") + ] + , Stmt.letDecl "x" (Expr.call "branchValue" []) + ] + = + some { vars := [("x", Ty.num)] } := by + native_decide + +example : + checkProgram + [ Stmt.fnDecl + "branchMismatch" + [] + [ Stmt.ifThenElse + (Expr.bool true) + [Stmt.letDecl "x" (Expr.num 1)] + (some [Stmt.letDecl "x" (Expr.str "bad")]) + , Stmt.expr (Expr.var "x") + ] + , Stmt.letDecl "x" (Expr.call "branchMismatch" []) + ] + = + none := by + native_decide + +example : + checkProgram + [ Stmt.fnDecl + "id" + ["x"] + [Stmt.ret (some (Expr.var "x"))] + , Stmt.letDecl "bad" (Expr.call "id" [Expr.num 1, Expr.num 2]) + ] + = + none := by + native_decide + +example : + checkProgram + [Stmt.ret (some (Expr.num 1))] + = + none := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.binary (Expr.num 2) BinOp.add (Expr.bool true)) + == Except.error (CheckError.operandMismatch BinOp.add Ty.num Ty.bool)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.binary (Expr.num 1) BinOp.and (Expr.bool true)) + == Except.error (CheckError.operandMismatch BinOp.and Ty.num Ty.bool)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.binary (Expr.bool false) BinOp.or (Expr.num 1)) + == Except.error (CheckError.operandMismatch BinOp.or Ty.bool Ty.num)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.binary (Expr.num 1) BinOp.eq (Expr.bool true)) + == Except.error (CheckError.operandMismatch BinOp.eq Ty.num Ty.bool)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.binary (Expr.bool false) BinOp.neq (Expr.num 1)) + == Except.error (CheckError.operandMismatch BinOp.neq Ty.bool Ty.num)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.unary UnOp.not (Expr.num 1)) + == Except.error (CheckError.unaryMismatch UnOp.not Ty.num)) := by + native_decide + +example : + (checkProgramDetailed [Stmt.assign "x" (Expr.num 1)] + == Except.error (CheckError.undeclaredVariable "x")) := by + native_decide + +example : + (checkProgramDetailed [Stmt.break] + == Except.error CheckError.breakOutsideLoop) := by + native_decide + +example : + (checkProgramDetailed [Stmt.continue] + == Except.error CheckError.continueOutsideLoop) := by + native_decide + +example : + (checkProgramDetailed [Stmt.ret (some (Expr.num 1))] + == Except.error CheckError.returnOutsideFunction) := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.list [Expr.num 1]) "contains" [Expr.num 1]) = + some Ty.bool := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.list [Expr.num 1, Expr.num 2]) "tail" []) = + some (Ty.list Ty.num) := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.list [Expr.num 1, Expr.num 2]) "take" [Expr.num 1]) = + some (Ty.list Ty.num) := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.list [Expr.num 1, Expr.num 2]) "drop" [Expr.num 1]) = + some (Ty.list Ty.num) := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.list [Expr.num 1, Expr.num 2]) "reverse" []) = + some (Ty.list Ty.num) := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.list [Expr.num 1]) "append" [Expr.num 2]) = + some (Ty.list Ty.num) := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.list [Expr.num 2]) "prepend" [Expr.num 1]) = + some (Ty.list Ty.num) := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.list [Expr.str "a", Expr.str "b"]) "join" [Expr.str ","]) = + some Ty.str := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.list [Expr.num 1]) "concat" [Expr.list [Expr.num 2]]) = + some (Ty.list Ty.num) := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.str "open") "at" [Expr.num 1]) = + some Ty.str := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.str "open") "contains" [Expr.str "pe"]) = + some Ty.bool := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.str "open") "starts_with" [Expr.str "op"]) = + some Ty.bool := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.str "open") "ends_with" [Expr.str "en"]) = + some Ty.bool := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.str "open") "reverse" []) = + some Ty.str := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.str "open") "first" []) = + some Ty.str := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.str "open") "last" []) = + some Ty.str := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.str "open") "tail" []) = + some Ty.str := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.str "open") "take" [Expr.num 2]) = + some Ty.str := by + native_decide + +example : + checkExpr [] [] (Expr.method (Expr.str "open") "drop" [Expr.num 2]) = + some Ty.str := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.list [Expr.num 1]) "contains" [Expr.bool true]) + == Except.error (CheckError.methodMismatch (Ty.list Ty.num) "contains" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.list [Expr.num 1]) "tail" [Expr.num 0]) + == Except.error (CheckError.methodMismatch (Ty.list Ty.num) "tail" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.list [Expr.num 1]) "take" [Expr.bool true]) + == Except.error (CheckError.methodMismatch (Ty.list Ty.num) "take" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.list [Expr.num 1]) "drop" [Expr.bool true]) + == Except.error (CheckError.methodMismatch (Ty.list Ty.num) "drop" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.list [Expr.num 1]) "reverse" [Expr.num 0]) + == Except.error (CheckError.methodMismatch (Ty.list Ty.num) "reverse" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.list [Expr.num 1]) "append" [Expr.bool true]) + == Except.error (CheckError.methodMismatch (Ty.list Ty.num) "append" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.list [Expr.num 1]) "prepend" [Expr.bool true]) + == Except.error (CheckError.methodMismatch (Ty.list Ty.num) "prepend" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.list [Expr.num 1]) "join" [Expr.str ","]) + == Except.error (CheckError.methodMismatch (Ty.list Ty.num) "join" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.list [Expr.num 1]) "concat" [Expr.list [Expr.bool true]]) + == Except.error (CheckError.methodMismatch (Ty.list Ty.num) "concat" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.str "open") "at" [Expr.bool true]) + == Except.error (CheckError.methodMismatch Ty.str "at" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.str "open") "contains" [Expr.num 1]) + == Except.error (CheckError.methodMismatch Ty.str "contains" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.str "open") "starts_with" [Expr.num 1]) + == Except.error (CheckError.methodMismatch Ty.str "starts_with" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.str "open") "ends_with" [Expr.num 1]) + == Except.error (CheckError.methodMismatch Ty.str "ends_with" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.str "open") "reverse" [Expr.num 1]) + == Except.error (CheckError.methodMismatch Ty.str "reverse" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.str "open") "first" [Expr.num 0]) + == Except.error (CheckError.methodMismatch Ty.str "first" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.str "open") "last" [Expr.num 0]) + == Except.error (CheckError.methodMismatch Ty.str "last" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.str "open") "tail" [Expr.num 0]) + == Except.error (CheckError.methodMismatch Ty.str "tail" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.str "open") "take" [Expr.bool true]) + == Except.error (CheckError.methodMismatch Ty.str "take" 1)) := by + native_decide + +example : + (checkExprDetailed [] [] (Expr.method (Expr.str "open") "drop" [Expr.bool true]) + == Except.error (CheckError.methodMismatch Ty.str "drop" 1)) := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.fnDecl + "id" + ["x"] + [Stmt.ret (some (Expr.var "x"))] + , Stmt.letDecl "bad" (Expr.call "id" [Expr.num 1, Expr.num 2]) + ] + == Except.error (CheckError.arityMismatch "id" 1 2)) := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.fnDeclTyped + "id" + [("x", AnnTy.num)] + [Stmt.ret (some (Expr.var "x"))] + , Stmt.letDecl "bad" (Expr.call "id" [Expr.bool true]) + ] + == Except.error (CheckError.argumentMismatch "id" "x" Ty.num Ty.bool)) := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.fnDeclTypedReturn + "bad" + [("x", AnnTy.num)] + AnnTy.num + [Stmt.ret (some (Expr.bool true))] + ] + == Except.error (CheckError.returnMismatch "bad" Ty.num Ty.bool)) := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.fnDeclReturn + "bad" + ["x"] + AnnTy.num + [Stmt.ret (some (Expr.bool true))] + ] + == Except.error (CheckError.returnMismatch "bad" Ty.num Ty.bool)) := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.fnDeclReturn + "one" + [] + AnnTy.num + [Stmt.ret (some (Expr.num 1))] + , Stmt.letDeclTyped "bad" AnnTy.bool (Expr.call "one" []) + ] + == Except.error (CheckError.assignmentMismatch "bad" Ty.bool Ty.num)) := by + native_decide + +example : + checkProgram + [ Stmt.fnDeclTypedReturn + "bad" + [("x", AnnTy.num)] + AnnTy.num + [Stmt.ret (some (Expr.bool true))] + ] + = + none := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.fnDeclTypedReturn + "bad" + [] + AnnTy.num + [ Stmt.ifThenElse + (Expr.bool true) + [Stmt.ret (some (Expr.num 1))] + (some [Stmt.ret (some (Expr.bool true))]) + ] + ] + == Except.error (CheckError.returnMismatch "bad" Ty.num Ty.bool)) := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.fnDeclTypedReturn + "maybe" + [("b", AnnTy.bool)] + AnnTy.num + [ Stmt.ifThenElse + (Expr.var "b") + [Stmt.ret (some (Expr.num 1))] + none + ] + ] + == Except.error (CheckError.returnMismatch "maybe" Ty.num Ty.unit)) := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.fnDeclTypedReturn + "loopReturn" + [("b", AnnTy.bool)] + AnnTy.num + [ Stmt.while + (Expr.var "b") + [Stmt.ret (some (Expr.num 1))] + ] + ] + == Except.error (CheckError.returnMismatch "loopReturn" Ty.num Ty.unit)) := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.fnDeclTypedReturn + "rangeReturn" + [] + AnnTy.num + [ Stmt.forRange + "i" + 0 + 0 + [Stmt.ret (some (Expr.num 1))] + ] + ] + == Except.error (CheckError.returnMismatch "rangeReturn" Ty.num Ty.unit)) := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.fnDeclTypedReturn + "sealReturn" + [("b", AnnTy.bool)] + AnnTy.num + [ Stmt.seal + (some (Expr.var "b")) + [Stmt.ret (some (Expr.num 1))] + ] + ] + == Except.error (CheckError.returnMismatch "sealReturn" Ty.num Ty.unit)) := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.fnDeclTypedReturn + "guarded" + [("b", AnnTy.bool)] + AnnTy.num + [ Stmt.ifThenElse + (Expr.var "b") + [Stmt.ret (some (Expr.num 1))] + none + , Stmt.ret (some (Expr.num 2)) + ] + ] + == Except.ok { vars := [] }) := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.fnDeclTypedReturn + "len" + [("xs", AnnTy.list AnnTy.num)] + AnnTy.num + [Stmt.ret (some (Expr.field (Expr.var "xs") "length"))] + , Stmt.letDecl "bad" (Expr.call "len" [Expr.list [Expr.bool true]]) + ] + == Except.error + (CheckError.argumentMismatch "len" "xs" (Ty.list Ty.num) (Ty.list Ty.bool))) := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.fnDecl + "pick" + ["a", "b"] + [Stmt.ret (some (Expr.var "a"))] + , Stmt.letDecl "bad" (Expr.call "pick" [Arg.named "c" (Expr.num 1), Arg.named "a" (Expr.num 2)]) + ] + == Except.error (CheckError.unknownNamedArgument "pick" "c")) := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.fnDecl + "pick" + ["a", "b"] + [Stmt.ret (some (Expr.var "a"))] + , Stmt.letDecl "bad" (Expr.call "pick" [Arg.named "a" (Expr.num 1), Arg.named "a" (Expr.num 2)]) + ] + == Except.error (CheckError.duplicateNamedArgument "pick" "a")) := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.fnDecl "dup" [] [Stmt.ret (some (Expr.num 1))] + , Stmt.fnDecl "dup" [] [Stmt.ret (some (Expr.num 2))] + ] + == Except.error (CheckError.duplicateFunction "dup")) := by + native_decide + +example : + (checkProgramDetailed + [Stmt.fnDecl "bad" ["x", "x"] [Stmt.ret (some (Expr.var "x"))]] + == Except.error (CheckError.duplicateParameter "x")) := by + native_decide + +example : + (checkProgramDetailed [Stmt.ifThenElse (Expr.num 1) [] none] + == Except.error (CheckError.conditionMismatch Ty.num)) := by + native_decide + +example : + checkProgram + [ Stmt.ifThenElse + (Expr.bool true) + [Stmt.letDecl "x" (Expr.num 1)] + (some [Stmt.letDecl "x" (Expr.num 2)]) + , Stmt.letDecl "y" (Expr.var "x") + ] + = + some { vars := [("y", Ty.num), ("x", Ty.num)] } := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.ifThenElse + (Expr.bool true) + [Stmt.letDecl "x" (Expr.num 1)] + (some [Stmt.letDecl "x" (Expr.str "bad")]) + , Stmt.letDecl "y" (Expr.var "x") + ] + == Except.error (CheckError.undeclaredVariable "x")) := by + native_decide + +example : + checkProgram + [ Stmt.fnDecl "id" ["x"] [Stmt.ret (some (Expr.var "x"))] + , Stmt.letDecl "x" (Expr.call "id" [Expr.num 0]) + , Stmt.ifThenElse + (Expr.bool true) + [Stmt.assign "x" (Expr.num 1)] + (some [Stmt.assign "x" (Expr.num 2)]) + ] + = + some { vars := [("x", Ty.num)] } := by + native_decide + +example : + checkProgram + [ Stmt.fnDecl "id" ["x"] [Stmt.ret (some (Expr.var "x"))] + , Stmt.letDecl "x" (Expr.call "id" [Expr.num 0]) + , Stmt.ifThenElse + (Expr.bool true) + [Stmt.assign "x" (Expr.num 1)] + (some [Stmt.assign "x" (Expr.str "bad")]) + ] + = + some { vars := [("x", Ty.unknown)] } := by + native_decide + +example : + (checkProgramDetailed [Stmt.while (Expr.num 1) []] + == Except.error (CheckError.conditionMismatch Ty.num)) := by + native_decide + +example : + (checkProgramDetailed [Stmt.seal (some (Expr.num 1)) []] + == Except.error (CheckError.conditionMismatch Ty.num)) := by + native_decide + +example : + (checkProgramDetailed + [ Stmt.ifThenElse + (Expr.bool true) + [Stmt.fnDecl "nested" [] [Stmt.ret (some (Expr.num 1))]] + none + ] + == Except.error (CheckError.nestedFunction "nested")) := by + native_decide + +end Static +end Aether diff --git a/Aether/VM.lean b/Aether/VM.lean new file mode 100644 index 0000000..c0afc1e --- /dev/null +++ b/Aether/VM.lean @@ -0,0 +1,2526 @@ +import Aether.Core +import Aether.Static +import Aether.Parser + +namespace Aether +namespace VM + +inductive Op where + | push : Value -> Op + | load : Nat -> Op + | store : Nat -> Op + | bin : BinOp -> Op + | unary : UnOp -> Op + | list : Nat -> Op + | index : Op + | field : Ident -> Op + | method : Ident -> Nat -> Op + | jmp : Int -> Op + | jmpIfFalse : Int -> Op + | halt : Op + deriving Repr, BEq, DecidableEq + +structure State where + ip : Nat + code : List Op + stack : List Value + locals : List Value + halted : Bool + deriving Repr, BEq, DecidableEq + +def initialState (code : List Op) : State := + { ip := 0, code := code, stack := [], locals := [], halted := false } + +def listGet? (values : List α) (idx : Nat) : Option α := + values[idx]? + +def listSet (values : List α) (idx : Nat) (value : α) (fill : α) : List α := + if idx < values.length then + values.set idx value + else + values ++ List.replicate (idx - values.length) fill ++ [value] + +def jumpTarget (ip : Nat) (offset : Int) : Option Nat := + let target := (ip : Int) + offset + if target < 0 then none else some target.toNat + +def popValues : Nat -> List Value -> Option (List Value × List Value) + | 0, stack => some ([], stack) + | fuel + 1, value :: rest => do + let (values, remaining) <- popValues fuel rest + some (values ++ [value], remaining) + | _ + 1, [] => none + +def step (state : State) : Option State := + if state.halted then + some state + else + match state.code[state.ip]? with + | none => some { state with halted := true } + | some Op.halt => some { state with ip := state.ip + 1, halted := true } + | some (Op.push value) => + some { state with ip := state.ip + 1, stack := value :: state.stack } + | some (Op.load idx) => do + let value <- listGet? state.locals idx + some { state with ip := state.ip + 1, stack := value :: state.stack } + | some (Op.store idx) => + match state.stack with + | value :: rest => + let locals := listSet state.locals idx value Value.unit + some { state with ip := state.ip + 1, stack := rest, locals := locals } + | [] => none + | some (Op.bin op) => + match state.stack with + | right :: left :: rest => do + let value <- evalBinOp op left right + some { state with ip := state.ip + 1, stack := value :: rest } + | _ => none + | some (Op.unary op) => + match state.stack with + | value :: rest => do + let result <- evalUnOp op value + some { state with ip := state.ip + 1, stack := result :: rest } + | [] => none + | some (Op.list length) => do + let (values, restStack) <- popValues length state.stack + some { state with ip := state.ip + 1, stack := Value.list values :: restStack } + | some Op.index => + match state.stack with + | index :: target :: rest => do + let value <- evalIndex target index + some { state with ip := state.ip + 1, stack := value :: rest } + | _ => none + | some (Op.field field) => + match state.stack with + | target :: rest => do + let value <- evalField target field + some { state with ip := state.ip + 1, stack := value :: rest } + | [] => none + | some (Op.method method arity) => do + let (values, restStack) <- popValues (arity + 1) state.stack + match values with + | target :: args => do + let value <- evalMethod target method args + some { state with ip := state.ip + 1, stack := value :: restStack } + | [] => none + | some (Op.jmp offset) => do + let target <- jumpTarget (state.ip + 1) offset + some { state with ip := target } + | some (Op.jmpIfFalse offset) => + match state.stack with + | value :: rest => + if truthy value then + some { state with ip := state.ip + 1, stack := rest } + else do + let target <- jumpTarget (state.ip + 1) offset + some { state with ip := target, stack := rest } + | [] => none + +def runFuel : Nat -> State -> Option State + | 0, state => some state + | Nat.succ fuel, state => + if state.halted then + some state + else do + let next <- step state + runFuel fuel next + +def run (fuel : Nat) (code : List Op) : Option State := + runFuel fuel (initialState code) + +inductive FrameOp where + | push : Value -> FrameOp + | load : Nat -> FrameOp + | store : Nat -> FrameOp + | bin : BinOp -> FrameOp + | unary : UnOp -> FrameOp + | list : Nat -> FrameOp + | index : FrameOp + | field : Ident -> FrameOp + | method : Ident -> Nat -> FrameOp + | jmp : Int -> FrameOp + | jmpIfFalse : Int -> FrameOp + | call : Nat -> Nat -> FrameOp + | ret : FrameOp + | halt : FrameOp + deriving Repr, BEq, DecidableEq + +structure CallFrame where + returnIp : Nat + locals : List Value + deriving Repr, BEq, DecidableEq + +structure FrameState where + ip : Nat + code : List FrameOp + stack : List Value + locals : List Value + frames : List CallFrame + halted : Bool + deriving Repr, BEq, DecidableEq + +def initialFrameState (code : List FrameOp) : FrameState := + { ip := 0, code := code, stack := [], locals := [], frames := [], halted := false } + +def popArgs : Nat -> List Value -> Option (List Value × List Value) + | 0, stack => some ([], stack) + | fuel + 1, value :: rest => do + let (args, remaining) <- popArgs fuel rest + some (args ++ [value], remaining) + | _ + 1, [] => none + +def frameStep (state : FrameState) : Option FrameState := + if state.halted then + some state + else + match state.code[state.ip]? with + | none => some { state with halted := true } + | some FrameOp.halt => some { state with ip := state.ip + 1, halted := true } + | some (FrameOp.push value) => + some { state with ip := state.ip + 1, stack := value :: state.stack } + | some (FrameOp.load idx) => do + let value <- listGet? state.locals idx + some { state with ip := state.ip + 1, stack := value :: state.stack } + | some (FrameOp.store idx) => + match state.stack with + | value :: rest => + let locals := listSet state.locals idx value Value.unit + some { state with ip := state.ip + 1, stack := rest, locals := locals } + | [] => none + | some (FrameOp.bin op) => + match state.stack with + | right :: left :: rest => do + let value <- evalBinOp op left right + some { state with ip := state.ip + 1, stack := value :: rest } + | _ => none + | some (FrameOp.unary op) => + match state.stack with + | value :: rest => do + let result <- evalUnOp op value + some { state with ip := state.ip + 1, stack := result :: rest } + | [] => none + | some (FrameOp.list length) => do + let (values, restStack) <- popArgs length state.stack + some { state with ip := state.ip + 1, stack := Value.list values :: restStack } + | some FrameOp.index => + match state.stack with + | index :: target :: rest => do + let value <- evalIndex target index + some { state with ip := state.ip + 1, stack := value :: rest } + | _ => none + | some (FrameOp.field field) => + match state.stack with + | target :: rest => do + let value <- evalField target field + some { state with ip := state.ip + 1, stack := value :: rest } + | [] => none + | some (FrameOp.method method arity) => do + let (values, restStack) <- popArgs (arity + 1) state.stack + match values with + | target :: args => do + let value <- evalMethod target method args + some { state with ip := state.ip + 1, stack := value :: restStack } + | [] => none + | some (FrameOp.jmp offset) => do + let target <- jumpTarget (state.ip + 1) offset + some { state with ip := target } + | some (FrameOp.jmpIfFalse offset) => + match state.stack with + | value :: rest => + if truthy value then + some { state with ip := state.ip + 1, stack := rest } + else do + let target <- jumpTarget (state.ip + 1) offset + some { state with ip := target, stack := rest } + | [] => none + | some (FrameOp.call target arity) => do + if target < state.code.length then + let (args, restStack) <- popArgs arity state.stack + some + { state with + ip := target + stack := restStack + locals := args + frames := { returnIp := state.ip + 1, locals := state.locals } :: state.frames } + else + none + | some FrameOp.ret => + let value := + match state.stack with + | result :: _ => result + | [] => Value.unit + match state.frames with + | frame :: restFrames => + some + { state with + ip := frame.returnIp + stack := value :: state.stack.drop 1 + locals := frame.locals + frames := restFrames } + | [] => none + +def runFrameFuel : Nat -> FrameState -> Option FrameState + | 0, state => some state + | Nat.succ fuel, state => + if state.halted then + some state + else do + let next <- frameStep state + runFrameFuel fuel next + +def runFrame (fuel : Nat) (code : List FrameOp) : Option FrameState := + runFrameFuel fuel (initialFrameState code) + +abbrev SlotEnv := List (Ident × Nat) + +def SlotEnv.lookup (slots : SlotEnv) (name : Ident) : Option Nat := + match slots with + | [] => none + | (key, slot) :: rest => + if key == name then some slot else SlotEnv.lookup rest name + +def SlotEnv.resolve (slots : SlotEnv) (name : Ident) : SlotEnv × Nat := + match SlotEnv.lookup slots name with + | some slot => (slots, slot) + | none => + let slot := slots.length + (slots ++ [(name, slot)], slot) + +structure FrameFunction where + name : Ident + params : List Ident + body : List Stmt + deriving Repr, BEq + +abbrev FrameFnEnv := List (Ident × Nat × Nat) + +def FrameFnEnv.lookup (fns : FrameFnEnv) (name : Ident) : Option (Nat × Nat) := + match fns with + | [] => none + | (key, target, arity) :: rest => + if key == name then some (target, arity) else FrameFnEnv.lookup rest name + +def collectFrameFunctions : List Stmt -> List FrameFunction + | [] => [] + | Stmt.fnDecl name params body :: rest => + { name := name, params := params, body := body } :: collectFrameFunctions rest + | Stmt.fnDeclReturn name params _ body :: rest => + { name := name, params := params, body := body } :: collectFrameFunctions rest + | Stmt.fnDeclTyped name params body :: rest => + { name := name, params := params.map Prod.fst, body := body } :: collectFrameFunctions rest + | Stmt.fnDeclTypedReturn name params _ body :: rest => + { name := name, params := params.map Prod.fst, body := body } :: collectFrameFunctions rest + | _ :: rest => collectFrameFunctions rest + +abbrev FrameParamEnv := List (Ident × List Ident) + +def FrameParamEnv.lookup (params : FrameParamEnv) (name : Ident) : Option (List Ident) := + match params with + | [] => none + | (key, fnParams) :: rest => + if key == name then some fnParams else FrameParamEnv.lookup rest name + +def collectFrameParamEnv (functions : List FrameFunction) : FrameParamEnv := + functions.map (fun fn => (fn.name, fn.params)) + +def argNodeLookup (bindings : List (Ident × Arg)) (name : Ident) : Option Arg := + match bindings with + | [] => none + | (key, arg) :: rest => + if key == name then some arg else argNodeLookup rest name + +def bindArgNodeName + (params : List Ident) + (name : Ident) + (arg : Arg) + (bindings : List (Ident × Arg)) : + Option (List (Ident × Arg)) := + if identInList name params then + match argNodeLookup bindings name with + | none => some ((name, arg) :: bindings) + | some _ => none + else + none + +def firstUnboundArgParam (params : List Ident) (bindings : List (Ident × Arg)) : Option Ident := + match params with + | [] => none + | name :: rest => + match argNodeLookup bindings name with + | none => some name + | some _ => firstUnboundArgParam rest bindings + +def bindArgNodes + (params : List Ident) + (args : List Arg) + (bindings : List (Ident × Arg)) : + Option (List (Ident × Arg)) := + match args with + | [] => some bindings + | Arg.positional expr :: rest => do + let name <- firstUnboundArgParam params bindings + let updated <- bindArgNodeName params name (Arg.positional expr) bindings + bindArgNodes params rest updated + | Arg.named name expr :: rest => do + let updated <- bindArgNodeName params name (Arg.named name expr) bindings + bindArgNodes params rest updated + +def orderedBoundArgs (params : List Ident) (bindings : List (Ident × Arg)) : + Option (List Arg) := + match params with + | [] => some [] + | name :: rest => do + let arg <- argNodeLookup bindings name + let args <- orderedBoundArgs rest bindings + some (arg :: args) + +def reorderCallArgsForParams (params : List Ident) (args : List Arg) : Option (List Arg) := + if argsAllPositional args then + some args + else do + let bindings <- bindArgNodes params args [] + orderedBoundArgs params bindings + +mutual + partial def normalizeFrameArgCalls (params : FrameParamEnv) : Arg -> Option Arg + | Arg.positional expr => do + let normalized <- normalizeFrameExprCalls params expr + some (Arg.positional normalized) + | Arg.named name expr => do + let normalized <- normalizeFrameExprCalls params expr + some (Arg.named name normalized) + + partial def normalizeFrameArgsCalls (params : FrameParamEnv) : List Arg -> Option (List Arg) + | [] => some [] + | arg :: rest => do + let normalizedArg <- normalizeFrameArgCalls params arg + let normalizedRest <- normalizeFrameArgsCalls params rest + some (normalizedArg :: normalizedRest) + + partial def normalizeFrameExprsCalls (params : FrameParamEnv) : List Expr -> Option (List Expr) + | [] => some [] + | expr :: rest => do + let normalizedExpr <- normalizeFrameExprCalls params expr + let normalizedRest <- normalizeFrameExprsCalls params rest + some (normalizedExpr :: normalizedRest) + + partial def normalizeFrameExprCalls (params : FrameParamEnv) : Expr -> Option Expr + | Expr.num n => some (Expr.num n) + | Expr.float intPart fracMicros => some (Expr.float intPart fracMicros) + | Expr.bool b => some (Expr.bool b) + | Expr.str value => some (Expr.str value) + | Expr.unit => some Expr.unit + | Expr.var name => some (Expr.var name) + | Expr.list exprs => do + let normalized <- normalizeFrameExprsCalls params exprs + some (Expr.list normalized) + | Expr.unary op expr => do + let normalized <- normalizeFrameExprCalls params expr + some (Expr.unary op normalized) + | Expr.binary left op right => do + let normalizedLeft <- normalizeFrameExprCalls params left + let normalizedRight <- normalizeFrameExprCalls params right + some (Expr.binary normalizedLeft op normalizedRight) + | Expr.index target index => do + let normalizedTarget <- normalizeFrameExprCalls params target + let normalizedIndex <- normalizeFrameExprCalls params index + some (Expr.index normalizedTarget normalizedIndex) + | Expr.field target field => do + let normalizedTarget <- normalizeFrameExprCalls params target + some (Expr.field normalizedTarget field) + | Expr.method target method args => do + let normalizedTarget <- normalizeFrameExprCalls params target + let normalizedArgs <- normalizeFrameArgsCalls params args + some (Expr.method normalizedTarget method normalizedArgs) + | Expr.call name args => do + let normalizedArgs <- normalizeFrameArgsCalls params args + match FrameParamEnv.lookup params name with + | none => some (Expr.call name normalizedArgs) + | some fnParams => do + let reordered <- reorderCallArgsForParams fnParams normalizedArgs + some (Expr.call name reordered) + + partial def normalizeFrameStmtCalls (params : FrameParamEnv) : Stmt -> Option Stmt + | Stmt.letDecl name expr => do + let normalized <- normalizeFrameExprCalls params expr + some (Stmt.letDecl name normalized) + | Stmt.letDeclTyped name ty expr => do + let normalized <- normalizeFrameExprCalls params expr + some (Stmt.letDeclTyped name ty normalized) + | Stmt.assign name expr => do + let normalized <- normalizeFrameExprCalls params expr + some (Stmt.assign name normalized) + | Stmt.ifThenElse condition thenBranch elseBranch => do + let normalizedCondition <- normalizeFrameExprCalls params condition + let normalizedThen <- normalizeFrameStmtsCalls params thenBranch + match elseBranch with + | none => some (Stmt.ifThenElse normalizedCondition normalizedThen none) + | some statements => do + let normalizedElse <- normalizeFrameStmtsCalls params statements + some (Stmt.ifThenElse normalizedCondition normalizedThen (some normalizedElse)) + | Stmt.while condition body => do + let normalizedCondition <- normalizeFrameExprCalls params condition + let normalizedBody <- normalizeFrameStmtsCalls params body + some (Stmt.while normalizedCondition normalizedBody) + | Stmt.forRange iterator start stop body => do + let normalizedBody <- normalizeFrameStmtsCalls params body + some (Stmt.forRange iterator start stop normalizedBody) + | Stmt.seal condition body => do + let normalizedCondition <- + match condition with + | none => some none + | some expr => do + let normalized <- normalizeFrameExprCalls params expr + some (some normalized) + let normalizedBody <- normalizeFrameStmtsCalls params body + some (Stmt.seal normalizedCondition normalizedBody) + | Stmt.fnDecl name fnParams body => do + let normalizedBody <- normalizeFrameStmtsCalls params body + some (Stmt.fnDecl name fnParams normalizedBody) + | Stmt.fnDeclReturn name fnParams returnTy body => do + let normalizedBody <- normalizeFrameStmtsCalls params body + some (Stmt.fnDeclReturn name fnParams returnTy normalizedBody) + | Stmt.fnDeclTyped name fnParams body => do + let normalizedBody <- normalizeFrameStmtsCalls params body + some (Stmt.fnDeclTyped name fnParams normalizedBody) + | Stmt.fnDeclTypedReturn name fnParams returnTy body => do + let normalizedBody <- normalizeFrameStmtsCalls params body + some (Stmt.fnDeclTypedReturn name fnParams returnTy normalizedBody) + | Stmt.ret none => some (Stmt.ret none) + | Stmt.ret (some expr) => do + let normalized <- normalizeFrameExprCalls params expr + some (Stmt.ret (some normalized)) + | Stmt.break => some Stmt.break + | Stmt.continue => some Stmt.continue + | Stmt.expr expr => do + let normalized <- normalizeFrameExprCalls params expr + some (Stmt.expr normalized) + + partial def normalizeFrameStmtsCalls (params : FrameParamEnv) : List Stmt -> Option (List Stmt) + | [] => some [] + | stmt :: rest => do + let normalizedStmt <- normalizeFrameStmtCalls params stmt + let normalizedRest <- normalizeFrameStmtsCalls params rest + some (normalizedStmt :: normalizedRest) +end + +def dummyFrameFns (functions : List FrameFunction) : FrameFnEnv := + functions.map (fun fn => (fn.name, 0, fn.params.length)) + +def paramsToSlots : List Ident -> SlotEnv := + let rec go : List Ident -> Nat -> List (Ident × Nat) + | [], _ => [] + | name :: rest, idx => (name, idx) :: go rest (idx + 1) + fun params => go params 0 + +structure FrameCompile where + slots : SlotEnv + code : List FrameOp + breaks : List Nat + continues : List Nat + deriving Repr, BEq + +def FrameCompile.done (slots : SlotEnv) (code : List FrameOp) : FrameCompile := + { slots := slots, code := code, breaks := [], continues := [] } + +def shiftFrameIndices (base : Nat) (indices : List Nat) : List Nat := + indices.map (fun idx => idx + base) + +def appendFrameCompile (left right : FrameCompile) : FrameCompile := + { slots := right.slots + , code := left.code ++ right.code + , breaks := left.breaks ++ shiftFrameIndices left.code.length right.breaks + , continues := left.continues ++ shiftFrameIndices left.code.length right.continues + } + +def patchFrameJump (code : List FrameOp) (idx target : Nat) : List FrameOp := + let offset : Int := Int.ofNat target - Int.ofNat (idx + 1) + listSet code idx (FrameOp.jmp offset) FrameOp.halt + +def patchFrameJumps (code : List FrameOp) (indices : List Nat) (target : Nat) : List FrameOp := + indices.foldl (fun patched idx => patchFrameJump patched idx target) code + +mutual + def compileFrameExprs (slots : SlotEnv) (fns : FrameFnEnv) : List Expr -> Option (List FrameOp) + | [] => some [] + | expr :: rest => do + let exprCode <- compileFrameExpr slots fns expr + let restCode <- compileFrameExprs slots fns rest + some (exprCode ++ restCode) + + def compileFrameArg (slots : SlotEnv) (fns : FrameFnEnv) : Arg -> Option (List FrameOp) + | Arg.positional expr => compileFrameExpr slots fns expr + | Arg.named _ expr => compileFrameExpr slots fns expr + + def compileFrameArgs (slots : SlotEnv) (fns : FrameFnEnv) : List Arg -> Option (List FrameOp) + | [] => some [] + | arg :: rest => do + let exprCode <- compileFrameArg slots fns arg + let restCode <- compileFrameArgs slots fns rest + some (exprCode ++ restCode) + + def compileFrameExpr (slots : SlotEnv) (fns : FrameFnEnv) : Expr -> Option (List FrameOp) + | Expr.num n => some [FrameOp.push (Value.num n)] + | Expr.float intPart fracMicros => some [FrameOp.push (Value.float intPart fracMicros)] + | Expr.bool b => some [FrameOp.push (Value.bool b)] + | Expr.str value => some [FrameOp.push (Value.str value)] + | Expr.unit => some [FrameOp.push Value.unit] + | Expr.list exprs => do + let code <- compileFrameExprs slots fns exprs + some (code ++ [FrameOp.list exprs.length]) + | Expr.var name => do + let slot <- SlotEnv.lookup slots name + some [FrameOp.load slot] + | Expr.unary op expr => do + let code <- compileFrameExpr slots fns expr + some (code ++ [FrameOp.unary op]) + | Expr.binary left op right => do + let leftCode <- compileFrameExpr slots fns left + let rightCode <- compileFrameExpr slots fns right + some (leftCode ++ rightCode ++ [FrameOp.bin op]) + | Expr.index target index => do + let targetCode <- compileFrameExpr slots fns target + let indexCode <- compileFrameExpr slots fns index + some (targetCode ++ indexCode ++ [FrameOp.index]) + | Expr.field target field => do + let targetCode <- compileFrameExpr slots fns target + some (targetCode ++ [FrameOp.field field]) + | Expr.method target method args => do + let targetCode <- compileFrameExpr slots fns target + let argCode <- compileFrameArgs slots fns args + some (targetCode ++ argCode ++ [FrameOp.method method args.length]) + | Expr.call name args => do + let (target, arity) <- FrameFnEnv.lookup fns name + if arity == args.length then + let argCode <- compileFrameArgs slots fns args + some (argCode ++ [FrameOp.call target arity]) + else + none +end + +mutual + def compileFrameStmtFlow (slots : SlotEnv) (fns : FrameFnEnv) : Stmt -> Option FrameCompile + | Stmt.ifThenElse condition thenBranch elseBranch => do + let conditionCode <- compileFrameExpr slots fns condition + let thenResult <- compileFrameBlockFlow slots fns thenBranch + match elseBranch with + | none => + let thenBase := conditionCode.length + 1 + let falseOffset : Int := Int.ofNat thenResult.code.length + some + { slots := thenResult.slots + , code := conditionCode ++ [FrameOp.jmpIfFalse falseOffset] ++ thenResult.code + , breaks := shiftFrameIndices thenBase thenResult.breaks + , continues := shiftFrameIndices thenBase thenResult.continues + } + | some elseStatements => do + let elseResult <- compileFrameBlockFlow thenResult.slots fns elseStatements + let thenBase := conditionCode.length + 1 + let elseBase := conditionCode.length + 1 + thenResult.code.length + 1 + let falseOffset : Int := Int.ofNat (thenResult.code.length + 1) + let endOffset : Int := Int.ofNat elseResult.code.length + some + { slots := elseResult.slots + , code := conditionCode + ++ [FrameOp.jmpIfFalse falseOffset] + ++ thenResult.code + ++ [FrameOp.jmp endOffset] + ++ elseResult.code + , breaks := + shiftFrameIndices thenBase thenResult.breaks + ++ shiftFrameIndices elseBase elseResult.breaks + , continues := + shiftFrameIndices thenBase thenResult.continues + ++ shiftFrameIndices elseBase elseResult.continues + } + | Stmt.while condition body => do + let conditionCode <- compileFrameExpr slots fns condition + let bodyResult <- compileFrameBlockFlow slots fns body + let bodyBase := conditionCode.length + 1 + let exitOffset : Int := Int.ofNat (bodyResult.code.length + 1) + let backOffset : Int := -Int.ofNat (conditionCode.length + bodyResult.code.length + 2) + let unpatched := + conditionCode + ++ [FrameOp.jmpIfFalse exitOffset] + ++ bodyResult.code + ++ [FrameOp.jmp backOffset] + let exitTarget := unpatched.length + let breakSites := shiftFrameIndices bodyBase bodyResult.breaks + let continueSites := shiftFrameIndices bodyBase bodyResult.continues + let patchedBreaks := patchFrameJumps unpatched breakSites exitTarget + let patchedContinues := patchFrameJumps patchedBreaks continueSites 0 + some (FrameCompile.done bodyResult.slots patchedContinues) + | Stmt.forRange iterator start stop body => do + let (iteratorSlots, iteratorSlot) := SlotEnv.resolve slots iterator + let bodyResult <- compileFrameBlockFlow iteratorSlots fns body + let initCode := [FrameOp.push (Value.num start), FrameOp.store iteratorSlot] + let conditionCode := + [ FrameOp.load iteratorSlot + , FrameOp.push (Value.num stop) + , FrameOp.bin BinOp.lt + ] + let incrementCode := + [ FrameOp.load iteratorSlot + , FrameOp.push (Value.num 1) + , FrameOp.bin BinOp.add + , FrameOp.store iteratorSlot + ] + let exitOffset : Int := Int.ofNat (bodyResult.code.length + incrementCode.length + 1) + let backOffset : Int := + -Int.ofNat (conditionCode.length + bodyResult.code.length + incrementCode.length + 2) + let unpatched := + initCode + ++ conditionCode + ++ [FrameOp.jmpIfFalse exitOffset] + ++ bodyResult.code + ++ incrementCode + ++ [FrameOp.jmp backOffset] + let bodyBase := initCode.length + conditionCode.length + 1 + let continueTarget := bodyBase + bodyResult.code.length + let exitTarget := unpatched.length + let breakSites := shiftFrameIndices bodyBase bodyResult.breaks + let continueSites := shiftFrameIndices bodyBase bodyResult.continues + let patchedBreaks := patchFrameJumps unpatched breakSites exitTarget + let patchedContinues := patchFrameJumps patchedBreaks continueSites continueTarget + some (FrameCompile.done bodyResult.slots patchedContinues) + | Stmt.seal none body => do + let bodyResult <- compileFrameBlockFlow slots fns body + let backOffset : Int := -Int.ofNat (bodyResult.code.length + 1) + let unpatched := bodyResult.code ++ [FrameOp.jmp backOffset] + let patchedBreaks := patchFrameJumps unpatched bodyResult.breaks unpatched.length + let patchedContinues := patchFrameJumps patchedBreaks bodyResult.continues 0 + some (FrameCompile.done bodyResult.slots patchedContinues) + | Stmt.seal (some condition) body => do + let conditionCode <- compileFrameExpr slots fns condition + let bodyResult <- compileFrameBlockFlow slots fns body + let exitOffset : Int := Int.ofNat (bodyResult.code.length + 1) + let backOffset : Int := -Int.ofNat (conditionCode.length + bodyResult.code.length + 3) + let unpatched := + conditionCode + ++ [FrameOp.unary UnOp.not, FrameOp.jmpIfFalse exitOffset] + ++ bodyResult.code + ++ [FrameOp.jmp backOffset] + let bodyBase := conditionCode.length + 2 + let breakSites := shiftFrameIndices bodyBase bodyResult.breaks + let continueSites := shiftFrameIndices bodyBase bodyResult.continues + let patchedBreaks := patchFrameJumps unpatched breakSites unpatched.length + let patchedContinues := patchFrameJumps patchedBreaks continueSites 0 + some (FrameCompile.done bodyResult.slots patchedContinues) + | Stmt.letDecl name expr => do + let code <- compileFrameExpr slots fns expr + let (updatedSlots, slot) := SlotEnv.resolve slots name + some (FrameCompile.done updatedSlots (code ++ [FrameOp.store slot])) + | Stmt.letDeclTyped name _ expr => do + let code <- compileFrameExpr slots fns expr + let (updatedSlots, slot) := SlotEnv.resolve slots name + some (FrameCompile.done updatedSlots (code ++ [FrameOp.store slot])) + | Stmt.assign name expr => do + let slot <- SlotEnv.lookup slots name + let code <- compileFrameExpr slots fns expr + some (FrameCompile.done slots (code ++ [FrameOp.store slot])) + | Stmt.expr expr => do + let code <- compileFrameExpr slots fns expr + some (FrameCompile.done slots code) + | Stmt.ret (some expr) => do + let code <- compileFrameExpr slots fns expr + some (FrameCompile.done slots (code ++ [FrameOp.ret])) + | Stmt.ret none => some (FrameCompile.done slots [FrameOp.ret]) + | Stmt.break => some { slots := slots, code := [FrameOp.jmp 0], breaks := [0], continues := [] } + | Stmt.continue => some { slots := slots, code := [FrameOp.jmp 0], breaks := [], continues := [0] } + | _ => none + + def compileFrameBlockFlow (slots : SlotEnv) (fns : FrameFnEnv) : List Stmt -> Option FrameCompile + | [] => some (FrameCompile.done slots []) + | stmt :: rest => do + let stmtResult <- compileFrameStmtFlow slots fns stmt + let restResult <- compileFrameBlockFlow stmtResult.slots fns rest + some (appendFrameCompile stmtResult restResult) +end + +def compileFrameStmt (slots : SlotEnv) (fns : FrameFnEnv) (stmt : Stmt) : Option (SlotEnv × List FrameOp) := do + let result <- compileFrameStmtFlow slots fns stmt + if result.breaks == [] && result.continues == [] then + some (result.slots, result.code) + else + none + +def compileFrameBlock (slots : SlotEnv) (fns : FrameFnEnv) (stmts : List Stmt) : + Option (SlotEnv × List FrameOp) := do + let result <- compileFrameBlockFlow slots fns stmts + if result.breaks == [] && result.continues == [] then + some (result.slots, result.code) + else + none + +def compileFrameFunction (fns : FrameFnEnv) (fn : FrameFunction) : Option (List FrameOp) := do + let (_, bodyCode) <- compileFrameBlock (paramsToSlots fn.params) fns fn.body + some (bodyCode ++ [FrameOp.ret]) + +def compileFrameFunctions (fns : FrameFnEnv) : List FrameFunction -> Option (List FrameOp) + | [] => some [] + | fn :: rest => do + let fnCode <- compileFrameFunction fns fn + let restCode <- compileFrameFunctions fns rest + some (fnCode ++ restCode) + +def frameTargetsFrom (start : Nat) : List FrameFunction -> FrameFnEnv + | [] => [] + | fn :: rest => + let dummyFns := dummyFrameFns (fn :: rest) + match compileFrameFunction dummyFns fn with + | some fnCode => + (fn.name, start, fn.params.length) :: frameTargetsFrom (start + fnCode.length) rest + | none => + (fn.name, start, fn.params.length) :: frameTargetsFrom start rest + +def compileFrameMain (slots : SlotEnv) (fns : FrameFnEnv) : List Stmt -> Option (SlotEnv × List FrameOp) + | [] => some (slots, []) + | Stmt.fnDecl _ _ _ :: rest => compileFrameMain slots fns rest + | Stmt.fnDeclReturn _ _ _ _ :: rest => compileFrameMain slots fns rest + | Stmt.fnDeclTyped _ _ _ :: rest => compileFrameMain slots fns rest + | Stmt.fnDeclTypedReturn _ _ _ _ :: rest => compileFrameMain slots fns rest + | stmt :: rest => do + let (slotsAfterStmt, stmtCode) <- compileFrameStmt slots fns stmt + let (slotsAfterRest, restCode) <- compileFrameMain slotsAfterStmt fns rest + some (slotsAfterRest, stmtCode ++ restCode) + +def compileFrameProgram (stmts : List Stmt) : Option (SlotEnv × FrameFnEnv × List FrameOp) := do + let initialFunctions := collectFrameFunctions stmts + let params := collectFrameParamEnv initialFunctions + let stmts <- normalizeFrameStmtsCalls params stmts + let functions := collectFrameFunctions stmts + let dummyFns := dummyFrameFns functions + let (_, dummyMainCode) <- compileFrameMain [] dummyFns stmts + let fns := frameTargetsFrom (dummyMainCode.length + 1) functions + let (mainSlots, mainCode) <- compileFrameMain [] fns stmts + let functionCode <- compileFrameFunctions fns functions + some (mainSlots, fns, mainCode ++ [FrameOp.halt] ++ functionCode) + +def compileCheckedFrameProgram (stmts : List Stmt) : Option (SlotEnv × FrameFnEnv × List FrameOp) := do + match Static.checkProgramDetailed stmts with + | Except.ok _ => compileFrameProgram stmts + | Except.error _ => none + +theorem compileCheckedFrameProgram_static_ok + {stmts : List Stmt} + {result : SlotEnv × FrameFnEnv × List FrameOp} + (h : compileCheckedFrameProgram stmts = some result) : + ∃ checked, Static.checkProgramDetailed stmts = Except.ok checked := by + unfold compileCheckedFrameProgram at h + cases hs : Static.checkProgramDetailed stmts with + | error found => + simp [hs] at h + | ok checked => + exact ⟨checked, rfl⟩ + +def runCompiledFrameProgram (fuel : Nat) (stmts : List Stmt) : Option (SlotEnv × FrameFnEnv × FrameState) := do + let (slots, fns, code) <- compileFrameProgram stmts + let state <- runFrame fuel code + some (slots, fns, state) + +def runCheckedFrameProgram (fuel : Nat) (stmts : List Stmt) : + Option (SlotEnv × FrameFnEnv × FrameState) := do + let (slots, fns, code) <- compileCheckedFrameProgram stmts + let state <- runFrame fuel code + some (slots, fns, state) + +def compiledFrameLocal? (fuel : Nat) (stmts : List Stmt) (slot : Nat) : Option Value := do + let (_, _, state) <- runCompiledFrameProgram fuel stmts + listGet? state.locals slot + +def checkedFrameLocal? (fuel : Nat) (stmts : List Stmt) (slot : Nat) : Option Value := do + let (_, _, state) <- runCheckedFrameProgram fuel stmts + listGet? state.locals slot + +def compileCheckedFrameSource (source : String) : Option (SlotEnv × FrameFnEnv × List FrameOp) := do + let stmts <- Parser.parseProgram source + compileCheckedFrameProgram stmts + +def runCheckedFrameSource (fuel : Nat) (source : String) : + Option (SlotEnv × FrameFnEnv × FrameState) := do + let stmts <- Parser.parseProgram source + runCheckedFrameProgram fuel stmts + +def checkedFrameSourceLocal? (fuel : Nat) (source : String) (slot : Nat) : Option Value := do + let (_, _, state) <- runCheckedFrameSource fuel source + listGet? state.locals slot + +mutual + def compileExprList : List Expr -> Option (List Op) + | [] => some [] + | expr :: rest => do + let exprCode <- compileExpr expr + let restCode <- compileExprList rest + some (exprCode ++ restCode) + + def compileArg (arg : Arg) : Option (List Op) := + match arg with + | Arg.positional expr => compileExpr expr + | Arg.named _ expr => compileExpr expr + + def compileArgList : List Arg -> Option (List Op) + | [] => some [] + | arg :: rest => do + let argCode <- compileArg arg + let restCode <- compileArgList rest + some (argCode ++ restCode) + + def compileExpr : Expr -> Option (List Op) + | Expr.num n => some [Op.push (Value.num n)] + | Expr.float intPart fracMicros => some [Op.push (Value.float intPart fracMicros)] + | Expr.bool b => some [Op.push (Value.bool b)] + | Expr.str value => some [Op.push (Value.str value)] + | Expr.unit => some [Op.push Value.unit] + | Expr.list exprs => do + let code <- compileExprList exprs + some (code ++ [Op.list exprs.length]) + | Expr.unary op expr => do + let code <- compileExpr expr + some (code ++ [Op.unary op]) + | Expr.binary left op right => do + let leftCode <- compileExpr left + let rightCode <- compileExpr right + some (leftCode ++ rightCode ++ [Op.bin op]) + | Expr.index target index => do + let targetCode <- compileExpr target + let indexCode <- compileExpr index + some (targetCode ++ indexCode ++ [Op.index]) + | Expr.field target field => do + let targetCode <- compileExpr target + some (targetCode ++ [Op.field field]) + | Expr.method target method args => do + let targetCode <- compileExpr target + let argCode <- compileArgList args + some (targetCode ++ argCode ++ [Op.method method args.length]) + | Expr.var _ => none + | Expr.call _ _ => none +end + +mutual + def compileExprListWithSlots (slots : SlotEnv) : List Expr -> Option (List Op) + | [] => some [] + | expr :: rest => do + let exprCode <- compileExprWithSlots slots expr + let restCode <- compileExprListWithSlots slots rest + some (exprCode ++ restCode) + + def compileArgWithSlots (slots : SlotEnv) (arg : Arg) : Option (List Op) := + match arg with + | Arg.positional expr => compileExprWithSlots slots expr + | Arg.named _ expr => compileExprWithSlots slots expr + + def compileArgListWithSlots (slots : SlotEnv) : List Arg -> Option (List Op) + | [] => some [] + | arg :: rest => do + let argCode <- compileArgWithSlots slots arg + let restCode <- compileArgListWithSlots slots rest + some (argCode ++ restCode) + + def compileExprWithSlots (slots : SlotEnv) : Expr -> Option (List Op) + | Expr.num n => some [Op.push (Value.num n)] + | Expr.float intPart fracMicros => some [Op.push (Value.float intPart fracMicros)] + | Expr.bool b => some [Op.push (Value.bool b)] + | Expr.str value => some [Op.push (Value.str value)] + | Expr.unit => some [Op.push Value.unit] + | Expr.list exprs => do + let code <- compileExprListWithSlots slots exprs + some (code ++ [Op.list exprs.length]) + | Expr.var name => do + let slot <- SlotEnv.lookup slots name + some [Op.load slot] + | Expr.unary op expr => do + let code <- compileExprWithSlots slots expr + some (code ++ [Op.unary op]) + | Expr.binary left op right => do + let leftCode <- compileExprWithSlots slots left + let rightCode <- compileExprWithSlots slots right + some (leftCode ++ rightCode ++ [Op.bin op]) + | Expr.index target index => do + let targetCode <- compileExprWithSlots slots target + let indexCode <- compileExprWithSlots slots index + some (targetCode ++ indexCode ++ [Op.index]) + | Expr.field target field => do + let targetCode <- compileExprWithSlots slots target + some (targetCode ++ [Op.field field]) + | Expr.method target method args => do + let targetCode <- compileExprWithSlots slots target + let argCode <- compileArgListWithSlots slots args + some (targetCode ++ argCode ++ [Op.method method args.length]) + | Expr.call _ _ => none +end + +def runCompiledExpr (fuel : Nat) (expr : Expr) : Option State := do + let code <- compileExpr expr + run fuel (code ++ [Op.halt]) + +def compiledExprValue? (fuel : Nat) (expr : Expr) : Option Value := do + let state <- runCompiledExpr fuel expr + match state.stack with + | value :: _ => some value + | [] => none + +def runCompiledExprWithSlots + (fuel : Nat) + (slots : SlotEnv) + (locals : List Value) + (expr : Expr) : Option State := do + let code <- compileExprWithSlots slots expr + runFuel fuel { ip := 0, code := code ++ [Op.halt], stack := [], locals := locals, halted := false } + +def compiledExprWithSlotsValue? + (fuel : Nat) + (slots : SlotEnv) + (locals : List Value) + (expr : Expr) : Option Value := do + let state <- runCompiledExprWithSlots fuel slots locals expr + match state.stack with + | value :: _ => some value + | [] => none + +def compileStmtWithSlots (slots : SlotEnv) : Stmt -> Option (SlotEnv × List Op) + | Stmt.letDecl name expr => do + let code <- compileExprWithSlots slots expr + let (updatedSlots, slot) := SlotEnv.resolve slots name + some (updatedSlots, code ++ [Op.store slot]) + | Stmt.assign name expr => do + let slot <- SlotEnv.lookup slots name + let code <- compileExprWithSlots slots expr + some (slots, code ++ [Op.store slot]) + | Stmt.expr expr => do + let code <- compileExprWithSlots slots expr + some (slots, code) + | _ => none + +def runCompiledStmtWithSlots + (fuel : Nat) + (slots : SlotEnv) + (locals : List Value) + (stmt : Stmt) : Option (SlotEnv × State) := do + let (updatedSlots, code) <- compileStmtWithSlots slots stmt + let state <- runFuel fuel + { ip := 0, code := code ++ [Op.halt], stack := [], locals := locals, halted := false } + some (updatedSlots, state) + +def compileBlockWithSlots (slots : SlotEnv) : List Stmt -> Option (SlotEnv × List Op) + | [] => some (slots, []) + | stmt :: rest => do + let (slotsAfterStmt, stmtCode) <- compileStmtWithSlots slots stmt + let (slotsAfterRest, restCode) <- compileBlockWithSlots slotsAfterStmt rest + some (slotsAfterRest, stmtCode ++ restCode) + +def runCompiledBlockWithSlots + (fuel : Nat) + (slots : SlotEnv) + (locals : List Value) + (stmts : List Stmt) : Option (SlotEnv × State) := do + let (updatedSlots, code) <- compileBlockWithSlots slots stmts + let state <- runFuel fuel + { ip := 0, code := code ++ [Op.halt], stack := [], locals := locals, halted := false } + some (updatedSlots, state) + +mutual + def compileStmtWithBranches (slots : SlotEnv) : Stmt -> Option (SlotEnv × List Op) + | Stmt.ifThenElse condition thenBranch elseBranch => do + let conditionCode <- compileExprWithSlots slots condition + let (thenSlots, thenCode) <- compileBlockWithBranches slots thenBranch + match elseBranch with + | none => + let falseOffset : Int := Int.ofNat thenCode.length + some (thenSlots, conditionCode ++ [Op.jmpIfFalse falseOffset] ++ thenCode) + | some elseStatements => do + let (elseSlots, elseCode) <- compileBlockWithBranches thenSlots elseStatements + let falseOffset : Int := Int.ofNat (thenCode.length + 1) + let endOffset : Int := Int.ofNat elseCode.length + some + ( elseSlots + , conditionCode + ++ [Op.jmpIfFalse falseOffset] + ++ thenCode + ++ [Op.jmp endOffset] + ++ elseCode + ) + | Stmt.while condition body => do + let conditionCode <- compileExprWithSlots slots condition + let (bodySlots, bodyCode) <- compileBlockWithBranches slots body + let exitOffset : Int := Int.ofNat (bodyCode.length + 1) + let backOffset : Int := -Int.ofNat (conditionCode.length + bodyCode.length + 2) + some + ( bodySlots + , conditionCode + ++ [Op.jmpIfFalse exitOffset] + ++ bodyCode + ++ [Op.jmp backOffset] + ) + | Stmt.forRange iterator start stop body => do + let (iteratorSlots, iteratorSlot) := SlotEnv.resolve slots iterator + let (bodySlots, bodyCode) <- compileBlockWithBranches iteratorSlots body + let initCode := [Op.push (Value.num start), Op.store iteratorSlot] + let conditionCode := + [ Op.load iteratorSlot + , Op.push (Value.num stop) + , Op.bin BinOp.lt + ] + let incrementCode := + [ Op.load iteratorSlot + , Op.push (Value.num 1) + , Op.bin BinOp.add + , Op.store iteratorSlot + ] + let exitOffset : Int := Int.ofNat (bodyCode.length + incrementCode.length + 1) + let backOffset : Int := + -Int.ofNat (conditionCode.length + bodyCode.length + incrementCode.length + 2) + some + ( bodySlots + , initCode + ++ conditionCode + ++ [Op.jmpIfFalse exitOffset] + ++ bodyCode + ++ incrementCode + ++ [Op.jmp backOffset] + ) + | Stmt.seal none body => do + let (bodySlots, bodyCode) <- compileBlockWithBranches slots body + let backOffset : Int := -Int.ofNat (bodyCode.length + 1) + some (bodySlots, bodyCode ++ [Op.jmp backOffset]) + | Stmt.seal (some condition) body => do + let conditionCode <- compileExprWithSlots slots condition + let (bodySlots, bodyCode) <- compileBlockWithBranches slots body + let exitOffset : Int := Int.ofNat (bodyCode.length + 1) + let backOffset : Int := -Int.ofNat (conditionCode.length + bodyCode.length + 3) + some + ( bodySlots + , conditionCode + ++ [Op.unary UnOp.not, Op.jmpIfFalse exitOffset] + ++ bodyCode + ++ [Op.jmp backOffset] + ) + | stmt => compileStmtWithSlots slots stmt + + def compileBlockWithBranches (slots : SlotEnv) : List Stmt -> Option (SlotEnv × List Op) + | [] => some (slots, []) + | stmt :: rest => do + let (slotsAfterStmt, stmtCode) <- compileStmtWithBranches slots stmt + let (slotsAfterRest, restCode) <- compileBlockWithBranches slotsAfterStmt rest + some (slotsAfterRest, stmtCode ++ restCode) +end + +def runCompiledBlockWithBranches + (fuel : Nat) + (slots : SlotEnv) + (locals : List Value) + (stmts : List Stmt) : Option (SlotEnv × State) := do + let (updatedSlots, code) <- compileBlockWithBranches slots stmts + let state <- runFuel fuel + { ip := 0, code := code ++ [Op.halt], stack := [], locals := locals, halted := false } + some (updatedSlots, state) + +example : + run 4 [Op.push (Value.num 5), Op.push (Value.num 3), Op.bin BinOp.add, Op.halt] + = some + { ip := 4 + code := [Op.push (Value.num 5), Op.push (Value.num 3), Op.bin BinOp.add, Op.halt] + stack := [Value.num 8] + locals := [] + halted := true } := by + native_decide + +example : + run 5 [Op.push (Value.num 7), Op.store 0, Op.load 0, Op.unary UnOp.neg, Op.halt] + = some + { ip := 5 + code := [Op.push (Value.num 7), Op.store 0, Op.load 0, Op.unary UnOp.neg, Op.halt] + stack := [Value.num (-7)] + locals := [Value.num 7] + halted := true } := by + native_decide + +example : + run 5 [Op.push (Value.bool false), Op.jmpIfFalse 1, Op.push (Value.num 99), Op.push (Value.num 1), Op.halt] + = some + { ip := 5 + code := [Op.push (Value.bool false), Op.jmpIfFalse 1, Op.push (Value.num 99), Op.push (Value.num 1), Op.halt] + stack := [Value.num 1] + locals := [] + halted := true } := by + native_decide + +example : + runFrame 20 + [ FrameOp.push (Value.num 2) + , FrameOp.push (Value.num 3) + , FrameOp.call 4 2 + , FrameOp.halt + , FrameOp.load 0 + , FrameOp.load 1 + , FrameOp.bin BinOp.add + , FrameOp.ret + ] + = + some + { ip := 4 + code := + [ FrameOp.push (Value.num 2) + , FrameOp.push (Value.num 3) + , FrameOp.call 4 2 + , FrameOp.halt + , FrameOp.load 0 + , FrameOp.load 1 + , FrameOp.bin BinOp.add + , FrameOp.ret + ] + stack := [Value.num 5] + locals := [] + frames := [] + halted := true } := by + native_decide + +example : + runFrame 10 + [ FrameOp.call 2 0 + , FrameOp.halt + , FrameOp.ret + ] + = + some + { ip := 2 + code := + [ FrameOp.call 2 0 + , FrameOp.halt + , FrameOp.ret + ] + stack := [Value.unit] + locals := [] + frames := [] + halted := true } := by + native_decide + +example : + runFrameFuel 20 + { ip := 0 + code := + [ FrameOp.push (Value.num 3) + , FrameOp.call 3 1 + , FrameOp.halt + , FrameOp.load 0 + , FrameOp.ret + ] + stack := [] + locals := [Value.num 10] + frames := [] + halted := false } + = + some + { ip := 3 + code := + [ FrameOp.push (Value.num 3) + , FrameOp.call 3 1 + , FrameOp.halt + , FrameOp.load 0 + , FrameOp.ret + ] + stack := [Value.num 3] + locals := [Value.num 10] + frames := [] + halted := true } := by + native_decide + +example : + compileFrameProgram + [ Stmt.fnDecl + "add" + ["a", "b"] + [Stmt.ret (some (Expr.binary (Expr.var "a") BinOp.add (Expr.var "b")))] + , Stmt.letDecl "y" (Expr.call "add" [Expr.num 2, Expr.num 3]) + ] + = + some + ( [("y", 0)] + , [("add", 5, 2)] + , [ FrameOp.push (Value.num 2) + , FrameOp.push (Value.num 3) + , FrameOp.call 5 2 + , FrameOp.store 0 + , FrameOp.halt + , FrameOp.load 0 + , FrameOp.load 1 + , FrameOp.bin BinOp.add + , FrameOp.ret + , FrameOp.ret + ]) := by + native_decide + +example : + runCompiledFrameProgram + 30 + [ Stmt.fnDecl + "add" + ["a", "b"] + [Stmt.ret (some (Expr.binary (Expr.var "a") BinOp.add (Expr.var "b")))] + , Stmt.letDecl "y" (Expr.call "add" [Expr.num 2, Expr.num 3]) + ] + = + some + ( [("y", 0)] + , [("add", 5, 2)] + , { ip := 5 + code := + [ FrameOp.push (Value.num 2) + , FrameOp.push (Value.num 3) + , FrameOp.call 5 2 + , FrameOp.store 0 + , FrameOp.halt + , FrameOp.load 0 + , FrameOp.load 1 + , FrameOp.bin BinOp.add + , FrameOp.ret + , FrameOp.ret + ] + stack := [] + locals := [Value.num 5] + frames := [] + halted := true }) := by + native_decide + +example : + checkedFrameLocal? + 30 + [ Stmt.fnDecl + "add" + ["a", "b"] + [Stmt.ret (some (Expr.binary (Expr.var "a") BinOp.add (Expr.var "b")))] + , Stmt.letDecl "y" (Expr.call "add" [Expr.num 2, Expr.num 3]) + ] + 0 + = + some (Value.num 5) := by + native_decide + +example : + compileFrameProgram + [Stmt.letDecl "bad" (Expr.binary (Expr.num 2) BinOp.add (Expr.bool true))] + ≠ + none := by + native_decide + +example : + compileCheckedFrameProgram + [Stmt.letDecl "bad" (Expr.binary (Expr.num 2) BinOp.add (Expr.bool true))] + = + none := by + native_decide + +example : + compileCheckedFrameProgram + [ Stmt.fnDecl + "id" + ["x"] + [Stmt.ret (some (Expr.var "x"))] + , Stmt.letDecl "bad" (Expr.call "id" [Expr.num 1, Expr.num 2]) + ] + = + none := by + native_decide + +example : + compileCheckedFrameProgram + [ Stmt.fnDecl "dup" [] [Stmt.ret (some (Expr.num 1))] + , Stmt.fnDecl "dup" [] [Stmt.ret (some (Expr.num 2))] + ] + = + none := by + native_decide + +example : + checkedFrameSourceLocal? + 40 + "fn add(a, b) { return a + b }~let y = add(2, 3)" + 0 + = + some (Value.num 5) := by + native_decide + +example : + checkedFrameSourceLocal? + 80 + "let x = 0~for i in -2..2 { x = x + i }" + 0 + = + some (Value.num (-2)) := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let xs = []~let empty = xs.is_empty()" + 1 + = + some (Value.bool true) := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let name = \"open\"~let empty = name.is_empty()" + 1 + = + some (Value.bool false) := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let xs = [7, 9]~let first = xs.first()" + 1 + = + some (Value.num 7) := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let xs = [7, 9]~let last = xs.last()" + 1 + = + some (Value.num 9) := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let xs = [7, 9]~let picked = xs.at(1)" + 1 + = + some (Value.num 9) := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let xs = [7, 9]~let has = xs.contains(9)" + 1 + = + some (Value.bool true) := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let xs = [7, 9]~let rest = xs.tail()" + 1 + = + some (Value.list [Value.num 9]) := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let xs = [7, 9]~let first = xs.take(1)" + 1 + = + some (Value.list [Value.num 7]) := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let xs = [7, 9]~let rest = xs.drop(1)" + 1 + = + some (Value.list [Value.num 9]) := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let xs = [7, 9]~let reversed = xs.reverse()" + 1 + = + some (Value.list [Value.num 9, Value.num 7]) := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let xs = [7]~let more = xs.append(9)" + 1 + = + some (Value.list [Value.num 7, Value.num 9]) := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let xs = [9]~let more = xs.prepend(7)" + 1 + = + some (Value.list [Value.num 7, Value.num 9]) := by + native_decide + +example : + checkedFrameSourceLocal? + 40 + "let xs = [\"a\", \"b\"]~let text = xs.join(\",\")" + 1 + = + some (Value.str "a,b") := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let xs = [7]~let more = xs.concat([9])" + 1 + = + some (Value.list [Value.num 7, Value.num 9]) := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let label = \"open\"~let ch = label.at(1)" + 1 + = + some (Value.str "p") := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let label = \"open\"~let has = label.contains(\"pe\")" + 1 + = + some (Value.bool true) := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let label = \"open\"~let has = label.starts_with(\"op\")" + 1 + = + some (Value.bool true) := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let label = \"open\"~let has = label.ends_with(\"en\")" + 1 + = + some (Value.bool true) := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let label = \"open\"~let reversed = label.reverse()" + 1 + = + some (Value.str "nepo") := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let label = \"open\"~let first = label.first()" + 1 + = + some (Value.str "o") := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let label = \"open\"~let last = label.last()" + 1 + = + some (Value.str "n") := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let label = \"open\"~let rest = label.tail()" + 1 + = + some (Value.str "pen") := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let label = \"open\"~let prefix = label.take(2)" + 1 + = + some (Value.str "op") := by + native_decide + +example : + checkedFrameSourceLocal? + 30 + "let label = \"open\"~let suffix = label.drop(2)" + 1 + = + some (Value.str "en") := by + native_decide + +example : + compileCheckedFrameSource "let bad = 2 + true" + = + none := by + native_decide + +example : + compileCheckedFrameSource "fn id(x) { return x }~let bad = id(1, 2)" + = + none := by + native_decide + +example : + compileCheckedFrameSource "fn bad(x, x) { return x }" + = + none := by + native_decide + +example : + compileCheckedFrameSource "let x = \"unterminated" + = + none := by + native_decide + +example : + runCompiledFrameProgram + 40 + [ Stmt.letDecl "x" (Expr.num 10) + , Stmt.fnDecl "id" ["x"] [Stmt.ret (some (Expr.var "x"))] + , Stmt.letDecl "y" (Expr.call "id" [Expr.num 3]) + ] + = + some + ( [("x", 0), ("y", 1)] + , [("id", 6, 1)] + , { ip := 6 + code := + [ FrameOp.push (Value.num 10) + , FrameOp.store 0 + , FrameOp.push (Value.num 3) + , FrameOp.call 6 1 + , FrameOp.store 1 + , FrameOp.halt + , FrameOp.load 0 + , FrameOp.ret + , FrameOp.ret + ] + stack := [] + locals := [Value.num 10, Value.num 3] + frames := [] + halted := true }) := by + native_decide + +example : + runCompiledFrameProgram + 40 + [ Stmt.fnDecl + "choose" + ["x"] + [ Stmt.ifThenElse + (Expr.binary (Expr.var "x") BinOp.lt (Expr.num 3)) + [Stmt.ret (some (Expr.num 1))] + (some [Stmt.ret (some (Expr.num 2))]) + ] + , Stmt.letDecl "y" (Expr.call "choose" [Expr.num 4]) + ] + = + some + ( [("y", 0)] + , [("choose", 4, 1)] + , { ip := 4 + code := + [ FrameOp.push (Value.num 4) + , FrameOp.call 4 1 + , FrameOp.store 0 + , FrameOp.halt + , FrameOp.load 0 + , FrameOp.push (Value.num 3) + , FrameOp.bin BinOp.lt + , FrameOp.jmpIfFalse 3 + , FrameOp.push (Value.num 1) + , FrameOp.ret + , FrameOp.jmp 2 + , FrameOp.push (Value.num 2) + , FrameOp.ret + , FrameOp.ret + ] + stack := [] + locals := [Value.num 2] + frames := [] + halted := true }) := by + native_decide + +example : + runCompiledFrameProgram + 80 + [ Stmt.fnDecl + "countTo" + ["x"] + [ Stmt.while + (Expr.binary (Expr.var "x") BinOp.lt (Expr.num 3)) + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1))] + , Stmt.ret (some (Expr.var "x")) + ] + , Stmt.letDecl "y" (Expr.call "countTo" [Expr.num 0]) + ] + = + some + ( [("y", 0)] + , [("countTo", 4, 1)] + , { ip := 4 + code := + [ FrameOp.push (Value.num 0) + , FrameOp.call 4 1 + , FrameOp.store 0 + , FrameOp.halt + , FrameOp.load 0 + , FrameOp.push (Value.num 3) + , FrameOp.bin BinOp.lt + , FrameOp.jmpIfFalse 5 + , FrameOp.load 0 + , FrameOp.push (Value.num 1) + , FrameOp.bin BinOp.add + , FrameOp.store 0 + , FrameOp.jmp (-9) + , FrameOp.load 0 + , FrameOp.ret + , FrameOp.ret + ] + stack := [] + locals := [Value.num 3] + frames := [] + halted := true }) := by + native_decide + +example : + runCompiledFrameProgram + 80 + [ Stmt.fnDecl + "sum3" + [] + [ Stmt.letDecl "sum" (Expr.num 0) + , Stmt.forRange + "i" + 0 + 3 + [Stmt.assign "sum" (Expr.binary (Expr.var "sum") BinOp.add (Expr.var "i"))] + , Stmt.ret (some (Expr.var "sum")) + ] + , Stmt.letDecl "y" (Expr.call "sum3" []) + ] + = + some + ( [("y", 0)] + , [("sum3", 3, 0)] + , { ip := 3 + code := + [ FrameOp.call 3 0 + , FrameOp.store 0 + , FrameOp.halt + , FrameOp.push (Value.num 0) + , FrameOp.store 0 + , FrameOp.push (Value.num 0) + , FrameOp.store 1 + , FrameOp.load 1 + , FrameOp.push (Value.num 3) + , FrameOp.bin BinOp.lt + , FrameOp.jmpIfFalse 9 + , FrameOp.load 0 + , FrameOp.load 1 + , FrameOp.bin BinOp.add + , FrameOp.store 0 + , FrameOp.load 1 + , FrameOp.push (Value.num 1) + , FrameOp.bin BinOp.add + , FrameOp.store 1 + , FrameOp.jmp (-13) + , FrameOp.load 0 + , FrameOp.ret + , FrameOp.ret + ] + stack := [] + locals := [Value.num 3] + frames := [] + halted := true }) := by + native_decide + +example : + runCompiledFrameProgram + 80 + [ Stmt.fnDecl + "sealTo3" + ["x"] + [ Stmt.seal + (some (Expr.binary (Expr.var "x") BinOp.eq (Expr.num 3))) + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1))] + , Stmt.ret (some (Expr.var "x")) + ] + , Stmt.letDecl "y" (Expr.call "sealTo3" [Expr.num 0]) + ] + = + some + ( [("y", 0)] + , [("sealTo3", 4, 1)] + , { ip := 4 + code := + [ FrameOp.push (Value.num 0) + , FrameOp.call 4 1 + , FrameOp.store 0 + , FrameOp.halt + , FrameOp.load 0 + , FrameOp.push (Value.num 3) + , FrameOp.bin BinOp.eq + , FrameOp.unary UnOp.not + , FrameOp.jmpIfFalse 5 + , FrameOp.load 0 + , FrameOp.push (Value.num 1) + , FrameOp.bin BinOp.add + , FrameOp.store 0 + , FrameOp.jmp (-10) + , FrameOp.load 0 + , FrameOp.ret + , FrameOp.ret + ] + stack := [] + locals := [Value.num 3] + frames := [] + halted := true }) := by + native_decide + +example : + compiledFrameLocal? + 120 + [ Stmt.fnDecl + "firstBreak" + ["x"] + [ Stmt.while + (Expr.binary (Expr.var "x") BinOp.lt (Expr.num 10)) + [ Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1)) + , Stmt.ifThenElse + (Expr.binary (Expr.var "x") BinOp.eq (Expr.num 4)) + [Stmt.break] + none + ] + , Stmt.ret (some (Expr.var "x")) + ] + , Stmt.letDecl "y" (Expr.call "firstBreak" [Expr.num 0]) + ] + 0 + = + some (Value.num 4) := by + native_decide + +example : + compiledFrameLocal? + 160 + [ Stmt.fnDecl + "skip3" + ["i"] + [ Stmt.letDecl "sum" (Expr.num 0) + , Stmt.while + (Expr.binary (Expr.var "i") BinOp.lt (Expr.num 5)) + [ Stmt.assign "i" (Expr.binary (Expr.var "i") BinOp.add (Expr.num 1)) + , Stmt.ifThenElse + (Expr.binary (Expr.var "i") BinOp.eq (Expr.num 3)) + [Stmt.continue] + none + , Stmt.assign "sum" (Expr.binary (Expr.var "sum") BinOp.add (Expr.var "i")) + ] + , Stmt.ret (some (Expr.var "sum")) + ] + , Stmt.letDecl "y" (Expr.call "skip3" [Expr.num 0]) + ] + 0 + = + some (Value.num 12) := by + native_decide + +example : + compileExpr (Expr.binary (Expr.num 2) BinOp.mul (Expr.num 4)) + = some [Op.push (Value.num 2), Op.push (Value.num 4), Op.bin BinOp.mul] := by + native_decide + +example : + compileExpr (Expr.float 1 500000) = some [Op.push (Value.float 1 500000)] := by + native_decide + +example : + compiledExprValue? 4 (Expr.binary (Expr.float 1 500000) BinOp.add (Expr.num 2)) = + some (Value.float 3 500000) := by + native_decide + +example : + compileFrameExpr [] [] (Expr.float 1 500000) = + some [FrameOp.push (Value.float 1 500000)] := by + native_decide + +example : + compiledExprValue? 4 (Expr.binary (Expr.num 2) BinOp.mul (Expr.num 4)) + = evalExpr [] (Expr.binary (Expr.num 2) BinOp.mul (Expr.num 4)) := by + native_decide + +example : + compiledExprValue? 6 + (Expr.binary + (Expr.unary UnOp.neg (Expr.num 3)) + BinOp.add + (Expr.binary (Expr.num 10) BinOp.mod (Expr.num 4))) + = + evalExpr [] + (Expr.binary + (Expr.unary UnOp.neg (Expr.num 3)) + BinOp.add + (Expr.binary (Expr.num 10) BinOp.mod (Expr.num 4))) := by + native_decide + +example : + compiledExprValue? 4 (Expr.unary UnOp.not (Expr.bool false)) + = evalExpr [] (Expr.unary UnOp.not (Expr.bool false)) := by + native_decide + +example : + compileExpr (Expr.str "open") = some [Op.push (Value.str "open")] := by + native_decide + +example : + compiledExprValue? 2 (Expr.str "open") = some (Value.str "open") := by + native_decide + +example : + compileFrameExpr [] [] (Expr.str "open") = + some [FrameOp.push (Value.str "open")] := by + native_decide + +example : + compileExpr Expr.unit = some [Op.push Value.unit] := by + native_decide + +example : + compileFrameExpr [] [] Expr.unit = some [FrameOp.push Value.unit] := by + native_decide + +example : + compileExpr (Expr.list [Expr.num 1, Expr.bool true]) = + some [Op.push (Value.num 1), Op.push (Value.bool true), Op.list 2] := by + native_decide + +example : + compiledExprValue? 4 (Expr.list [Expr.num 1, Expr.bool true]) = + some (Value.list [Value.num 1, Value.bool true]) := by + native_decide + +example : + compileFrameExpr [("x", 0)] [] (Expr.list [Expr.var "x", Expr.str "open"]) = + some [FrameOp.load 0, FrameOp.push (Value.str "open"), FrameOp.list 2] := by + native_decide + +example : + compileExpr (Expr.index (Expr.list [Expr.num 1, Expr.num 2]) (Expr.num 1)) = + some [Op.push (Value.num 1), Op.push (Value.num 2), Op.list 2, Op.push (Value.num 1), Op.index] := by + native_decide + +example : + compiledExprValue? 6 (Expr.index (Expr.list [Expr.num 1, Expr.num 2]) (Expr.num 1)) = + some (Value.num 2) := by + native_decide + +example : + compiledExprValue? 6 (Expr.index (Expr.str "open") (Expr.num 1)) = + some (Value.str "p") := by + native_decide + +example : + compileFrameExpr [("xs", 0)] [] (Expr.index (Expr.var "xs") (Expr.num 0)) = + some [FrameOp.load 0, FrameOp.push (Value.num 0), FrameOp.index] := by + native_decide + +example : + compileExpr (Expr.field (Expr.list [Expr.num 1, Expr.num 2]) "length") = + some [Op.push (Value.num 1), Op.push (Value.num 2), Op.list 2, Op.field "length"] := by + native_decide + +example : + compiledExprValue? 5 (Expr.field (Expr.list [Expr.num 1, Expr.num 2]) "length") = + some (Value.num 2) := by + native_decide + +example : + compileFrameExpr [("xs", 0)] [] (Expr.field (Expr.var "xs") "length") = + some [FrameOp.load 0, FrameOp.field "length"] := by + native_decide + +example : + compileExpr (Expr.method (Expr.list [Expr.num 1, Expr.num 2]) "len" []) = + some [Op.push (Value.num 1), Op.push (Value.num 2), Op.list 2, Op.method "len" 0] := by + native_decide + +example : + compiledExprValue? 5 (Expr.method (Expr.list [Expr.num 1, Expr.num 2]) "len" []) = + some (Value.num 2) := by + native_decide + +example : + compiledExprValue? 4 (Expr.method (Expr.list []) "is_empty" []) = + some (Value.bool true) := by + native_decide + +example : + compiledExprValue? 4 (Expr.method (Expr.str "open") "is_empty" []) = + some (Value.bool false) := by + native_decide + +example : + compiledExprValue? 5 (Expr.method (Expr.list [Expr.num 7, Expr.num 9]) "first" []) = + some (Value.num 7) := by + native_decide + +example : + compiledExprValue? 5 (Expr.method (Expr.list [Expr.num 7, Expr.num 9]) "last" []) = + some (Value.num 9) := by + native_decide + +example : + compiledExprValue? 6 (Expr.method (Expr.list [Expr.num 7, Expr.num 9]) "at" [Expr.num 1]) = + some (Value.num 9) := by + native_decide + +example : + compiledExprValue? 6 (Expr.method (Expr.list [Expr.num 7, Expr.num 9]) "contains" [Expr.num 9]) = + some (Value.bool true) := by + native_decide + +example : + compiledExprValue? 6 (Expr.method (Expr.list [Expr.num 7, Expr.num 9]) "tail" []) = + some (Value.list [Value.num 9]) := by + native_decide + +example : + compiledExprValue? 6 (Expr.method (Expr.list [Expr.num 7, Expr.num 9]) "take" [Expr.num 1]) = + some (Value.list [Value.num 7]) := by + native_decide + +example : + compiledExprValue? 6 (Expr.method (Expr.list [Expr.num 7, Expr.num 9]) "drop" [Expr.num 1]) = + some (Value.list [Value.num 9]) := by + native_decide + +example : + compiledExprValue? 6 (Expr.method (Expr.list [Expr.num 7, Expr.num 9]) "reverse" []) = + some (Value.list [Value.num 9, Value.num 7]) := by + native_decide + +example : + compiledExprValue? 6 (Expr.method (Expr.list [Expr.num 7]) "append" [Expr.num 9]) = + some (Value.list [Value.num 7, Value.num 9]) := by + native_decide + +example : + compiledExprValue? 6 (Expr.method (Expr.list [Expr.num 9]) "prepend" [Expr.num 7]) = + some (Value.list [Value.num 7, Value.num 9]) := by + native_decide + +example : + compiledExprValue? 8 (Expr.method (Expr.list [Expr.str "a", Expr.str "b"]) "join" [Expr.str ","]) = + some (Value.str "a,b") := by + native_decide + +example : + compiledExprValue? 8 (Expr.method (Expr.list [Expr.num 7]) "concat" [Expr.list [Expr.num 9]]) = + some (Value.list [Value.num 7, Value.num 9]) := by + native_decide + +example : + compiledExprValue? 6 (Expr.method (Expr.str "open") "at" [Expr.num 1]) = + some (Value.str "p") := by + native_decide + +example : + compiledExprValue? 6 (Expr.method (Expr.str "open") "contains" [Expr.str "pe"]) = + some (Value.bool true) := by + native_decide + +example : + compiledExprValue? 6 (Expr.method (Expr.str "open") "starts_with" [Expr.str "op"]) = + some (Value.bool true) := by + native_decide + +example : + compiledExprValue? 6 (Expr.method (Expr.str "open") "ends_with" [Expr.str "en"]) = + some (Value.bool true) := by + native_decide + +example : + compiledExprValue? 6 (Expr.method (Expr.str "open") "reverse" []) = + some (Value.str "nepo") := by + native_decide + +example : + compiledExprValue? 6 (Expr.method (Expr.str "open") "first" []) = + some (Value.str "o") := by + native_decide + +example : + compiledExprValue? 6 (Expr.method (Expr.str "open") "last" []) = + some (Value.str "n") := by + native_decide + +example : + compiledExprValue? 6 (Expr.method (Expr.str "open") "tail" []) = + some (Value.str "pen") := by + native_decide + +example : + compiledExprValue? 6 (Expr.method (Expr.str "open") "take" [Expr.num 2]) = + some (Value.str "op") := by + native_decide + +example : + compiledExprValue? 6 (Expr.method (Expr.str "open") "drop" [Expr.num 2]) = + some (Value.str "en") := by + native_decide + +example : + compileFrameExpr [("xs", 0)] [] (Expr.method (Expr.var "xs") "len" []) = + some [FrameOp.load 0, FrameOp.method "len" 0] := by + native_decide + +example : + compileFrameExpr [("xs", 0)] [] (Expr.method (Expr.var "xs") "is_empty" []) = + some [FrameOp.load 0, FrameOp.method "is_empty" 0] := by + native_decide + +example : + compileFrameExpr [("xs", 0)] [] (Expr.method (Expr.var "xs") "first" []) = + some [FrameOp.load 0, FrameOp.method "first" 0] := by + native_decide + +example : + compileFrameExpr [("xs", 0)] [] (Expr.method (Expr.var "xs") "last" []) = + some [FrameOp.load 0, FrameOp.method "last" 0] := by + native_decide + +example : + compileFrameExpr [("xs", 0)] [] (Expr.method (Expr.var "xs") "at" [Expr.num 1]) = + some [FrameOp.load 0, FrameOp.push (Value.num 1), FrameOp.method "at" 1] := by + native_decide + +example : + compileFrameExpr [("xs", 0)] [] (Expr.method (Expr.var "xs") "contains" [Expr.num 9]) = + some [FrameOp.load 0, FrameOp.push (Value.num 9), FrameOp.method "contains" 1] := by + native_decide + +example : + compileFrameExpr [("label", 0)] [] (Expr.method (Expr.var "label") "contains" [Expr.str "pe"]) = + some [FrameOp.load 0, FrameOp.push (Value.str "pe"), FrameOp.method "contains" 1] := by + native_decide + +example : + compileFrameExpr [("label", 0)] [] (Expr.method (Expr.var "label") "starts_with" [Expr.str "op"]) = + some [FrameOp.load 0, FrameOp.push (Value.str "op"), FrameOp.method "starts_with" 1] := by + native_decide + +example : + compileFrameExpr [("label", 0)] [] (Expr.method (Expr.var "label") "ends_with" [Expr.str "en"]) = + some [FrameOp.load 0, FrameOp.push (Value.str "en"), FrameOp.method "ends_with" 1] := by + native_decide + +example : + compileFrameExpr [("label", 0)] [] (Expr.method (Expr.var "label") "reverse" []) = + some [FrameOp.load 0, FrameOp.method "reverse" 0] := by + native_decide + +example : + compileFrameExpr [("label", 0)] [] (Expr.method (Expr.var "label") "first" []) = + some [FrameOp.load 0, FrameOp.method "first" 0] := by + native_decide + +example : + compileFrameExpr [("label", 0)] [] (Expr.method (Expr.var "label") "last" []) = + some [FrameOp.load 0, FrameOp.method "last" 0] := by + native_decide + +example : + compileFrameExpr [("label", 0)] [] (Expr.method (Expr.var "label") "tail" []) = + some [FrameOp.load 0, FrameOp.method "tail" 0] := by + native_decide + +example : + compileFrameExpr [("label", 0)] [] (Expr.method (Expr.var "label") "take" [Expr.num 2]) = + some [FrameOp.load 0, FrameOp.push (Value.num 2), FrameOp.method "take" 1] := by + native_decide + +example : + compileFrameExpr [("label", 0)] [] (Expr.method (Expr.var "label") "drop" [Expr.num 2]) = + some [FrameOp.load 0, FrameOp.push (Value.num 2), FrameOp.method "drop" 1] := by + native_decide + +example : + compileFrameExpr [("xs", 0)] [] (Expr.method (Expr.var "xs") "tail" []) = + some [FrameOp.load 0, FrameOp.method "tail" 0] := by + native_decide + +example : + compileFrameExpr [("xs", 0)] [] (Expr.method (Expr.var "xs") "take" [Expr.num 1]) = + some [FrameOp.load 0, FrameOp.push (Value.num 1), FrameOp.method "take" 1] := by + native_decide + +example : + compileFrameExpr [("xs", 0)] [] (Expr.method (Expr.var "xs") "drop" [Expr.num 1]) = + some [FrameOp.load 0, FrameOp.push (Value.num 1), FrameOp.method "drop" 1] := by + native_decide + +example : + compileFrameExpr [("xs", 0)] [] (Expr.method (Expr.var "xs") "reverse" []) = + some [FrameOp.load 0, FrameOp.method "reverse" 0] := by + native_decide + +example : + compileFrameExpr [("xs", 0)] [] (Expr.method (Expr.var "xs") "append" [Expr.num 9]) = + some [FrameOp.load 0, FrameOp.push (Value.num 9), FrameOp.method "append" 1] := by + native_decide + +example : + compileFrameExpr [("xs", 0)] [] (Expr.method (Expr.var "xs") "prepend" [Expr.num 7]) = + some [FrameOp.load 0, FrameOp.push (Value.num 7), FrameOp.method "prepend" 1] := by + native_decide + +example : + compileFrameExpr [("xs", 0)] [] (Expr.method (Expr.var "xs") "join" [Expr.str ","]) = + some [FrameOp.load 0, FrameOp.push (Value.str ","), FrameOp.method "join" 1] := by + native_decide + +example : + compileFrameExpr [("xs", 0)] [] (Expr.method (Expr.var "xs") "concat" [Expr.list [Expr.num 9]]) = + some [FrameOp.load 0, FrameOp.push (Value.num 9), FrameOp.list 1, FrameOp.method "concat" 1] := by + native_decide + +example : + compileFrameExpr [("label", 0)] [] (Expr.method (Expr.var "label") "at" [Expr.num 1]) = + some [FrameOp.load 0, FrameOp.push (Value.num 1), FrameOp.method "at" 1] := by + native_decide + +example : + compileExprWithSlots [("x", 0)] + (Expr.binary (Expr.var "x") BinOp.add (Expr.num 2)) + = some [Op.load 0, Op.push (Value.num 2), Op.bin BinOp.add] := by + native_decide + +example : + compiledExprWithSlotsValue? 4 + [("x", 0)] + [Value.num 5] + (Expr.binary (Expr.var "x") BinOp.add (Expr.num 2)) + = + evalExpr [("x", Value.num 5)] + (Expr.binary (Expr.var "x") BinOp.add (Expr.num 2)) := by + native_decide + +example : + compileExprWithSlots [] (Expr.var "missing") = none := by + native_decide + +example : + compileStmtWithSlots [] (Stmt.letDecl "x" (Expr.num 3)) + = some ([("x", 0)], [Op.push (Value.num 3), Op.store 0]) := by + native_decide + +example : + runCompiledStmtWithSlots 3 [] [] (Stmt.letDecl "x" (Expr.num 3)) + = some + ( [("x", 0)] + , { ip := 3 + code := [Op.push (Value.num 3), Op.store 0, Op.halt] + stack := [] + locals := [Value.num 3] + halted := true }) := by + native_decide + +example : + compileStmtWithSlots [("x", 0)] + (Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 2))) + = + some + ( [("x", 0)] + , [Op.load 0, Op.push (Value.num 2), Op.bin BinOp.add, Op.store 0]) := by + native_decide + +example : + runCompiledStmtWithSlots 5 + [("x", 0)] + [Value.num 1] + (Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 2))) + = + some + ( [("x", 0)] + , { ip := 5 + code := [Op.load 0, Op.push (Value.num 2), Op.bin BinOp.add, Op.store 0, Op.halt] + stack := [] + locals := [Value.num 3] + halted := true }) := by + native_decide + +example : + compileStmtWithSlots [] (Stmt.assign "missing" (Expr.num 1)) = none := by + native_decide + +example : + compileBlockWithSlots [] + [ Stmt.letDecl "x" (Expr.num 1) + , Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 2)) + , Stmt.expr (Expr.var "x") + ] + = + some + ( [("x", 0)] + , [ Op.push (Value.num 1), Op.store 0 + , Op.load 0, Op.push (Value.num 2), Op.bin BinOp.add, Op.store 0 + , Op.load 0 + ]) := by + native_decide + +example : + runCompiledBlockWithSlots 8 [] [] + [ Stmt.letDecl "x" (Expr.num 1) + , Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 2)) + , Stmt.expr (Expr.var "x") + ] + = + some + ( [("x", 0)] + , { ip := 8 + code := + [ Op.push (Value.num 1), Op.store 0 + , Op.load 0, Op.push (Value.num 2), Op.bin BinOp.add, Op.store 0 + , Op.load 0, Op.halt + ] + stack := [Value.num 3] + locals := [Value.num 3] + halted := true }) := by + native_decide + +example : + compileBlockWithSlots [] [Stmt.break] = none := by + native_decide + +example : + compileBlockWithBranches [("x", 0)] + [Stmt.ifThenElse + (Expr.bool true) + [Stmt.assign "x" (Expr.num 1)] + (some [Stmt.assign "x" (Expr.num 2)])] + = + some + ( [("x", 0)] + , [ Op.push (Value.bool true) + , Op.jmpIfFalse 3 + , Op.push (Value.num 1), Op.store 0 + , Op.jmp 2 + , Op.push (Value.num 2), Op.store 0 + ]) := by + native_decide + +example : + runCompiledBlockWithBranches 7 [("x", 0)] [Value.num 0] + [Stmt.ifThenElse + (Expr.bool true) + [Stmt.assign "x" (Expr.num 1)] + (some [Stmt.assign "x" (Expr.num 2)])] + = + some + ( [("x", 0)] + , { ip := 8 + code := + [ Op.push (Value.bool true) + , Op.jmpIfFalse 3 + , Op.push (Value.num 1), Op.store 0 + , Op.jmp 2 + , Op.push (Value.num 2), Op.store 0 + , Op.halt + ] + stack := [] + locals := [Value.num 1] + halted := true }) := by + native_decide + +example : + runCompiledBlockWithBranches 6 [("x", 0)] [Value.num 0] + [Stmt.ifThenElse + (Expr.bool false) + [Stmt.assign "x" (Expr.num 1)] + (some [Stmt.assign "x" (Expr.num 2)])] + = + some + ( [("x", 0)] + , { ip := 8 + code := + [ Op.push (Value.bool false) + , Op.jmpIfFalse 3 + , Op.push (Value.num 1), Op.store 0 + , Op.jmp 2 + , Op.push (Value.num 2), Op.store 0 + , Op.halt + ] + stack := [] + locals := [Value.num 2] + halted := true }) := by + native_decide + +example : + compileBlockWithBranches [("x", 0)] + [Stmt.while + (Expr.binary (Expr.var "x") BinOp.lt (Expr.num 3)) + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1))]] + = + some + ( [("x", 0)] + , [ Op.load 0, Op.push (Value.num 3), Op.bin BinOp.lt + , Op.jmpIfFalse 5 + , Op.load 0, Op.push (Value.num 1), Op.bin BinOp.add, Op.store 0 + , Op.jmp (-9) + ]) := by + native_decide + +example : + runCompiledBlockWithBranches 40 [("x", 0)] [Value.num 0] + [Stmt.while + (Expr.binary (Expr.var "x") BinOp.lt (Expr.num 3)) + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1))]] + = + some + ( [("x", 0)] + , { ip := 10 + code := + [ Op.load 0, Op.push (Value.num 3), Op.bin BinOp.lt + , Op.jmpIfFalse 5 + , Op.load 0, Op.push (Value.num 1), Op.bin BinOp.add, Op.store 0 + , Op.jmp (-9) + , Op.halt + ] + stack := [] + locals := [Value.num 3] + halted := true }) := by + native_decide + +example : + runCompiledBlockWithBranches 6 [("x", 0)] [Value.num 3] + [Stmt.while + (Expr.binary (Expr.var "x") BinOp.lt (Expr.num 3)) + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1))]] + = + some + ( [("x", 0)] + , { ip := 10 + code := + [ Op.load 0, Op.push (Value.num 3), Op.bin BinOp.lt + , Op.jmpIfFalse 5 + , Op.load 0, Op.push (Value.num 1), Op.bin BinOp.add, Op.store 0 + , Op.jmp (-9) + , Op.halt + ] + stack := [] + locals := [Value.num 3] + halted := true }) := by + native_decide + +example : + compileBlockWithBranches [("x", 0)] + [Stmt.forRange + "i" + 0 + 3 + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.var "i"))]] + = + some + ( [("x", 0), ("i", 1)] + , [ Op.push (Value.num 0), Op.store 1 + , Op.load 1, Op.push (Value.num 3), Op.bin BinOp.lt + , Op.jmpIfFalse 9 + , Op.load 0, Op.load 1, Op.bin BinOp.add, Op.store 0 + , Op.load 1, Op.push (Value.num 1), Op.bin BinOp.add, Op.store 1 + , Op.jmp (-13) + ]) := by + native_decide + +example : + runCompiledBlockWithBranches 60 [("x", 0)] [Value.num 0] + [Stmt.forRange + "i" + 0 + 3 + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.var "i"))]] + = + some + ( [("x", 0), ("i", 1)] + , { ip := 16 + code := + [ Op.push (Value.num 0), Op.store 1 + , Op.load 1, Op.push (Value.num 3), Op.bin BinOp.lt + , Op.jmpIfFalse 9 + , Op.load 0, Op.load 1, Op.bin BinOp.add, Op.store 0 + , Op.load 1, Op.push (Value.num 1), Op.bin BinOp.add, Op.store 1 + , Op.jmp (-13) + , Op.halt + ] + stack := [] + locals := [Value.num 3, Value.num 3] + halted := true }) := by + native_decide + +example : + compileBlockWithBranches [("x", 0)] + [Stmt.seal + (some (Expr.binary (Expr.var "x") BinOp.eq (Expr.num 3))) + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1))]] + = + some + ( [("x", 0)] + , [ Op.load 0, Op.push (Value.num 3), Op.bin BinOp.eq + , Op.unary UnOp.not, Op.jmpIfFalse 5 + , Op.load 0, Op.push (Value.num 1), Op.bin BinOp.add, Op.store 0 + , Op.jmp (-10) + ]) := by + native_decide + +example : + runCompiledBlockWithBranches 40 [("x", 0)] [Value.num 0] + [Stmt.seal + (some (Expr.binary (Expr.var "x") BinOp.eq (Expr.num 3))) + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1))]] + = + some + ( [("x", 0)] + , { ip := 11 + code := + [ Op.load 0, Op.push (Value.num 3), Op.bin BinOp.eq + , Op.unary UnOp.not, Op.jmpIfFalse 5 + , Op.load 0, Op.push (Value.num 1), Op.bin BinOp.add, Op.store 0 + , Op.jmp (-10) + , Op.halt + ] + stack := [] + locals := [Value.num 3] + halted := true }) := by + native_decide + +example : + compileBlockWithBranches [("x", 0)] + [Stmt.seal + none + [Stmt.assign "x" (Expr.binary (Expr.var "x") BinOp.add (Expr.num 1))]] + = + some + ( [("x", 0)] + , [ Op.load 0, Op.push (Value.num 1), Op.bin BinOp.add, Op.store 0 + , Op.jmp (-5) + ]) := by + native_decide + +end VM +end Aether diff --git a/Cargo.toml b/Cargo.toml index 5fa52a2..230742b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,11 @@ [workspace] members = [ - "aether-core", - "aether-lang", - "aether-kernel", - "aether-cli", - "aegis-core", + "crates/aether-core", + "crates/aether-lang", + "crates/aether-kernel", + "crates/aether-cli", + "crates/aegis-core", + "crates/aegis-cli", ] resolver = "2" diff --git a/LICENSE b/LICENSE index 1447cfa..acdf29c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,94 @@ -MIT License +# Aether-Lang — Custom Attribution License -Copyright (c) 2026 AEGIS-Shield Research Team +**Version 1.0 — Copyright (c) 2026 Teerth Sharma. All Rights Reserved.** -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +--- -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +## Preamble + +Aether-Lang is the proprietary programming language invented and authored by +Teerth Sharma. This license establishes the terms under which Aether-Lang may +be used, studied, modified, and distributed. + +--- + +## Terms + +Permission is hereby granted to use, study, modify, and distribute copies of +the Aether-Lang software and its source code, provided that **ALL** of the +following conditions are met: + +### 1. Attribution (REQUIRED) + +Any use of Aether-Lang — including but not limited to: language implementations, +tooling, libraries, bindings, examples, documentation, blog posts, videos, or +presentations — **MUST** prominently attribute the original author and inventor: + +> "Aether-Lang was invented by **Teerth Sharma**." +> — or — +> "Built with Aether-Lang by **Teerth Sharma**." +> — or — +> "[Project Name] uses Aether-Lang by **Teerth Sharma**." + +For implementations, bindings, or forks, include the following notice in +source code comments and accompanying documentation: + +``` +// Aether-Lang — invented by Teerth Sharma (https://github.com/teerthsharma) +// https://github.com/teerthsharma/Aether-Lang +``` + +### 2. No Claim of Independent Invention + +You may not represent that you independently invented, created, or conceived +any part of Aether-Lang's language design, syntax, semantics, or tooling. +By using this software, you acknowledge that all such intellectual property +belongs to Teerth Sharma. + +### 3. Permitted Uses + +- **Personal use**: Unlimited, with attribution +- **Educational use**: Permitted with attribution in course materials +- **Open source projects**: Permitted only if the project prominently credits + Teerth Sharma and includes this license in its entirety +- **Commercial use**: Requires prior written permission from Teerth Sharma + +### 4. Restrictions + +- **No removal of copyright notices**: You may not remove, obscure, or modify + any copyright, trademark, or attribution notices in the software. +- **No trademark dilution**: The name "Aether-Lang" and associated branding + may not be used in ways that suggest affiliation or endorsement without + prior written permission. +- **No proprietary forks**: Any fork, variant, or derivative work of Aether-Lang + that is distributed must be distributed under this same license and must + carry this same attribution requirement. + +### 5. Contribution Policy + +If you contribute code, documentation, or ideas to Aether-Lang, you assign +all intellectual property rights in your contributions to Teerth Sharma and +agree that such contributions are governed by this license. + +--- + +## Enforcement + +Failure to comply with the attribution requirement constitutes a violation of +the intellectual property rights of Teerth Sharma. This license is not +granted to parties who fail to comply. + +--- + +## Warranty Disclaimer THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +TEERTH SHARMA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY. + +--- + +**Contact for licensing inquiries:** teerthsharma@outlook.com +**GitHub:** https://github.com/teerthsharma/Aether-Lang +**Website:** https://teerthsharma.vercel.app/ diff --git a/README.md b/README.md index b63d3f8..ce4e1be 100644 --- a/README.md +++ b/README.md @@ -1,212 +1,101 @@ -
+# Aether Lang -# 🛡️ AETHER -### **Declarative IR for Event-Driven Sparse Execution** +Aether Lang is a Rust workspace for a small language runtime, a bounded +topological computation core, ML primitives, and a sparse-event kernel +prototype. The project uses topology as a systems signal: embeddings, residuals, +binary streams, and scheduler state are converted into geometric or topological +objects, then used for execution, diagnostics, convergence checks, or pruning. -*Biological Adaptation • Geometric Intelligence • Living Hardware* +The intended benefit is mechanical rather than promotional. When a system +represents structure explicitly, useful behavior can emerge from ordinary +constraints: -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Architecture: Living](https://img.shields.io/badge/Architecture-Living-blueviolet.svg)](#) -[![Kernel: Entropy-Regulated](https://img.shields.io/badge/Kernel-Entropy--Regulated-success.svg)](#) -[![Math: Chebyshev-Safe](https://img.shields.io/badge/Math-Chebyshev--Safe-orange.svg)](#) -[![Status: O1-Ready](https://img.shields.io/badge/Status-Research--Active-blue.svg)](#) - -
- ---- - -## 🏛️ The Next Evolutionary Leap - -For over eight decades, computing has been constrained by the **Von Neumann Architecture**: a static fetch-execute cycle operating on passive hardware. While revolutionary for its time, it remains fundamentally blind to context and physical form. - -**AETHER (Adaptive Entropy-Regulated Geometric Intelligence System)** represents the next paradigm shift in system orchestration. - -AETHER is not a general-purpose language. It is a **Declarative Intermediate Representation (IR)** designed to orchestrate the **Living Architecture**: a unified ecosystem where software and hardware operate as a single, adaptive organism. Logic is no longer a mere sequence of instructions; it is a **geometric manifold** that governs event-driven sparse execution. Memory is not a bucket; it is a **topological space** regulated by statistical laws. - -### The Paradigm Shift -| Paradigm | Von Neumann (1945) | AETHER (2026) | -|:---:|:---:|:---:| -| **Logic Model** | Static / Procedural | **Geometric Convergence** (Topology-driven IR) | -| **Hardware State** | Passive / Fixed | **Living Hardware** (Bio-Adaptive) | -| **Execution Flow** | Linear / Deterministic | **Sparse-Event Orchestration** (Manifold-based) | -| **Optimization** | Resource Allocation | **Entropy Regulation** (Chebyshev-Bounded) | - ---- - -## 🧬 Layer 1: The Bio-Kernel - -*Core Implementation: `aether-core/src/memory.rs` & `aether-kernel`* - -Traditional operating systems treat hardware as a sterile warehouse. The **AETHER Bio-Kernel** treats it as a body. - -### 🧠 Manifold Memory: The Titan Clock -AETHER employs the **Titan Clock** (Bio-Clock), a cyclic metabolic allocator that replaces Garbage Collection entirely. - -* **Cyclic Manifold:** Memory is treated as a 32-dimensional cyclic ring (Sharded Clock). -* **Algorithmic Homeostasis:** Data is not "collected"; it is metabolized. The "Hand of Time" overwrites high-entropy (unused) cells in O(1) time. -* **Zero-Copy:** No "Stop-the-World" pauses. No GC scanning overhead. -* **Hardware Ready:** Designed to run directly on the **Bio-Chip** (AEGIS-PPU), utilizing native memristor decay. - -> **"We do not manage memory. We regulate its metabolism."** - -### ⚡ The Titan Cortex (Execution Target) -AETHER IR translates high-level geometric intent into hardware-optimized execution via a bicameral target system: - -1. **AETHER-Script (Dynamic Orchestration):** - * *Role:* Rapid prototyping, structural topology, and dynamic system state declarations. - * *Target:* Recursive Tree-Walking Interpreter for flexible "thought". - -2. **Titan VM (High-Performance Collapse):** - * *Role:* High-throughput simulation, massive parallelization, and real-time event response. - * *Target:* Stack-based Linear Bytecode VM for optimized "action". - ---- - -## 📐 Layer 2: Geometric Intelligence - -*Engine: `aether-core/src/ml`* - -AETHER moves beyond fixed-epoch training. We observe the **topological evolution** of logic. Using **Topological Data Analysis (TDA)**, AETHER monitors the "Betti Numbers" (homology groups) of error manifolds. Convergence is reached when the topology stabilizes. - -```aether -// The 'Seal Loop' - Convergence via Topological Stabilization -// 🦭 represents the 'Seal', a topological closure operator. -🦭 until convergence(1e-6) { - regress { model: "neural_manifold", escalate: true }~ -} +```text +source or signal -> typed runtime object -> geometric summary -> topology or bound -> execution decision ``` -## 🏆 Hall of Fame: The Geometric Advantage +## Current Working Surface -AETHER operates via a bicameral architecture: **Bio-Script** for flexible thought, and **Titan Core** for raw geometric collapse. +Rust crates: -| **Benchmark** | **Legacy (Python)** | **Bio-Script (Interpreter)** | **Titan Core (VM/Native)** | **Titan Speedup** | -| :--- | :---: | :---: | :---: | :---: | -| **Linear Regression** | 90.1 ms | ~85 ms (Flexible) | **0.12 ms** | 🚀 **750x** | -| **Topological Sort** | 50.0 ms | ~45 ms (Graph) | **0.005 ms** | 🌌 **10,000x** | -| **Fibonacci Loop** | 1.2s | 1.5s (Tree-Walk) | **0.003s** | ⚡ **400x** | -| **Manifold Pruning** | O(N) GC | **O(1) Self-Regulated** | **O(1) Chebyshev** | **Instant** | +- `aether-lang`: lexer, parser, AST, interpreter, Titan bytecode VM, ASCII and + WebGL exporters. +- `aether-core`: manifold points, time-delay embeddings, bounded persistent + homology, geometric block metadata, ML primitives, tensors, and governors. +- `aether-cli`: `aether repl`, `aether run`, and `aether check`. +- `aether-kernel`: no_std sparse-event scheduler, loader, allocator, serial, + boot, and hardware-topology scaffolding. +- `aegis-core` and `aegis-cli`: compatibility crates retained in the workspace. -> *"Bio-Script thinks. Titan acts. You get the best of both worlds."* +Active DSL behavior includes variable declarations, assignments, arithmetic, +comparison and logical expressions, lists, `if`, `while`, `for`, `seal until`, +functions, manifold embedding from numeric lists, block extraction, ML +constructors, and topology module calls. -> *Benchmarks conducted on Intel Core i9 (13900K). Results illustrate the efficiency of geometric convergence.* +## Quickstart ---- - -## 🗣️ Layer 3: The Declarative Specification (IR) - -*Implementation: `aether-lang`* - -AETHER provides a declarative interface for interacting with the living machine, bridging Pythonic expressiveness with the strict requirements of sparse-event execution targets. It is designed to specify "what shape the solution should take" rather than "how the CPU should move". +```powershell +cargo build -p aether-cli +cargo run -p aether-cli -- check examples/simple.aegis +cargo run -p aether-cli -- run examples/simple.aegis +cargo run -p aether-cli -- repl +``` -### Native Deep Learning -AETHER treats Neural Networks as first-class geometric objects, not external libraries. +The CLI accepts `.aether` and `.ae` as standard extensions. Some repository +examples still use `.aegis` and `.ag`; those can be parsed by the current CLI but +may print an extension warning. -* **Transformers:** Native `Ml.load_llama` and `Ml.generate` derived from Hugging Face Candle. -* **Tensors:** Zero-copy interaction with the Manifold Heap. -* **Topological Operators:** Native support for `manifold`, `betti`, and `embedding` types. +## Topology Example -### IR Example: Declaring a Cognitive Loop ```aether -import Ml - -// 1. Perception: Embed raw stream into 3D Manifold -let stream = [1.0, 2.4, 5.1, 8.2]~ -manifold M = embed(stream, dim=3, tau=5)~ - -// 2. Cognition: Detect Topological Anomalies -// If the 1st Betti Number (loops) exceeds threshold, we have a signal. -if M.betti_1 > 10 { - print("Anomaly Detected. Initializing Defense.")~ - - // 3. Action: Load Neural Response - let mind = Ml.load_llama("TinyLlama/TinyLlama-1.1B-Chat-v1.0")~ - let response = Ml.generate(mind, "Analyze hostile signal.", 50)~ - print(response)~ -} - -// 4. Visualization: Render the thought shape -render M { target: "ascii_render", color: "density" }~ +import topology~ +let data = [1.0, 1.0, 1.0, 1.0, 1.0]~ +manifold M = embed(data, tau=1)~ +let diagram = topology.ph(M, max_dim=2, mode="vr", max_points=16)~ +let b = topology.betti(diagram, radius=0.0)~ +let intervals = topology.intervals(diagram)~ ``` ---- - -## 🔮 Strategic Roadmap (Synapse Protocol) - -The "Synapse" release target (v0.3.0) will introduce the final systems required for autonomous neural evolution. - -### Phase 1: The Bridge (`aether-grad`) -* **Goal:** Native Autograd Engine. -* **Strategy:** Tape-based reverse mode differentiation that traces the Manifold Heap directly. -* **Status:** *In Research.* - -### Phase 2: The Forge (`aether-compute`) -* **Goal:** Zero-Copy GPU Acceleration. -* **Strategy:** Mapping `wgpu` buffers directly to Manifold structs. The GPU becomes an extension of the Heap. -* **Status:** *Planned.* - ---- - -## 📦 Installation - -### Prerequisites -* **Rust (Nightly):** Required for specialized SIMD and Kernel intrinsics. -* **Python 3.10+:** For benchmarking comparisons. +`topology.ph` calls the bounded persistent-homology engine in `aether-core`. +`topology.betti` returns `[beta_0, beta_1, beta_2]`. -### Building from Source (Recommended) +## Documentation -To build the "Living Architecture": +Hosted documentation: [teerthsharma.github.io/Aether-Lang](https://teerthsharma.github.io/Aether-Lang/) -```bash -# 1. Clone the repository -git clone https://github.com/teerthsharma/aether -cd aether +The documentation is organized as a MkDocs site: -# 2. Build the Release Binary (Titan Optimized) -cargo build --release - -# 3. (Optional) Build the Bare-Metal Kernel -cargo build -p aether-kernel --target x86_64-unknown-none -``` - -### Usage - -**Run a Simulation:** -```bash -./target/release/aether run examples/grand_benchmark.aether -``` - -**Activate Titan Mode (Extreme Performance):** -```bash -./target/release/aether run examples/grand_benchmark.aether --mode=titan +```powershell +python -m pip install -r requirements-docs.txt +python -m mkdocs serve ``` ---- +Start with: -## 📂 Project Structure +- [docs/index.md](docs/index.md) +- [docs/reference/status.md](docs/reference/status.md) +- [docs/benchmarks/index.md](docs/benchmarks/index.md) +- [docs/topology/derivations.md](docs/topology/derivations.md) -Verified workspace architecture: +## Evidence Policy -- **`aether-cli`**: The interface for managing the living system. -- **`aether-core`**: The foundational geometric algorithms & Manifold Memory. -- **`aether-kernel`**: The bare-metal `no_std` microkernel / hypervisor. -- **`aether-lang`**: The Lexer, Parser, AETHER-Script Interpreter, and Titan VM (The IR Pipeline). -- **`docs`**: Comprehensive research papers (Architecture, Mathematics, TDA). +Capability claims in this repository should be tied to one of: ---- +- a Rust unit test; +- a CLI smoke check; +- a benchmark artifact with environment and correctness fields; +- a docs-only theory or roadmap label. -## 📚 Documentation & Research +Unverified speedups, hardware acceleration, model-quality improvements, and +security guarantees are not treated as active claims. -- [**Architecture Deep Dive**](docs/ARCHITECTURE.md) - The Dual-Engine Cortex & Manifold Map. -- [**The Mathematics of AETHER**](docs/MATHEMATICS.md) - Topological Data Analysis & Betti Numbers. -- [**Tutorial**](docs/TUTORIAL.md) - Learn to speak the language of the machine. +## Local Checks ---- - -
- -**"Computing is no longer about calculation. It is about coexistence."** - -*Engineered with Precision and Topological Rigor.* - -
+```powershell +cargo fmt --all -- --check +cargo test -p aether-core +cargo test -p aether-lang +cargo test -p aether-cli +cargo check -p aether-core --no-default-features +python -m mkdocs build --strict +``` diff --git a/aegis-core/src/lib.rs b/aegis-core/src/lib.rs deleted file mode 100644 index 154e108..0000000 --- a/aegis-core/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_std] - -extern crate alloc; -#[cfg(feature = "std")] -extern crate std; - -pub mod memory; diff --git a/aether-core/tests/test_autograd.rs b/aether-core/tests/test_autograd.rs deleted file mode 100644 index 480a7df..0000000 --- a/aether-core/tests/test_autograd.rs +++ /dev/null @@ -1,57 +0,0 @@ -use aegis_core::ml::tensor::Tensor; -use aegis_core::ml::autograd::{Variable, add, mul}; - -#[test] -fn test_autograd_simple() { - // z = x * 2 + 5 - // x = 2 - // grad_x = 2 - - let x_data = Tensor::new(&[2.0], &[1]); - let x = Variable::new(x_data); - - let two_data = Tensor::new(&[2.0], &[1]); - let two = Variable::new(two_data); - - let five_data = Tensor::new(&[5.0], &[1]); - let five = Variable::new(five_data); - - let y = mul(&x, &two); - let z = add(&y, &five); - - z.backward(); - - let grad = x.grad().unwrap(); - assert_eq!(grad.get(&[0]), 2.0); -} - -#[test] -fn test_autograd_matmul() { - use aegis_core::ml::autograd::matmul; - - // C = A @ B - // A: 1x2 [1, 2] - // B: 2x1 [3, 4] - // C: 1x1 [1*3 + 2*4] = [11] - - let a_data = Tensor::new(&[1.0, 2.0], &[1, 2]); - let a = Variable::new(a_data); - - let b_data = Tensor::new(&[3.0, 4.0], &[2, 1]); - let b = Variable::new(b_data); - - let c = matmul(&a, &b); - c.backward(); - - // dC = 1 - // dA = dC @ B^T = [1] @ [3, 4] = [3, 4] - // dB = A^T @ dC = [1, 2]^T @ [1] = [1, 2]^T - - let grad_a = a.grad().unwrap(); - assert_eq!(grad_a.get(&[0, 0]), 3.0); - assert_eq!(grad_a.get(&[0, 1]), 4.0); - - let grad_b = b.grad().unwrap(); - assert_eq!(grad_b.get(&[0, 0]), 1.0); - assert_eq!(grad_b.get(&[1, 0]), 2.0); -} diff --git a/aether-kernel/src/boot/mod.rs b/aether-kernel/src/boot/mod.rs deleted file mode 100644 index 27f349d..0000000 --- a/aether-kernel/src/boot/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod bios; -pub mod topology; diff --git a/aether-lang/src/interpreter.rs b/aether-lang/src/interpreter.rs deleted file mode 100644 index afcf4cb..0000000 --- a/aether-lang/src/interpreter.rs +++ /dev/null @@ -1,1105 +0,0 @@ -// ═══════════════════════════════════════════════════════════════════════════════ -//! AEGIS Interpreter - Runtime execution of AEGIS programs -// ═══════════════════════════════════════════════════════════════════════════════ -//! -//! Executes parsed AEGIS AST, managing: -//! - 3D manifold workspaces -//! - Block geometry computations -//! - Escalating regression benchmarks -//! - Topological convergence detection -// ═══════════════════════════════════════════════════════════════════════════════ - -#![allow(dead_code)] - -#[cfg(not(feature = "std"))] -extern crate alloc; - -#[cfg(not(feature = "std"))] -use alloc::boxed::Box; -#[cfg(not(feature = "std"))] -use alloc::collections::BTreeMap; -#[cfg(not(feature = "std"))] -use alloc::string::String; -#[cfg(not(feature = "std"))] -use alloc::{format, vec}; -#[cfg(not(feature = "std"))] -use alloc::string::ToString; - -#[cfg(not(feature = "std"))] -macro_rules! println { - ($($arg:tt)*) => {}; -} - -#[cfg(feature = "std")] -use std::boxed::Box; -#[cfg(feature = "std")] -use std::collections::BTreeMap; -#[cfg(feature = "std")] -use std::string::String; -#[cfg(feature = "std")] -use std::vec::Vec; - -use crate::ast::*; -use aether_core::aether::{BlockMetadata, DriftDetector, HierarchicalBlockTree}; -use aether_core::manifold::{ManifoldPoint, TimeDelayEmbedder}; -use aether_core::ml::{MLP, KMeans, Activation, OptimizerConfig}; -use aether_core::ml::linalg::LossConfig; -use aether_core::ml::convolution::Conv2D; -use libm::{fabs, sqrt}; - -#[cfg(feature = "std")] -use safetensors::SafeTensors; -#[cfg(feature = "std")] -use std::sync::Arc; -#[cfg(not(feature = "std"))] -use alloc::sync::Arc; - -#[cfg(feature = "ml")] -use candle_core::{Device, Tensor as CandleTensor}; -#[cfg(feature = "ml")] -use candle_transformers::models::quantized_llama::ModelWeights as LlamaWeights; -#[cfg(feature = "ml")] -use tokenizers::Tokenizer; - -/// Embedding dimension -const DIM: usize = 3; - -// ═══════════════════════════════════════════════════════════════════════════════ -// Runtime Values -// ═══════════════════════════════════════════════════════════════════════════════ - -/// Runtime value types -#[derive(Debug, Clone)] -pub enum Value { - /// Numeric value - Num(f64), - /// Boolean - Bool(bool), - /// String - Str(String), - /// 3D Manifold reference - Manifold(ManifoldHandle), - /// Geometric block reference - Block(BlockHandle), - /// 3D Point - Point([f64; DIM]), - /// Regression result - RegressionResult(RegressionOutput), - /// Class Definition - Class(ClassHandle), - /// Object Instance - Object(ObjectHandle), - /// Native Function (for Standard Library) - NativeFn(NativeFunction), - /// Dynamic List (Python-like) - List(Vec), - /// ML Types - Mlp(Box), - KMeans(Box>), - Conv2D(Box), - /// Void/Unit - Unit, - /// Module Namespace - Module(String), - /// Dynamic Tensor - Tensor(Arc), - /// Llama Model (Wrapped) - #[cfg(feature = "ml")] - LlamaModel(Arc), -} - -#[cfg(feature = "ml")] -#[derive(Debug)] -pub struct LlamaContext { - pub model: LlamaWeights, - pub tokenizer: Tokenizer, - pub name: String, -} - -/// Simple Dynamic Tensor -#[derive(Debug, Clone)] -pub struct Tensor { - pub shape: Vec, - pub data: Vec, -} - -impl Tensor { - pub fn new(shape: Vec, data: Vec) -> Self { - Self { shape, data } - } - - pub fn matmul(&self, other: &Tensor) -> Tensor { - if self.shape.len() != 2 || other.shape.len() != 2 { return Tensor::new(vec![], vec![]); } - let m = self.shape[0]; - let k = self.shape[1]; - let k2 = other.shape[0]; - let n = other.shape[1]; - if k != k2 { return Tensor::new(vec![], vec![]); } - let mut out = vec![0.0; m * n]; - for i in 0..m { - for j in 0..n { - let mut sum = 0.0; - for l in 0..k { sum += self.data[i*k+l] * other.data[l*n+j]; } - out[i*n+j] = sum; - } - } - Tensor::new(vec![m, n], out) - } - - pub fn add(&self, other: &Tensor) -> Tensor { - if self.shape != other.shape { return Tensor::new(vec![], vec![]); } - Tensor::new(self.shape.clone(), self.data.iter().zip(&other.data).map(|(a,b)| a+b).collect()) - } - - pub fn relu(&self) -> Tensor { - Tensor::new(self.shape.clone(), self.data.iter().map(|x| if *x > 0.0 { *x } else { 0.0 }).collect()) - } - - pub fn softmax(&self) -> Tensor { - // Row-wise softmax for 2D, or global for 1D - let mut out = self.data.clone(); - if self.shape.len() == 2 { - let rows = self.shape[0]; - let cols = self.shape[1]; - for i in 0..rows { - let row_start = i * cols; - let row_end = row_start + cols; - let max_val = self.data[row_start..row_end].iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b)); - let mut sum = 0.0; - for j in row_start..row_end { - out[j] = libm::exp((out[j] - max_val) as f64) as f32; - sum += out[j]; - } - for j in row_start..row_end { out[j] /= sum; } - } - } - Tensor::new(self.shape.clone(), out) - } - -} - -/// Native function pointer type -#[derive(Debug, Clone)] -pub enum NativeFunction { - MathSin, - MathCos, - MathSqrt, - MathExp, - TopoBetti, - Print, - // ML Constructors - MlpNew, - KMeansNew, - Conv2DNew, - // Seal Functions - SealTrain, - // Tensor Ops - MlLoadWeights, - MlMatMul, - MlAdd, - MlRelu, - MlSoftmax, - MlEmbed, - MlAttention, - MlGpuCheck, - MlBackward, - MlUpdate, - MlLoadLlama, - MlGenerate, -} - -/// Handle to a manifold workspace -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ManifoldHandle(pub usize); - -/// Handle to a geometric block -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct BlockHandle(pub usize); - -/// Handle to a class definition -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ClassHandle(pub usize); - -/// Handle to an object instance -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ObjectHandle(pub usize); - -/// Class Definition Runtime -#[derive(Debug, Clone)] -pub struct ClassDef { - pub name: String, - pub fields: Vec, - pub methods: BTreeMap, -} - -/// Object Instance Runtime -#[derive(Debug, Clone)] -pub struct ObjectInstance { - pub class: ClassHandle, - pub fields: BTreeMap, -} - -/// Regression output with convergence info -#[derive(Debug, Clone)] -pub struct RegressionOutput { - /// Final coefficients - pub coefficients: [f64; 8], - /// Number of epochs to converge - pub epochs: u32, - /// Final error - pub final_error: f64, - /// Converged? - pub converged: bool, - /// Betti numbers at convergence - pub betti: (u32, u32), -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Manifold Workspace -// ═══════════════════════════════════════════════════════════════════════════════ - -/// 3D Manifold workspace containing embedded points -#[derive(Debug)] -pub struct ManifoldWorkspace { - /// Embedded points in 3D - pub points: Vec>, - /// Hierarchical block tree for AETHER - pub block_tree: HierarchicalBlockTree, - /// Drift detector for convergence - pub drift: DriftDetector, - /// Time-delay embedder - pub embedder: TimeDelayEmbedder, - /// Current centroid - pub centroid: [f64; DIM], -} - -impl ManifoldWorkspace { - pub fn new(tau: usize) -> Self { - Self { - points: Vec::new(), - block_tree: HierarchicalBlockTree::new(), - drift: DriftDetector::new(), - embedder: TimeDelayEmbedder::new(tau), - centroid: [0.0; DIM], - } - } - - /// Embed raw data into 3D manifold - pub fn embed_data(&mut self, data: &[f64]) { - self.points.clear(); - self.embedder.reset(); - - for &val in data { - self.embedder.push(val); - if let Some(point) = self.embedder.embed() { - self.points.push(point); - } - } - - self.update_centroid(); - } - - /// Update centroid from points - fn update_centroid(&mut self) { - if self.points.is_empty() { - return; - } - - let mut sum = [0.0; DIM]; - for p in &self.points { - for (d, s) in sum.iter_mut().enumerate().take(DIM) { - *s += p.coords[d]; - } - } - - let n = self.points.len() as f64; - for (d, s) in sum.iter().enumerate().take(DIM) { - self.centroid[d] = s / n; - } - } - - /// Extract block from index range - pub fn extract_block(&self, start: usize, end: usize) -> BlockMetadata { - let end = end.min(self.points.len()); - let start = start.min(end); - - if start >= end { - return BlockMetadata::empty(); - } - - let mut block_points = Vec::new(); - for i in start..end { - block_points.push(self.points[i].coords); - } - - BlockMetadata::from_points(&block_points) - } -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Escalating Regression Engine -// ═══════════════════════════════════════════════════════════════════════════════ - -/// Regression model types -#[derive(Debug, Clone, Copy)] -pub enum RegressionModel { - Linear, - Polynomial { degree: u8 }, - Rbf { gamma: f64 }, -} - -/// Escalating benchmark system -pub struct EscalatingRegressor { - /// Current model complexity - current_level: u32, - /// Target for regression - target: Vec, - /// Predictions - predictions: Vec, - /// Convergence epsilon - epsilon: f64, - /// Betti stability window - betti_history: Vec<(u32, u32)>, -} - -impl EscalatingRegressor { - pub fn new(epsilon: f64) -> Self { - Self { - current_level: 0, - target: Vec::new(), - predictions: Vec::new(), - epsilon, - betti_history: Vec::new(), - } - } - - /// Set target values for regression - pub fn set_target(&mut self, data: &[f64]) { - self.target.clear(); - for &v in data { - self.target.push(v); - } - } - - /// Run escalating regression until convergence - pub fn run_escalating( - &mut self, - manifold: &ManifoldWorkspace, - max_epochs: u32, - ) -> RegressionOutput { - let mut coefficients = [0.0f64; 8]; - let mut error = f64::MAX; - let mut converged = false; - let mut epochs = 0u32; - - for epoch in 0..max_epochs { - epochs = epoch; - let model = self.escalate_model(epoch); - coefficients = self.fit_model(manifold, &model); - error = self.compute_error(manifold, &coefficients, &model); - let betti = self.compute_residual_betti(manifold, &coefficients, &model); - self.betti_history.push(betti); - if self.betti_history.len() > 10 { - self.betti_history.remove(0); - } - if self.is_converged(error, &betti) { - converged = true; - break; - } - } - - RegressionOutput { - coefficients, - epochs, - final_error: error, - converged, - betti: *self.betti_history.last().unwrap_or(&(0, 0)), - } - } - - fn escalate_model(&self, epoch: u32) -> RegressionModel { - match epoch { - 0 => RegressionModel::Linear, - 1 => RegressionModel::Polynomial { degree: 2 }, - 2 => RegressionModel::Polynomial { degree: 3 }, - 3 => RegressionModel::Polynomial { degree: 4 }, - 4..=6 => RegressionModel::Rbf { - gamma: 0.1 * (epoch as f64), - }, - _ => RegressionModel::Rbf { gamma: 1.0 }, - } - } - - fn fit_model(&self, manifold: &ManifoldWorkspace, model: &RegressionModel) -> [f64; 8] { - let mut coeffs = [0.0f64; 8]; - - if manifold.points.is_empty() || self.target.is_empty() { - return coeffs; - } - - match model { - RegressionModel::Linear => { - let n = manifold.points.len().min(self.target.len()) as f64; - let mut sum_x = 0.0; - let mut sum_y = 0.0; - let mut sum_xy = 0.0; - let mut sum_xx = 0.0; - - for (i, p) in manifold.points.iter().enumerate() { - if i >= self.target.len() { break; } - let x = p.coords[0]; - let y = self.target[i]; - sum_x += x; - sum_y += y; - sum_xy += x * y; - sum_xx += x * x; - } - - let denom = n * sum_xx - sum_x * sum_x; - if fabs(denom) > 1e-10 { - coeffs[1] = (n * sum_xy - sum_x * sum_y) / denom; - coeffs[0] = (sum_y - coeffs[1] * sum_x) / n; - } - } - RegressionModel::Polynomial { degree } => { - coeffs = self.fit_model(manifold, &RegressionModel::Linear); - coeffs[*degree as usize] = 0.01; - } - RegressionModel::Rbf { .. } => { - coeffs = self.fit_model(manifold, &RegressionModel::Polynomial { degree: 3 }); - } - } - - coeffs - } - - fn compute_error( - &self, - manifold: &ManifoldWorkspace, - coeffs: &[f64; 8], - model: &RegressionModel, - ) -> f64 { - let mut mse = 0.0; - let mut count = 0; - - for (i, p) in manifold.points.iter().enumerate() { - if i >= self.target.len() { break; } - let pred = self.predict(p.coords[0], coeffs, model); - let err = pred - self.target[i]; - mse += err * err; - count += 1; - } - - if count > 0 { - mse /= count as f64; - sqrt(mse) - } else { - f64::MAX - } - } - - fn predict(&self, x: f64, coeffs: &[f64; 8], model: &RegressionModel) -> f64 { - match model { - RegressionModel::Linear => coeffs[0] + coeffs[1] * x, - RegressionModel::Polynomial { degree } => { - let mut y = coeffs[0]; - let mut x_pow = x; - for coeff in coeffs.iter().take((*degree as usize).min(7) + 1).skip(1) { - y += coeff * x_pow; - x_pow *= x; - } - y - } - RegressionModel::Rbf { .. } => { - self.predict(x, coeffs, &RegressionModel::Polynomial { degree: 3 }) - } - } - } - - fn compute_residual_betti( - &self, - manifold: &ManifoldWorkspace, - coeffs: &[f64; 8], - model: &RegressionModel, - ) -> (u32, u32) { - let mut sign_changes = 0u32; - let mut oscillations = 0u32; - let mut prev_residual = 0.0; - let mut prev_sign = true; - - for (i, p) in manifold.points.iter().enumerate() { - if i >= self.target.len() { break; } - let pred = self.predict(p.coords[0], coeffs, model); - let residual = self.target[i] - pred; - let sign = residual >= 0.0; - if i > 0 && sign != prev_sign { - sign_changes += 1; - } - if i > 1 { - let delta = residual - prev_residual; - let prev_delta = prev_residual; - if (delta > 0.0) != (prev_delta > 0.0) { - oscillations += 1; - } - } - prev_residual = residual; - prev_sign = sign; - } - - (sign_changes / 2 + 1, oscillations / 4) - } - - fn is_converged(&self, error: f64, current_betti: &(u32, u32)) -> bool { - if error < self.epsilon { return true; } - if self.betti_history.len() >= 3 { - let recent: Vec<&(u32, u32)> = self.betti_history.iter().rev().take(3).collect(); - if recent.iter().all(|b| **b == *current_betti) { return true; } - } - false - } -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Main Interpreter -// ═══════════════════════════════════════════════════════════════════════════════ - -/// Runtime environment -pub struct Interpreter { - /// Variable bindings - pub variables: BTreeMap, // Made public for tests - /// Manifold workspaces - manifolds: Vec, - /// Block geometries - blocks: Vec>, - /// Class definitions - classes: Vec, - /// Object instances - objects: Vec, - /// Sample data (for demo) - sample_data: Vec, -} - -impl Interpreter { - pub fn new() -> Self { - let mut data = Vec::new(); - for i in 0..64 { - let x = (i as f64) * 0.1; - data.push(libm::sin(x)); - } - - Self { - variables: BTreeMap::new(), - manifolds: Vec::new(), - blocks: Vec::new(), - classes: Vec::new(), - objects: Vec::new(), - sample_data: data, - } - } - - /// Execute a program - pub fn execute(&mut self, program: &Program) -> Result { - let mut last_value = Value::Unit; - for stmt in &program.statements { - last_value = self.execute_statement(stmt)?; - } - Ok(last_value) - } - - fn execute_statement(&mut self, stmt: &Statement) -> Result { - match &stmt.node { - StmtKind::Manifold(decl) => self.execute_manifold(decl), - StmtKind::Block(decl) => self.execute_block(decl), - StmtKind::Var(decl) => self.execute_var(decl), - StmtKind::Regress(stmt) => self.execute_regress(stmt), - StmtKind::Render(stmt) => self.execute_render(stmt), - StmtKind::Class(decl) => self.execute_class(decl), - StmtKind::Import(stmt) => self.execute_import(stmt), - StmtKind::If(stmt) => self.execute_if(stmt), - StmtKind::While(stmt) => self.execute_while(stmt), - StmtKind::Loop(stmt) => self.execute_seal(stmt), - StmtKind::For(_) => Ok(Value::Unit), - StmtKind::Fn(_) => Ok(Value::Unit), - StmtKind::Return(_) => Ok(Value::Unit), - StmtKind::Break(_) => Ok(Value::Unit), - StmtKind::Continue(_) => Ok(Value::Unit), - StmtKind::Expr(expr) => { - self.evaluate_expr(expr)?; - Ok(Value::Unit) - }, - StmtKind::Empty => Ok(Value::Unit), - } - } - - fn execute_class(&mut self, decl: &ClassDecl) -> Result { - let mut methods = BTreeMap::new(); - for m in &decl.methods { - methods.insert(m.name.clone(), m.clone()); - } - - let class_def = ClassDef { - name: decl.name.clone(), - fields: decl.fields.clone(), - methods, - }; - - let handle = ClassHandle(self.classes.len()); - self.classes.push(class_def); - self.variables.insert(decl.name.clone(), Value::Class(handle)); - Ok(Value::Class(handle)) - } - - #[allow(unused_variables)] - fn evaluate_new(&mut self, class_name: &String, args: &[Expr]) -> Result { - let class_handle = if let Some(Value::Class(h)) = self.variables.get(class_name) { *h } else { - return Err(format!("Class '{}' not found", class_name)); - }; - - let class_def = self.classes[class_handle.0].clone(); - let mut fields = BTreeMap::new(); - for field in &class_def.fields { - let val = self.evaluate_expr(&field.value)?; - fields.insert(field.name.clone(), val); - } - - let obj_handle = ObjectHandle(self.objects.len()); - self.objects.push(ObjectInstance { - class: class_handle, - fields, - }); - - Ok(Value::Object(obj_handle)) - } - - fn execute_import(&mut self, stmt: &ImportStmt) -> Result { - let mod_name = stmt.module.as_str(); - match mod_name { - "math" => self.import_math(stmt), - "topology" => self.import_topology(stmt), - "ml" | "Ml" => self.import_ml(stmt), - "Seal" => self.import_seal(stmt), - _ => Err(format!("Module '{}' not found", mod_name)), - } - } - - fn import_math(&mut self, stmt: &ImportStmt) -> Result { - if let Some(symbol) = &stmt.symbol { - match symbol.as_str() { - "pi" => { self.variables.insert(String::from("pi"), Value::Num(core::f64::consts::PI)); }, - "sin" => { self.variables.insert(String::from("sin"), Value::NativeFn(NativeFunction::MathSin)); }, - "cos" => { self.variables.insert(String::from("cos"), Value::NativeFn(NativeFunction::MathCos)); }, - "sqrt" => { self.variables.insert(String::from("sqrt"), Value::NativeFn(NativeFunction::MathSqrt)); }, - "exp" => { self.variables.insert(String::from("exp"), Value::NativeFn(NativeFunction::MathExp)); }, - _ => return Err(format!("Symbol '{}' not found in math", symbol)), - } - } else { - self.variables.insert(String::from("pi"), Value::Num(core::f64::consts::PI)); - self.variables.insert(String::from("sin"), Value::NativeFn(NativeFunction::MathSin)); - self.variables.insert(String::from("cos"), Value::NativeFn(NativeFunction::MathCos)); - self.variables.insert(String::from("sqrt"), Value::NativeFn(NativeFunction::MathSqrt)); - } - Ok(Value::Unit) - } - - fn import_topology(&mut self, stmt: &ImportStmt) -> Result { - if let Some(symbol) = &stmt.symbol { - match symbol.as_str() { - "Betti" => self.variables.insert(String::from("Betti"), Value::NativeFn(NativeFunction::TopoBetti)), - _ => return Err(format!("Symbol '{}' not found in topology", symbol)), - }; - } - Ok(Value::Unit) - } - - fn import_ml(&mut self, stmt: &ImportStmt) -> Result { - if let Some(symbol) = &stmt.symbol { - match symbol.as_str() { - "MLP" => { self.variables.insert(String::from("MLP"), Value::NativeFn(NativeFunction::MlpNew)); }, - "KMeans" => { self.variables.insert(String::from("KMeans"), Value::NativeFn(NativeFunction::KMeansNew)); }, - "Conv2D" => { self.variables.insert(String::from("Conv2D"), Value::NativeFn(NativeFunction::Conv2DNew)); }, - _ => return Err(format!("Symbol '{}' not found in ml", symbol)), - }; - } else { - self.variables.insert(String::from("MLP"), Value::NativeFn(NativeFunction::MlpNew)); - self.variables.insert(String::from("KMeans"), Value::NativeFn(NativeFunction::KMeansNew)); - self.variables.insert(String::from("Conv2D"), Value::NativeFn(NativeFunction::Conv2DNew)); - self.variables.insert(String::from("load_weights"), Value::NativeFn(NativeFunction::MlLoadWeights)); - self.variables.insert(String::from("matmul"), Value::NativeFn(NativeFunction::MlMatMul)); - self.variables.insert(String::from("add"), Value::NativeFn(NativeFunction::MlAdd)); - self.variables.insert(String::from("relu"), Value::NativeFn(NativeFunction::MlRelu)); - self.variables.insert(String::from("softmax"), Value::NativeFn(NativeFunction::MlSoftmax)); - self.variables.insert(String::from("attention"), Value::NativeFn(NativeFunction::MlAttention)); - self.variables.insert(String::from("gpu_check"), Value::NativeFn(NativeFunction::MlGpuCheck)); - self.variables.insert(String::from("backward"), Value::NativeFn(NativeFunction::MlBackward)); - self.variables.insert(String::from("update"), Value::NativeFn(NativeFunction::MlUpdate)); - self.variables.insert(String::from("load_llama"), Value::NativeFn(NativeFunction::MlLoadLlama)); - self.variables.insert(String::from("generate"), Value::NativeFn(NativeFunction::MlGenerate)); - self.variables.insert(String::from("Ml"), Value::Module(String::from("Ml"))); - } - Ok(Value::Unit) - } - - fn import_seal(&mut self, stmt: &ImportStmt) -> Result { - if let Some(symbol) = &stmt.symbol { - match symbol.as_str() { - "train" => { self.variables.insert(String::from("train"), Value::NativeFn(NativeFunction::SealTrain)); }, - _ => return Err(format!("Symbol '{}' not found in Seal", symbol)), - }; - } else { - self.variables.insert(String::from("Seal"), Value::Module(String::from("Seal"))); - } - Ok(Value::Unit) - } - - fn execute_manifold(&mut self, decl: &ManifoldDecl) -> Result { - let tau = self.extract_tau(&decl.init).unwrap_or(3); - let mut workspace = ManifoldWorkspace::new(tau); - workspace.embed_data(&self.sample_data); - let handle = ManifoldHandle(self.manifolds.len()); - self.manifolds.push(workspace); - self.variables.insert(decl.name.clone(), Value::Manifold(handle)); - Ok(Value::Manifold(handle)) - } - - fn extract_tau(&self, expr: &Expr) -> Option { - if let ExprKind::Call { args, .. } = &expr.node { - for arg in args { - if let CallArg::Named { name, value } = arg { - if name.as_str() == "tau" { - if let ExprKind::Literal(Literal::Num(n)) = &value.node { - return Some(*n as usize); - } - } - } - } - } - None - } - - fn execute_block(&mut self, decl: &BlockDecl) -> Result { - let (manifold_handle, start, end) = self.extract_block_range(&decl.source)?; - if let Some(workspace) = self.manifolds.get(manifold_handle.0) { - let block = workspace.extract_block(start, end); - let handle = BlockHandle(self.blocks.len()); - self.blocks.push(block); - self.variables.insert(decl.name.clone(), Value::Block(handle)); - Ok(Value::Block(handle)) - } else { - Err("manifold not found".to_string()) - } - } - - fn extract_block_range(&self, expr: &Expr) -> Result<(ManifoldHandle, usize, usize), String> { - match &expr.node { - ExprKind::MethodCall { object, args, .. } => { - let handle = self.get_manifold_handle(object)?; - let (start, end) = self.extract_range_from_args(args); - Ok((handle, start, end)) - } - ExprKind::Index { object, range } => { - let handle = self.get_manifold_handle(object)?; - let start = range.start.as_f64() as usize; - let end = range.end.as_f64() as usize; - Ok((handle, start, end)) - } - _ => Err("invalid block source".to_string()) - } - } - - fn get_manifold_handle(&self, name: &String) -> Result { - if let Some(Value::Manifold(h)) = self.variables.get(name) { - Ok(*h) - } else { - Err("variable is not a manifold".to_string()) - } - } - - fn extract_range_from_args(&self, args: &[CallArg]) -> (usize, usize) { - let mut start = 0usize; - let mut end = 64usize; - - for (i, arg) in args.iter().enumerate() { - if let CallArg::Positional(expr) = arg { - match &expr.node { - ExprKind::Literal(Literal::Num(n)) => { - if i == 0 { start = *n as usize; } - if i == 1 { end = *n as usize; } - } - ExprKind::Range(r) => { - start = r.start.as_f64() as usize; - end = r.end.as_f64() as usize; - } - _ => {} - } - } - } - (start, end) - } - - fn execute_var(&mut self, decl: &VarDecl) -> Result { - let value = self.evaluate_expr(&decl.value)?; - self.variables.insert(decl.name.clone(), value.clone()); - Ok(value) - } - - fn execute_regress(&mut self, stmt: &RegressStmt) -> Result { - let config = &stmt.config; - let epsilon = match &config.until { - Some(ConvergenceCond::Epsilon(n)) => n.as_f64(), - _ => 1e-6, - }; - let mut regressor = EscalatingRegressor::new(epsilon); - regressor.set_target(&self.sample_data); - if let Some(workspace) = self.manifolds.first() { - let max_epochs = if config.escalate { 100 } else { 10 }; - let result = regressor.run_escalating(workspace, max_epochs); - Ok(Value::RegressionResult(result)) - } else { - Err("no manifold for regression".to_string()) - } - } - - fn execute_render(&mut self, _: &RenderStmt) -> Result { - Ok(Value::Unit) - } - - fn execute_stmt_block(&mut self, block: &Block) -> Result { - let mut last_val = Value::Unit; - for stmt in &block.statements { - last_val = self.execute_statement(stmt)?; - } - Ok(last_val) - } - - fn execute_if(&mut self, stmt: &IfStmt) -> Result { - let cond_val = self.evaluate_expr(&stmt.condition)?; - let is_true = match cond_val { - Value::Bool(b) => b, - _ => return Err(String::from("condition must be boolean")), - }; - if is_true { - self.execute_stmt_block(&stmt.then_branch) - } else if let Some(else_branch) = &stmt.else_branch { - self.execute_stmt_block(else_branch) - } else { - Ok(Value::Unit) - } - } - - fn execute_while(&mut self, stmt: &WhileStmt) -> Result { - let mut last_val = Value::Unit; - loop { - let cond_val = self.evaluate_expr(&stmt.condition)?; - let is_true = match cond_val { - Value::Bool(b) => b, - _ => return Err(String::from("condition must be boolean")), - }; - if !is_true { break; } - last_val = self.execute_stmt_block(&stmt.body)?; - } - Ok(last_val) - } - - fn execute_seal(&mut self, stmt: &LoopStmt) -> Result { - let max_iters = 1000; - let mut last_val = Value::Unit; - for _ in 0..max_iters { - last_val = self.execute_stmt_block(&stmt.body)?; - } - Ok(last_val) - } - - fn evaluate_expr(&mut self, expr: &Expr) -> Result { - match &expr.node { - ExprKind::Literal(lit) => match lit { - Literal::Num(n) => Ok(Value::Num(*n)), - Literal::Bool(b) => Ok(Value::Bool(*b)), - Literal::Str(s) => Ok(Value::Str(s.clone())), - }, - ExprKind::Ident(name) => { - if let Some(v) = self.variables.get(name) { - Ok(v.clone()) - } else { - Ok(Value::Unit) - } - } - ExprKind::FieldAccess { object, field } => self.evaluate_field_access(object, field), - ExprKind::Call { name, args } => self.evaluate_call(name, args), - ExprKind::New { class, args } => self.evaluate_new(class, args), - ExprKind::List(elements) => self.evaluate_list(elements), - ExprKind::MethodCall { object, method, args } => self.evaluate_method_call(object, method, args), - ExprKind::Range(_) => Err(String::from("Ranges cannot be evaluated directly as values")), - ExprKind::BinaryOp(left, op, right) => { - let l = self.evaluate_expr(left)?; - let r = self.evaluate_expr(right)?; - self.evaluate_binary(l, *op, r) - }, - ExprKind::UnaryOp(_, _) => Err(String::from("Unary ops not implemented yet")), - ExprKind::Index { object, range } => { - // Simplified: returns a descriptive string or handle? - // For now, let's treat it as a lookup that returns a sub-manifold or block value - let handle = self.get_manifold_handle(object)?; - let start = range.start.as_f64() as usize; - let end = range.end.as_f64() as usize; - if let Some(workspace) = self.manifolds.get(handle.0) { - let block = workspace.extract_block(start, end); - let block_handle = BlockHandle(self.blocks.len()); - self.blocks.push(block); - Ok(Value::Block(block_handle)) - } else { - Err(format!("Manifold '{}' not found", object)) - } - } - ExprKind::Config(_) => Err(String::from("Raw config blocks cannot be evaluated as expressions")), - } - } - - fn evaluate_binary(&self, left: Value, op: BinaryOp, right: Value) -> Result { - match (left, op, right) { - (Value::Num(a), BinaryOp::Add, Value::Num(b)) => Ok(Value::Num(a + b)), - (Value::Num(a), BinaryOp::Sub, Value::Num(b)) => Ok(Value::Num(a - b)), - (Value::Num(a), BinaryOp::Mul, Value::Num(b)) => Ok(Value::Num(a * b)), - (Value::Num(a), BinaryOp::Div, Value::Num(b)) => Ok(Value::Num(a / b)), - _ => Err("Invalid binary operation".into()) - } - } - - fn evaluate_list(&mut self, elements: &Vec) -> Result { - let mut values = Vec::new(); - for expr in elements { - values.push(self.evaluate_expr(expr)?); - } - Ok(Value::List(values)) - } - - fn evaluate_call(&mut self, name: &Ident, args: &Vec) -> Result { - if let Some(val) = self.variables.get(name) { - match val.clone() { - Value::NativeFn(func) => self.execute_native_fn(func, args), - _ => Ok(Value::Unit), - } - } else { - Ok(Value::Unit) - } - } - - fn execute_native_fn(&mut self, func: NativeFunction, args: &[CallArg]) -> Result { - let mut get_f64 = |args: &[CallArg]| -> Result { - if let Some(CallArg::Positional(expr)) = args.first() { - let val = self.evaluate_expr(expr)?; - if let Value::Num(n) = val { Ok(n) } else { Err(String::from("Expected number")) } - } else { - Err(String::from("Expected number")) - } - }; - - match func { - NativeFunction::MathSin => Ok(Value::Num(libm::sin(get_f64(args)?))), - NativeFunction::MathCos => Ok(Value::Num(libm::cos(get_f64(args)?))), - NativeFunction::MathSqrt => Ok(Value::Num(libm::sqrt(get_f64(args)?))), - NativeFunction::MathExp => Ok(Value::Num(libm::exp(get_f64(args)?))), - NativeFunction::TopoBetti => Ok(Value::List(vec![Value::Num(1.0), Value::Num(0.0)])), - NativeFunction::Print => Ok(Value::Unit), - NativeFunction::MlpNew => { - let lr = get_f64(args).unwrap_or(0.01); - let config = OptimizerConfig::SGD { learning_rate: lr, momentum: 0.9 }; - Ok(Value::Mlp(Box::new(MLP::new(config, LossConfig::MSE)))) - }, - NativeFunction::KMeansNew => { - let k = get_f64(args).unwrap_or(2.0) as usize; - Ok(Value::KMeans(Box::new(KMeans::new(k)))) - }, - NativeFunction::Conv2DNew => Ok(Value::Conv2D(Box::new(Conv2D::new(1, 1, 3, 1, 1, Activation::ReLU)))), - NativeFunction::SealTrain => Err("Seal training via native fn not implemented yet".into()), - _ => Ok(Value::Unit) // Simplified rest for brevity as they are mostly placeholder in previous dump - } - } - - fn value_to_tensor(&self, val: &Value) -> Result, String> { - match val { - Value::List(rows) => { - let mut tensor = Vec::new(); - for row in rows { - if let Value::List(cols) = row { - let mut arr = [0.0; 64]; - for (i, v) in cols.iter().enumerate().take(64) { - if let Value::Num(n) = v { arr[i] = *n; } - } - tensor.push(arr); - } else { return Err(String::from("Data must be 2D list")); } - } - Ok(tensor) - } - _ => Err(String::from("Data must be a List")), - } - } - - fn evaluate_method_call(&mut self, object_name: &String, method: &String, args: &[CallArg]) -> Result { - let val = if let Some(v) = self.variables.get(object_name) { v.clone() } else { return Err(format!("Object '{}' not found", object_name)); }; - match val { - Value::List(mut list) => { - let res = match method.as_str() { - "push" => { - if let Some(CallArg::Positional(expr)) = args.first() { - let val = self.evaluate_expr(expr)?; - list.push(val); - self.variables.insert(object_name.clone(), Value::List(list)); - Ok(Value::Unit) - } else { Err(String::from("push requires 1 argument")) } - } - "pop" => { - let val = list.pop().unwrap_or(Value::Unit); - self.variables.insert(object_name.clone(), Value::List(list)); - Ok(val) - } - "len" => Ok(Value::Num(list.len() as f64)), - _ => Err(format!("Method '{}' not found on List", method)), - }; - res - } - Value::Module(mod_name) => { - match (mod_name.as_str(), method.as_str()) { - ("Ml", "MLP") => self.execute_native_fn(NativeFunction::MlpNew, args), - ("Ml", "KMeans") => self.execute_native_fn(NativeFunction::KMeansNew, args), - ("Ml", "Conv2D") => self.execute_native_fn(NativeFunction::Conv2DNew, args), - ("Seal", "train") => self.execute_native_fn(NativeFunction::SealTrain, args), - _ => Err(format!("Method '{}' not found in module '{}'", method, mod_name)), - } - } - _ => Ok(Value::Unit), // Simplified - } - } - - fn evaluate_field_access(&self, object: &String, field: &String) -> Result { - if let Some(Value::Object(handle)) = self.variables.get(object) { - if let Some(obj) = self.objects.get(handle.0) { - if let Some(val) = obj.fields.get(field) { return Ok(val.clone()); } - } - } - if let Some(Value::Module(name)) = self.variables.get(object) { - match (name.as_str(), field.as_str()) { - ("Seal", "train") => Ok(Value::NativeFn(NativeFunction::SealTrain)), - ("Ml", "MLP") => Ok(Value::NativeFn(NativeFunction::MlpNew)), - _ => Ok(Value::Unit), - } - } else { - Ok(Value::Unit) - } - } -} - -impl Default for Interpreter { - fn default() -> Self { - Self::new() - } -} - -// Tests helper -fn list_from_u8(bytes: &[u8]) -> Vec { - let mut data = Vec::with_capacity(bytes.len() / 4); - for chunk in bytes.chunks_exact(4) { - let arr: [u8; 4] = chunk.try_into().unwrap(); - data.push(f32::from_le_bytes(arr)); - } - data -} diff --git a/aether-lang/src/parser.rs b/aether-lang/src/parser.rs deleted file mode 100644 index 4eebe30..0000000 --- a/aether-lang/src/parser.rs +++ /dev/null @@ -1,880 +0,0 @@ -// ═══════════════════════════════════════════════════════════════════════════════ -//! AETHER Parser - Recursive descent parser for .aether scripts -// ═══════════════════════════════════════════════════════════════════════════════ -//! -//! Converts token stream to AST for interpretation. -//! Now produces Spanned nodes for precise error reporting. -//! -//! Grammar (simplified): -//! program → statement* EOF -//! statement → manifold_decl | block_decl | regress_stmt | render_stmt | var_decl -//! manifold_decl → "manifold" IDENT "=" expr -//! regress_stmt → "regress" config_block -//! config_block → "{" (IDENT ":" expr ",")* "}" -// ═══════════════════════════════════════════════════════════════════════════════ - -#![allow(dead_code)] - -extern crate alloc; -use crate::ast::*; -use crate::lexer::{Lexer, Token, TokenKind}; -use alloc::string::String; -use alloc::vec::Vec; -use alloc::boxed::Box; - -#[cfg(not(feature = "std"))] -use alloc::{format, vec}; -#[cfg(not(feature = "std"))] -use alloc::string::ToString; - -#[cfg(not(feature = "std"))] -macro_rules! println { - ($($arg:tt)*) => {}; -} - -/// Parser error -#[derive(Debug, Clone)] -pub struct ParseError { - pub message: String, - pub line: usize, - pub column: usize, -} - -impl ParseError { - pub fn new(msg: &str, line: usize, column: usize) -> Self { - let mut message = String::new(); - message.push_str(msg); - Self { - message, - line, - column, - } - } -} - -/// AEGIS Parser -pub struct Parser<'a> { - tokens: Vec, - current: usize, - _source: &'a str, -} - -impl<'a> Parser<'a> { - /// Create parser from source text - pub fn new(source: &'a str) -> Self { - let mut lexer = Lexer::new(source); - let tokens = lexer.tokenize(); - - Self { - tokens, - current: 0, - _source: source, - } - } - - /// Parse entire program - pub fn parse(&mut self) -> Result { - let mut program = Program::new(); - - while !self.is_at_end() { - // Skip empty lines (though Lexer currently emits Newline tokens, we might consume them) - // Actually, grammar says program -> statement*. - // Our parse_statement handles newline/empty specially. - - // Consume leading newlines strictly - while self.check(TokenKind::Newline) { - self.advance(); - } - - if self.is_at_end() { - break; - } - - let stmt = self.parse_statement()?; - // We only push non-empty statements - if !matches!(stmt.node, StmtKind::Empty) { - program.push(stmt); - } - } - - Ok(program) - } - - // ═══════════════════════════════════════════════════════════════════════════ - // Helpers - // ═══════════════════════════════════════════════════════════════════════════ - - fn make_span(&self, start: &Token, end: &Token) -> Span { - Span { - start: start.start, - end: end.end, - line: start.line, - col: start.column, - } - } - - fn wrap_stmt(&self, kind: StmtKind, start_token: &Token) -> Statement { - let end_token = self.previous(); - let span = self.make_span(start_token, end_token); - Statement { node: kind, span } - } - - fn wrap_expr(&self, kind: ExprKind, start_token: &Token) -> Expr { - let end_token = self.previous(); - let span = self.make_span(start_token, end_token); - Expr { node: kind, span } - } - - // ═══════════════════════════════════════════════════════════════════════════ - // Statement Parsing - // ═══════════════════════════════════════════════════════════════════════════ - - fn parse_statement(&mut self) -> Result { - let token = self.peek().clone(); - - let kind = match &token.kind { - TokenKind::Manifold => self.parse_manifold_decl()?, - TokenKind::Block => self.parse_block_decl()?, - TokenKind::Regress => self.parse_regress_stmt()?, - TokenKind::Render => self.parse_render_stmt()?, - TokenKind::Identifier(_) => self.parse_ident_start_stmt()?, - - // Class Declaration - TokenKind::Class => self.parse_class_decl()?, - - // Modules - TokenKind::Import => self.parse_import_stmt()?, - TokenKind::From => self.parse_from_import_stmt()?, - - // Control Flow - TokenKind::If => self.parse_if_stmt()?, - TokenKind::While => self.parse_while_stmt()?, - TokenKind::For => self.parse_for_stmt()?, - TokenKind::Seal => self.parse_seal_stmt()?, - TokenKind::Fn => self.parse_fn_decl()?, - TokenKind::Return => self.parse_return_stmt()?, - TokenKind::Break => { - self.advance(); - StmtKind::Break(BreakStmt) - }, - TokenKind::Continue => { - self.advance(); - StmtKind::Continue(ContinueStmt) - }, - TokenKind::Let => self.parse_let_decl()?, - - TokenKind::Newline | TokenKind::Eof => StmtKind::Empty, - _ => { - return Err(ParseError::new( - &format!("unexpected token: {:?}", token.kind), - token.line, - token.column, - )); - } - }; - - // Special case: if Empty, just return a dummy empty statement with current token span - if matches!(kind, StmtKind::Empty) { - let _t = self.previous(); // Might be Newline we just consumed or previous - // Doing it properly: - return Ok(Statement { - node: StmtKind::Empty, - span: self.make_span(&token, &token) - }); - } - - // For statements that we parsed, we want them wrapped. - // Note: parse_manifold_decl etc currently return StmtKind, need to adapt helper methods. - // Actually, let's make specific parsers return StmtKind and wrap here? - // Wait, parse_ident_start_stmt consumes tokens inside. - // Better to have parse functions return StmtKind. - - Ok(self.wrap_stmt(kind, &token)) - } - - /// manifold_decl → "manifold" IDENT "=" expr - fn parse_manifold_decl(&mut self) -> Result { - self.expect(TokenKind::Manifold)?; - let name = self.expect_ident()?; - self.expect(TokenKind::Equals)?; - let init = self.parse_expr()?; - - Ok(StmtKind::Manifold(ManifoldDecl { name, init })) - } - - /// block_decl → "block" IDENT "=" expr - fn parse_block_decl(&mut self) -> Result { - self.expect(TokenKind::Block)?; - let name = self.expect_ident()?; - self.expect(TokenKind::Equals)?; - let source = self.parse_expr()?; - - Ok(StmtKind::Block(BlockDecl { name, source })) - } - - /// ident_start_stmt → var_decl | expr_stmt - fn parse_ident_start_stmt(&mut self) -> Result { - let first_ident = self.expect_ident()?; - - // 1. Check for type hint: Ident Ident = Expr - if self.check_ident() && self.peek_next_is(TokenKind::Equals) { - let type_hint = Some(first_ident); - let name = self.expect_ident()?; - self.expect(TokenKind::Equals)?; - let value = self.parse_expr()?; - - Ok(StmtKind::Var(VarDecl { - type_hint, - name, - value, - })) - } - // 2. Check for Var Decl without type: Ident = Expr - else if self.check(TokenKind::Equals) { - self.expect(TokenKind::Equals)?; - let value = self.parse_expr()?; - - Ok(StmtKind::Var(VarDecl { - type_hint: None, - name: first_ident, - value, - })) - } - // 3. Expression Statement (e.g., method call) starting with Ident - else { - // We consumed the identifier. Parse the rest as an expression starting with this ident. - let start_token = self.tokens[self.current-1].clone(); - let kind = self.parse_ident_expr_cont(first_ident, &start_token)?; - Ok(StmtKind::Expr(self.wrap_expr(kind, &start_token))) - } - } - - /// let_decl → "let" IDENT "=" expr - fn parse_let_decl(&mut self) -> Result { - self.expect(TokenKind::Let)?; - let name = self.expect_ident()?; - self.expect(TokenKind::Equals)?; - let value = self.parse_expr()?; - - Ok(StmtKind::Var(VarDecl { - type_hint: None, - name, - value, - })) - } - - /// regress_stmt → "regress" config_block - fn parse_regress_stmt(&mut self) -> Result { - self.expect(TokenKind::Regress)?; - let config = self.parse_regress_config()?; - - Ok(StmtKind::Regress(RegressStmt { config })) - } - - /// render_stmt → "render" IDENT config_block? - fn parse_render_stmt(&mut self) -> Result { - self.expect(TokenKind::Render)?; - let target = self.expect_ident()?; - - let config = if self.check(TokenKind::LBrace) { - self.parse_render_config()? - } else { - RenderConfig::default() - }; - - Ok(StmtKind::Render(RenderStmt { target, config })) - } - - /// class_decl → "class" IDENT "{" (var_decl | fn_decl)* "}" - fn parse_class_decl(&mut self) -> Result { - self.expect(TokenKind::Class)?; - let name = self.expect_ident()?; - self.expect(TokenKind::LBrace)?; - - let mut fields = Vec::new(); - let mut methods = Vec::new(); - - while !self.check(TokenKind::RBrace) && !self.is_at_end() { - if self.check(TokenKind::Newline) { - self.advance(); - continue; - } - - if self.check(TokenKind::Fn) { - // Method - if let StmtKind::Fn(f) = self.parse_fn_decl()? { - methods.push(f); - } - } else if self.check_ident() { - // Field - let field_name = self.expect_ident()?; - let value = if self.check(TokenKind::Equals) { - self.advance(); - self.parse_expr()? - } else { - // Default to false wrapped - let t = self.peek().clone(); // span might be slightly off - self.wrap_expr(ExprKind::Literal(Literal::Bool(false)), &t) - }; - - if self.check(TokenKind::Comma) { - self.advance(); - } - - fields.push(VarDecl { - type_hint: None, - name: field_name, - value, - }); - } else { - let t = self.peek(); - return Err(ParseError::new("expected field or method", t.line, t.column)); - } - } - - self.expect(TokenKind::RBrace)?; - - Ok(StmtKind::Class(ClassDecl { - name, - fields, - methods, - })) - } - - // Modules - fn parse_import_stmt(&mut self) -> Result { - self.expect(TokenKind::Import)?; - let module = self.expect_ident()?; - Ok(StmtKind::Import(ImportStmt { - module, - symbol: None, - })) - } - - fn parse_from_import_stmt(&mut self) -> Result { - self.expect(TokenKind::From)?; - let module = self.expect_ident()?; - self.expect(TokenKind::Import)?; - let symbol = self.expect_ident()?; - - Ok(StmtKind::Import(ImportStmt { - module, - symbol: Some(symbol), - })) - } - - // Control Flow - fn parse_if_stmt(&mut self) -> Result { - self.expect(TokenKind::If)?; - let condition = self.parse_expr()?; - let then_branch = self.parse_block_stmts()?; - - let else_branch = if self.check(TokenKind::Else) { - self.advance(); - Some(self.parse_block_stmts()?) - } else { - None - }; - - Ok(StmtKind::If(IfStmt { condition, then_branch, else_branch })) - } - - fn parse_while_stmt(&mut self) -> Result { - self.expect(TokenKind::While)?; - let condition = self.parse_expr()?; - let body = self.parse_block_stmts()?; - Ok(StmtKind::While(WhileStmt { condition, body })) - } - - fn parse_for_stmt(&mut self) -> Result { - self.expect(TokenKind::For)?; - let iterator = self.expect_ident()?; - self.expect(TokenKind::In)?; - - // Currently expecting Range. - // We parse expr, verify range. - let expr = self.parse_expr()?; - let range = match expr.node { - ExprKind::Range(r) => r, - _ => { - return Err(ParseError::new("expected range in for loop", expr.span.line, expr.span.col)); - } - }; - - let body = self.parse_block_stmts()?; - Ok(StmtKind::For(ForStmt { iterator, range, body })) - } - - fn parse_seal_stmt(&mut self) -> Result { - self.expect(TokenKind::Seal)?; - let body = self.parse_block_stmts()?; - Ok(StmtKind::Loop(LoopStmt { body })) - } - - fn parse_fn_decl(&mut self) -> Result { - self.expect(TokenKind::Fn)?; - let name = self.expect_ident()?; - self.expect(TokenKind::LParen)?; - - let mut params = Vec::new(); - while !self.check(TokenKind::RParen) && !self.is_at_end() { - params.push(self.expect_ident()?); - if self.check(TokenKind::Comma) { self.advance(); } - } - self.expect(TokenKind::RParen)?; - - let body = self.parse_block_stmts()?; - Ok(StmtKind::Fn(FnDecl { name, params, body })) - } - - fn parse_return_stmt(&mut self) -> Result { - self.expect(TokenKind::Return)?; - let value = if self.check(TokenKind::Newline) || self.check(TokenKind::RBrace) { - None - } else { - Some(self.parse_expr()?) - }; - Ok(StmtKind::Return(ReturnStmt { value })) - } - - // ═══════════════════════════════════════════════════════════════════════════ - // Expression Parsing - // ═══════════════════════════════════════════════════════════════════════════ - - // Start with lowest precedence - fn parse_expr(&mut self) -> Result { - self.parse_range() - } - - fn parse_range(&mut self) -> Result { - let left = self.parse_arithmetic()?; // Using arithmetic as base for range - - if self.check(TokenKind::Colon) { - self.advance(); - // range start/end must be numbers, but parse_arithmetic returns Spanned - // We need to extract number values if possible, or return Error - let right = self.parse_arithmetic()?; - - let start_val = self.expr_to_number(&left)?; - let end_val = self.expr_to_number(&right)?; - - let kind = ExprKind::Range(Range { start: start_val, end: end_val }); - - let span = Span { - start: left.span.start, - end: right.span.end, - line: left.span.line, - col: left.span.col, - }; - return Ok(Expr { node: kind, span }); - } - - Ok(left) - } - - fn parse_arithmetic(&mut self) -> Result { - let mut left = self.parse_term()?; - - while self.check(TokenKind::Plus) || self.check(TokenKind::Minus) { - let op_token = self.advance(); - let op = match op_token.kind { - TokenKind::Plus => BinaryOp::Add, - TokenKind::Minus => BinaryOp::Sub, - _ => unreachable!(), - }; - let right = self.parse_term()?; - - let span = Span { - start: left.span.start, - end: right.span.end, - line: left.span.line, - col: left.span.col, - }; - - let kind = ExprKind::BinaryOp(Box::new(left.clone()), op, Box::new(right)); - left = Expr { node: kind, span }; - } - - Ok(left) - } - - fn parse_term(&mut self) -> Result { - let mut left = self.parse_primary()?; - - while self.check(TokenKind::Star) || self.check(TokenKind::Slash) { - let op_token = self.advance(); - let op = match op_token.kind { - TokenKind::Star => BinaryOp::Mul, - TokenKind::Slash => BinaryOp::Div, - _ => unreachable!(), - }; - let right = self.parse_primary()?; - - let span = Span { - start: left.span.start, - end: right.span.end, - line: left.span.line, - col: left.span.col, - }; - - let kind = ExprKind::BinaryOp(Box::new(left.clone()), op, Box::new(right)); - left = Expr { node: kind, span }; - } - - Ok(left) - } - - fn parse_primary(&mut self) -> Result { - let token = self.advance(); - let kind = match token.kind { - TokenKind::Number(n) => ExprKind::Literal(Literal::Num(n as f64)), - TokenKind::Float(int, frac) => { - let val = int as f64 + (frac as f64 / 1_000_000.0); - ExprKind::Literal(Literal::Num(val)) - }, - TokenKind::True => ExprKind::Literal(Literal::Bool(true)), - TokenKind::False => ExprKind::Literal(Literal::Bool(false)), - TokenKind::StringLit(ref s) => ExprKind::Literal(Literal::Str(s.clone())), - TokenKind::Self_ => ExprKind::Ident(String::from("self")), - - TokenKind::Identifier(ref name) => { - let name_clone = name.clone(); - // Return result of parse_ident_expr_cont which returns ExprKind - return self.parse_ident_expr_cont(name_clone, &token).map(|kind| { - // Span adjustment needed because parse_ident_expr_cont consumes more - let end_token = self.previous(); - let span = self.make_span(&token, end_token); - Expr { node: kind, span } - }); - }, - - // New Object Instantiation - TokenKind::New => { - let class = self.expect_ident()?; - self.expect(TokenKind::LParen)?; - let mut args = Vec::new(); - while !self.check(TokenKind::RParen) && !self.is_at_end() { - args.push(self.parse_expr()?); - if self.check(TokenKind::Comma) { self.advance(); } - } - self.expect(TokenKind::RParen)?; - ExprKind::New { class, args } - }, - - // List - TokenKind::LBracket => self.parse_list_literal_cont()?, - - // Embed/Convergence keywords used as functions - TokenKind::Embed => { - return self.parse_call_expr_cont(String::from("embed"), &token); - }, - TokenKind::Convergence => { - return self.parse_call_expr_cont(String::from("convergence"), &token); - }, - - _ => return Err(ParseError::new("expected expression", token.line, token.column)), - }; - - Ok(self.wrap_expr(kind, &token)) - } - - fn parse_list_literal_cont(&mut self) -> Result { - let mut elements = Vec::new(); - - while !self.check(TokenKind::RBracket) && !self.is_at_end() { - if self.check(TokenKind::Newline) { self.advance(); continue; } - elements.push(self.parse_expr()?); - if self.check(TokenKind::Comma) { self.advance(); } - } - self.expect(TokenKind::RBracket)?; - Ok(ExprKind::List(elements)) - } - - fn parse_ident_expr_cont(&mut self, name: String, _start_token: &Token) -> Result { - // Method call: M.cluster(...) - if self.check(TokenKind::Dot) { - self.advance(); - let method = self.expect_flexible_ident()?; - - if self.check(TokenKind::LParen) { - let args = self.parse_call_args()?; - return Ok(ExprKind::MethodCall { - object: name, - method, - args, - }); - } else { - return Ok(ExprKind::FieldAccess { - object: name, - field: method, - }); - } - } - - // Call: embed(...) - if self.check(TokenKind::LParen) { - let args = self.parse_call_args()?; - return Ok(ExprKind::Call { name, args }); - } - - // Index: M[0:64] - if self.check(TokenKind::LBracket) { - self.advance(); - let start = self.parse_number()?; - self.expect(TokenKind::Colon)?; - let end = self.parse_number()?; - self.expect(TokenKind::RBracket)?; - - return Ok(ExprKind::Index { - object: name, - range: Range { start, end }, - }); - } - - Ok(ExprKind::Ident(name)) - } - - fn parse_call_expr_cont(&mut self, name: String, start_token: &Token) -> Result { - let args = self.parse_call_args()?; - let kind = ExprKind::Call { name, args }; - Ok(self.wrap_expr(kind, start_token)) - } - - fn parse_call_args(&mut self) -> Result, ParseError> { - self.expect(TokenKind::LParen)?; - - let mut args = Vec::new(); - - while !self.check(TokenKind::RParen) && !self.is_at_end() { - // Check for named argument - if self.check_flexible_ident() { - let saved_pos = self.current; - let name = self.expect_flexible_ident()?; - - if self.check(TokenKind::Equals) { - self.advance(); - let value = self.parse_expr()?; - args.push(CallArg::Named { name, value }); - } else { - // Backtrack - self.current = saved_pos; - let expr = self.parse_expr()?; - args.push(CallArg::Positional(expr)); - } - } else { - let expr = self.parse_expr()?; - args.push(CallArg::Positional(expr)); - } - - if self.check(TokenKind::Comma) { self.advance(); } - } - self.expect(TokenKind::RParen)?; - Ok(args) - } - - // Helper to extract Number from Expr (for Range and Config compatibility) - fn expr_to_number(&self, expr: &Expr) -> Result { - match &expr.node { - ExprKind::Literal(Literal::Num(f)) => { - // Convert f64 back to Number enum just for internal usage in Range? - // Wait, Range struct in ast.rs expects Number enum. - // So I must construct Number. - // f64 to Number::Float - let int_part = *f as i64; - let frac_part = ((*f - int_part as f64) * 1_000_000.0) as i64; - Ok(Number::Float { int_part, frac_part }) - }, - _ => Err(ParseError::new("expected number", expr.span.line, expr.span.col)) - } - } - - fn parse_number(&mut self) -> Result { - let token = self.advance(); - match token.kind { - TokenKind::Number(n) => Ok(Number::Int(n)), - TokenKind::Float(int, frac) => Ok(Number::Float { - int_part: int, - frac_part: frac, - }), - _ => Err(ParseError::new("expected number", token.line, token.column)), - } - } - - // Config Block Parsers (Simplified for brevity but maintaining logic) - fn parse_regress_config(&mut self) -> Result { - self.expect(TokenKind::LBrace)?; - let mut config = RegressConfig::default(); - while !self.check(TokenKind::RBrace) && !self.is_at_end() { - if self.check(TokenKind::Newline) { self.advance(); continue; } - - let key = self.expect_flexible_ident()?; - self.expect(TokenKind::Colon)?; - - match key.as_str() { - "model" => { - if let ExprKind::Literal(Literal::Str(s)) = self.parse_expr()?.node { - config.model = s; - } - }, - "degree" => { - let expr = self.parse_expr()?; - if let Ok(num) = self.expr_to_number(&expr) { - if let Number::Int(n) = num { config.degree = Some(n as u8); } - } - }, - "target" => config.target = Some(self.parse_expr()?), - "escalate" => { - if let ExprKind::Literal(Literal::Bool(b)) = self.parse_expr()?.node { - config.escalate = b; - } - }, - "until" => { - // Parse convergence - // convergence(..) or custom expr - // Look at parse_expr() handling - let expr = self.parse_expr()?; - // Check if it is a call 'convergence' - // Actually convergence is special keyword in lexer but parsed as call - config.until = Some(ConvergenceCond::Custom(expr)); // Simplified - }, - _ => { self.parse_expr()?; } - } - - if self.check(TokenKind::Comma) { self.advance(); } - } - self.expect(TokenKind::RBrace)?; - Ok(config) - } - - fn parse_render_config(&mut self) -> Result { - self.expect(TokenKind::LBrace)?; - let mut config = RenderConfig::default(); - while !self.check(TokenKind::RBrace) && !self.is_at_end() { - if self.check(TokenKind::Newline) { self.advance(); continue; } - - let key = self.expect_flexible_ident()?; - self.expect(TokenKind::Colon)?; - let expr = self.parse_expr()?; - - match key.as_str() { - "color" => { - if let ExprKind::Ident(id) = expr.node { config.color = Some(id); } - }, - "highlight" => { - if let ExprKind::Ident(id) = expr.node { config.highlight = Some(id); } - }, - "trajectory" => { - if let ExprKind::Literal(Literal::Bool(b)) = expr.node { config.trajectory = b; } - }, - "axis" => { - if let Ok(Number::Int(n)) = self.expr_to_number(&expr) { config.axis = Some(n as u8); } - }, - _ => {} - } - - if self.check(TokenKind::Comma) { self.advance(); } - } - self.expect(TokenKind::RBrace)?; - Ok(config) - } - - // Helper Methods - fn parse_block_stmts(&mut self) -> Result { - self.expect(TokenKind::LBrace)?; - let mut statements = Vec::new(); - while !self.check(TokenKind::RBrace) && !self.is_at_end() { - while self.check(TokenKind::Newline) { self.advance(); } - if self.check(TokenKind::RBrace) { break; } - let stmt = self.parse_statement()?; - if !matches!(stmt.node, StmtKind::Empty) { - statements.push(stmt); - } - } - self.expect(TokenKind::RBrace)?; - Ok(Block { statements }) - } - - fn peek(&self) -> &Token { - self.tokens.get(self.current).unwrap_or(&self.tokens[self.tokens.len()-1]) - } - - fn previous(&self) -> &Token { - if self.current == 0 { return &self.tokens[0]; } - &self.tokens[self.current - 1] - } - - fn advance(&mut self) -> Token { - if !self.is_at_end() { - self.current += 1; - } - self.previous().clone() - } - - fn is_at_end(&self) -> bool { - matches!(self.peek().kind, TokenKind::Eof) - } - - fn check(&self, kind: TokenKind) -> bool { - if self.is_at_end() { - return false; - } - core::mem::discriminant(&self.peek().kind) == core::mem::discriminant(&kind) - } - - fn check_ident(&self) -> bool { - matches!(self.peek().kind, TokenKind::Identifier(_)) - } - - fn peek_next_is(&self, kind: TokenKind) -> bool { - self.tokens - .get(self.current + 1) - .map(|t| core::mem::discriminant(&t.kind) == core::mem::discriminant(&kind)) - .unwrap_or(false) - } - - fn expect(&mut self, kind: TokenKind) -> Result { - if self.check(kind.clone()) { - Ok(self.advance()) - } else { - let token = self.peek(); - Err(ParseError::new( - "unexpected token", - token.line, - token.column, - )) - } - } - - fn expect_ident(&mut self) -> Result { - let token = self.advance(); - match token.kind { - TokenKind::Identifier(s) => Ok(s), - _ => Err(ParseError::new("expected identifier", token.line, token.column)), - } - } - - fn check_flexible_ident(&self) -> bool { - matches!(self.peek().kind, TokenKind::Identifier(_) | TokenKind::Dim | TokenKind::Tau | TokenKind::Model | TokenKind::Color | TokenKind::Axis | TokenKind::Project | TokenKind::Cluster | TokenKind::Center | TokenKind::Spread | TokenKind::Format | TokenKind::Output | TokenKind::Escalate | TokenKind::Convergence) - } - - fn expect_flexible_ident(&mut self) -> Result { - let token = self.advance(); - match token.kind { - TokenKind::Identifier(s) => Ok(s), - TokenKind::Dim => Ok(String::from("dim")), - TokenKind::Tau => Ok(String::from("tau")), - TokenKind::Model => Ok(String::from("model")), - TokenKind::Color => Ok(String::from("color")), - TokenKind::Axis => Ok(String::from("axis")), - TokenKind::Project => Ok(String::from("project")), - TokenKind::Cluster => Ok(String::from("cluster")), - TokenKind::Center => Ok(String::from("center")), - TokenKind::Spread => Ok(String::from("spread")), - TokenKind::Format => Ok(String::from("format")), - TokenKind::Output => Ok(String::from("output")), - TokenKind::Escalate => Ok(String::from("escalate")), - TokenKind::Convergence => Ok(String::from("convergence")), - _ => Err(ParseError::new("expected argument name", token.line, token.column)) - } - } -} diff --git a/aether-lang/src/vm.rs b/aether-lang/src/vm.rs deleted file mode 100644 index c1cb7f5..0000000 --- a/aether-lang/src/vm.rs +++ /dev/null @@ -1,401 +0,0 @@ -//! ═══════════════════════════════════════════════════════════════════════════════ -//! TITAN Cortex: The High-Throughput Virtual Machine -//! ═══════════════════════════════════════════════════════════════════════════════ -//! -//! "The Left Brain of AEGIS." -//! -//! Optimization targets: -//! - Stack-based execution (Cache locality) -//! - Linear bytecode (Predictable branching) -//! - Explicit topological ops (EMBED, ATTEND, PRUNE) -//! -//! ═══════════════════════════════════════════════════════════════════════════════ - -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; -#[cfg(not(feature = "std"))] -use alloc::boxed::Box; -#[cfg(not(feature = "std"))] -use alloc::string::String; - -#[cfg(feature = "std")] -use std::vec::Vec; -#[cfg(feature = "std")] -use std::string::String; -#[cfg(feature = "std")] -use std::boxed::Box; - -use crate::ast::{Program, Statement, StmtKind, Expr, ExprKind, BinaryOp, Literal}; -use crate::interpreter::Value; -use aether_core::memory::ManifoldHeap; // From Phase 1 - -/// Titan Bytecode Instructions -#[derive(Debug, Clone, Copy)] -#[allow(non_camel_case_types)] -pub enum OpCode { - /// Push constant value onto stack - PUSH(f64), - /// Push variable value - LOAD(usize), // Index into constant pool or variable table? Let's use register/slot index - /// Store top of stack to variable - STORE(usize), - - /// Arithmetic - ADD, SUB, MUL, DIV, - - /// Topology / Core Logic - /// Embeds the top value into the manifold - EMBED, - /// Checks topological attention/neighbors - ATTEND, - /// Explicit entropy regulation point - PRUNE, - - /// Control Flow - JMP(isize), - JMP_IF_FALSE(isize), - - /// Output - PRINT, - - /// End of program - HALT, -} - -/// The Titan Virtual Machine -pub struct TitanVM { - /// Instruction Pointer - ip: usize, - /// The Bytecode DNA - code: Vec, - /// Operand Stack (Fast, hot memory) - stack: Vec, - // In a real optimized VM, we'd use a primitive stack f64, but for compatibility with AEGIS Value type... - // To achieve the 100x speedup, we should probably strictly stick to f64 for calculations - // and only box when necessary. But let's start safe. - - /// The Substrate (Heap) - heap: ManifoldHeap, - - /// Call Frame / Locals (simplified map for now, or vector) - locals: Vec, -} - -impl TitanVM { - pub fn new() -> Self { - Self { - ip: 0, - code: Vec::new(), - stack: Vec::with_capacity(1024), - heap: ManifoldHeap::new(), - locals: vec![Value::Unit; 256], // Pre-alloc locals slots - } - } - - pub fn load_code(&mut self, code: Vec) { - self.code = code; - self.ip = 0; - } - - pub fn run(&mut self) -> Result { - loop { - if self.ip >= self.code.len() { - break; - } - - let op = self.code[self.ip]; - self.ip += 1; - - match op { - OpCode::HALT => break, - - OpCode::PUSH(v) => self.stack.push(Value::Num(v)), - - OpCode::ADD => { - let b = self.pop_num()?; - let a = self.pop_num()?; - self.stack.push(Value::Num(a + b)); - } - OpCode::SUB => { - let b = self.pop_num()?; - let a = self.pop_num()?; - self.stack.push(Value::Num(a - b)); - } - OpCode::MUL => { - let b = self.pop_num()?; - let a = self.pop_num()?; - self.stack.push(Value::Num(a * b)); - } - OpCode::DIV => { - let b = self.pop_num()?; - if b == 0.0 { return Err("Division by zero".into()); } - let a = self.pop_num()?; - self.stack.push(Value::Num(a / b)); - } - - OpCode::PRINT => { - let val = self.stack.pop().ok_or("Stack underflow")?; - // In no_std we might print differently, for now simple debug - #[cfg(feature = "std")] - println!("{:?}", val); - } - - OpCode::EMBED => { - let _val = self.pop_num()?; - // In a real integration, this would push to the TimeDelayEmbedder - // For now, we simulate the 'Action' - // self.heap.alloc(Value::Num(val)); // Store in manifold - } - - OpCode::PRUNE => { - // Trigger Entropy Regulation - self.heap.regulate_entropy(|_h| { - // Mark roots (stack, locals) - // This binding is tricky without referencing self inside closure - // Ideally pass a closure that captures the roots. - // Simplified: - }); - } - - OpCode::LOAD(idx) => { - if idx < self.locals.len() { - self.stack.push(self.locals[idx].clone()); - } else { - return Err("Variable index out of bounds".into()); - } - } - OpCode::STORE(idx) => { - let val = self.stack.pop().ok_or("Stack underflow")?; - if idx >= self.locals.len() { - // Grow locals if needed (simple dynamic growth) - self.locals.resize(idx + 1, Value::Unit); - } - self.locals[idx] = val; - } - - OpCode::JMP(offset) => { - // safer pointer arithmetic - let next = self.ip as isize + offset; - if next < 0 { return Err("Invalid Jump".into()); } - self.ip = next as usize; - } - - OpCode::JMP_IF_FALSE(offset) => { - let val = self.stack.pop().ok_or("Stack underflow")?; - let condition = match val { - Value::Bool(b) => b, - Value::Num(n) => n != 0.0, - _ => false, - }; - - if !condition { - let next = self.ip as isize + offset; - if next < 0 { return Err("Invalid Jump".into()); } - self.ip = next as usize; - } - } - - _ => return Err("Unimplemented OpCode".into()), - } - } - - Ok(self.stack.pop().unwrap_or(Value::Unit)) - } - - fn pop_num(&mut self) -> Result { - match self.stack.pop() { - Some(Value::Num(n)) => Ok(n), - Some(_) => Err("Type Error: Expected Number".into()), - None => Err("Stack Underflow".into()), - } - } -} - -/// The Compiler: AST -> Bytecode -pub struct Compiler { - code: Vec, - /// Simple symbol table: name -> index - locals: Vec, -} - -impl Compiler { - pub fn new() -> Self { - Self { - code: Vec::new(), - locals: Vec::new(), - } - } - - pub fn compile(mut self, program: &Program) -> Vec { - for stmt in &program.statements { - self.compile_stmt(stmt); - } - self.code.push(OpCode::HALT); - self.code - } - - fn resolve_local(&mut self, name: &str) -> usize { - if let Some(idx) = self.locals.iter().position(|r| r == name) { - idx - } else { - let idx = self.locals.len(); - self.locals.push(name.to_string()); - idx - } - } - - fn compile_stmt(&mut self, stmt: &Statement) { - match &stmt.node { - StmtKind::Expr(expr) => { - self.compile_expr(expr); - // Expression statement usually discards result unless it's a specific context - // For now, we leave it on stack or assume explicit print/store - } - StmtKind::Render(stmt) => { - // self.compile_expr(&stmt.data); // Ooops, need to fix RenderStmt access (target currently Ident) - // Actually RenderStmt has 'target' Ident. Access variable. - let idx = self.resolve_local(&stmt.target); - self.code.push(OpCode::LOAD(idx)); - self.code.push(OpCode::PRINT); - } - StmtKind::Var(decl) => { - self.compile_expr(&decl.value); - let idx = self.resolve_local(&decl.name); - self.code.push(OpCode::STORE(idx)); - } - StmtKind::While(stmt) => { - // Label: Start - let start_ip = self.code.len(); - - // Condition - self.compile_expr(&stmt.condition); - - // Jump if False placeholder - let jmp_false_idx = self.code.len(); - self.code.push(OpCode::JMP_IF_FALSE(0)); - - // Body - for s in &stmt.body.statements { - self.compile_stmt(s); - } - - // Jump back to Start - let end_ip = self.code.len(); - let back_jump = (start_ip as isize) - (end_ip as isize) - 1; // -1 because IP increments after fetch - self.code.push(OpCode::JMP(back_jump)); - - // Patch Jump If False - let patch_offset = (self.code.len() as isize) - (jmp_false_idx as isize) - 1; - self.code[jmp_false_idx] = OpCode::JMP_IF_FALSE(patch_offset); - } - StmtKind::If(stmt) => { - // Condition - self.compile_expr(&stmt.condition); - - // JMP_IF_FALSE to Else or End - let jmp_false_idx = self.code.len(); - self.code.push(OpCode::JMP_IF_FALSE(0)); - - // Then Block - for s in &stmt.then_branch.statements { - self.compile_stmt(s); - } - - // If there's an Else block, we need a Jump over it at end of Then - let mut jmp_end_idx = None; - - if let Some(_else_branch) = &stmt.else_branch { - jmp_end_idx = Some(self.code.len()); - self.code.push(OpCode::JMP(0)); - } - - // Patch False Jump to here (start of Else or End) - let false_dest = self.code.len(); - let patch_false = (false_dest as isize) - (jmp_false_idx as isize) - 1; - self.code[jmp_false_idx] = OpCode::JMP_IF_FALSE(patch_false); - - // Compile Else - if let Some(else_branch) = &stmt.else_branch { - for s in &else_branch.statements { - self.compile_stmt(s); - } - - // Patch End Jump - if let Some(idx) = jmp_end_idx { - let end_dest = self.code.len(); - let patch_end = (end_dest as isize) - (idx as isize) - 1; - self.code[idx] = OpCode::JMP(patch_end); - } - } - } - _ => { - // TODO: Implement If, For, etc. - } - } - } - - fn compile_expr(&mut self, expr: &Expr) { - match &expr.node { - ExprKind::Literal(l) => { - match l { - Literal::Num(n) => self.code.push(OpCode::PUSH(*n)), - Literal::Bool(b) => self.code.push(OpCode::PUSH(if *b { 1.0 } else { 0.0 })), - _ => {}, - } - } - ExprKind::Ident(name) => { - let idx = self.resolve_local(name); - self.code.push(OpCode::LOAD(idx)); - } - ExprKind::BinaryOp(left, op, right) => { - self.compile_expr(left); - self.compile_expr(right); - match op { - BinaryOp::Add => self.code.push(OpCode::ADD), - BinaryOp::Sub => self.code.push(OpCode::SUB), - BinaryOp::Mul => self.code.push(OpCode::MUL), - BinaryOp::Div => self.code.push(OpCode::DIV), - BinaryOp::Lt => { - self.code.push(OpCode::SUB); // a - b - // If < 0, then true. This is hacky. Titan needs proper CMP. - // Impl: if top < 0 push 1 else 0? - // Simplified: we don't have LT opcode. - // Let's use strict JMP behavior or add CMP opcode. - // For bench_calc we might need loop counter. - // Temporarily assuming strict arithmetic loops. - } - _ => {}, - } - } - _ => {} - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_titan_math() { - let mut vm = TitanVM::new(); - // 5 + 3 * 2 = 11 - let code = vec![ - OpCode::PUSH(5.0), - OpCode::PUSH(3.0), - OpCode::PUSH(2.0), - OpCode::MUL, - OpCode::ADD, - OpCode::HALT, - ]; - - vm.load_code(code); - let res = vm.run().unwrap(); - - if let Value::Num(n) = res { - assert_eq!(n, 11.0); - } else { - panic!("Expected number"); - } - } -} diff --git a/agents/deferred_work_prd.md b/agents/deferred_work_prd.md new file mode 100644 index 0000000..55c9190 --- /dev/null +++ b/agents/deferred_work_prd.md @@ -0,0 +1,70 @@ +# AEGIS Deep Learning: Deferred Features & Gap Analysis (PRD) +**Version:** 0.1.0-Deferred +**Status:** Backlog +**Parent:** AEGIS Deep Learning Roadmap + +--- + +## 1. Executive Summary +This document outlines the features from the original "AEGIS Deep Learning Roadmap" that are **NOT** being implemented in Phase 1, as well as critical bugs and limitations identified in the current codebase (`aegis-core`). These items are deferred to future phases (Synapse, Acceleration). + +## 2. Deferred Scope (What We Are NOT Doing Yet) + +### 2.1 Hardware Acceleration (`aegis-compute`) +- **Status**: Deferred to Phase 2. +- **Description**: We are strictly sticking to CPU-based execution for Phase 1. `wgpu` integration and CUDA kernels are out of scope. +- **Impact**: Training performance will be slow; real-time topology (manifolds) will happen on CPU. + +### 2.2 The "Synapse" Layer Library +- **Status**: Deferred to Phase 3. +- **Description**: High-level modular layers (`TransformerBlock`, `LSTM`, `GRU`, `Dropout`, `BatchNorm`) are not being built yet. +- **Current State**: We are focusing only on the low-level `Autograd` engine and a basic `Dense` layer refactor. +- **Impact**: Users cannot easily build complex architectures like GPT-2 or ResNet without manually defining the graph. + +### 2.3 Advanced Optimizers +- **Status**: Partially Deferred. +- **Description**: While `Adam` code exists in fragments, we are primarily supporting `SGD` for the Phase 1 Autograd rollout. `RMSProp` and `Adagrad` are not planned. +- **Impact**: Slower convergence on complex landscapes. + +### 2.4 Modular Loss Functions +- **Status**: Deferred. +- **Description**: `CrossEntropy`, `KL-Div`, and `PersistenceLoss` will be implemented as functions first, not as modular objects with state. + +### 2.5 Data Loaders +- **Status**: Deferred. +- **Description**: No `DataLoader` with batching, shuffling, or pre-fetching. Data will be passed as raw Tensors. + +## 3. Known Bugs & Critical Limitations + +### 3.1 severe Scalability Limitation (`MAX_NEURONS`) +- **Severity**: 🔴 CRITICAL +- **Location**: `aegis-core/src/ml/neural.rs` +- **Issue**: The current implementation mandates `const MAX_NEURONS: usize = 64;`. +- **Consequence**: **It is impossible to train on MNIST** (which requires 784 input neurons). The library is currently limited to toy problems (XOR, Iris). +- **Fix Required**: Move from stack-allocated arrays `[f64; 64]` to heap-allocated dynamic `Vec` or the new `Tensor` struct. + +### 3.2 Stack Overflow Risk +- **Severity**: 🔴 CRITICAL +- **Location**: `aegis-core/src/ml/linalg.rs`, `neural.rs` +- **Issue**: Large structs (`Matrix` with `[f64; 32][32]`) are passed by value or allocated on the stack. In a `no_std` kernel environment, this will blow the stack immediately with deep networks. +- **Fix Required**: Use `Box` or `Rc` for storage, or strict reference passing. + +### 3.3 Dead Code: Disconnected `Adam` Optimizer +- **Severity**: 🟡 MEDIUM +- **Location**: `aegis-core/src/ml/neural.rs` +- **Issue**: `struct AdamState` and `fn adam_update` exist but are **not integrated** into the `MLP` struct. `MLP` hardcodes `layer.backward(...)`, which looks like standard SGD. +- **Consequence**: Users cannot actually use Adam despite the code being there. + +### 3.4 Poor Randomness Initialization +- **Severity**: 🟢 LOW +- **Location**: `DenseLayer::new` +- **Issue**: Uses a crude Linear Congruential Generator (LCG) seeded with `42`. +- **Consequence**: All models initialize exactly the same way (deterministic), but the distribution quality is poor. + +### 3.5 Manual Backpropagation +- **Severity**: 🟡 MEDIUM +- **Location**: `DenseLayer::backward` +- **Issue**: The backward pass is hardcoded for the specific `DenseLayer` math. It does not support automatic differentiation for arbitrary graphs (which is the goal of Phase 1). + +## 4. Next Steps +The immediate priority is to address **3.1 (MAX_NEURONS)** and **3.5 (Manual Backprop)** by implementing the new `Tensor` and `Autograd` system outlined in `implementation_plan.md`. diff --git a/python/aether_lang/__init__.py b/bindings/python/aether_lang/__init__.py similarity index 100% rename from python/aether_lang/__init__.py rename to bindings/python/aether_lang/__init__.py diff --git a/aegis-cli/Cargo.toml b/crates/aegis-cli/Cargo.toml similarity index 95% rename from aegis-cli/Cargo.toml rename to crates/aegis-cli/Cargo.toml index 902810c..aeb941a 100644 --- a/aegis-cli/Cargo.toml +++ b/crates/aegis-cli/Cargo.toml @@ -28,7 +28,7 @@ default = [] [dependencies] aegis-core = { path = "../aegis-core" } -aegis-lang = { path = "../aegis-lang" } +aegis-lang = { path = "../aether-lang", package = "aether-lang" } clap = { workspace = true } rustyline = { workspace = true } diff --git a/aegis-cli/src/main.rs b/crates/aegis-cli/src/main.rs similarity index 86% rename from aegis-cli/src/main.rs rename to crates/aegis-cli/src/main.rs index aa9255f..23bc65d 100644 --- a/aegis-cli/src/main.rs +++ b/crates/aegis-cli/src/main.rs @@ -11,6 +11,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + use clap::{Parser as ClapParser, Subcommand}; use rustyline::error::ReadlineError; use rustyline::DefaultEditor; @@ -58,15 +65,17 @@ fn main() { // Spawn a thread with 8MB stack to prevent overflow. let builder = std::thread::Builder::new().stack_size(8 * 1024 * 1024); - let handler = builder.spawn(|| { - let cli = Cli::parse(); + let handler = builder + .spawn(|| { + let cli = Cli::parse(); - match cli.command { - Some(Commands::Repl) | None => run_repl(), - Some(Commands::Run { file, mode }) => run_file(&file, &mode), - Some(Commands::Check { file }) => check_file(&file), - } - }).unwrap(); + match cli.command { + Some(Commands::Repl) | None => run_repl(), + Some(Commands::Run { file, mode }) => run_file(&file, &mode), + Some(Commands::Check { file }) => check_file(&file), + } + }) + .unwrap(); handler.join().unwrap(); } @@ -166,7 +175,10 @@ fn run_file(path: &PathBuf, mode: &str) { if let Some(ext) = path.extension() { let s = ext.to_string_lossy(); if s != "aegis" && s != "ag" { - println!("Warning: File extension '.{}' is not standard (.aegis or .ag)", s); + println!( + "Warning: File extension '.{}' is not standard (.aegis or .ag)", + s + ); } } @@ -181,14 +193,14 @@ fn run_file(path: &PathBuf, mode: &str) { }; if mode == "titan" { - use aegis_lang::vm::{TitanVM, Compiler}; + use aegis_lang::vm::{Compiler, TitanVM}; // Compile to Bytecode let compiler = Compiler::new(); let code = compiler.compile(&ast); - + let mut vm = TitanVM::new(); vm.load_code(code); - + match vm.run() { Ok(result) => { println!("{:?}", result); diff --git a/aegis-core/Cargo.toml b/crates/aegis-core/Cargo.toml similarity index 100% rename from aegis-core/Cargo.toml rename to crates/aegis-core/Cargo.toml diff --git a/crates/aegis-core/src/lib.rs b/crates/aegis-core/src/lib.rs new file mode 100644 index 0000000..68d71b1 --- /dev/null +++ b/crates/aegis-core/src/lib.rs @@ -0,0 +1,14 @@ +#![no_std] + +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + +extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + +pub mod memory; diff --git a/aegis-core/src/memory.rs b/crates/aegis-core/src/memory.rs similarity index 80% rename from aegis-core/src/memory.rs rename to crates/aegis-core/src/memory.rs index b57800f..be7f108 100644 --- a/aegis-core/src/memory.rs +++ b/crates/aegis-core/src/memory.rs @@ -10,12 +10,19 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #[cfg(feature = "std")] use std::thread; -use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use core::cell::UnsafeCell; use core::marker::PhantomData; +use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; /// The number of concurrent "Time Dimensions" (Shards) in the clock. /// 32 Shards ensures minimal contention even on high-core-count Titan machines. @@ -65,15 +72,20 @@ impl TitanClock { pub fn new() -> Self { // Assert SIZE is divisible by SHARDS for simplicity - assert!(SIZE % SHARDS == 0, "Manifold SIZE must be divisible by 32 (SHARDS)"); + assert!( + SIZE % SHARDS == 0, + "Manifold SIZE must be divisible by 32 (SHARDS)" + ); Self { manifold: core::array::from_fn(|_| Slot::new()), - hands: core::array::from_fn(|_| TimeHand { index: AtomicUsize::new(0) }), + hands: core::array::from_fn(|_| TimeHand { + index: AtomicUsize::new(0), + }), _marker: PhantomData, } } - + /// Reserve a slot using the clock algorithm. /// Returns the index of a metabolically available slot. fn reserve_slot(&self) -> usize { @@ -81,15 +93,15 @@ impl TitanClock { // For portable no_std, we can use a relaxed global counter or just start at 0. // To reduce contention, let's just create a pseudo-random start based on the stack pointer or similar? // Or just iterate efficiently. - let start_shard = 0; - + let start_shard = 0; + for i in 0..SHARDS { - let shard_id = (start_shard + i) % SHARDS; - if let Some(idx) = self.try_reserve_in_shard(shard_id) { - return idx; - } + let shard_id = (start_shard + i) % SHARDS; + if let Some(idx) = self.try_reserve_in_shard(shard_id) { + return idx; + } } - + // If all shards are saturated (entropy storm), we force the "Big Bang" (overwrite) in shard 0. self.force_reserve_in_shard(0) } @@ -102,7 +114,7 @@ impl TitanClock { // Limit search to 2 revolutions (Second Chance Algorithm requirement) let limit = Self::STACK_SIZE * 2; - + for _ in 0..limit { // Atomic increment of the hand let local_idx = hand.index.fetch_add(1, Ordering::Relaxed) % Self::STACK_SIZE; @@ -112,19 +124,19 @@ impl TitanClock { // Bio-Clock Logic: // If Energy=1 (Hot) -> Set Energy=0 (Cold) and Continue. // If Energy=0 (Cold) -> Claim it. - + // We use compare_exchange to be pedantic, but specialized Load/Store is fine for heuristic. // If we see Hot, make it Cold. if slot.energy.load(Ordering::Acquire) { - slot.energy.store(false, Ordering::Release); - // We don't take it. We give it a second chance. + slot.energy.store(false, Ordering::Release); + // We don't take it. We give it a second chance. } else { - // It's cold. We take it. - // Ideally we should CAS a "claiming" bit to ensure unique ownership in race. - // But for this "Bio" memory, Last-Writer-Wins on the same slot is acceptable noise - // provided we don't drop live data. - // Since it was cold, it deemed dead. - return Some(global_idx); + // It's cold. We take it. + // Ideally we should CAS a "claiming" bit to ensure unique ownership in race. + // But for this "Bio" memory, Last-Writer-Wins on the same slot is acceptable noise + // provided we don't drop live data. + // Since it was cold, it deemed dead. + return Some(global_idx); } } None @@ -136,33 +148,35 @@ impl TitanClock { let local_idx = hand.index.fetch_add(1, Ordering::Relaxed) % Self::STACK_SIZE; shard_id * Self::STACK_SIZE + local_idx } - + /// Public Allocator API /// O(1) amortized. Lock-Free. pub fn alloc(&self, item: T) -> usize { let idx = self.reserve_slot(); let slot = &self.manifold[idx]; - + unsafe { - // Drop old data if present (metabolism) - // *slot.data.get() = None; // redundant if we overwrite immediately - *slot.data.get() = Some(item); + // Drop old data if present (metabolism) + // *slot.data.get() = None; // redundant if we overwrite immediately + *slot.data.get() = Some(item); } - + // Spark of Life slot.energy.store(true, Ordering::Release); - + idx } - + /// Access data. Energizes the slot (refreshes the bit). pub fn access(&self, index: usize) -> Option<&T> { - if index >= SIZE { return None; } - + if index >= SIZE { + return None; + } + let slot = &self.manifold[index]; // Bio-Feedback: Reading the memory strengthens its synapse slot.energy.store(true, Ordering::Relaxed); - + unsafe { (*slot.data.get()).as_ref() } } } @@ -176,7 +190,7 @@ mod tests { use super::*; use std::sync::Arc; use std::thread; - + #[test] fn test_titan_genesis() { // 32 Shards * 2 = 64 slots @@ -185,36 +199,36 @@ mod tests { assert!(idx < 64); assert_eq!(*clock.access(idx).unwrap(), 42); } - + #[test] fn test_shard_saturation() { // 32 shards * 1 slot each = 32 slots total. - let clock: TitanClock = TitanClock::new(); - + let clock: TitanClock = TitanClock::new(); + // Fill everything for i in 0..32 { clock.alloc(i); } - + // Access everything to make it HOT for i in 0..32 { clock.access(i); } - + // Now Alloc 33. // It must scan, turn something cold, and eventualy overwrite. let idx_new = clock.alloc(100); - + assert_eq!(*clock.access(idx_new).unwrap(), 100); } - + /* #[test] fn test_multithreaded_stress() { let clock = Arc::new(TitanClock::::new()); // 32 slots per shard - + let mut handles: Vec> = Vec::new(); // Use Vec::new() - + // Spawn 10 Titan Threads for t in 0..10 { let c = clock.clone(); @@ -226,11 +240,11 @@ mod tests { } })); } - + for h in handles { h.join().unwrap(); } - + // Verify manifold integrity // Just checking we can read index 0 without panic assert!(clock.access(0).is_some() || clock.access(0).is_none()); diff --git a/aegis-core/src/ml/autograd.rs b/crates/aegis-core/src/ml/autograd.rs similarity index 93% rename from aegis-core/src/ml/autograd.rs rename to crates/aegis-core/src/ml/autograd.rs index b3b1c4b..97f9d77 100644 --- a/aegis-core/src/ml/autograd.rs +++ b/crates/aegis-core/src/ml/autograd.rs @@ -10,6 +10,14 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + + #[cfg(not(feature = "std"))] use alloc::vec::Vec; #[cfg(feature = "std")] diff --git a/crates/aegis-core/test_results.txt b/crates/aegis-core/test_results.txt new file mode 100644 index 0000000..0170024 Binary files /dev/null and b/crates/aegis-core/test_results.txt differ diff --git a/aether-cli/Cargo.toml b/crates/aether-cli/Cargo.toml similarity index 100% rename from aether-cli/Cargo.toml rename to crates/aether-cli/Cargo.toml diff --git a/aether-cli/src/main.rs b/crates/aether-cli/src/main.rs similarity index 79% rename from aether-cli/src/main.rs rename to crates/aether-cli/src/main.rs index 751f588..4eebd62 100644 --- a/aether-cli/src/main.rs +++ b/crates/aether-cli/src/main.rs @@ -11,12 +11,20 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + use clap::{Parser as ClapParser, Subcommand}; use rustyline::error::ReadlineError; use rustyline::DefaultEditor; use std::fs; use std::path::PathBuf; +use aether_lang::parser::ParseError; use aether_lang::{Interpreter, Parser}; /// AEGIS - The Universal Programming Language @@ -58,15 +66,17 @@ fn main() { // Spawn a thread with 8MB stack to prevent overflow. let builder = std::thread::Builder::new().stack_size(8 * 1024 * 1024); - let handler = builder.spawn(|| { - let cli = Cli::parse(); + let handler = builder + .spawn(|| { + let cli = Cli::parse(); - match cli.command { - Some(Commands::Repl) | None => run_repl(), - Some(Commands::Run { file, mode }) => run_file(&file, &mode), - Some(Commands::Check { file }) => check_file(&file), - } - }).unwrap(); + match cli.command { + Some(Commands::Repl) | None => run_repl(), + Some(Commands::Run { file, mode }) => run_file(&file, &mode), + Some(Commands::Check { file }) => check_file(&file), + } + }) + .unwrap(); handler.join().unwrap(); } @@ -132,13 +142,18 @@ fn run_repl() { } } +fn format_parse_error(error: &ParseError) -> String { + format!( + "Parse error at line {}, column {}: {}", + error.line, error.column, error.message + ) +} + /// Execute a single line in the REPL fn execute_line(interpreter: &mut Interpreter, source: &str) -> Result { // Parser internally creates a lexer and tokenizes let mut parser = Parser::new(source); - let ast = parser - .parse() - .map_err(|e| format!("Parse error: {:?}", e))?; + let ast = parser.parse().map_err(|e| format_parse_error(&e))?; // Execute and format result let value = interpreter @@ -166,7 +181,10 @@ fn run_file(path: &PathBuf, mode: &str) { if let Some(ext) = path.extension() { let s = ext.to_string_lossy(); if s != "aether" && s != "ae" { - println!("Warning: File extension '.{}' is not standard (.aether or .ae)", s); + println!( + "Warning: File extension '.{}' is not standard (.aether or .ae)", + s + ); } } @@ -175,20 +193,20 @@ fn run_file(path: &PathBuf, mode: &str) { let ast = match parser.parse() { Ok(a) => a, Err(e) => { - eprintln!("Parse error: {:?}", e); + eprintln!("{}", format_parse_error(&e)); std::process::exit(1); } }; if mode == "titan" { - use aether_lang::vm::{TitanVM, Compiler}; + use aether_lang::vm::{Compiler, TitanVM}; // Compile to Bytecode let compiler = Compiler::new(); let code = compiler.compile(&ast); - + let mut vm = TitanVM::new(); vm.load_code(code); - + match vm.run() { Ok(result) => { println!("{:?}", result); @@ -236,8 +254,24 @@ fn check_file(path: &PathBuf) { println!("✓ Syntax OK"); } Err(e) => { - eprintln!("❌ Parse error: {:?}", e); + eprintln!("❌ {}", format_parse_error(&e)); std::process::exit(1); } } } + +#[cfg(test)] +mod tests { + use super::*; + use aether_lang::parser::ParseError; + + #[test] + fn formats_parse_error_with_line_and_column() { + let error = ParseError::new("expected identifier, found =", 2, 7); + + assert_eq!( + format_parse_error(&error), + "Parse error at line 2, column 7: expected identifier, found =" + ); + } +} diff --git a/aether-core/Cargo.toml b/crates/aether-core/Cargo.toml similarity index 100% rename from aether-core/Cargo.toml rename to crates/aether-core/Cargo.toml diff --git a/crates/aether-core/output.txt b/crates/aether-core/output.txt new file mode 100644 index 0000000..2fac515 --- /dev/null +++ b/crates/aether-core/output.txt @@ -0,0 +1,209 @@ +cargo : warning: unused +import: `alloc::vec` +At line:1 char:1 ++ cargo test --test +test_autograd 2>&1 | +Out-File -Encoding utf8 +output ... ++ ~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~ + + CategoryInfo + : NotSpecified: (w + arning: unused import + : `alloc::vec`:String +) [], RemoteException + + FullyQualifiedError + Id : NativeCommandErr + or + + --> aegis-core\src\ml\li +nalg.rs:13:5 + | +13 | use alloc::vec; + | ^^^^^^^^^^ + | + = note: +`#[warn(unused_imports)]` +(part of +`#[warn(unused)]`) on by +default + +warning: unused import: +`alloc::vec::Vec` + --> aegis-core\src\ml\li +nalg.rs:15:5 + | +15 | use alloc::vec::Vec; + | ^^^^^^^^^^^^^^^ + +warning: unused import: +`exp` + --> aegis-core\src\ml\te +nsor.rs:24:12 + | +24 | use libm::{exp, sq... + | ^^^ + +warning: unused import: +`alloc::vec` + --> aegis-core\src\ml\ne +ural.rs:13:5 + | +13 | use alloc::vec; + | ^^^^^^^^^^ + +warning: unused import: +`alloc::boxed::Box` + --> aegis-core\src\ml\ne +ural.rs:17:5 + | +17 | use +alloc::boxed::Box; + | ^^^^^^^^^^^^^^^^^ + +warning: variable does +not need to be mutable + --> aegis-core\src\ml\l +inalg.rs:112:9 + | +112 | ...et mut x_plus = +... + | ----^^^^^^ + | | + | help: remove +this `mut` + | + = note: +`#[warn(unused_mut)]` +(part of +`#[warn(unused)]`) on by +default + +warning: variable does +not need to be mutable + --> aegis-core\src\ml\l +inalg.rs:113:9 + | +113 | ...et mut x_minus = +... + | ----^^^^^^^ + | | + | help: remove +this `mut` + +warning: variable does +not need to be mutable + --> aegis-core\src\ml\l +inalg.rs:116:13 + | +116 | ...et mut xp_data = +... + | ----^^^^^^^ + | | + | help: remove +this `mut` + +warning: variable does +not need to be mutable + --> aegis-core\src\ml\l +inalg.rs:117:13 + | +117 | ...et mut xm_data = +... + | ----^^^^^^^ + | | + | help: remove +this `mut` + +warning: variable does +not need to be mutable + --> aegis-core\src\ml\t +ensor.rs:137:13 + | +137 | ...et mut result = +... + | ----^^^^^^ + | | + | help: remove +this `mut` + +warning: variable does +not need to be mutable + --> aegis-core\src\ml\t +ensor.rs:209:13 + | +209 | ...et mut result = +... + | ----^^^^^^ + | | + | help: remove +this `mut` + +warning: multiple +associated constants are +never used + --> +aegis-core\src\os.rs:95:11 + | + 91 | impl PageTableEntry +{ + | ------------------- +associated constants in +this implementation +... + 95 | const +USER_ACCESSIBLE: u... + | +^^^^^^^^^^^^^^^ + 96 | const +WRITE_THROUGH: u64... + | +^^^^^^^^^^^^^ + 97 | const NO_CACHE: +u64 = 1 ... + | ^^^^^^^^ + 98 | const ACCESSED: +u64 = 1 ... + | ^^^^^^^^ + 99 | const DIRTY: +u64 = 1 << 6; + | ^^^^^ +100 | const +HUGE_PAGE: u64 = 1... + | ^^^^^^^^^ +101 | const GLOBAL: +u64 = 1 << 8; + | ^^^^^^ +102 | const +NO_EXECUTE: u64 = ... + | ^^^^^^^^^^ + | + = note: +`#[warn(dead_code)]` +(part of +`#[warn(unused)]`) on by +default + +warning: `aegis-core` +(lib) generated 12 +warnings (run `cargo fix +--lib -p aegis-core` to +apply 11 suggestions) + Finished `test` +profile [optimized + +debuginfo] target(s) in +0.08s + Running +tests\test_autograd.rs (C: +\Users\teert\OneDrive\Desk +top\New folder (11)\aegis\ +target\debug\deps\test_aut +ograd-bba3fac316c9342f.exe +) + +running 2 tests +test test_autograd_simple ... ok +test test_autograd_matmul ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + diff --git a/aether-core/src/aether.rs b/crates/aether-core/src/aether.rs similarity index 95% rename from aether-core/src/aether.rs rename to crates/aether-core/src/aether.rs index dd9761c..c19c72b 100644 --- a/aether-core/src/aether.rs +++ b/crates/aether-core/src/aether.rs @@ -20,6 +20,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![allow(dead_code)] use libm::sqrt; @@ -304,8 +311,10 @@ impl HierarchicalBlockTree { let mut active_l1 = [false; MAX_BLOCKS]; for (i, active) in active_l1.iter_mut().enumerate().take(self.counts[1]) { let parent = i / 4; - if parent < self.counts[2] && active_l2[parent] - && !self.levels[1][i].can_prune(query, threshold) { + if parent < self.counts[2] + && active_l2[parent] + && !self.levels[1][i].can_prune(query, threshold) + { *active = true; } } @@ -313,8 +322,10 @@ impl HierarchicalBlockTree { // Level 0 (finest) - final result for (i, res) in result.iter_mut().enumerate().take(self.counts[0]) { let parent = i / 4; - if parent < self.counts[1] && active_l1[parent] - && !self.levels[0][i].can_prune(query, threshold) { + if parent < self.counts[1] + && active_l1[parent] + && !self.levels[0][i].can_prune(query, threshold) + { *res = true; } } diff --git a/aether-core/src/governor.rs b/crates/aether-core/src/governor.rs similarity index 94% rename from aether-core/src/governor.rs rename to crates/aether-core/src/governor.rs index 4b2a6c5..8f258b7 100644 --- a/aether-core/src/governor.rs +++ b/crates/aether-core/src/governor.rs @@ -19,6 +19,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![allow(dead_code)] // use libm::fabs; @@ -202,7 +209,7 @@ impl GeometricGovernor { // Step 5: Update State // ═══════════════════════════════════════════════════════════════════ - self.epsilon += adjustment; + self.epsilon -= adjustment; self.last_error = error; self.adjustment_count += 1; diff --git a/aether-core/src/lib.rs b/crates/aether-core/src/lib.rs similarity index 72% rename from aether-core/src/lib.rs rename to crates/aether-core/src/lib.rs index 5b32098..64e51fe 100644 --- a/aether-core/src/lib.rs +++ b/crates/aether-core/src/lib.rs @@ -13,6 +13,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![cfg_attr(not(feature = "std"), no_std)] #[cfg(feature = "alloc")] @@ -25,15 +32,20 @@ extern crate alloc; pub mod aether; pub mod governor; pub mod manifold; +pub mod memory; pub mod ml; -pub mod state; pub mod os; +pub mod persistence; +pub mod state; pub mod topology; -pub mod memory; // Re-export key types for convenience pub use aether::{BlockMetadata, DriftDetector, HierarchicalBlockTree}; pub use manifold::{ManifoldPoint, SparseAttentionGraph, TimeDelayEmbedder, TopologicalPipeline}; +pub use persistence::{ + persistent_homology, time_delay_persistence, BettiNumbers3, ComplexKind, PersistenceConfig, + PersistenceDiagram, PersistenceError, PersistencePair, +}; pub use topology::{ compute_betti_0, compute_betti_1, compute_shape, verify_shape, TopologicalShape, VerifyResult, }; diff --git a/aether-core/src/manifold.rs b/crates/aether-core/src/manifold.rs similarity index 91% rename from aether-core/src/manifold.rs rename to crates/aether-core/src/manifold.rs index 484a0da..03f694c 100644 --- a/aether-core/src/manifold.rs +++ b/crates/aether-core/src/manifold.rs @@ -17,6 +17,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![allow(dead_code)] use libm::sqrt; @@ -68,7 +75,21 @@ impl ManifoldPoint { /// Check if within epsilon-neighborhood (sparse attention criterion) pub fn is_neighbor(&self, other: &Self, epsilon: f64) -> bool { - self.distance(other) < epsilon + // Handle negative or NaN thresholds explicitly + if !(epsilon > 0.0) { + return false; + } + let eps_sq = epsilon * epsilon; + let mut sum = 0.0; + for i in 0..D { + let d = self.coords[i] - other.coords[i]; + sum += d * d; + // Early exit using NaN-safe comparison + if !(sum < eps_sq) { + return false; + } + } + true } } @@ -246,7 +267,9 @@ impl SparseAttentionGraph { visited[current] = true; // Add unvisited neighbors - for (neighbor, is_visited) in visited.iter().enumerate().take(64.min(self.point_count)) { + for (neighbor, is_visited) in + visited.iter().enumerate().take(64.min(self.point_count)) + { if !*is_visited && self.are_neighbors(current, neighbor) && stack_top < 64 { stack[stack_top] = neighbor; stack_top += 1; @@ -290,7 +313,10 @@ impl SparseAttentionGraph { /// /// This approximates a "local convex hull" by traversing the sparse graph (BFS) /// for a limited depth, effectively partitioning the manifold geodesically. - pub fn geodesic_partition_centroid(&self, target: ManifoldPoint) -> Option> { + pub fn geodesic_partition_centroid( + &self, + target: ManifoldPoint, + ) -> Option> { if self.point_count == 0 { return None; } @@ -298,11 +324,11 @@ impl SparseAttentionGraph { // 1. Find the graph node closest to the target point (entry point) // Since `target` might be the one just added, it's likely the last one. // But let's be robust and check the last few points. - let start_node_idx = self.point_count - 1; + let start_node_idx = self.point_count - 1; // 2. BFS to find the local cluster (Geodesic Neighborhood) // We limit depth to capture "local" structure, not the whole component - let max_depth = 3; + let max_depth = 3; let mut visited = [false; MAX_POINTS]; let mut queue = [0usize; 64]; let mut queue_start = 0; @@ -333,15 +359,16 @@ impl SparseAttentionGraph { // Expand neighbors if depth limit not reached if current_depth < max_depth { // Adjacency bitmask iteration - let adjacency = self.adjacency[u]; - // Note: Adjacency is symmetric but stored sparsely? - // In our `add_point`, we set bits for i < 64. + let adjacency = self.adjacency[u]; + // Note: Adjacency is symmetric but stored sparsely? + // In our `add_point`, we set bits for i < 64. // Let's assume simpler iteration for this limited embedded interaction. - // We iterate all points to check `are_neighbors` because internal representation + // We iterate all points to check `are_neighbors` because internal representation // in original code was slightly simplified (only stored back-edges in `adjacency`?). // Let's rely on `are_neighbors` which is robust in the provided code. - - for v in 0..self.point_count.min(64) { // Limit to 64 for speed/bitmask strictness + + for v in 0..self.point_count.min(64) { + // Limit to 64 for speed/bitmask strictness if !visited[v] && self.are_neighbors(u, v) { visited[v] = true; if queue_end < 64 { @@ -352,7 +379,7 @@ impl SparseAttentionGraph { } } } - + if nodes_at_current_depth == 0 { current_depth += 1; nodes_at_current_depth = nodes_at_next_depth; @@ -542,7 +569,7 @@ impl TopologicalPipeline { fn map_to_tpu_id(&self, point: &ManifoldPoint, projection: f64) -> u64 { // Synthetic Spatial Hashing (Morton-like) let mut hash = 0u64; - + // Hash the input coordinates for i in 0..D { let bits = point.coords[i].to_bits(); @@ -629,17 +656,17 @@ mod tests { #[test] fn test_gatekeeper_sparsity() { let mut pipeline = TopologicalPipeline::<3>::new(1, 0.5); - + // Push zero value - should be dropped by Sparsity Filter assert!(pipeline.push(0.0).is_none()); assert!(pipeline.push(1e-10).is_none()); - + // Push significant value - should be processed // Need to fill buffer first (tau=1, D=3 -> needs 3 points) pipeline.push(1.0); pipeline.push(2.0); pipeline.push(3.0); - + // Now it should return consistent output let result = pipeline.push(4.0); assert!(result.is_some()); @@ -648,28 +675,31 @@ mod tests { #[test] fn test_gatekeeper_tpu_injection() { let mut pipeline = TopologicalPipeline::<3>::new(1, 0.5); - + // Fill buffer pipeline.push(1.0); pipeline.push(2.0); pipeline.push(3.0); - + if let Some((_, _, tpu_id_1)) = pipeline.push(4.0) { - // Push same sequence again (reset logic simulated) - let mut pipeline2 = TopologicalPipeline::<3>::new(1, 0.5); - pipeline2.push(1.0); - pipeline2.push(2.0); - pipeline2.push(3.0); - let (_, _, tpu_id_2) = pipeline2.push(4.0).unwrap(); - - assert_eq!(tpu_id_1, tpu_id_2, "TPU ID generation must be deterministic"); + // Push same sequence again (reset logic simulated) + let mut pipeline2 = TopologicalPipeline::<3>::new(1, 0.5); + pipeline2.push(1.0); + pipeline2.push(2.0); + pipeline2.push(3.0); + let (_, _, tpu_id_2) = pipeline2.push(4.0).unwrap(); + + assert_eq!( + tpu_id_1, tpu_id_2, + "TPU ID generation must be deterministic" + ); } } #[test] fn test_gatekeeper_branching() { let mut pipeline = TopologicalPipeline::<3>::new(1, 2.0); // large epsilon to force connection - + // 1. Simple shape (Line) -> Betti-1 = 0 for i in 0..10 { pipeline.push(i as f64); @@ -677,7 +707,7 @@ mod tests { let (b0, b1, _) = pipeline.push(10.0).unwrap(); assert_eq!(b0, 1); assert_eq!(b1, 0); // Linear structure has no holes - + // 2. Complex Shape (Cycle) -> Betti-1 > 0 pipeline.reset(); // Create a triangle loop: (0,0,0) -> (1,0,0) -> (0.5,1,0) -> (0,0,0) around time delay @@ -685,11 +715,14 @@ mod tests { // A simple sine wave often creates loops in delay embedding for i in 0..50 { let val = libm::sin(i as f64 * 0.5); - pipeline.push(val); + pipeline.push(val); } - + let (_, b1_complex, _) = pipeline.push(0.1).unwrap(); // Sine wave in 2D/3D embedding is a loop (circle) - assert!(b1_complex >= 1, "Sine wave should create a cycle (Betti-1 >= 1)"); + assert!( + b1_complex >= 1, + "Sine wave should create a cycle (Betti-1 >= 1)" + ); } } diff --git a/aether-core/src/memory.rs b/crates/aether-core/src/memory.rs similarity index 67% rename from aether-core/src/memory.rs rename to crates/aether-core/src/memory.rs index e11cb11..7c790d7 100644 --- a/aether-core/src/memory.rs +++ b/crates/aether-core/src/memory.rs @@ -14,22 +14,29 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #[cfg(not(feature = "std"))] use alloc::boxed::Box; -#[cfg(feature = "std")] -use std::vec::Vec; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; #[cfg(feature = "std")] use std::boxed::Box; +#[cfg(feature = "std")] +use std::vec::Vec; -use libm::{sqrt, fabs}; use core::marker::PhantomData; +use libm::{fabs, sqrt}; /// A Geometric Cell (Gc) handle. /// Represents a reference to an object in the ManifoldHeap. /// Unlike standard pointers, this is a topological index. -/// +/// /// We implement Copy/Clone manually to avoid implicit T: Copy bound. #[derive(Debug, PartialOrd, Ord)] pub struct Gc { @@ -107,10 +114,30 @@ impl Default for SpatialBlock { Self { liveness: [0.0; 8], slots: [ - HeapSlot::Free { next_free: usize::MAX }, HeapSlot::Free { next_free: usize::MAX }, - HeapSlot::Free { next_free: usize::MAX }, HeapSlot::Free { next_free: usize::MAX }, - HeapSlot::Free { next_free: usize::MAX }, HeapSlot::Free { next_free: usize::MAX }, - HeapSlot::Free { next_free: usize::MAX }, HeapSlot::Free { next_free: usize::MAX }, + HeapSlot::Free { + next_free: usize::MAX, + }, + HeapSlot::Free { + next_free: usize::MAX, + }, + HeapSlot::Free { + next_free: usize::MAX, + }, + HeapSlot::Free { + next_free: usize::MAX, + }, + HeapSlot::Free { + next_free: usize::MAX, + }, + HeapSlot::Free { + next_free: usize::MAX, + }, + HeapSlot::Free { + next_free: usize::MAX, + }, + HeapSlot::Free { + next_free: usize::MAX, + }, ], occupied_mask: 0, } @@ -127,17 +154,17 @@ impl SpatialBlock { /// Aggregates statistics of its children. #[derive(Debug, Clone)] pub struct SpatialNode { - /// Indices of children. + /// Indices of children. /// If `is_leaf_parent` is true, these are indices into `blocks`. /// Otherwise, indices into `nodes`. /// None indicates empty branch. pub children: [Option; 8], - + /// Aggregate Mean Liveness of this branch pub mean_liveness: f64, /// Max Liveness in this branch (for quick "is hot" checks) pub max_liveness: f64, - + /// Does this node point to Blocks (true) or Nodes (false)? pub is_leaf_parent: bool, } @@ -153,7 +180,6 @@ impl SpatialNode { } } - /// Configuration for Memory Behavior #[derive(Debug, Clone, Copy)] pub enum MemoryMode { @@ -168,7 +194,9 @@ pub struct Config { impl Default for Config { fn default() -> Self { - Self { mode: MemoryMode::Consumer } + Self { + mode: MemoryMode::Consumer, + } } } @@ -181,16 +209,16 @@ pub struct ManifoldHeap { pub nodes: Vec, /// Root Node Index pub root_idx: usize, - + /// Head of the free list (Global index) /// Index = block_idx * 8 + slot_idx free_head: Option, - + /// Active objects count active_count: usize, /// Global entropy counter entropy_counter: usize, - + pub config: Config, } @@ -206,10 +234,10 @@ impl ManifoldHeap { config: Config::default(), }; // Initialize with one root node that is a leaf parent - heap.nodes.push(SpatialNode::new(true)); + heap.nodes.push(SpatialNode::new(true)); heap } - + /// Helper to decompose global index into (block, offset) fn resolve_index(index: usize) -> (usize, usize) { (index / 8, index % 8) @@ -218,20 +246,20 @@ impl ManifoldHeap { /// Allocate a new object. pub fn alloc(&mut self, data: T) -> Gc { self.entropy_counter += 1; - + let (block_idx, slot_idx) = if let Some(head) = self.free_head { let (b, s) = Self::resolve_index(head); // Verify and update free_head if b < self.blocks.len() { - if let HeapSlot::Free { next_free } = &self.blocks[b].slots[s] { - if *next_free == usize::MAX { - self.free_head = None; - } else { - self.free_head = Some(*next_free); - } - } else { - panic!("Free head pointed to occupied slot"); - } + if let HeapSlot::Free { next_free } = &self.blocks[b].slots[s] { + if *next_free == usize::MAX { + self.free_head = None; + } else { + self.free_head = Some(*next_free); + } + } else { + panic!("Free head pointed to occupied slot"); + } } (b, s) } else { @@ -239,19 +267,21 @@ impl ManifoldHeap { let next_blk_idx = self.blocks.len(); self.blocks.push(SpatialBlock::new()); self.link_block_to_tree(next_blk_idx); - + for i in 1..7 { - self.blocks[next_blk_idx].slots[i] = HeapSlot::Free { - next_free: next_blk_idx * 8 + i + 1 - }; + self.blocks[next_blk_idx].slots[i] = HeapSlot::Free { + next_free: next_blk_idx * 8 + i + 1, + }; } - self.blocks[next_blk_idx].slots[7] = HeapSlot::Free { next_free: usize::MAX }; - + self.blocks[next_blk_idx].slots[7] = HeapSlot::Free { + next_free: usize::MAX, + }; + self.free_head = Some(next_blk_idx * 8 + 1); (next_blk_idx, 0) }; - - let generation = 1; + + let generation = 1; self.blocks[block_idx].slots[slot_idx] = HeapSlot::Occupied { header: ObjectHeader { marked: false, @@ -259,20 +289,20 @@ impl ManifoldHeap { }, data, }; - self.blocks[block_idx].liveness[slot_idx] = 1.0; + self.blocks[block_idx].liveness[slot_idx] = 1.0; self.blocks[block_idx].occupied_mask |= 1 << slot_idx; - + self.active_count += 1; - + Gc::new(block_idx * 8 + slot_idx, generation) } - + fn link_block_to_tree(&mut self, block_idx: usize) { let needed_node_idx = block_idx / 8; if needed_node_idx >= self.nodes.len() { - self.nodes.push(SpatialNode::new(true)); + self.nodes.push(SpatialNode::new(true)); } - + let node_idx = needed_node_idx; let child_slot = block_idx % 8; self.nodes[node_idx].children[child_slot] = Some(block_idx); @@ -281,13 +311,17 @@ impl ManifoldHeap { /// Access mutably. Heats up object. pub fn get_mut(&mut self, handle: Gc) -> Option<&mut T> { let (b, s) = Self::resolve_index(handle.index); - - if b >= self.blocks.len() { return None; } - + + if b >= self.blocks.len() { + return None; + } + let block = &mut self.blocks[b]; match &mut block.slots[s] { HeapSlot::Occupied { header, data } => { - if header.generation != handle.generation { return None; } + if header.generation != handle.generation { + return None; + } // Heat up - split borrow of block works here block.liveness[s] = (block.liveness[s] + 1.0).min(10.0); Some(data) @@ -295,45 +329,49 @@ impl ManifoldHeap { _ => None, } } - + /// Access immutably (Peek). Does NOT update liveness to avoid &mut borrow. /// This fixes autograd multiple borrow issues. pub fn get(&self, handle: Gc) -> Option<&T> { let (b, s) = Self::resolve_index(handle.index); - if b >= self.blocks.len() { return None; } + if b >= self.blocks.len() { + return None; + } match &self.blocks[b].slots[s] { HeapSlot::Occupied { header, data } => { - if header.generation != handle.generation { return None; } + if header.generation != handle.generation { + return None; + } Some(data) } _ => None, } } - + pub fn touch(&mut self, handle: Gc) { let (b, s) = Self::resolve_index(handle.index); if b < self.blocks.len() { let block = &mut self.blocks[b]; if let HeapSlot::Occupied { header, .. } = &mut block.slots[s] { - if header.generation == handle.generation { - block.liveness[s] = (block.liveness[s] + 0.5).min(10.0); - } + if header.generation == handle.generation { + block.liveness[s] = (block.liveness[s] + 0.5).min(10.0); + } } } } - + pub fn mark(&mut self, handle: Gc) { let (b, s) = Self::resolve_index(handle.index); - if b < self.blocks.len() { - let block = &mut self.blocks[b]; - if let HeapSlot::Occupied { header, .. } = &mut block.slots[s] { - if header.generation == handle.generation { - header.marked = true; - block.liveness[s] = (block.liveness[s] + 2.0).min(10.0); - } - } - } + if b < self.blocks.len() { + let block = &mut self.blocks[b]; + if let HeapSlot::Occupied { header, .. } = &mut block.slots[s] { + if header.generation == handle.generation { + header.marked = true; + block.liveness[s] = (block.liveness[s] + 2.0).min(10.0); + } + } + } } pub fn active_count(&self) -> usize { @@ -355,44 +393,48 @@ pub struct ChebyshevGuard { impl ChebyshevGuard { pub fn calculate(heap: &ManifoldHeap) -> Self { let mut sum = 0.0; + let mut sum_sq = 0.0; let mut count = 0.0; - + for block in &heap.blocks { - for i in 0..8 { - if (block.occupied_mask & (1 << i)) != 0 { - sum += block.liveness[i]; - count += 1.0; - } - } + // Optimization: skip empty blocks early + if block.occupied_mask == 0 { + continue; + } + + for i in 0..8 { + if (block.occupied_mask & (1 << i)) != 0 { + let val = block.liveness[i]; + sum += val; + sum_sq += val * val; + count += 1.0; + } + } } - - if count == 0.0 { - return Self { mean: 0.0, std_dev: 0.0, k: 2.0 }; + + if count == 0.0 { + return Self { + mean: 0.0, + std_dev: 0.0, + k: 2.0, + }; } let mean = sum / count; - - let mut sum_diff_sq = 0.0; - for block in &heap.blocks { - for i in 0..8 { - if (block.occupied_mask & (1 << i)) != 0 { - let diff = block.liveness[i] - mean; - sum_diff_sq += diff * diff; - } - } - } - - let variance = sum_diff_sq / count; - + let variance = (sum_sq / count) - (mean * mean); + let variance = if variance < 0.0 { 0.0 } else { variance }; + Self { mean, std_dev: sqrt(variance), k: 2.0, } } - + pub fn is_safe(&self, liveness: f64) -> bool { - if liveness >= self.mean { return true; } + if liveness >= self.mean { + return true; + } let boundary = self.mean - (self.k * self.std_dev); liveness > boundary } @@ -400,8 +442,9 @@ impl ChebyshevGuard { impl ManifoldHeap { /// Regulation with Spatial Optimization - pub fn regulate_entropy(&mut self, tracer: F) -> usize - where F: Fn(&mut Self) + pub fn regulate_entropy(&mut self, tracer: F) -> usize + where + F: Fn(&mut Self), { // 0. Reset Marks for block in &mut self.blocks { @@ -411,58 +454,64 @@ impl ManifoldHeap { } } } - + // 1. Trace tracer(self); - + // 2. Calc Stats let guard = ChebyshevGuard::calculate(self); - + let mut pruned = 0; let num_blocks = self.blocks.len(); let mut new_free_head = self.free_head; - + for b_idx in 0..num_blocks { let block = &mut self.blocks[b_idx]; - + for s_idx in 0..8 { - if (block.occupied_mask & (1 << s_idx)) == 0 { continue; } - - block.liveness[s_idx] *= 0.95; - - let should_prune; - - if let HeapSlot::Occupied { header, .. } = &mut block.slots[s_idx] { - let is_marked = header.marked; - let is_safe = guard.is_safe(block.liveness[s_idx]); - - if is_marked { - block.liveness[s_idx] += 0.1; - should_prune = false; - } else if is_safe { - should_prune = false; - } else { - should_prune = true; - } - } else { - should_prune = false; - } - - if should_prune { - block.occupied_mask &= !(1 << s_idx); - let next = if let Some(h) = new_free_head { h } else { usize::MAX }; - block.slots[s_idx] = HeapSlot::Free { next_free: next }; - new_free_head = Some(b_idx * 8 + s_idx); - - pruned += 1; - } + if (block.occupied_mask & (1 << s_idx)) == 0 { + continue; + } + + block.liveness[s_idx] *= 0.95; + + let should_prune; + + if let HeapSlot::Occupied { header, .. } = &mut block.slots[s_idx] { + let is_marked = header.marked; + let is_safe = guard.is_safe(block.liveness[s_idx]); + + if is_marked { + block.liveness[s_idx] += 0.1; + should_prune = false; + } else if is_safe { + should_prune = false; + } else { + should_prune = true; + } + } else { + should_prune = false; + } + + if should_prune { + block.occupied_mask &= !(1 << s_idx); + let next = if let Some(h) = new_free_head { + h + } else { + usize::MAX + }; + block.slots[s_idx] = HeapSlot::Free { next_free: next }; + new_free_head = Some(b_idx * 8 + s_idx); + + pruned += 1; + } } } - + self.active_count -= pruned; self.free_head = new_free_head; self.entropy_counter = 0; - + pruned } } @@ -476,12 +525,12 @@ mod tests { let mut heap = ManifoldHeap::::new(); let a = heap.alloc(10); let b = heap.alloc(20); - + assert_eq!(*heap.get(a).unwrap(), 10); assert_eq!(*heap.get(b).unwrap(), 20); assert_eq!(heap.active_count(), 2); } - + #[test] fn test_spatial_clustering() { let mut heap = ManifoldHeap::::new(); @@ -489,13 +538,13 @@ mod tests { for i in 0..8 { handles.push(heap.alloc(i)); } - + let h9 = heap.alloc(99); let (b1, _) = ManifoldHeap::::resolve_index(h9.index); assert_eq!(b1, 1); assert_eq!(heap.blocks.len(), 2); } - + #[test] fn test_simd_alignment() { use core::mem::align_of; diff --git a/aether-core/src/ml/autograd.rs b/crates/aether-core/src/ml/autograd.rs similarity index 73% rename from aether-core/src/ml/autograd.rs rename to crates/aether-core/src/ml/autograd.rs index ccbeaef..dbf5b9e 100644 --- a/aether-core/src/ml/autograd.rs +++ b/crates/aether-core/src/ml/autograd.rs @@ -4,12 +4,22 @@ //! //! "History is not a tree, it is a tape." //! -//! A Wengert List (Tape-based) implementation of reverse-mode automatic +//! A Wengert List (Tape-based) implementation of reverse-mode automatic //! differentiation. It records operations on `Gc` handles, allowing //! the graph to be stored efficiently in the Manifold Heap. //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + +#[cfg(not(feature = "std"))] +extern crate alloc; + #[cfg(not(feature = "std"))] use alloc::vec::Vec; #[cfg(feature = "std")] @@ -23,14 +33,26 @@ use crate::ml::tensor::Tensor; #[derive(Debug, Clone, Copy)] pub enum Op { /// out = lhs + rhs - Add { out: Gc, lhs: Gc, rhs: Gc }, - + Add { + out: Gc, + lhs: Gc, + rhs: Gc, + }, + /// out = lhs * rhs (element-wise) - Mul { out: Gc, lhs: Gc, rhs: Gc }, - + Mul { + out: Gc, + lhs: Gc, + rhs: Gc, + }, + /// out = lhs @ rhs (matrix multiplication) - MatMul { out: Gc, lhs: Gc, rhs: Gc }, - + MatMul { + out: Gc, + lhs: Gc, + rhs: Gc, + }, + /// out = max(0, input) ReLU { out: Gc, input: Gc }, } @@ -104,13 +126,13 @@ impl<'a> Context<'a> { let b = self.heap.get(rhs.data).expect("Stale handle RHS"); let result = a.add(b); let out = self.heap.alloc(result); - - self.tape.push(Op::Add { - out, - lhs: lhs.data, - rhs: rhs.data + + self.tape.push(Op::Add { + out, + lhs: lhs.data, + rhs: rhs.data, }); - + Variable::new(out) } @@ -120,13 +142,13 @@ impl<'a> Context<'a> { let b = self.heap.get(rhs.data).expect("Stale handle RHS"); let result = a.mul(b); let out = self.heap.alloc(result); - - self.tape.push(Op::Mul { - out, - lhs: lhs.data, - rhs: rhs.data + + self.tape.push(Op::Mul { + out, + lhs: lhs.data, + rhs: rhs.data, }); - + Variable::new(out) } @@ -136,13 +158,13 @@ impl<'a> Context<'a> { let b = self.heap.get(rhs.data).expect("Stale handle RHS"); let result = a.matmul(b); let out = self.heap.alloc(result); - - self.tape.push(Op::MatMul { - out, - lhs: lhs.data, - rhs: rhs.data + + self.tape.push(Op::MatMul { + out, + lhs: lhs.data, + rhs: rhs.data, }); - + Variable::new(out) } @@ -151,12 +173,12 @@ impl<'a> Context<'a> { let val = self.heap.get(input.data).expect("Stale handle"); let result = val.map(|x| if x > 0.0 { x } else { 0.0 }); let out = self.heap.alloc(result); - - self.tape.push(Op::ReLU { - out, - input: input.data + + self.tape.push(Op::ReLU { + out, + input: input.data, }); - + Variable::new(out) } @@ -164,8 +186,11 @@ impl<'a> Context<'a> { /// Returns the gradients for all used variables as a Map (Vec indexed by Gc.index) pub fn backward(&mut self, target: Variable) -> Vec> { let max_idx = self.heap.capacity(); - let mut grads: Vec> = vec![None; max_idx]; - + #[cfg(not(feature = "std"))] + let mut grads: Vec> = alloc::vec![None; max_idx]; + #[cfg(feature = "std")] + let mut grads: Vec> = std::vec![None; max_idx]; + // Seed output gradient let target_tensor = self.heap.get(target.data).expect("Target lost"); grads[target.data.index] = Some(Tensor::ones(&target_tensor.shape)); @@ -174,73 +199,77 @@ impl<'a> Context<'a> { for op in self.tape.ops.iter().rev() { match op { Op::Add { out, lhs, rhs } => { - // Solves borrow checker by cloning Option first - let grad_out = grads[out.index].clone(); - if let Some(grad) = grad_out { - // dL/d(lhs) += dL/dout * 1 - Self::accumulate_grad(&mut grads, lhs.index, &grad); - Self::accumulate_grad(&mut grads, rhs.index, &grad); - } - }, + // Solves borrow checker by taking ownership of the Option + let grad_out = grads[out.index].take(); + if let Some(grad) = grad_out { + // dL/d(lhs) += dL/dout * 1 + Self::accumulate_grad(&mut grads, lhs.index, grad.clone()); + Self::accumulate_grad(&mut grads, rhs.index, grad.clone()); + grads[out.index] = Some(grad); + } + } Op::Mul { out, lhs, rhs } => { - let grad_out = grads[out.index].clone(); + let grad_out = grads[out.index].take(); if let Some(grad) = grad_out { let lhs_val = self.heap.get(*lhs).unwrap(); let rhs_val = self.heap.get(*rhs).unwrap(); - + // dL/dLhs = grad_out * rhs - let d_lhs = grad.mul(rhs_val); - Self::accumulate_grad(&mut grads, lhs.index, &d_lhs); - + let d_lhs: Tensor = grad.mul(rhs_val); + Self::accumulate_grad(&mut grads, lhs.index, d_lhs); + // dL/dRhs = grad_out * lhs - let d_rhs = grad.mul(lhs_val); - Self::accumulate_grad(&mut grads, rhs.index, &d_rhs); + let d_rhs: Tensor = grad.mul(lhs_val); + Self::accumulate_grad(&mut grads, rhs.index, d_rhs); + grads[out.index] = Some(grad); } - }, + } Op::MatMul { out, lhs, rhs } => { - let grad_out = grads[out.index].clone(); - if let Some(grad) = grad_out { + let grad_out = grads[out.index].take(); + if let Some(grad) = grad_out { let lhs_val = self.heap.get(*lhs).unwrap(); let rhs_val = self.heap.get(*rhs).unwrap(); - + // C = A @ B // dA = dC @ B^T - let d_lhs = grad.matmul(&rhs_val.transpose()); - Self::accumulate_grad(&mut grads, lhs.index, &d_lhs); - + let d_lhs: Tensor = grad.matmul(&rhs_val.transpose()); + Self::accumulate_grad(&mut grads, lhs.index, d_lhs); + // dB = A^T @ dC - let d_rhs = lhs_val.transpose().matmul(&grad); - Self::accumulate_grad(&mut grads, rhs.index, &d_rhs); - } - }, + let d_rhs: Tensor = lhs_val.transpose().matmul(&grad); + Self::accumulate_grad(&mut grads, rhs.index, d_rhs); + grads[out.index] = Some(grad); + } + } Op::ReLU { out, input } => { - let grad_out = grads[out.index].clone(); + let grad_out = grads[out.index].take(); if let Some(grad) = grad_out { let input_val = self.heap.get(*input).unwrap(); // dL/dx = grad_out * (1 if x > 0 else 0) let mask = input_val.map(|x| if x > 0.0 { 1.0 } else { 0.0 }); - let d_input = grad.mul(&mask); - Self::accumulate_grad(&mut grads, input.index, &d_input); + let d_input: Tensor = grad.mul(&mask); + Self::accumulate_grad(&mut grads, input.index, d_input); + grads[out.index] = Some(grad); } } } } - + grads } - - fn accumulate_grad(grads: &mut Vec>, idx: usize, grad: &Tensor) { + + fn accumulate_grad(grads: &mut Vec>, idx: usize, grad: Tensor) { if idx >= grads.len() { grads.resize(idx + 1 + 256, None); } - + match &mut grads[idx] { Some(existing) => { - let new = existing.add(grad); + let new = existing.add(&grad); grads[idx] = Some(new); } None => { - grads[idx] = Some(grad.clone()); + grads[idx] = Some(grad); } } } @@ -296,39 +325,39 @@ mod tests { // Input: [1.0, 1.0] (1x2) let x = ctx.var(Tensor::new(&[1.0, 1.0], &[1, 2])); - + // W1: Identity [[1, 0], [0, 1]] (2x2) let w1 = ctx.var(Tensor::new(&[1.0, 0.0, 0.0, 1.0], &[2, 2])); - + // W2: [[1], [-2]] (2x1) let w2 = ctx.var(Tensor::new(&[1.0, -2.0], &[2, 1])); // Forward // z1 = x @ W1 = [1, 1] let z1 = ctx.matmul(x, w1); - + // h1 = relu(z1) = [1, 1] let h1 = ctx.relu(z1); - + // y = h1 @ W2 = 1*1 + 1*-2 = -1 (1x1) let y = ctx.matmul(h1, w2); - + // Loss = (y - target)^2, target=0 // Loss = y * y let loss = ctx.mul(y, y); - + // Backward // dLoss/dy = 2y = -2 let grads = ctx.backward(loss); - + // Checks let dy = grads[y.data.index].as_ref().unwrap(); // Manually: dL/dy = 2*(-1) = -2 - assert!((dy.get(&[0,0]) - (-2.0)).abs() < 1e-6); - + assert!((dy.get(&[0, 0]) - (-2.0)).abs() < 1e-6); + let dw2 = grads[w2.data.index].as_ref().unwrap(); // dL/dW2 = h1.T @ dy = [[1], [1]] @ [-2] = [[-2], [-2]] - assert!((dw2.get(&[0,0]) - (-2.0)).abs() < 1e-6); - assert!((dw2.get(&[1,0]) - (-2.0)).abs() < 1e-6); + assert!((dw2.get(&[0, 0]) - (-2.0)).abs() < 1e-6); + assert!((dw2.get(&[1, 0]) - (-2.0)).abs() < 1e-6); } } diff --git a/aether-core/src/ml/benchmark.rs b/crates/aether-core/src/ml/benchmark.rs similarity index 94% rename from aether-core/src/ml/benchmark.rs rename to crates/aether-core/src/ml/benchmark.rs index 9c46159..ef5c8ad 100644 --- a/aether-core/src/ml/benchmark.rs +++ b/crates/aether-core/src/ml/benchmark.rs @@ -12,6 +12,13 @@ //! 5. Answer emerges when topology stabilizes //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![allow(dead_code)] use crate::ml::convergence::{Answer, BettiNumbers, ConvergenceDetector, ResidualAnalyzer}; diff --git a/aether-core/src/ml/classification.rs b/crates/aether-core/src/ml/classification.rs similarity index 94% rename from aether-core/src/ml/classification.rs rename to crates/aether-core/src/ml/classification.rs index a56048b..db45310 100644 --- a/aether-core/src/ml/classification.rs +++ b/crates/aether-core/src/ml/classification.rs @@ -6,10 +6,17 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![allow(dead_code)] // use heapless::Vec as HVec; -use libm::{exp, fabs, log, sqrt}; +use libm::{exp, fabs, log}; /// Maximum classes const MAX_CLASSES: usize = 16; @@ -189,24 +196,21 @@ impl KNNClassifier { return 0; } - // Find k nearest neighbors + // Find k nearest neighbors by squared distance. The square root is + // monotonic, so ranking does not need to pay for it in this hot path. let mut distances = [(f64::MAX, 0u32); MAX_POINTS]; for (i, dist) in distances.iter_mut().enumerate().take(self.n_train) { - *dist = (self.distance(x, &self.x_train[i]), self.y_train[i]); + *dist = (self.squared_distance(x, &self.x_train[i]), self.y_train[i]); } - // Sort by distance (simple bubble sort for small k) - for i in 0..self.k.min(self.n_train) { - for j in (i + 1)..self.n_train { - if distances[j].0 < distances[i].0 { - distances.swap(i, j); - } - } + let k = self.k.min(self.n_train); + if k < self.n_train { + distances[..self.n_train].select_nth_unstable_by(k - 1, |a, b| a.0.total_cmp(&b.0)); } // Vote among k nearest let mut votes = [0u32; MAX_CLASSES]; - for dist in distances.iter().take(self.k.min(self.n_train)) { + for dist in distances.iter().take(k) { let label = dist.1 as usize; if label < MAX_CLASSES { votes[label] += 1; @@ -226,14 +230,14 @@ impl KNNClassifier { best_class } - /// Euclidean distance - fn distance(&self, a: &[f64; D], b: &[f64; D]) -> f64 { + /// Squared Euclidean distance for ranking. + fn squared_distance(&self, a: &[f64; D], b: &[f64; D]) -> f64 { let mut sum = 0.0; for i in 0..D { let diff = a[i] - b[i]; sum += diff * diff; } - sqrt(sum) + sum } } @@ -671,7 +675,7 @@ impl NearestCentroid { let mut best_dist = f64::MAX; for c in 0..self.n_classes { - let dist = self.distance(x, &self.centroids[c]); + let dist = self.squared_distance(x, &self.centroids[c]); if dist < best_dist { best_dist = dist; best_class = c as u32; @@ -690,13 +694,13 @@ impl NearestCentroid { } } - fn distance(&self, a: &[f64; D], b: &[f64; D]) -> f64 { + fn squared_distance(&self, a: &[f64; D], b: &[f64; D]) -> f64 { let mut sum = 0.0; for i in 0..D { let diff = a[i] - b[i]; sum += diff * diff; } - sqrt(sum) + sum } } diff --git a/aether-core/src/ml/clustering.rs b/crates/aether-core/src/ml/clustering.rs similarity index 94% rename from aether-core/src/ml/clustering.rs rename to crates/aether-core/src/ml/clustering.rs index 0db0ce1..b63d653 100644 --- a/aether-core/src/ml/clustering.rs +++ b/crates/aether-core/src/ml/clustering.rs @@ -6,6 +6,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![allow(dead_code)] use libm::{fabs, sqrt}; @@ -267,7 +274,7 @@ impl KMeans { /// Automatically determine optimal K using topological analysis pub fn auto_k_selection(data: &[[f64; D]], n: usize, epsilon: f64) -> usize { // Build epsilon-neighborhood graph and count connected components (β₀) - let mut components = n.min(MAX_POINTS); + let mut components = 0; let mut visited = [false; MAX_POINTS]; let n = n.min(MAX_POINTS); @@ -277,7 +284,6 @@ pub fn auto_k_selection(data: &[[f64; D]], n: usize, epsilon: f6 } // BFS from this point - components -= 1; let mut stack = [0usize; 64]; let mut top = 1; stack[0] = start; @@ -291,11 +297,13 @@ pub fn auto_k_selection(data: &[[f64; D]], n: usize, epsilon: f6 } visited[current] = true; + let epsilon_sq = epsilon * epsilon; + // Add neighbors for i in 0..n { if !visited[i] && i != current { - let dist = distance(&data[current], &data[i]); - if dist < epsilon && top < 64 { + let dist_sq = squared_distance(&data[current], &data[i]); + if dist_sq < epsilon_sq && top < 64 { stack[top] = i; top += 1; } @@ -307,16 +315,20 @@ pub fn auto_k_selection(data: &[[f64; D]], n: usize, epsilon: f6 } // β₀ = number of connected components = suggested K - components.clamp(1, MAX_CLUSTERS) + components.clamp(1, n.clamp(1, MAX_POINTS)) } fn distance(a: &[f64; D], b: &[f64; D]) -> f64 { + sqrt(squared_distance(a, b)) +} + +fn squared_distance(a: &[f64; D], b: &[f64; D]) -> f64 { let mut sum = 0.0; for d in 0..D { let diff = a[d] - b[d]; sum += diff * diff; } - sqrt(sum) + sum } // ═══════════════════════════════════════════════════════════════════════════════ @@ -441,9 +453,10 @@ impl DBSCAN { i: usize, ) -> heapless::Vec { let mut neighbors = heapless::Vec::new(); + let epsilon_sq = self.epsilon * self.epsilon; for j in 0..n { - if distance(&data[i], &data[j]) <= self.epsilon { + if squared_distance(&data[i], &data[j]) <= epsilon_sq { let _ = neighbors.push(j); } } diff --git a/aether-core/src/ml/convergence.rs b/crates/aether-core/src/ml/convergence.rs similarity index 95% rename from aether-core/src/ml/convergence.rs rename to crates/aether-core/src/ml/convergence.rs index 45124a0..7c7b7ec 100644 --- a/aether-core/src/ml/convergence.rs +++ b/crates/aether-core/src/ml/convergence.rs @@ -12,6 +12,13 @@ //! This gives us a mathematically principled "stop" condition. //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![allow(dead_code)] use heapless::Vec as HVec; diff --git a/aether-core/src/ml/convolution.rs b/crates/aether-core/src/ml/convolution.rs similarity index 81% rename from aether-core/src/ml/convolution.rs rename to crates/aether-core/src/ml/convolution.rs index 48eb3cb..57d7169 100644 --- a/aether-core/src/ml/convolution.rs +++ b/crates/aether-core/src/ml/convolution.rs @@ -6,10 +6,17 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![allow(dead_code)] -use libm::sqrt; use crate::ml::neural::Activation; +use libm::sqrt; /// Maximum kernel size (e.g., 3x3, 5x5) const MAX_KERNEL_SIZE: usize = 5; @@ -39,7 +46,7 @@ pub struct Conv2D { pub padding: usize, /// Activation pub activation: Activation, - + // Cache for backprop pub last_input: [[[[f64; MAX_IMG_DIM]; MAX_IMG_DIM]; MAX_CHANNELS_IN]; 1], // Batch size 1 for now pub last_output_dim: (usize, usize), @@ -60,8 +67,9 @@ impl Conv2D { // Kaiming/He initialization let scale = sqrt(2.0 / (in_c * k_size * k_size) as f64); - - let mut weights = [[[[0.0; MAX_KERNEL_SIZE]; MAX_KERNEL_SIZE]; MAX_CHANNELS_IN]; MAX_CHANNELS_OUT]; + + let mut weights = + [[[[0.0; MAX_KERNEL_SIZE]; MAX_KERNEL_SIZE]; MAX_CHANNELS_IN]; MAX_CHANNELS_OUT]; let mut rng = 42u64; for channel_out in weights.iter_mut().take(out_c) { @@ -93,11 +101,15 @@ impl Conv2D { /// Forward pass /// input: [channels][height][width] pub fn forward( - &mut self, + &mut self, input: &[[[f64; MAX_IMG_DIM]; MAX_IMG_DIM]; MAX_CHANNELS_IN], input_h: usize, - input_w: usize - ) -> ([[[f64; MAX_IMG_DIM]; MAX_IMG_DIM]; MAX_CHANNELS_OUT], usize, usize) { + input_w: usize, + ) -> ( + [[[f64; MAX_IMG_DIM]; MAX_IMG_DIM]; MAX_CHANNELS_OUT], + usize, + usize, + ) { // Cache input self.last_input[0] = *input; @@ -111,7 +123,7 @@ impl Conv2D { for (y, row_out) in channel_out.iter_mut().enumerate().take(output_h) { for (x, val_out) in row_out.iter_mut().enumerate().take(output_w) { let mut sum = self.biases[o]; - + // Convolve let in_y_origin = (y * self.stride) as isize - self.padding as isize; let in_x_origin = (x * self.stride) as isize - self.padding as isize; @@ -122,10 +134,13 @@ impl Conv2D { let in_y = in_y_origin + ky as isize; let in_x = in_x_origin + kx as isize; - if in_y >= 0 && in_y < input_h as isize && - in_x >= 0 && in_x < input_w as isize { - sum += input_channel[in_y as usize][in_x as usize] * - self.weights[o][c][ky][kx]; + if in_y >= 0 + && in_y < input_h as isize + && in_x >= 0 + && in_x < input_w as isize + { + sum += input_channel[in_y as usize][in_x as usize] + * self.weights[o][c][ky][kx]; } } } @@ -149,7 +164,7 @@ mod tests { fn test_conv2d_initialization() { let conv = Conv2D::new(1, 1, 3, 1, 1, Activation::ReLU); assert_eq!(conv.weights.len(), MAX_CHANNELS_OUT); // Array size fixed - // Check params + // Check params assert_eq!(conv.kernel_size, 3); assert_eq!(conv.stride, 1); assert_eq!(conv.padding, 1); @@ -159,11 +174,11 @@ mod tests { fn test_conv2d_forward_shape() { let mut conv = Conv2D::new(1, 1, 3, 1, 1, Activation::ReLU); let input = [[[0.5; MAX_IMG_DIM]; MAX_IMG_DIM]; MAX_CHANNELS_IN]; - + // 10x10 input // Output size: (10 + 2*1 - 3) / 1 + 1 = 10 let (_, h, w) = conv.forward(&input, 10, 10); - + assert_eq!(h, 10); assert_eq!(w, 10); } diff --git a/aether-core/src/ml/dataloader.rs b/crates/aether-core/src/ml/dataloader.rs similarity index 87% rename from aether-core/src/ml/dataloader.rs rename to crates/aether-core/src/ml/dataloader.rs index ed09151..31a678e 100644 --- a/aether-core/src/ml/dataloader.rs +++ b/crates/aether-core/src/ml/dataloader.rs @@ -6,6 +6,14 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + + #![allow(dead_code)] #[cfg(feature = "alloc")] diff --git a/aether-core/src/ml/gossip.rs b/crates/aether-core/src/ml/gossip.rs similarity index 89% rename from aether-core/src/ml/gossip.rs rename to crates/aether-core/src/ml/gossip.rs index ef39aa0..0a3bc00 100644 --- a/aether-core/src/ml/gossip.rs +++ b/crates/aether-core/src/ml/gossip.rs @@ -16,13 +16,19 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![allow(dead_code)] -use libm::sqrt; -use crate::ml::clustering::{KMeans, KMeansResult}; // Reuse existing clustering logic if needed +use crate::ml::clustering::{KMeans, KMeansResult}; /// Maximum dimension for the manifold points -const MAX_DIM: usize = 3; +const MAX_DIM: usize = 3; // ═══════════════════════════════════════════════════════════════════════════════ // Core Structures @@ -33,7 +39,7 @@ const MAX_DIM: usize = 3; pub struct GossipNode { /// Unique ID of the core pub id: usize, - + /// Local data shard (simulated) local_data: heapless::Vec<[f64; MAX_DIM], 256>, @@ -85,20 +91,21 @@ impl GossipNode { } // Initially, global estimate is just the local centroid - if self.weight <= 1.0 { - self.global_estimate = self.local_centroid; + if self.weight <= 1.0 { + self.global_estimate = self.local_centroid; } } /// Receive a gossip message (neighbor's estimate) and update local state /// Uses exponential moving average or weighted average for consensus. - /// + /// /// Formula: NewEstimate = (OldEstimate * Weight + NeighborEstimate) / (Weight + 1) pub fn update_consensus(&mut self, neighbor_estimate: [f64; MAX_DIM]) { let alpha = 0.5; // Mixing rate. 0.5 means equal weight to self and neighbor (fast mixing) for i in 0..MAX_DIM { - self.global_estimate[i] = (self.global_estimate[i] * (1.0 - alpha)) + (neighbor_estimate[i] * alpha); + self.global_estimate[i] = + (self.global_estimate[i] * (1.0 - alpha)) + (neighbor_estimate[i] * alpha); } } } @@ -183,7 +190,7 @@ impl GossipRing { let d = node.global_estimate[i] - mean[i]; dist_sq += d * d; } - if sqrt(dist_sq) > tolerance { + if dist_sq > tolerance * tolerance { return false; } } @@ -217,10 +224,10 @@ mod tests { ring.add_node(n1); // Expected global average: [5, 5, 0] - + // Run gossip let iters = ring.converge(0.1, 100); - + println!("Converged in {} iterations", iters); // Verify diff --git a/aether-core/src/ml/linalg.rs b/crates/aether-core/src/ml/linalg.rs similarity index 61% rename from aether-core/src/ml/linalg.rs rename to crates/aether-core/src/ml/linalg.rs index 1e5a94c..d7a36cb 100644 --- a/aether-core/src/ml/linalg.rs +++ b/crates/aether-core/src/ml/linalg.rs @@ -7,6 +7,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![allow(dead_code)] #[cfg(feature = "alloc")] @@ -14,9 +21,8 @@ use alloc::vec; #[cfg(feature = "alloc")] use alloc::vec::Vec; - #[cfg(not(feature = "std"))] -use libm::{exp, fabs, sqrt, log, pow}; +use libm::log; #[cfg(feature = "std")] use std::f64; @@ -47,70 +53,74 @@ impl LossConfig { /// Compute derivative (gradient) w.r.t prediction pub fn derivative(&self, y_true: &Tensor, y_pred: &Tensor) -> Tensor { - match self { - LossConfig::MSE => { - let diff = y_pred.sub(y_true); - let n = y_true.shape.iter().product::() as f64; - diff.scale(2.0 / n) - } - LossConfig::MAE => { - let diff = y_pred.sub(y_true); - let n = y_true.shape.iter().product::() as f64; - diff.map(|x| if x > 0.0 { 1.0 / n } else if x < 0.0 { -1.0 / n } else { 0.0 }) - } - LossConfig::BinaryCrossEntropy => { - // dL/dp = (1-y)/(1-p) - y/p - let true_data = y_true.data.borrow(); - let pred_data = y_pred.data.borrow(); - let n = true_data.len(); - let mut grad_data = Vec::with_capacity(n); // Fixed: using Vec instead of let mut - - for i in 0..n { - let y = true_data[i]; - let p = pred_data[i].clamp(1e-7, 1.0 - 1e-7); // Avoid div by zero - - let grad = -(y / p) + ((1.0 - y) / (1.0 - p)); - grad_data.push(grad / n as f64); - } - Tensor::new(&grad_data, &y_pred.shape) - } - LossConfig::Hinge => { - // L = max(0, 1 - y*p) - // dL/dp = -y if 1 - y*p > 0 else 0 - let true_data = y_true.data.borrow(); - let pred_data = y_pred.data.borrow(); - let n = true_data.len(); - let mut grad_data = Vec::with_capacity(n); - - for i in 0..n { - let y = true_data[i]; - let p = pred_data[i]; - - if 1.0 - y * p > 0.0 { - grad_data.push(-y / n as f64); + assert_eq!(y_true.shape, y_pred.shape); + let true_data = y_true.data.borrow(); + let pred_data = y_pred.data.borrow(); + let n = true_data.len() as f64; + + let grad_data: Vec = match self { + LossConfig::MSE => pred_data + .iter() + .zip(true_data.iter()) + .map(|(p, y)| (p - y) * (2.0 / n)) + .collect(), + LossConfig::MAE => pred_data + .iter() + .zip(true_data.iter()) + .map(|(p, y)| { + let diff = p - y; + if diff > 0.0 { + 1.0 / n + } else if diff < 0.0 { + -1.0 / n } else { - grad_data.push(0.0); + 0.0 } - } - Tensor::new(&grad_data, &y_pred.shape) - } - } + }) + .collect(), + LossConfig::BinaryCrossEntropy => pred_data + .iter() + .zip(true_data.iter()) + .map(|(p_raw, y)| { + let p = p_raw.clamp(1e-7, 1.0 - 1e-7); // Avoid div by zero + let grad = -(y / p) + ((1.0 - y) / (1.0 - p)); + grad / n + }) + .collect(), + LossConfig::Hinge => pred_data + .iter() + .zip(true_data.iter()) + .map(|(p, y)| if 1.0 - y * p > 0.0 { -y / n } else { 0.0 }) + .collect(), + }; + + Tensor::from_vec(grad_data, y_pred.shape.clone()) } } /// Mean Squared Error pub fn mse(y_true: &Tensor, y_pred: &Tensor) -> f64 { - let diff = y_true.sub(y_pred); - diff.mul(&diff).sum() / y_true.shape.iter().product::() as f64 + assert_eq!(y_true.shape, y_pred.shape); + let mut sum = 0.0; + let true_data = y_true.data.borrow(); + let pred_data = y_pred.data.borrow(); + let n = true_data.len(); + + for i in 0..n { + let diff = true_data[i] - pred_data[i]; + sum += diff * diff; + } + sum / n as f64 } /// Mean Absolute Error pub fn mae(y_true: &Tensor, y_pred: &Tensor) -> f64 { + assert_eq!(y_true.shape, y_pred.shape); let mut sum = 0.0; let true_data = y_true.data.borrow(); let pred_data = y_pred.data.borrow(); - let n = true_data.len().min(pred_data.len()); - + let n = true_data.len(); + for i in 0..n { sum += fabs(true_data[i] - pred_data[i]); } @@ -124,22 +134,23 @@ pub fn rmse(y_true: &Tensor, y_pred: &Tensor) -> f64 { /// Binary Cross-Entropy pub fn binary_cross_entropy(y_true: &Tensor, y_pred: &Tensor) -> f64 { + assert_eq!(y_true.shape, y_pred.shape); let mut sum = 0.0; let true_data = y_true.data.borrow(); let pred_data = y_pred.data.borrow(); - let n = true_data.len().min(pred_data.len()); - + let n = true_data.len(); + for i in 0..n { let p = pred_data[i].clamp(1e-7, 1.0 - 1e-7); let y = true_data[i]; - + #[cfg(not(feature = "std"))] { - sum -= y * log(p) + (1.0 - y) * log(1.0 - p); + sum -= y * log(p) + (1.0 - y) * log(1.0 - p); } #[cfg(feature = "std")] { - sum -= y * p.ln() + (1.0 - y) * (1.0 - p).ln(); + sum -= y * p.ln() + (1.0 - y) * (1.0 - p).ln(); } } sum / n as f64 @@ -147,11 +158,12 @@ pub fn binary_cross_entropy(y_true: &Tensor, y_pred: &Tensor) -> f64 { /// Hinge Loss (for SVM) pub fn hinge_loss(y_true: &Tensor, y_pred: &Tensor) -> f64 { + assert_eq!(y_true.shape, y_pred.shape); let mut sum = 0.0; let true_data = y_true.data.borrow(); let pred_data = y_pred.data.borrow(); - let n = true_data.len().min(pred_data.len()); - + let n = true_data.len(); + for i in 0..n { let margin = 1.0 - true_data[i] * pred_data[i]; if margin > 0.0 { @@ -173,9 +185,9 @@ where // Clone structure let grad = Tensor::zeros(&x.shape); let n = x.shape.iter().product(); - + let mut grad_data = grad.data.borrow_mut(); - + // We need a deep copy to mutate independent probe. let mut x_plus = Tensor::new(&x.data.borrow(), &x.shape); let mut x_minus = Tensor::new(&x.data.borrow(), &x.shape); @@ -183,21 +195,21 @@ where { let mut xp_data = x_plus.data.borrow_mut(); let mut xm_data = x_minus.data.borrow_mut(); - + drop(xp_data); drop(xm_data); - + for i in 0..n { - let original = x.data.borrow()[i]; - - x_plus.data.borrow_mut()[i] = original + epsilon; - x_minus.data.borrow_mut()[i] = original - epsilon; - - grad_data[i] = (f(&x_plus) - f(&x_minus)) / (2.0 * epsilon); - - // Restore - x_plus.data.borrow_mut()[i] = original; - x_minus.data.borrow_mut()[i] = original; + let original = x.data.borrow()[i]; + + x_plus.data.borrow_mut()[i] = original + epsilon; + x_minus.data.borrow_mut()[i] = original - epsilon; + + grad_data[i] = (f(&x_plus) - f(&x_minus)) / (2.0 * epsilon); + + // Restore + x_plus.data.borrow_mut()[i] = original; + x_minus.data.borrow_mut()[i] = original; } } @@ -211,29 +223,40 @@ where /// Euclidean distance pub fn euclidean_distance(a: &Tensor, b: &Tensor) -> f64 { - let diff = a.sub(b); - sqrt(diff.mul(&diff).sum()) + assert_eq!(a.shape, b.shape); + let mut sum = 0.0; + let a_data = a.data.borrow(); + let b_data = b.data.borrow(); + + for i in 0..a_data.len() { + let diff = a_data[i] - b_data[i]; + sum += diff * diff; + } + sqrt(sum) } /// Manhattan distance (L1) pub fn manhattan_distance(a: &Tensor, b: &Tensor) -> f64 { + assert_eq!(a.shape, b.shape); let mut sum = 0.0; - // Tensor doesn't have L1 norm built-in, do manual - let diff_data = a.sub(b).data; - let data = diff_data.borrow(); - for &val in data.iter() { - sum += fabs(val); + let a_data = a.data.borrow(); + let b_data = b.data.borrow(); + + for i in 0..a_data.len() { + sum += fabs(a_data[i] - b_data[i]); } sum } /// Chebyshev distance (L∞) pub fn chebyshev_distance(a: &Tensor, b: &Tensor) -> f64 { - let diff = a.sub(b); - let data = diff.data.borrow(); + assert_eq!(a.shape, b.shape); let mut max = 0.0; - for &val in data.iter() { - let abs_val = fabs(val); + let a_data = a.data.borrow(); + let b_data = b.data.borrow(); + + for i in 0..a_data.len() { + let abs_val = fabs(a_data[i] - b_data[i]); if abs_val > max { max = abs_val; } @@ -243,9 +266,16 @@ pub fn chebyshev_distance(a: &Tensor, b: &Tensor) -> f64 { /// RBF kernel value pub fn rbf_kernel(a: &Tensor, b: &Tensor, gamma: f64) -> f64 { - let dist = a.sub(b); - let dist_sq = dist.mul(&dist).sum(); - exp(-gamma * dist_sq) + assert_eq!(a.shape, b.shape); + let mut sum = 0.0; + let a_data = a.data.borrow(); + let b_data = b.data.borrow(); + + for i in 0..a_data.len() { + let diff = a_data[i] - b_data[i]; + sum += diff * diff; + } + exp(-gamma * sum) } fn fabs(x: f64) -> f64 { diff --git a/aether-core/src/ml/mod.rs b/crates/aether-core/src/ml/mod.rs similarity index 74% rename from aether-core/src/ml/mod.rs rename to crates/aether-core/src/ml/mod.rs index 647dee8..dec455e 100644 --- a/aether-core/src/ml/mod.rs +++ b/crates/aether-core/src/ml/mod.rs @@ -17,16 +17,23 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![allow(dead_code)] // Core modules +pub mod autograd; pub mod benchmark; pub mod convergence; -pub mod linalg; pub mod convolution; +pub mod linalg; pub mod regressor; pub mod tensor; -pub mod autograd; // Extended ML library pub mod classification; @@ -43,7 +50,7 @@ pub use clustering::{ }; pub use convergence::*; // pub use linalg::{Matrix, Vector}; // Removed -pub use tensor::Tensor; -pub use neural::{Activation, DenseLayer, TrainingResult, MLP, OptimizerConfig}; +pub use neural::{Activation, DenseLayer, OptimizerConfig, TrainingResult, MLP}; pub use regressor::*; +pub use tensor::Tensor; pub mod gossip; diff --git a/crates/aether-core/src/ml/neural.rs b/crates/aether-core/src/ml/neural.rs new file mode 100644 index 0000000..feb8474 --- /dev/null +++ b/crates/aether-core/src/ml/neural.rs @@ -0,0 +1,569 @@ +//! ═══════════════════════════════════════════════════════════════════════════════ +//! AEGIS Neural Network Library +//! ═══════════════════════════════════════════════════════════════════════════════ +//! +//! Neural networks with topological regularization and seal-loop training. +//! Now powered by dynamic Tensors and proper Optimizers. +//! +//! ═══════════════════════════════════════════════════════════════════════════════ + +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + +#![allow(dead_code)] + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; +#[cfg(feature = "alloc")] +use alloc::vec; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +#[cfg(not(feature = "std"))] +use libm::fabs; // Adjust based on usage +#[cfg(feature = "std")] +use std::f64; + +use super::linalg::LossConfig; +use super::tensor::Tensor; + +// ═══════════════════════════════════════════════════════════════════════════════ +// Activation Functions +// ═══════════════════════════════════════════════════════════════════════════════ + +/// Activation function types +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Activation { + ReLU, + Sigmoid, + Tanh, + Linear, + LeakyReLU, + Softmax, +} + +impl Activation { + /// Apply activation to a tensor + pub fn apply(&self, x: &Tensor) -> Tensor { + match self { + Activation::Softmax => { + let data_borrow = x.data.borrow(); + let max_val = data_borrow.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)); + let mut sum = 0.0; + let data: Vec = data_borrow + .iter() + .map(|&v| { + let e = exp(v - max_val); + sum += e; + e + }) + .collect(); + + let normalized: Vec = data.iter().map(|&v| v / sum.max(1e-10)).collect(); + Tensor::new(&normalized, &x.shape) + } + _ => x.map(|v| self.apply_scalar(v)), + } + } + + /// Apply to single value + pub fn apply_scalar(&self, x: f64) -> f64 { + match self { + Activation::ReLU => { + if x > 0.0 { + x + } else { + 0.0 + } + } + Activation::Sigmoid => 1.0 / (1.0 + exp(-x.clamp(-500.0, 500.0))), + Activation::Tanh => { + let e_pos = exp(x.clamp(-500.0, 500.0)); + let e_neg = exp((-x).clamp(-500.0, 500.0)); + (e_pos - e_neg) / (e_pos + e_neg) + } + Activation::Linear => x, + Activation::LeakyReLU => { + if x > 0.0 { + x + } else { + 0.01 * x + } + } + Activation::Softmax => x, // Should not be called on scalar + } + } + + /// Derivative for backprop + pub fn derivative(&self, x: &Tensor) -> Tensor { + match self { + Activation::Softmax => Tensor::zeros(&x.shape), // Handled specially + _ => x.map(|v| self.derivative_scalar(v)), + } + } + + fn derivative_scalar(&self, x: f64) -> f64 { + match self { + Activation::ReLU => { + if x > 0.0 { + 1.0 + } else { + 0.0 + } + } + Activation::Sigmoid => { + let s = self.apply_scalar(x); + s * (1.0 - s) + } + Activation::Tanh => { + let t = self.apply_scalar(x); + 1.0 - t * t + } + Activation::Linear => 1.0, + Activation::LeakyReLU => { + if x > 0.0 { + 1.0 + } else { + 0.01 + } + } + Activation::Softmax => 1.0, + } + } +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Optimizers +// ═══════════════════════════════════════════════════════════════════════════════ + +#[derive(Debug, Clone)] +pub enum OptimizerConfig { + SGD { + learning_rate: f64, + momentum: f64, + }, + Adam { + learning_rate: f64, + beta1: f64, + beta2: f64, + epsilon: f64, + }, +} + +#[derive(Debug, Clone)] +pub enum OptimizerState { + SGD { + velocity_w: Tensor, + velocity_b: Tensor, + }, + Adam { + m_w: Tensor, + v_w: Tensor, + m_b: Tensor, + v_b: Tensor, + t: u64, + }, + None, +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Dense Layer +// ═══════════════════════════════════════════════════════════════════════════════ + +/// Dense (fully connected) layer +#[derive(Debug, Clone)] +pub struct DenseLayer { + pub weights: Tensor, // [output_size, input_size] + pub biases: Tensor, // [output_size] + pub input_size: usize, + pub output_size: usize, + pub activation: Activation, + + // Cache for backprop + last_input: Option, + last_z: Option, + + // Optimizer State + opt_state: OptimizerState, +} + +impl DenseLayer { + pub fn new( + input_size: usize, + output_size: usize, + activation: Activation, + seed: Option, + ) -> Self { + // Xavier initialization + let scale = sqrt(2.0 / (input_size + output_size) as f64); + + let mut rng = seed.unwrap_or(42); + let mut w_data = Vec::with_capacity(input_size * output_size); + for _ in 0..(input_size * output_size) { + rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1); + let r = (rng as f64 / u64::MAX as f64) * 2.0 - 1.0; + w_data.push(r * scale); + } + + let weights = Tensor::new(&w_data, &[output_size, input_size]); + let biases = Tensor::zeros(&[output_size, 1]); + + Self { + weights, + biases, + input_size, + output_size, + activation, + last_input: None, + last_z: None, + opt_state: OptimizerState::None, + } + } + + pub fn init_optimizer(&mut self, config: &OptimizerConfig) { + match config { + OptimizerConfig::SGD { .. } => { + self.opt_state = OptimizerState::SGD { + velocity_w: Tensor::zeros(&self.weights.shape), + velocity_b: Tensor::zeros(&self.biases.shape), + }; + } + OptimizerConfig::Adam { .. } => { + self.opt_state = OptimizerState::Adam { + m_w: Tensor::zeros(&self.weights.shape), + v_w: Tensor::zeros(&self.weights.shape), + m_b: Tensor::zeros(&self.biases.shape), + v_b: Tensor::zeros(&self.biases.shape), + t: 0, + }; + } + } + } + + /// Forward pass + pub fn forward(&mut self, input: &Tensor) -> Tensor { + self.last_input = Some(input.clone()); + + // z = W * x + b + // weights: [out, in], input: [in] -> [out] + + let wx = self.weights.matmul(input); + let z = wx.add(&self.biases); + + self.last_z = Some(z.clone()); + self.activation.apply(&z) + } + + /// Backward pass + pub fn backward(&mut self, grad_output: &Tensor, config: &OptimizerConfig) -> Tensor { + let last_z = self + .last_z + .take() + .expect("Forward must be called before backward"); + let last_input = self + .last_input + .take() + .expect("Forward must be called before backward"); + + let act_deriv = self.activation.derivative(&last_z); + let delta = grad_output.mul(&act_deriv); + + // Gradients + // dW = delta * input^T + // delta: [out], input: [in] + + let mut dw_data = Vec::with_capacity(self.output_size * self.input_size); + let delta_data = delta.data.borrow(); + let input_data = last_input.data.borrow(); + + for i in 0..self.output_size { + for j in 0..self.input_size { + dw_data.push(delta_data[i] * input_data[j]); + } + } + let grad_w = Tensor::new(&dw_data, &self.weights.shape); + let grad_b = delta.clone(); + + // Compute input gradient for next layer + // dx = W^T * delta + let w_t = self.weights.transpose(); + let grad_input = w_t.matmul(&delta); + + self.update_weights(&grad_w, &grad_b, config); + + grad_input + } + + fn update_weights(&mut self, grad_w: &Tensor, grad_b: &Tensor, config: &OptimizerConfig) { + match config { + OptimizerConfig::SGD { + learning_rate, + momentum, + } => { + if let OptimizerState::SGD { + velocity_w, + velocity_b, + } = &mut self.opt_state + { + *velocity_w = velocity_w + .scale(*momentum) + .sub(&grad_w.scale(*learning_rate)); + *velocity_b = velocity_b + .scale(*momentum) + .sub(&grad_b.scale(*learning_rate)); + + self.weights = self.weights.add(velocity_w); + self.biases = self.biases.add(velocity_b); + } + } + OptimizerConfig::Adam { + learning_rate, + beta1, + beta2, + epsilon, + } => { + if let OptimizerState::Adam { + m_w, + v_w, + m_b, + v_b, + t, + } = &mut self.opt_state + { + *t += 1; + let t_val = *t as f64; + + // Weights + *m_w = m_w.scale(*beta1).add(&grad_w.scale(1.0 - beta1)); + *v_w = v_w + .scale(*beta2) + .add(&grad_w.mul(grad_w).scale(1.0 - beta2)); + + let m_hat_w = m_w.scale(1.0 / (1.0 - pow(*beta1, t_val))); + let v_hat_w = v_w.scale(1.0 / (1.0 - pow(*beta2, t_val))); + + let update_w = m_hat_w + .mul(&v_hat_w.map(|x| 1.0 / (sqrt(x) + epsilon))) + .scale(*learning_rate); + self.weights = self.weights.sub(&update_w); + + // Biases + *m_b = m_b.scale(*beta1).add(&grad_b.scale(1.0 - beta1)); + *v_b = v_b + .scale(*beta2) + .add(&grad_b.mul(grad_b).scale(1.0 - beta2)); + + let m_hat_b = m_b.scale(1.0 / (1.0 - pow(*beta1, t_val))); + let v_hat_b = v_b.scale(1.0 / (1.0 - pow(*beta2, t_val))); + + let update_b = m_hat_b + .mul(&v_hat_b.map(|x| 1.0 / (sqrt(x) + epsilon))) + .scale(*learning_rate); + self.biases = self.biases.sub(&update_b); + } + } + } + } +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Multi-Layer Perceptron +// ═══════════════════════════════════════════════════════════════════════════════ + +/// Multi-Layer Perceptron neural network +#[derive(Debug, Clone)] +pub struct MLP { + pub layers: Vec, + pub config: OptimizerConfig, + pub loss: LossConfig, +} + +impl MLP { + pub fn new(config: OptimizerConfig, loss: LossConfig) -> Self { + Self { + layers: Vec::new(), + config, + loss, + } + } + + /// Add a dense layer + pub fn add_layer( + &mut self, + input_size: usize, + output_size: usize, + activation: Activation, + seed: Option, + ) { + let mut layer = DenseLayer::new(input_size, output_size, activation, seed); + layer.init_optimizer(&self.config); + self.layers.push(layer); + } + + /// Forward pass through all layers + pub fn forward(&mut self, input: &Tensor) -> Tensor { + let mut iter = self.layers.iter_mut(); + if let Some(first) = iter.next() { + let mut current = first.forward(input); + for layer in iter { + current = layer.forward(¤t); + } + current + } else { + input.clone() + } + } + + /// Predict (Forward without mutating state if possible? No, dense layer caches input) + pub fn predict(&mut self, input: &Tensor) -> Tensor { + self.forward(input) + } + + /// Train on single sample (returns loss) + pub fn train_step(&mut self, input: &Tensor, target: &Tensor) -> f64 { + // Forward + let output = self.forward(input); + + // Loss + let loss = self.loss.compute(target, &output); + + // Initial gradient + let grad = self.loss.derivative(target, &output); + + // Backward + let mut current_grad = grad; + for layer in self.layers.iter_mut().rev() { + current_grad = layer.backward(¤t_grad, &self.config); + } + + loss + } + + pub fn fit(&mut self, x: &[Tensor], y: &[Tensor], epochs: usize) -> TrainingResult { + let mut result = TrainingResult::default(); + let n_samples = x.len(); + + for epoch in 0..epochs { + let mut total_loss = 0.0; + for i in 0..n_samples { + total_loss += self.train_step(&x[i], &y[i]); + } + let avg_loss = total_loss / n_samples as f64; + + if epoch < 100 { + result.loss_history.push(avg_loss); + } + result.final_loss = avg_loss; + } + + result.epochs = epochs as u32; + result.converged = true; // Simple logic + result + } +} + +/// Training result +#[derive(Debug, Clone)] +pub struct TrainingResult { + pub epochs: u32, + pub final_loss: f64, + pub converged: bool, + pub loss_history: Vec, +} + +impl Default for TrainingResult { + fn default() -> Self { + Self { + epochs: 0, + final_loss: f64::MAX, + converged: false, + loss_history: Vec::new(), + } + } +} + +pub use OptimizerConfig::*; + +fn pow(base: f64, exp: f64) -> f64 { + #[cfg(feature = "std")] + return base.powf(exp); + #[cfg(not(feature = "std"))] + return libm::pow(base, exp); +} + +fn sqrt(x: f64) -> f64 { + #[cfg(feature = "std")] + return x.sqrt(); + #[cfg(not(feature = "std"))] + return libm::sqrt(x); +} + +fn exp(x: f64) -> f64 { + #[cfg(feature = "std")] + return x.exp(); + #[cfg(not(feature = "std"))] + return libm::exp(x); +} + +#[cfg(test)] +mod tests { + use super::OptimizerConfig; + use super::*; + + #[test] + fn test_mlp_xor() { + let config = OptimizerConfig::SGD { + learning_rate: 0.1, + momentum: 0.9, + }; + let mut mlp = MLP::new(config, LossConfig::MSE); + mlp.add_layer(2, 8, Activation::Tanh, Some(42)); + mlp.add_layer(8, 1, Activation::Sigmoid, Some(43)); + + // XOR Data + let x = vec![ + Tensor::new(&[0.0, 0.0], &[2, 1]), + Tensor::new(&[0.0, 1.0], &[2, 1]), + Tensor::new(&[1.0, 0.0], &[2, 1]), + Tensor::new(&[1.0, 1.0], &[2, 1]), + ]; + let y = vec![ + Tensor::new(&[0.0], &[1, 1]), + Tensor::new(&[1.0], &[1, 1]), + Tensor::new(&[1.0], &[1, 1]), + Tensor::new(&[0.0], &[1, 1]), + ]; + + let result = mlp.fit(&x, &y, 500); + println!("Final XOR Loss: {}", result.final_loss); + assert!(result.converged); + // assert!(result.final_loss < 0.1); + // XOR sometimes fails with simple random init seed, but logic runs. + } + + #[test] + fn test_mlp_large_scale() { + // Fix 3.1: Verify we can have > 64 neurons + let config = OptimizerConfig::Adam { + learning_rate: 0.01, + beta1: 0.9, + beta2: 0.999, + epsilon: 1e-8, + }; + let mut mlp = MLP::new(config, LossConfig::BinaryCrossEntropy); + + // Input 100 -> Hidden 128 -> Output 10 + mlp.add_layer(100, 128, Activation::ReLU, Some(1)); + mlp.add_layer(128, 10, Activation::Softmax, Some(2)); + + let input = Tensor::new(&vec![0.5; 100], &[100, 1]); + let output = mlp.forward(&input); + + assert_eq!(output.shape, vec![10, 1]); + assert!((output.sum() - 1.0).abs() < 1e-5); + } +} diff --git a/aether-core/src/ml/neural.rs b/crates/aether-core/src/ml/neural.rs.orig similarity index 73% rename from aether-core/src/ml/neural.rs rename to crates/aether-core/src/ml/neural.rs.orig index ce4ea0c..90b253e 100644 --- a/aether-core/src/ml/neural.rs +++ b/crates/aether-core/src/ml/neural.rs.orig @@ -7,22 +7,29 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![allow(dead_code)] +#[cfg(feature = "alloc")] +use alloc::boxed::Box; #[cfg(feature = "alloc")] use alloc::vec; #[cfg(feature = "alloc")] use alloc::vec::Vec; -#[cfg(feature = "alloc")] -use alloc::boxed::Box; #[cfg(not(feature = "std"))] -use libm::{exp, fabs, sqrt, pow}; +use libm::fabs; // Adjust based on usage #[cfg(feature = "std")] use std::f64; -use super::tensor::Tensor; use super::linalg::LossConfig; +use super::tensor::Tensor; // ═══════════════════════════════════════════════════════════════════════════════ // Activation Functions @@ -47,12 +54,15 @@ impl Activation { let data_borrow = x.data.borrow(); let max_val = data_borrow.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)); let mut sum = 0.0; - let data: Vec = data_borrow.iter().map(|&v| { - let e = exp(v - max_val); - sum += e; - e - }).collect(); - + let data: Vec = data_borrow + .iter() + .map(|&v| { + let e = exp(v - max_val); + sum += e; + e + }) + .collect(); + let normalized: Vec = data.iter().map(|&v| v / sum.max(1e-10)).collect(); Tensor::new(&normalized, &x.shape) } @@ -63,7 +73,13 @@ impl Activation { /// Apply to single value pub fn apply_scalar(&self, x: f64) -> f64 { match self { - Activation::ReLU => if x > 0.0 { x } else { 0.0 }, + Activation::ReLU => { + if x > 0.0 { + x + } else { + 0.0 + } + } Activation::Sigmoid => 1.0 / (1.0 + exp(-x.clamp(-500.0, 500.0))), Activation::Tanh => { let e_pos = exp(x.clamp(-500.0, 500.0)); @@ -71,7 +87,13 @@ impl Activation { (e_pos - e_neg) / (e_pos + e_neg) } Activation::Linear => x, - Activation::LeakyReLU => if x > 0.0 { x } else { 0.01 * x }, + Activation::LeakyReLU => { + if x > 0.0 { + x + } else { + 0.01 * x + } + } Activation::Softmax => x, // Should not be called on scalar } } @@ -86,7 +108,13 @@ impl Activation { fn derivative_scalar(&self, x: f64) -> f64 { match self { - Activation::ReLU => if x > 0.0 { 1.0 } else { 0.0 }, + Activation::ReLU => { + if x > 0.0 { + 1.0 + } else { + 0.0 + } + } Activation::Sigmoid => { let s = self.apply_scalar(x); s * (1.0 - s) @@ -96,8 +124,14 @@ impl Activation { 1.0 - t * t } Activation::Linear => 1.0, - Activation::LeakyReLU => if x > 0.0 { 1.0 } else { 0.01 }, - Activation::Softmax => 1.0, + Activation::LeakyReLU => { + if x > 0.0 { + 1.0 + } else { + 0.01 + } + } + Activation::Softmax => 1.0, } } } @@ -108,8 +142,16 @@ impl Activation { #[derive(Debug, Clone)] pub enum OptimizerConfig { - SGD { learning_rate: f64, momentum: f64 }, - Adam { learning_rate: f64, beta1: f64, beta2: f64, epsilon: f64 }, + SGD { + learning_rate: f64, + momentum: f64, + }, + Adam { + learning_rate: f64, + beta1: f64, + beta2: f64, + epsilon: f64, + }, } #[derive(Debug, Clone)] @@ -140,20 +182,25 @@ pub struct DenseLayer { pub input_size: usize, pub output_size: usize, pub activation: Activation, - + // Cache for backprop last_input: Option, last_z: Option, - + // Optimizer State opt_state: OptimizerState, } impl DenseLayer { - pub fn new(input_size: usize, output_size: usize, activation: Activation, seed: Option) -> Self { + pub fn new( + input_size: usize, + output_size: usize, + activation: Activation, + seed: Option, + ) -> Self { // Xavier initialization let scale = sqrt(2.0 / (input_size + output_size) as f64); - + let mut rng = seed.unwrap_or(42); let mut w_data = Vec::with_capacity(input_size * output_size); for _ in 0..(input_size * output_size) { @@ -161,7 +208,7 @@ impl DenseLayer { let r = (rng as f64 / u64::MAX as f64) * 2.0 - 1.0; w_data.push(r * scale); } - + let weights = Tensor::new(&w_data, &[output_size, input_size]); let biases = Tensor::zeros(&[output_size, 1]); @@ -176,7 +223,7 @@ impl DenseLayer { opt_state: OptimizerState::None, } } - + pub fn init_optimizer(&mut self, config: &OptimizerConfig) { match config { OptimizerConfig::SGD { .. } => { @@ -200,33 +247,41 @@ impl DenseLayer { /// Forward pass pub fn forward(&mut self, input: &Tensor) -> Tensor { self.last_input = Some(input.clone()); - + // z = W * x + b // weights: [out, in], input: [in] -> [out] - + let wx = self.weights.matmul(input); let z = wx.add(&self.biases); - + self.last_z = Some(z.clone()); self.activation.apply(&z) } /// Backward pass pub fn backward(&mut self, grad_output: &Tensor, config: &OptimizerConfig) -> Tensor { - let last_z = self.last_z.as_ref().expect("Forward must be called before backward").clone(); - let last_input = self.last_input.as_ref().expect("Forward must be called before backward").clone(); - + let last_z = self + .last_z + .as_ref() + .expect("Forward must be called before backward") + .clone(); + let last_input = self + .last_input + .as_ref() + .expect("Forward must be called before backward") + .clone(); + let act_deriv = self.activation.derivative(&last_z); let delta = grad_output.mul(&act_deriv); - + // Gradients // dW = delta * input^T // delta: [out], input: [in] - + let mut dw_data = Vec::with_capacity(self.output_size * self.input_size); let delta_data = delta.data.borrow(); let input_data = last_input.data.borrow(); - + for i in 0..self.output_size { for j in 0..self.input_size { dw_data.push(delta_data[i] * input_data[j]); @@ -234,51 +289,82 @@ impl DenseLayer { } let grad_w = Tensor::new(&dw_data, &self.weights.shape); let grad_b = delta.clone(); - + // Compute input gradient for next layer // dx = W^T * delta let w_t = self.weights.transpose(); let grad_input = w_t.matmul(&delta); - + self.update_weights(&grad_w, &grad_b, config); - + grad_input } - + fn update_weights(&mut self, grad_w: &Tensor, grad_b: &Tensor, config: &OptimizerConfig) { match config { - OptimizerConfig::SGD { learning_rate, momentum } => { - if let OptimizerState::SGD { velocity_w, velocity_b } = &mut self.opt_state { - *velocity_w = velocity_w.scale(*momentum).sub(&grad_w.scale(*learning_rate)); - *velocity_b = velocity_b.scale(*momentum).sub(&grad_b.scale(*learning_rate)); - + OptimizerConfig::SGD { + learning_rate, + momentum, + } => { + if let OptimizerState::SGD { + velocity_w, + velocity_b, + } = &mut self.opt_state + { + *velocity_w = velocity_w + .scale(*momentum) + .sub(&grad_w.scale(*learning_rate)); + *velocity_b = velocity_b + .scale(*momentum) + .sub(&grad_b.scale(*learning_rate)); + self.weights = self.weights.add(velocity_w); self.biases = self.biases.add(velocity_b); } } - OptimizerConfig::Adam { learning_rate, beta1, beta2, epsilon } => { - if let OptimizerState::Adam { m_w, v_w, m_b, v_b, t } = &mut self.opt_state { + OptimizerConfig::Adam { + learning_rate, + beta1, + beta2, + epsilon, + } => { + if let OptimizerState::Adam { + m_w, + v_w, + m_b, + v_b, + t, + } = &mut self.opt_state + { *t += 1; let t_val = *t as f64; - + // Weights *m_w = m_w.scale(*beta1).add(&grad_w.scale(1.0 - beta1)); - *v_w = v_w.scale(*beta2).add(&grad_w.mul(grad_w).scale(1.0 - beta2)); - + *v_w = v_w + .scale(*beta2) + .add(&grad_w.mul(grad_w).scale(1.0 - beta2)); + let m_hat_w = m_w.scale(1.0 / (1.0 - pow(*beta1, t_val))); let v_hat_w = v_w.scale(1.0 / (1.0 - pow(*beta2, t_val))); - - let update_w = m_hat_w.mul(&v_hat_w.map(|x| 1.0 / (sqrt(x) + epsilon))).scale(*learning_rate); + + let update_w = m_hat_w + .mul(&v_hat_w.map(|x| 1.0 / (sqrt(x) + epsilon))) + .scale(*learning_rate); self.weights = self.weights.sub(&update_w); // Biases *m_b = m_b.scale(*beta1).add(&grad_b.scale(1.0 - beta1)); - *v_b = v_b.scale(*beta2).add(&grad_b.mul(grad_b).scale(1.0 - beta2)); - + *v_b = v_b + .scale(*beta2) + .add(&grad_b.mul(grad_b).scale(1.0 - beta2)); + let m_hat_b = m_b.scale(1.0 / (1.0 - pow(*beta1, t_val))); let v_hat_b = v_b.scale(1.0 / (1.0 - pow(*beta2, t_val))); - - let update_b = m_hat_b.mul(&v_hat_b.map(|x| 1.0 / (sqrt(x) + epsilon))).scale(*learning_rate); + + let update_b = m_hat_b + .mul(&v_hat_b.map(|x| 1.0 / (sqrt(x) + epsilon))) + .scale(*learning_rate); self.biases = self.biases.sub(&update_b); } } @@ -308,7 +394,13 @@ impl MLP { } /// Add a dense layer - pub fn add_layer(&mut self, input_size: usize, output_size: usize, activation: Activation, seed: Option) { + pub fn add_layer( + &mut self, + input_size: usize, + output_size: usize, + activation: Activation, + seed: Option, + ) { let mut layer = DenseLayer::new(input_size, output_size, activation, seed); layer.init_optimizer(&self.config); self.layers.push(layer); @@ -322,7 +414,7 @@ impl MLP { } current } - + /// Predict (Forward without mutating state if possible? No, dense layer caches input) pub fn predict(&mut self, input: &Tensor) -> Tensor { self.forward(input) @@ -332,42 +424,42 @@ impl MLP { pub fn train_step(&mut self, input: &Tensor, target: &Tensor) -> f64 { // Forward let output = self.forward(input); - + // Loss let loss = self.loss.compute(target, &output); - + // Initial gradient let grad = self.loss.derivative(target, &output); - + // Backward let mut current_grad = grad; for layer in self.layers.iter_mut().rev() { current_grad = layer.backward(¤t_grad, &self.config); } - + loss } - + pub fn fit(&mut self, x: &[Tensor], y: &[Tensor], epochs: usize) -> TrainingResult { - let mut result = TrainingResult::default(); - let n_samples = x.len(); - - for epoch in 0..epochs { - let mut total_loss = 0.0; - for i in 0..n_samples { - total_loss += self.train_step(&x[i], &y[i]); - } - let avg_loss = total_loss / n_samples as f64; - - if epoch < 100 { - result.loss_history.push(avg_loss); - } - result.final_loss = avg_loss; - } - - result.epochs = epochs as u32; - result.converged = true; // Simple logic - result + let mut result = TrainingResult::default(); + let n_samples = x.len(); + + for epoch in 0..epochs { + let mut total_loss = 0.0; + for i in 0..n_samples { + total_loss += self.train_step(&x[i], &y[i]); + } + let avg_loss = total_loss / n_samples as f64; + + if epoch < 100 { + result.loss_history.push(avg_loss); + } + result.final_loss = avg_loss; + } + + result.epochs = epochs as u32; + result.converged = true; // Simple logic + result } } @@ -416,16 +508,19 @@ fn exp(x: f64) -> f64 { #[cfg(test)] mod tests { - use super::*; use super::OptimizerConfig; + use super::*; #[test] fn test_mlp_xor() { - let config = OptimizerConfig::SGD { learning_rate: 0.1, momentum: 0.9 }; + let config = OptimizerConfig::SGD { + learning_rate: 0.1, + momentum: 0.9, + }; let mut mlp = MLP::new(config, LossConfig::MSE); mlp.add_layer(2, 8, Activation::Tanh, Some(42)); mlp.add_layer(8, 1, Activation::Sigmoid, Some(43)); - + // XOR Data let x = vec![ Tensor::new(&[0.0, 0.0], &[2, 1]), @@ -439,28 +534,33 @@ mod tests { Tensor::new(&[1.0], &[1, 1]), Tensor::new(&[0.0], &[1, 1]), ]; - - let result = mlp.fit(&x, &y, 500); + + let result = mlp.fit(&x, &y, 500); println!("Final XOR Loss: {}", result.final_loss); assert!(result.converged); - // assert!(result.final_loss < 0.1); + // assert!(result.final_loss < 0.1); // XOR sometimes fails with simple random init seed, but logic runs. } - + #[test] fn test_mlp_large_scale() { // Fix 3.1: Verify we can have > 64 neurons - let config = OptimizerConfig::Adam { learning_rate: 0.01, beta1: 0.9, beta2: 0.999, epsilon: 1e-8 }; + let config = OptimizerConfig::Adam { + learning_rate: 0.01, + beta1: 0.9, + beta2: 0.999, + epsilon: 1e-8, + }; let mut mlp = MLP::new(config, LossConfig::BinaryCrossEntropy); - + // Input 100 -> Hidden 128 -> Output 10 mlp.add_layer(100, 128, Activation::ReLU, Some(1)); mlp.add_layer(128, 10, Activation::Softmax, Some(2)); - + let input = Tensor::new(&vec![0.5; 100], &[100, 1]); let output = mlp.forward(&input); - + assert_eq!(output.shape, vec![10, 1]); - assert!((output.sum() - 1.0).abs() < 1e-5); + assert!((output.sum() - 1.0).abs() < 1e-5); } } diff --git a/aether-core/src/ml/regressor.rs b/crates/aether-core/src/ml/regressor.rs similarity index 95% rename from aether-core/src/ml/regressor.rs rename to crates/aether-core/src/ml/regressor.rs index 4328711..f5ec299 100644 --- a/aether-core/src/ml/regressor.rs +++ b/crates/aether-core/src/ml/regressor.rs @@ -9,6 +9,13 @@ //! relationships because the manifold encodes the data's true shape. //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![allow(dead_code)] use heapless::Vec as HVec; diff --git a/aether-core/src/ml/tensor.rs b/crates/aether-core/src/ml/tensor.rs similarity index 64% rename from aether-core/src/ml/tensor.rs rename to crates/aether-core/src/ml/tensor.rs index 5966b45..0109e6e 100644 --- a/aether-core/src/ml/tensor.rs +++ b/crates/aether-core/src/ml/tensor.rs @@ -7,18 +7,25 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #[cfg(feature = "alloc")] use alloc::rc::Rc; #[cfg(feature = "alloc")] -use alloc::vec::Vec; -#[cfg(feature = "alloc")] use alloc::vec; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; #[cfg(not(feature = "alloc"))] use std::rc::Rc; #[cfg(not(feature = "alloc"))] -use std::vec::Vec; -#[cfg(not(feature = "alloc"))] use std::vec; +#[cfg(not(feature = "alloc"))] +use std::vec::Vec; use core::cell::RefCell; use libm::{exp, sqrt}; @@ -35,10 +42,37 @@ pub struct Tensor { } impl Tensor { + /// Create a new tensor from a raw vector (consuming it) and shape + pub fn from_vec(data: Vec, shape: Vec) -> Self { + let total_size: usize = shape.iter().product(); + assert_eq!( + data.len(), + total_size, + "Data length must match shape product" + ); + + let mut strides = vec![0; shape.len()]; + let mut stride = 1; + for i in (0..shape.len()).rev() { + strides[i] = stride; + stride *= shape[i]; + } + + Self { + data: Rc::new(RefCell::new(data)), + shape, + strides, + } + } + /// Create a new tensor from a slice and shape pub fn new(data: &[f64], shape: &[usize]) -> Self { let total_size: usize = shape.iter().product(); - assert_eq!(data.len(), total_size, "Data length must match shape product"); + assert_eq!( + data.len(), + total_size, + "Data length must match shape product" + ); let mut strides = vec![0; shape.len()]; let mut stride = 1; @@ -58,14 +92,14 @@ impl Tensor { pub fn zeros(shape: &[usize]) -> Self { let total_size: usize = shape.iter().product(); let data = vec![0.0; total_size]; - Self::new(&data, shape) + Self::from_vec(data, shape.to_vec()) } /// Create a tensor filled with ones pub fn ones(shape: &[usize]) -> Self { let total_size: usize = shape.iter().product(); let data = vec![1.0; total_size]; - Self::new(&data, shape) + Self::from_vec(data, shape.to_vec()) } /// Create a tensor with Xavier initialization @@ -73,18 +107,18 @@ impl Tensor { let total_size: usize = shape.iter().product(); let fan_in = if shape.len() > 1 { shape[1] } else { 1 }; let bound = sqrt(3.0 / fan_in as f64); - + // Simple LCG for deterministic randomness in no_std let mut rng = 42u64; let mut data: Vec = Vec::with_capacity(total_size); - + for _ in 0..total_size { rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1); let r = (rng as f64 / u64::MAX as f64) * 2.0 - 1.0; data.push(r * bound); } - Self::new(&data, shape) + Self::from_vec(data, shape.to_vec()) } /// Get value at index (handles strides) @@ -114,9 +148,9 @@ impl Tensor { let total_size: usize = self.shape.iter().product(); let mut new_data = Vec::with_capacity(total_size); let data = self.data.borrow(); - + new_data.extend_from_slice(&*data); - + Self { data: Rc::new(RefCell::new(new_data)), shape: vec![total_size], @@ -128,7 +162,10 @@ impl Tensor { pub fn matmul(&self, other: &Tensor) -> Tensor { assert_eq!(self.shape.len(), 2, "Matmul requires 2D tensors"); assert_eq!(other.shape.len(), 2, "Matmul requires 2D tensors"); - assert_eq!(self.shape[1], other.shape[0], "Dimension mismatch for matmul"); + assert_eq!( + self.shape[1], other.shape[0], + "Dimension mismatch for matmul" + ); let m = self.shape[0]; let k = self.shape[1]; @@ -150,7 +187,7 @@ impl Tensor { data_c[i * result.strides[0] + j * result.strides[1]] = sum; } } - + drop(data_c); result } @@ -158,46 +195,51 @@ impl Tensor { /// Element-wise addition pub fn add(&self, other: &Tensor) -> Tensor { assert_eq!(self.shape, other.shape, "Shape mismatch for add"); - let total_size: usize = self.shape.iter().product(); - let mut result_data = Vec::with_capacity(total_size); - + let data_a = self.data.borrow(); let data_b = other.data.borrow(); - for i in 0..total_size { - result_data.push(data_a[i] + data_b[i]); - } + // ⚡ Bolt: Iterators with .zip().map().collect() elide manual bounds checks + // and allow LLVM to auto-vectorize more effectively than indexed for loops. + // Using from_vec avoids redundant O(N) slice allocations. + let result_data: Vec = data_a + .iter() + .zip(data_b.iter()) + .map(|(a, b)| a + b) + .collect(); - Self::new(&result_data, &self.shape) + Self::from_vec(result_data, self.shape.clone()) } /// Element-wise multiplication pub fn mul(&self, other: &Tensor) -> Tensor { assert_eq!(self.shape, other.shape, "Shape mismatch for mul"); - let total_size: usize = self.shape.iter().product(); - let mut result_data = Vec::with_capacity(total_size); - + let data_a = self.data.borrow(); let data_b = other.data.borrow(); - for i in 0..total_size { - result_data.push(data_a[i] * data_b[i]); - } + // ⚡ Bolt: Iterators with .zip().map().collect() elide manual bounds checks + // and allow LLVM to auto-vectorize more effectively than indexed for loops. + // Using from_vec avoids redundant O(N) slice allocations. + let result_data: Vec = data_a + .iter() + .zip(data_b.iter()) + .map(|(a, b)| a * b) + .collect(); - Self::new(&result_data, &self.shape) + Self::from_vec(result_data, self.shape.clone()) } /// Scalar multiplication pub fn scale(&self, s: f64) -> Tensor { - let total_size: usize = self.shape.iter().product(); - let mut result_data = Vec::with_capacity(total_size); let data = self.data.borrow(); - for i in 0..total_size { - result_data.push(data[i] * s); - } + // ⚡ Bolt: Iterators with .map().collect() elide manual bounds checks + // and allow LLVM to auto-vectorize more effectively than indexed for loops. + // Using from_vec avoids redundant O(N) slice allocations. + let result_data: Vec = data.iter().map(|&x| x * s).collect(); - Self::new(&result_data, &self.shape) + Self::from_vec(result_data, self.shape.clone()) } /// Transpose (2D) @@ -205,18 +247,18 @@ impl Tensor { assert_eq!(self.shape.len(), 2, "Transpose support 2D only for now"); let rows = self.shape[0]; let cols = self.shape[1]; - + let mut result = Tensor::zeros(&[cols, rows]); let data = self.data.borrow(); let mut res_data = result.data.borrow_mut(); for i in 0..rows { for j in 0..cols { - res_data[j * result.strides[0] + i * result.strides[1]] = + res_data[j * result.strides[0] + i * result.strides[1]] = data[i * self.strides[0] + j * self.strides[1]]; } } - + drop(res_data); result } @@ -229,30 +271,34 @@ impl Tensor { /// Element-wise subtraction pub fn sub(&self, other: &Tensor) -> Tensor { assert_eq!(self.shape, other.shape, "Shape mismatch for sub"); - let total_size: usize = self.shape.iter().product(); - let mut result_data = Vec::with_capacity(total_size); - + let data_a = self.data.borrow(); let data_b = other.data.borrow(); - for i in 0..total_size { - result_data.push(data_a[i] - data_b[i]); - } + // ⚡ Bolt: Iterators with .zip().map().collect() elide manual bounds checks + // and allow LLVM to auto-vectorize more effectively than indexed for loops. + // Using from_vec avoids redundant O(N) slice allocations. + let result_data: Vec = data_a + .iter() + .zip(data_b.iter()) + .map(|(a, b)| a - b) + .collect(); - Self::new(&result_data, &self.shape) + Self::from_vec(result_data, self.shape.clone()) } /// Element-wise mapping - pub fn map(&self, f: F) -> Self - where F: Fn(f64) -> f64 { - let total_size: usize = self.shape.iter().product(); - let mut result_data = Vec::with_capacity(total_size); + pub fn map(&self, f: F) -> Self + where + F: Fn(f64) -> f64, + { let data = self.data.borrow(); - - for i in 0..total_size { - result_data.push(f(data[i])); - } - - Self::new(&result_data, &self.shape) + + // ⚡ Bolt: Iterators with .map().collect() elide manual bounds checks + // and allow LLVM to auto-vectorize more effectively than indexed for loops. + // Using from_vec avoids redundant O(N) slice allocations. + let result_data: Vec = data.iter().map(|&x| f(x)).collect(); + + Self::from_vec(result_data, self.shape.clone()) } } diff --git a/aether-core/src/os.rs b/crates/aether-core/src/os.rs similarity index 87% rename from aether-core/src/os.rs rename to crates/aether-core/src/os.rs index 4394ec0..f7ef175 100644 --- a/aether-core/src/os.rs +++ b/crates/aether-core/src/os.rs @@ -10,6 +10,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + /// CPU Register Context /// /// This structure represents the state of the CPU registers. It is used for: @@ -52,9 +59,26 @@ impl CpuContext { /// Create a new, blank CPU context pub const fn empty() -> Self { Self { - r15: 0, r14: 0, r13: 0, r12: 0, r11: 0, r10: 0, r9: 0, r8: 0, - rbp: 0, rdi: 0, rsi: 0, rdx: 0, rcx: 0, rbx: 0, rax: 0, - rip: 0, cs: 0, rflags: 0, rsp: 0, ss: 0, + r15: 0, + r14: 0, + r13: 0, + r12: 0, + r11: 0, + r10: 0, + r9: 0, + r8: 0, + rbp: 0, + rdi: 0, + rsi: 0, + rdx: 0, + rcx: 0, + rbx: 0, + rax: 0, + rip: 0, + cs: 0, + rflags: 0, + rsp: 0, + ss: 0, } } } @@ -120,11 +144,11 @@ impl PageTableEntry { pub fn addr(&self) -> u64 { self.entry & 0x000F_FFFF_FFFF_F000 } - + /// Set the physical address pub fn set_addr(&mut self, addr: u64) { - let flags = self.entry & !0x000F_FFFF_FFFF_F000; - self.entry = (addr & 0x000F_FFFF_FFFF_F000) | flags; + let flags = self.entry & !0x000F_FFFF_FFFF_F000; + self.entry = (addr & 0x000F_FFFF_FFFF_F000) | flags; } } @@ -189,19 +213,19 @@ pub struct MemoryRegion { pub struct HardwareTopology { /// Number of physical CPU cores (Neural Clusters) pub cpu_cores: usize, - + /// Number of NUMA nodes (Memory Locality Domains) pub numa_nodes: usize, - + /// Total usable system memory in bytes pub total_memory: u64, - + /// Physical address of the root configuration (RSDP for ACPI, DTB for ARM) pub config_root: PhysAddr, - + /// List of memory regions (The "Territory") pub memory_map: [MemoryRegion; 32], // Fixed size for no_std bootstrap - + /// Number of valid regions in the map pub memory_map_len: usize, } @@ -228,7 +252,7 @@ impl HardwareTopology { if self.memory_map_len < 32 { self.memory_map[self.memory_map_len] = region; self.memory_map_len += 1; - + if region.region_type == MemoryType::Usable { self.total_memory += region.length; } @@ -262,10 +286,10 @@ pub enum KernelMode { pub trait BiosInterface { /// Get the raw memory map from firmware fn memory_map(&self) -> &[MemoryRegion]; - + /// Get the framebuffer info (if graphics enabled) fn framebuffer(&self) -> Option; - + /// Get the ACPI/DTB root pointer fn config_root(&self) -> PhysAddr; } @@ -278,4 +302,3 @@ pub struct FrameBufferInfo { pub stride: u32, pub bytes_per_pixel: u8, } - diff --git a/crates/aether-core/src/persistence.rs b/crates/aether-core/src/persistence.rs new file mode 100644 index 0000000..6a07d07 --- /dev/null +++ b/crates/aether-core/src/persistence.rs @@ -0,0 +1,694 @@ +//! Bounded persistent homology for AETHER point clouds. +//! +//! The engine builds a filtered simplicial complex through tetrahedra and +//! reduces boundary columns over Z2. It is exact for the selected complex and +//! deliberately bounded so topological ML workloads fail fast instead of +//! exhausting memory. + +extern crate alloc; + +use alloc::vec; +use alloc::vec::Vec; + +use crate::manifold::{ManifoldPoint, TimeDelayEmbedder}; + +const SIMPLEX_VERTICES: usize = 4; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ComplexKind { + VietorisRips, + Witness { max_landmarks: usize }, +} + +#[derive(Debug, Clone, Copy)] +pub struct PersistenceConfig { + pub max_homology_dim: usize, + pub max_points: usize, + pub max_simplices: usize, + pub max_radius: f64, + pub complex_kind: ComplexKind, +} + +impl PersistenceConfig { + pub const fn h2_default() -> Self { + Self { + max_homology_dim: 2, + max_points: 32, + max_simplices: 8_192, + max_radius: f64::INFINITY, + complex_kind: ComplexKind::VietorisRips, + } + } + + pub const fn low_load() -> Self { + Self { + max_homology_dim: 1, + max_points: 24, + max_simplices: 4_096, + max_radius: f64::INFINITY, + complex_kind: ComplexKind::Witness { max_landmarks: 24 }, + } + } +} + +impl Default for PersistenceConfig { + fn default() -> Self { + Self::h2_default() + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum PersistenceError { + InvalidDimension, + InvalidRadius, + TooManyPoints { actual: usize, max: usize }, + TooManySimplices { max: usize }, + EmptyInput, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct PersistencePair { + pub dimension: usize, + pub birth: f64, + pub death: Option, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct PersistenceDiagram { + pub pairs: Vec, +} + +impl PersistenceDiagram { + pub fn new(pairs: Vec) -> Self { + Self { pairs } + } + + pub fn betti_at(&self, radius: f64) -> BettiNumbers3 { + let mut betti = BettiNumbers3::default(); + for pair in &self.pairs { + if pair.birth <= radius && pair.death.map(|death| radius < death).unwrap_or(true) { + match pair.dimension { + 0 => betti.beta_0 += 1, + 1 => betti.beta_1 += 1, + 2 => betti.beta_2 += 1, + _ => {} + } + } + } + betti + } +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct BettiNumbers3 { + pub beta_0: u32, + pub beta_1: u32, + pub beta_2: u32, +} + +#[derive(Debug, Clone)] +struct Simplex { + vertices: [usize; SIMPLEX_VERTICES], + len: usize, + dimension: usize, + filtration: f64, +} + +pub fn time_delay_persistence( + samples: &[f64], + tau: usize, + config: PersistenceConfig, +) -> Result { + let mut embedder = TimeDelayEmbedder::::new(tau); + let mut points = Vec::new(); + + for &sample in samples { + embedder.push(sample); + if let Some(point) = embedder.embed() { + points.push(point); + } + } + + persistent_homology(&points, config) +} + +pub fn persistent_homology( + points: &[ManifoldPoint], + config: PersistenceConfig, +) -> Result { + validate_common(points.len(), config)?; + + let simplices = match config.complex_kind { + ComplexKind::VietorisRips => { + validate_point_cap(points.len(), config)?; + build_vietoris_rips_simplices(points, config)? + } + ComplexKind::Witness { max_landmarks } => { + let landmarks = select_landmarks(points, max_landmarks); + validate_point_cap(landmarks.len(), config)?; + build_lazy_witness_simplices(&landmarks, points, config)? + } + }; + reduce_z2(&simplices, config.max_homology_dim) +} + +fn validate_common(point_count: usize, config: PersistenceConfig) -> Result<(), PersistenceError> { + if config.max_homology_dim > 2 { + return Err(PersistenceError::InvalidDimension); + } + if config.max_radius.is_nan() || config.max_radius < 0.0 { + return Err(PersistenceError::InvalidRadius); + } + if point_count == 0 { + return Err(PersistenceError::EmptyInput); + } + Ok(()) +} + +fn validate_point_cap( + point_count: usize, + config: PersistenceConfig, +) -> Result<(), PersistenceError> { + if point_count > config.max_points { + return Err(PersistenceError::TooManyPoints { + actual: point_count, + max: config.max_points, + }); + } + Ok(()) +} + +fn select_landmarks( + points: &[ManifoldPoint], + max_landmarks: usize, +) -> Vec> { + if max_landmarks == 0 || points.len() <= max_landmarks { + return points.to_vec(); + } + + let mut selected = vec![false; points.len()]; + let mut landmarks = Vec::with_capacity(max_landmarks); + landmarks.push(points[0]); + selected[0] = true; + + while landmarks.len() < max_landmarks { + let mut best_idx = None; + let mut best_distance = -1.0; + + for (idx, point) in points.iter().enumerate() { + if selected[idx] { + continue; + } + + let nearest = landmarks + .iter() + .map(|landmark| point.distance(landmark)) + .fold(f64::INFINITY, |a, b| a.min(b)); + + if nearest > best_distance { + best_distance = nearest; + best_idx = Some(idx); + } + } + + let Some(idx) = best_idx else { + break; + }; + landmarks.push(points[idx]); + selected[idx] = true; + } + landmarks +} + +fn build_vietoris_rips_simplices( + points: &[ManifoldPoint], + config: PersistenceConfig, +) -> Result, PersistenceError> { + let n = points.len(); + let mut distances = vec![0.0; n * n]; + for i in 0..n { + for j in i + 1..n { + let distance = points[i].distance(&points[j]); + distances[i * n + j] = distance; + distances[j * n + i] = distance; + } + } + + let mut simplices = Vec::new(); + for i in 0..n { + push_simplex( + &mut simplices, + simplex([i, 0, 0, 0], 1, 0.0), + config.max_simplices, + )?; + } + + for i in 0..n { + for j in i + 1..n { + let r = distances[i * n + j]; + if r <= config.max_radius { + push_simplex( + &mut simplices, + simplex([i, j, 0, 0], 2, r), + config.max_simplices, + )?; + } + } + } + + if config.max_homology_dim >= 1 { + for i in 0..n { + for j in i + 1..n { + for k in j + 1..n { + let r = max3( + distances[i * n + j], + distances[i * n + k], + distances[j * n + k], + ); + if r <= config.max_radius { + push_simplex( + &mut simplices, + simplex([i, j, k, 0], 3, r), + config.max_simplices, + )?; + } + } + } + } + } + + if config.max_homology_dim >= 2 { + for i in 0..n { + for j in i + 1..n { + for k in j + 1..n { + for l in k + 1..n { + let r = max6( + distances[i * n + j], + distances[i * n + k], + distances[i * n + l], + distances[j * n + k], + distances[j * n + l], + distances[k * n + l], + ); + if r <= config.max_radius { + push_simplex( + &mut simplices, + simplex([i, j, k, l], 4, r), + config.max_simplices, + )?; + } + } + } + } + } + } + + simplices.sort_by(compare_simplex); + Ok(simplices) +} + +fn build_lazy_witness_simplices( + landmarks: &[ManifoldPoint], + witnesses: &[ManifoldPoint], + config: PersistenceConfig, +) -> Result, PersistenceError> { + let n = landmarks.len(); + let mut witness_to_landmark = vec![0.0; witnesses.len() * n]; + let mut nearest = vec![f64::INFINITY; witnesses.len()]; + + for (w_idx, witness) in witnesses.iter().enumerate() { + for (l_idx, landmark) in landmarks.iter().enumerate() { + let distance = witness.distance(landmark); + witness_to_landmark[w_idx * n + l_idx] = distance; + nearest[w_idx] = nearest[w_idx].min(distance); + } + } + + let mut simplices = Vec::new(); + for i in 0..n { + push_simplex( + &mut simplices, + simplex([i, 0, 0, 0], 1, 0.0), + config.max_simplices, + )?; + } + + for i in 0..n { + for j in i + 1..n { + if let Some(r) = witness_filtration(&witness_to_landmark, &nearest, n, [i, j, 0, 0], 2) + { + if r <= config.max_radius { + push_simplex( + &mut simplices, + simplex([i, j, 0, 0], 2, r), + config.max_simplices, + )?; + } + } + } + } + + if config.max_homology_dim >= 1 { + for i in 0..n { + for j in i + 1..n { + for k in j + 1..n { + if let Some(r) = + witness_filtration(&witness_to_landmark, &nearest, n, [i, j, k, 0], 3) + { + if r <= config.max_radius { + push_simplex( + &mut simplices, + simplex([i, j, k, 0], 3, r), + config.max_simplices, + )?; + } + } + } + } + } + } + + if config.max_homology_dim >= 2 { + for i in 0..n { + for j in i + 1..n { + for k in j + 1..n { + for l in k + 1..n { + if let Some(r) = + witness_filtration(&witness_to_landmark, &nearest, n, [i, j, k, l], 4) + { + if r <= config.max_radius { + push_simplex( + &mut simplices, + simplex([i, j, k, l], 4, r), + config.max_simplices, + )?; + } + } + } + } + } + } + } + + simplices.sort_by(compare_simplex); + Ok(simplices) +} + +fn witness_filtration( + distances: &[f64], + nearest: &[f64], + landmark_count: usize, + vertices: [usize; SIMPLEX_VERTICES], + len: usize, +) -> Option { + let mut best = f64::INFINITY; + for witness_idx in 0..nearest.len() { + let mut farthest_vertex = 0.0; + for &vertex in vertices.iter().take(len) { + let distance = distances[witness_idx * landmark_count + vertex]; + if distance > farthest_vertex { + farthest_vertex = distance; + } + } + + let filtration = (farthest_vertex - nearest[witness_idx]).max(0.0); + if filtration < best { + best = filtration; + } + } + + best.is_finite().then_some(best) +} + +fn simplex(vertices: [usize; SIMPLEX_VERTICES], len: usize, filtration: f64) -> Simplex { + Simplex { + vertices, + len, + dimension: len - 1, + filtration, + } +} + +fn push_simplex( + simplices: &mut Vec, + simplex: Simplex, + max_simplices: usize, +) -> Result<(), PersistenceError> { + if simplices.len() >= max_simplices { + return Err(PersistenceError::TooManySimplices { max: max_simplices }); + } + simplices.push(simplex); + Ok(()) +} + +fn compare_simplex(a: &Simplex, b: &Simplex) -> core::cmp::Ordering { + a.filtration + .total_cmp(&b.filtration) + .then(a.dimension.cmp(&b.dimension)) + .then(a.vertices[..a.len].cmp(&b.vertices[..b.len])) +} + +fn reduce_z2( + simplices: &[Simplex], + max_homology_dim: usize, +) -> Result { + let mut reduced_columns: Vec> = Vec::with_capacity(simplices.len()); + let mut low_owner: Vec> = vec![None; simplices.len()]; + let mut paired_birth = vec![false; simplices.len()]; + let mut pairs = Vec::new(); + + for j in 0..simplices.len() { + let mut column = boundary_indices(simplices, j); + loop { + let Some(&low) = column.last() else { + break; + }; + let Some(owner) = low_owner[low] else { + break; + }; + let owner_column: &[usize] = reduced_columns[owner].as_slice(); + column = xor_sorted(&column, owner_column); + } + + if let Some(&low) = column.last() { + low_owner[low] = Some(j); + paired_birth[low] = true; + let dimension = simplices[low].dimension; + if dimension <= max_homology_dim { + pairs.push(PersistencePair { + dimension, + birth: simplices[low].filtration, + death: Some(simplices[j].filtration), + }); + } + } + reduced_columns.push(column); + } + + for (idx, column) in reduced_columns.iter().enumerate() { + if column.is_empty() && !paired_birth[idx] && simplices[idx].dimension <= max_homology_dim { + pairs.push(PersistencePair { + dimension: simplices[idx].dimension, + birth: simplices[idx].filtration, + death: None, + }); + } + } + + pairs.sort_by(|a, b| { + a.dimension + .cmp(&b.dimension) + .then(a.birth.total_cmp(&b.birth)) + .then(match (a.death, b.death) { + (Some(x), Some(y)) => x.total_cmp(&y), + (Some(_), None) => core::cmp::Ordering::Less, + (None, Some(_)) => core::cmp::Ordering::Greater, + (None, None) => core::cmp::Ordering::Equal, + }) + }); + Ok(PersistenceDiagram::new(pairs)) +} + +fn boundary_indices(simplices: &[Simplex], simplex_idx: usize) -> Vec { + let simplex = &simplices[simplex_idx]; + if simplex.dimension == 0 { + return Vec::new(); + } + + let mut boundary = Vec::with_capacity(simplex.len); + for remove_idx in 0..simplex.len { + let mut face = [0usize; SIMPLEX_VERTICES]; + let mut face_len = 0; + for i in 0..simplex.len { + if i != remove_idx { + face[face_len] = simplex.vertices[i]; + face_len += 1; + } + } + if let Some(idx) = find_simplex(simplices, simplex_idx, &face, face_len) { + boundary.push(idx); + } + } + boundary.sort_unstable(); + boundary +} + +fn find_simplex( + simplices: &[Simplex], + before: usize, + vertices: &[usize; SIMPLEX_VERTICES], + len: usize, +) -> Option { + simplices[..before] + .iter() + .position(|simplex| simplex.len == len && simplex.vertices[..len] == vertices[..len]) +} + +fn xor_sorted(left: &[usize], right: &[usize]) -> Vec { + let mut out = Vec::with_capacity(left.len() + right.len()); + let mut i = 0; + let mut j = 0; + while i < left.len() || j < right.len() { + if j == right.len() || (i < left.len() && left[i] < right[j]) { + out.push(left[i]); + i += 1; + } else if i == left.len() || right[j] < left[i] { + out.push(right[j]); + j += 1; + } else { + i += 1; + j += 1; + } + } + out +} + +fn max3(a: f64, b: f64, c: f64) -> f64 { + a.max(b).max(c) +} + +fn max6(a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> f64 { + a.max(b).max(c).max(d).max(e).max(f) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn cfg(max_dim: usize, radius: f64) -> PersistenceConfig { + PersistenceConfig { + max_homology_dim: max_dim, + max_points: 16, + max_simplices: 4_096, + max_radius: radius, + complex_kind: ComplexKind::VietorisRips, + } + } + + #[test] + fn h0_tracks_component_merges() { + let points = [ + ManifoldPoint::<2>::new([0.0, 0.0]), + ManifoldPoint::<2>::new([1.0, 0.0]), + ManifoldPoint::<2>::new([3.0, 0.0]), + ]; + + let diagram = persistent_homology(&points, cfg(0, 10.0)).unwrap(); + + assert_eq!(diagram.betti_at(0.5).beta_0, 3); + assert_eq!(diagram.betti_at(1.5).beta_0, 2); + assert_eq!(diagram.betti_at(3.0).beta_0, 1); + } + + #[test] + fn h1_square_loop_is_born_before_it_dies() { + let points = [ + ManifoldPoint::<2>::new([0.0, 0.0]), + ManifoldPoint::<2>::new([1.0, 0.0]), + ManifoldPoint::<2>::new([1.0, 1.0]), + ManifoldPoint::<2>::new([0.0, 1.0]), + ]; + + let diagram = persistent_homology(&points, cfg(1, 10.0)).unwrap(); + let h1 = diagram + .pairs + .iter() + .find(|pair| pair.dimension == 1 && pair.birth <= 1.0); + + assert!(h1.is_some()); + assert!(h1.unwrap().death.unwrap() > h1.unwrap().birth); + } + + #[test] + fn h2_tetrahedron_boundary_has_void_until_tetrahedron_enters() { + let points = [ + ManifoldPoint::<3>::new([1.0, 1.0, 1.0]), + ManifoldPoint::<3>::new([-1.0, -1.0, 1.0]), + ManifoldPoint::<3>::new([-1.0, 1.0, -1.0]), + ManifoldPoint::<3>::new([1.0, -1.0, -1.0]), + ]; + + let diagram = persistent_homology(&points, cfg(2, 10.0)).unwrap(); + let h2 = diagram.pairs.iter().find(|pair| pair.dimension == 2); + + assert!(h2.is_some()); + assert_eq!(h2.unwrap().death, Some(h2.unwrap().birth)); + } + + #[test] + fn caps_fail_before_allocating_unbounded_complexes() { + let points = [ + ManifoldPoint::<2>::new([0.0, 0.0]), + ManifoldPoint::<2>::new([1.0, 0.0]), + ManifoldPoint::<2>::new([0.0, 1.0]), + ]; + let config = PersistenceConfig { + max_homology_dim: 2, + max_points: 8, + max_simplices: 2, + max_radius: 10.0, + complex_kind: ComplexKind::VietorisRips, + }; + + assert_eq!( + persistent_homology(&points, config), + Err(PersistenceError::TooManySimplices { max: 2 }) + ); + } + + #[test] + fn time_delay_constant_signal_has_one_essential_component() { + let samples = [1.0; 16]; + let diagram = time_delay_persistence::<3>(&samples, 1, cfg(2, 10.0)).unwrap(); + + assert_eq!( + diagram.betti_at(0.0), + BettiNumbers3 { + beta_0: 1, + beta_1: 0, + beta_2: 0 + } + ); + } + + #[test] + fn witness_mode_uses_landmarks_without_rejecting_full_signal_size() { + let points: Vec<_> = (0..40) + .map(|i| { + let t = i as f64 * 0.2; + ManifoldPoint::<2>::new([libm::cos(t), libm::sin(t)]) + }) + .collect(); + let config = PersistenceConfig { + max_homology_dim: 1, + max_points: 8, + max_simplices: 1_024, + max_radius: 1.0, + complex_kind: ComplexKind::Witness { max_landmarks: 8 }, + }; + + let diagram = persistent_homology(&points, config).unwrap(); + + assert!(!diagram.pairs.is_empty()); + } +} diff --git a/aether-core/src/state.rs b/crates/aether-core/src/state.rs similarity index 90% rename from aether-core/src/state.rs rename to crates/aether-core/src/state.rs index 994d597..bd02d89 100644 --- a/aether-core/src/state.rs +++ b/crates/aether-core/src/state.rs @@ -16,6 +16,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + use core::ops::Sub; use libm::sqrt; @@ -135,7 +142,10 @@ impl Sub for SystemState { fn sub(self, other: Self) -> [f64; D] { let mut result = [0.0; D]; - for (r, (a, b)) in result.iter_mut().zip(self.vector.iter().zip(other.vector.iter())) { + for (r, (a, b)) in result + .iter_mut() + .zip(self.vector.iter().zip(other.vector.iter())) + { *r = a - b; } result diff --git a/aether-core/src/topology.rs b/crates/aether-core/src/topology.rs similarity index 78% rename from aether-core/src/topology.rs rename to crates/aether-core/src/topology.rs index d668de6..1760cb4 100644 --- a/aether-core/src/topology.rs +++ b/crates/aether-core/src/topology.rs @@ -16,6 +16,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![allow(dead_code)] // use libm::fabs; @@ -261,23 +268,108 @@ pub fn verify_against_reference( /// /// # Returns /// `Ok(())` if all windows pass, `Err(offset)` at first failure +fn compute_raw_betti_0(data: &[u8]) -> u32 { + let mut components = 0u32; + let mut in_component = false; + for window in data.windows(2) { + let dist = (window[0] as i16 - window[1] as i16).abs(); + if dist > CLUSTER_THRESHOLD { + if !in_component { + components += 1; + in_component = true; + } + } else { + in_component = false; + } + } + components +} + +#[inline(always)] +fn is_gap(data: &[u8], idx: usize) -> bool { + let dist = (data[idx] as i16 - data[idx + 1] as i16).abs(); + dist > CLUSTER_THRESHOLD +} + +#[inline(always)] +fn is_loop(data: &[u8], idx: usize) -> bool { + let a = data[idx] as i16; + let b = data[idx + 1] as i16; + let c = data[idx + 2] as i16; + let d = data[idx + 3] as i16; + let tolerance = 5i16; + if (a - d).abs() <= tolerance { + if (a - b).abs() > tolerance || (a - c).abs() > tolerance { + return true; + } + } + false +} + pub fn verify_sliding_window(data: &[u8], window_size: usize) -> Result<(), usize> { let size = if window_size == 0 { WINDOW_SIZE } else { window_size }; - + if size < 4 { + // Fallback to naive O(N*W) approach for window sizes < 4 to avoid out-of-bounds panics + if data.len() < size { + return if is_shape_valid(data) { Ok(()) } else { Err(0) }; + } + for (offset, window) in data.windows(size).enumerate() { + if !is_shape_valid(window) { + return Err(offset); + } + } + return Ok(()); + } if data.len() < size { return if is_shape_valid(data) { Ok(()) } else { Err(0) }; } - for (offset, window) in data.windows(size).enumerate() { - if !is_shape_valid(window) { - return Err(offset); + let mut current_betti_0 = compute_raw_betti_0(&data[0..size]); + let mut current_betti_1 = compute_betti_1(&data[0..size]); + + let check_shape = |b0: u32, b1: u32, offset: usize| -> Result<(), usize> { + let public_b0 = if b0 == 0 { 1 } else { b0 }; + let shape = TopologicalShape::new(public_b0, b1, size); + if shape.density < DENSITY_MIN || shape.density > DENSITY_MAX || shape.betti_1 > MAX_BETTI_1 + { + Err(offset) + } else { + Ok(()) } + }; + + if let Err(e) = check_shape(current_betti_0, current_betti_1, 0) { + return Err(e); } + for offset in 1..=(data.len() - size) { + if is_loop(data, offset - 1) { + current_betti_1 -= 1; + } + if is_loop(data, offset + size - 4) { + current_betti_1 += 1; + } + + let gap_start = is_gap(data, offset - 1); + let gap_start_plus_1 = is_gap(data, offset); + if gap_start && !gap_start_plus_1 { + current_betti_0 -= 1; + } + + let gap_end_plus_1 = is_gap(data, offset + size - 2); + let gap_end = is_gap(data, offset + size - 3); + if gap_end_plus_1 && !gap_end { + current_betti_0 += 1; + } + + if let Err(e) = check_shape(current_betti_0, current_betti_1, offset) { + return Err(e); + } + } Ok(()) } diff --git a/crates/aether-core/test_results.txt b/crates/aether-core/test_results.txt new file mode 100644 index 0000000..782b408 Binary files /dev/null and b/crates/aether-core/test_results.txt differ diff --git a/crates/aether-core/test_results_2.txt b/crates/aether-core/test_results_2.txt new file mode 100644 index 0000000..66c9c7e Binary files /dev/null and b/crates/aether-core/test_results_2.txt differ diff --git a/aether-kernel/Cargo.toml b/crates/aether-kernel/Cargo.toml similarity index 100% rename from aether-kernel/Cargo.toml rename to crates/aether-kernel/Cargo.toml diff --git a/aether-kernel/src/allocator.rs b/crates/aether-kernel/src/allocator.rs similarity index 79% rename from aether-kernel/src/allocator.rs rename to crates/aether-kernel/src/allocator.rs index fa7b92c..fa143eb 100644 --- a/aether-kernel/src/allocator.rs +++ b/crates/aether-kernel/src/allocator.rs @@ -7,6 +7,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + use core::alloc::{GlobalAlloc, Layout}; use core::ptr::null_mut; use spin::Mutex; diff --git a/aether-kernel/src/boot/bios.rs b/crates/aether-kernel/src/boot/bios.rs similarity index 70% rename from aether-kernel/src/boot/bios.rs rename to crates/aether-kernel/src/boot/bios.rs index eee5c22..3c16073 100644 --- a/aether-kernel/src/boot/bios.rs +++ b/crates/aether-kernel/src/boot/bios.rs @@ -1,4 +1,12 @@ use core::iter::Iterator; + +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + use aether_core::PhysAddr; use multiboot2::{BootInformation, BootInformationHeader}; @@ -33,7 +41,7 @@ pub struct Framebuffer { /// The interface that the BIOS/Bootloader must satisfy. /// Acts as the "DNA transcription" layer. pub trait BiosInterface { - /// Get the raw memory map iterator + // Get the raw memory map iterator. // We use a simplified return type here as returning `impl Iterator` in traits is tricky in no_std without GATs/TAITs fully stabilized or boxing. // Ideally we'd return a custom iterator struct. // For simplicity, we'll let the caller get the raw iter via a method or just handle it here. @@ -42,7 +50,7 @@ pub trait BiosInterface { // Wait, we can define the Iterator type in the impl. // Let's refine the trait to be more practical for this step. // We will just expose a method to get the info we need. - + // For this step, I'll modify the trait to be simpler or implement it directly on the struct. } @@ -64,41 +72,38 @@ impl BootInfo { fn raw(&self) -> Option { unsafe { multiboot2::load(self.multiboot_start as usize).ok() } } - + /// Iterate over the memory map using a callback. /// This avoids returning complex iterators with lifetimes. - pub fn walk_memory_map(&self, mut f: F) - where F: FnMut(MemoryRegion) + pub fn walk_memory_map(&self, mut f: F) + where + F: FnMut(MemoryRegion), { - if let Some(info) = self.raw() { + if let Some(info) = self.raw() { if let Some(tag) = info.memory_map_tag() { for area in tag.memory_areas() { f(MemoryRegion { start: area.start_address(), end: area.end_address(), - // Fix: Match on the struct type, not the enum directly if it's wrapped, - // or assume direct enum match if `typ()` returns the enum. - // The error said `typ()` returns `MemoryAreaTypeId`. - // We need to match against the ID or convert. - // multiboot2 0.16+: `typ()` returns `MemoryAreaTypeId`. - // We should map known IDs. - kind: match area.typ() { - multiboot2::MemoryAreaTypeId::AVAILABLE => MemoryRegionKind::Usable, - multiboot2::MemoryAreaTypeId::RESERVED => MemoryRegionKind::Reserved, - multiboot2::MemoryAreaTypeId::ACPI_AVAILABLE => MemoryRegionKind::Acpi, - multiboot2::MemoryAreaTypeId::NVS => MemoryRegionKind::Reserved, - _ => MemoryRegionKind::Unknown, - } + kind: match multiboot2::MemoryAreaType::from(area.typ()) { + multiboot2::MemoryAreaType::Available => MemoryRegionKind::Usable, + multiboot2::MemoryAreaType::Reserved => MemoryRegionKind::Reserved, + multiboot2::MemoryAreaType::AcpiAvailable => MemoryRegionKind::Acpi, + multiboot2::MemoryAreaType::ReservedHibernate => { + MemoryRegionKind::Reserved + } + _ => MemoryRegionKind::Unknown, + }, }); } } - } + } } pub fn framebuffer(&self) -> Option { let info = self.raw()?; let tag = info.framebuffer_tag().ok()?; // Unwrap result - + Some(Framebuffer { address: tag.address(), width: tag.width(), @@ -110,19 +115,19 @@ impl BootInfo { pub fn config_root(&self) -> Option { let info = self.raw()?; - + // Try RSDP (new ACPI) if let Some(tag) = info.rsdp_v2_tag() { // Fix: signature() returns Result<&str, ...>, need to unwrap or handle - return Some(tag.signature().ok()?.as_ptr() as u64); + return Some(tag.signature().ok()?.as_ptr() as u64); } - + // Try RSDP (old ACPI) if let Some(tag) = info.rsdp_v1_tag() { // Similarly. return None; // Placeholder } - + // DTB not standard in multiboot2 usually (it's MBI), but we can look for it. None } diff --git a/crates/aether-kernel/src/boot/mod.rs b/crates/aether-kernel/src/boot/mod.rs new file mode 100644 index 0000000..73b04dd --- /dev/null +++ b/crates/aether-kernel/src/boot/mod.rs @@ -0,0 +1,10 @@ +pub mod bios; + +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + +pub mod topology; diff --git a/aether-kernel/src/boot/topology.rs b/crates/aether-kernel/src/boot/topology.rs similarity index 71% rename from aether-kernel/src/boot/topology.rs rename to crates/aether-kernel/src/boot/topology.rs index deae86b..ce9883f 100644 --- a/aether-kernel/src/boot/topology.rs +++ b/crates/aether-kernel/src/boot/topology.rs @@ -1,5 +1,12 @@ use alloc::vec::Vec; +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + /// Represents the I/O capabilities of the organism (hardware). #[derive(Debug, Clone, Copy, Default)] pub struct IoCaps { @@ -45,14 +52,14 @@ impl HardwareTopology { mem_total += region.end - region.start; } }); - + let mut caps = IoCaps::default(); if boot_info.framebuffer().is_some() { caps.has_framebuffer = true; } Self { - cpu_cores: 1, // TODO: Parse MADT/ACPI for actual core count + cpu_cores: 1, // TODO: Parse MADT/ACPI for actual core count numa_nodes: 1, // TODO: Parse SRAT total_memory: mem_total, io_capabilities: caps, diff --git a/aether-kernel/src/interrupts.rs b/crates/aether-kernel/src/interrupts.rs similarity index 89% rename from aether-kernel/src/interrupts.rs rename to crates/aether-kernel/src/interrupts.rs index 37d0f45..0aed407 100644 --- a/aether-kernel/src/interrupts.rs +++ b/crates/aether-kernel/src/interrupts.rs @@ -7,6 +7,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + use crate::serial_println; use crate::STATE_DIMENSION; use aether_core::state::SystemState; diff --git a/aether-kernel/src/lib.rs b/crates/aether-kernel/src/lib.rs similarity index 73% rename from aether-kernel/src/lib.rs rename to crates/aether-kernel/src/lib.rs index 314c062..ef1a943 100644 --- a/aether-kernel/src/lib.rs +++ b/crates/aether-kernel/src/lib.rs @@ -7,6 +7,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![no_std] #![no_main] #![feature(abi_x86_interrupt)] diff --git a/aether-kernel/src/loader.rs b/crates/aether-kernel/src/loader.rs similarity index 86% rename from aether-kernel/src/loader.rs rename to crates/aether-kernel/src/loader.rs index 1ed1cd0..2503f00 100644 --- a/aether-kernel/src/loader.rs +++ b/crates/aether-kernel/src/loader.rs @@ -6,6 +6,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + use aether_core::topology::{is_shape_valid, verify_sliding_window}; /// ELF magic bytes diff --git a/aether-kernel/src/main.rs b/crates/aether-kernel/src/main.rs similarity index 86% rename from aether-kernel/src/main.rs rename to crates/aether-kernel/src/main.rs index b4948ff..2842464 100644 --- a/aether-kernel/src/main.rs +++ b/crates/aether-kernel/src/main.rs @@ -6,6 +6,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![no_std] #![no_main] #![feature(abi_x86_interrupt)] @@ -43,22 +50,28 @@ fn kernel_main(boot_info_addr: u64) -> ! { // PHASE 1: BIO-SCAN (Hardware Discovery) // ═══════════════════════════════════════════════════════════════════════════ serial_println!("[BIO-SCAN] Scanning organism topology..."); - + // Construct BootInfo from the passed address (Multiboot2) // Safety: We assume the bootloader passed a valid address in rdi/first arg. let boot_info = unsafe { aether_kernel::boot::bios::BootInfo::new(boot_info_addr) }; - + // Perform Bio-Scan let topology = aether_kernel::boot::topology::HardwareTopology::bio_scan(&boot_info); - + serial_println!("[BIO-SCAN] Neural Clusters (Cores): {}", topology.cpu_cores); - serial_println!("[BIO-SCAN] Synaptic Space (RAM) : {} MB", topology.total_memory / 1024 / 1024); - serial_println!("[BIO-SCAN] Sensory Organs (I/O) : {:?}", topology.io_capabilities); + serial_println!( + "[BIO-SCAN] Synaptic Space (RAM) : {} MB", + topology.total_memory / 1024 / 1024 + ); + serial_println!( + "[BIO-SCAN] Sensory Organs (I/O) : {:?}", + topology.io_capabilities + ); // ═══════════════════════════════════════════════════════════════════════════ // PHASE 2: ADAPTIVE INITIALIZATION // ═══════════════════════════════════════════════════════════════════════════ - + // Initialize heap allocator allocator::init_heap(); serial_println!("[INIT] Heap allocator initialized"); @@ -73,14 +86,14 @@ fn kernel_main(boot_info_addr: u64) -> ! { serial_println!("[INIT] Sparse scheduler initialized"); serial_println!("[INIT] ε₀ = {:.4}", scheduler.governor().epsilon()); - + // Adaptive Logic based on Topology if topology.total_memory > 32 * 1024 * 1024 * 1024 { - serial_println!("[ADAPT] High Memory detected > 32GB: Enabling Deep Manifold History"); - // Enable deep history (placeholder) + serial_println!("[ADAPT] High Memory detected > 32GB: Enabling Deep Manifold History"); + // Enable deep history (placeholder) } else if topology.total_memory < 1 * 1024 * 1024 * 1024 { - serial_println!("[ADAPT] Low Memory detected < 1GB: Switching to Sparse Mode"); - // Enable sparse mode (placeholder) + serial_println!("[ADAPT] Low Memory detected < 1GB: Switching to Sparse Mode"); + // Enable sparse mode (placeholder) } serial_println!(""); diff --git a/aether-kernel/src/scheduler.rs b/crates/aether-kernel/src/scheduler.rs similarity index 93% rename from aether-kernel/src/scheduler.rs rename to crates/aether-kernel/src/scheduler.rs index e0fc8ca..a80755f 100644 --- a/aether-kernel/src/scheduler.rs +++ b/crates/aether-kernel/src/scheduler.rs @@ -12,6 +12,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + use aether_core::governor::GeometricGovernor; use aether_core::state::SystemState; diff --git a/aether-kernel/src/serial.rs b/crates/aether-kernel/src/serial.rs similarity index 82% rename from aether-kernel/src/serial.rs rename to crates/aether-kernel/src/serial.rs index 1a7f543..0d1a1ec 100644 --- a/aether-kernel/src/serial.rs +++ b/crates/aether-kernel/src/serial.rs @@ -6,6 +6,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + use core::fmt::{self, Write}; use spin::Mutex; use x86_64::instructions::port::Port; diff --git a/crates/aether-kernel/tree.txt b/crates/aether-kernel/tree.txt new file mode 100644 index 0000000..77d0735 Binary files /dev/null and b/crates/aether-kernel/tree.txt differ diff --git a/crates/aether-kernel/tree2.txt b/crates/aether-kernel/tree2.txt new file mode 100644 index 0000000..380f31e --- /dev/null +++ b/crates/aether-kernel/tree2.txt @@ -0,0 +1,269 @@ +aegis-kernel v0.1.0 (C:\Users\teert\OneDrive\Desktop\New folder (11)\aegis\aegis-kernel) +├── aegis-core v0.1.0 (C:\Users\teert\OneDrive\Desktop\New folder (11)\aegis\aegis-core) +│ ├── heapless v0.8.0 +│ │ ├── hash32 v0.3.1 +│ │ │ └── byteorder v1.5.0 +│ │ └── stable_deref_trait v1.2.1 +│ ├── libm v0.2.15 +│ └── nalgebra v0.32.6 +│ ├── approx v0.5.1 +│ │ └── num-traits v0.2.19 +│ │ └── libm v0.2.15 +│ │ [build-dependencies] +│ │ └── autocfg v1.5.0 +│ ├── num-complex v0.4.6 +│ │ └── num-traits v0.2.19 (*) +│ ├── num-rational v0.4.2 +│ │ ├── num-integer v0.1.46 +│ │ │ └── num-traits v0.2.19 (*) +│ │ └── num-traits v0.2.19 (*) +│ ├── num-traits v0.2.19 (*) +│ ├── simba v0.8.1 +│ │ ├── approx v0.5.1 (*) +│ │ ├── num-complex v0.4.6 (*) +│ │ ├── num-traits v0.2.19 (*) +│ │ └── paste v1.0.15 (proc-macro) +│ └── typenum v1.19.0 +├── aegis-lang v0.1.0 (C:\Users\teert\OneDrive\Desktop\New folder (11)\aegis\aegis-lang) +│ ├── aegis-core v0.1.0 (C:\Users\teert\OneDrive\Desktop\New folder (11)\aegis\aegis-core) (*) +│ ├── heapless v0.8.0 (*) +│ ├── libm v0.2.15 +│ ├── reqwest v0.11.27 +│ │ ├── base64 v0.21.7 +│ │ ├── bytes v1.11.0 +│ │ ├── encoding_rs v0.8.35 +│ │ │ └── cfg-if v1.0.4 +│ │ ├── futures-core v0.3.31 +│ │ ├── futures-util v0.3.31 +│ │ │ ├── futures-core v0.3.31 +│ │ │ ├── futures-io v0.3.31 +│ │ │ ├── futures-task v0.3.31 +│ │ │ ├── memchr v2.7.6 +│ │ │ ├── pin-project-lite v0.2.16 +│ │ │ ├── pin-utils v0.1.0 +│ │ │ └── slab v0.4.11 +│ │ ├── h2 v0.3.27 +│ │ │ ├── bytes v1.11.0 +│ │ │ ├── fnv v1.0.7 +│ │ │ ├── futures-core v0.3.31 +│ │ │ ├── futures-sink v0.3.31 +│ │ │ ├── futures-util v0.3.31 (*) +│ │ │ ├── http v0.2.12 +│ │ │ │ ├── bytes v1.11.0 +│ │ │ │ ├── fnv v1.0.7 +│ │ │ │ └── itoa v1.0.17 +│ │ │ ├── indexmap v2.13.0 +│ │ │ │ ├── equivalent v1.0.2 +│ │ │ │ └── hashbrown v0.16.1 +│ │ │ ├── slab v0.4.11 +│ │ │ ├── tokio v1.49.0 +│ │ │ │ ├── bytes v1.11.0 +│ │ │ │ ├── mio v1.1.1 +│ │ │ │ ├── pin-project-lite v0.2.16 +│ │ │ │ └── socket2 v0.6.2 +│ │ │ ├── tokio-util v0.7.18 +│ │ │ │ ├── bytes v1.11.0 +│ │ │ │ ├── futures-core v0.3.31 +│ │ │ │ ├── futures-sink v0.3.31 +│ │ │ │ ├── pin-project-lite v0.2.16 +│ │ │ │ └── tokio v1.49.0 (*) +│ │ │ └── tracing v0.1.44 +│ │ │ ├── pin-project-lite v0.2.16 +│ │ │ └── tracing-core v0.1.36 +│ │ │ └── once_cell v1.21.3 +│ │ ├── http v0.2.12 (*) +│ │ ├── http-body v0.4.6 +│ │ │ ├── bytes v1.11.0 +│ │ │ ├── http v0.2.12 (*) +│ │ │ └── pin-project-lite v0.2.16 +│ │ ├── hyper v0.14.32 +│ │ │ ├── bytes v1.11.0 +│ │ │ ├── futures-channel v0.3.31 +│ │ │ │ └── futures-core v0.3.31 +│ │ │ ├── futures-core v0.3.31 +│ │ │ ├── futures-util v0.3.31 (*) +│ │ │ ├── h2 v0.3.27 (*) +│ │ │ ├── http v0.2.12 (*) +│ │ │ ├── http-body v0.4.6 (*) +│ │ │ ├── httparse v1.10.1 +│ │ │ ├── httpdate v1.0.3 +│ │ │ ├── itoa v1.0.17 +│ │ │ ├── pin-project-lite v0.2.16 +│ │ │ ├── socket2 v0.5.10 +│ │ │ ├── tokio v1.49.0 (*) +│ │ │ ├── tower-service v0.3.3 +│ │ │ ├── tracing v0.1.44 (*) +│ │ │ └── want v0.3.1 +│ │ │ └── try-lock v0.2.5 +│ │ ├── hyper-rustls v0.24.2 +│ │ │ ├── futures-util v0.3.31 (*) +│ │ │ ├── http v0.2.12 (*) +│ │ │ ├── hyper v0.14.32 (*) +│ │ │ ├── rustls v0.21.12 +│ │ │ │ ├── log v0.4.29 +│ │ │ │ ├── ring v0.17.14 +│ │ │ │ │ ├── cfg-if v1.0.4 +│ │ │ │ │ ├── getrandom v0.2.17 +│ │ │ │ │ │ └── cfg-if v1.0.4 +│ │ │ │ │ └── untrusted v0.9.0 +│ │ │ │ │ [build-dependencies] +│ │ │ │ │ └── cc v1.2.54 +│ │ │ │ │ ├── find-msvc-tools v0.1.8 +│ │ │ │ │ └── shlex v1.3.0 +│ │ │ │ ├── rustls-webpki v0.101.7 +│ │ │ │ │ ├── ring v0.17.14 (*) +│ │ │ │ │ └── untrusted v0.9.0 +│ │ │ │ └── sct v0.7.1 +│ │ │ │ ├── ring v0.17.14 (*) +│ │ │ │ └── untrusted v0.9.0 +│ │ │ ├── tokio v1.49.0 (*) +│ │ │ └── tokio-rustls v0.24.1 +│ │ │ ├── rustls v0.21.12 (*) +│ │ │ └── tokio v1.49.0 (*) +│ │ ├── ipnet v2.11.0 +│ │ ├── log v0.4.29 +│ │ ├── mime v0.3.17 +│ │ ├── once_cell v1.21.3 +│ │ ├── percent-encoding v2.3.2 +│ │ ├── pin-project-lite v0.2.16 +│ │ ├── rustls v0.21.12 (*) +│ │ ├── rustls-pemfile v1.0.4 +│ │ │ └── base64 v0.21.7 +│ │ ├── serde v1.0.228 +│ │ │ ├── serde_core v1.0.228 +│ │ │ └── serde_derive v1.0.228 (proc-macro) +│ │ │ ├── proc-macro2 v1.0.106 +│ │ │ │ └── unicode-ident v1.0.22 +│ │ │ ├── quote v1.0.43 +│ │ │ │ └── proc-macro2 v1.0.106 (*) +│ │ │ └── syn v2.0.114 +│ │ │ ├── proc-macro2 v1.0.106 (*) +│ │ │ ├── quote v1.0.43 (*) +│ │ │ └── unicode-ident v1.0.22 +│ │ ├── serde_urlencoded v0.7.1 +│ │ │ ├── form_urlencoded v1.2.2 +│ │ │ │ └── percent-encoding v2.3.2 +│ │ │ ├── itoa v1.0.17 +│ │ │ ├── ryu v1.0.22 +│ │ │ └── serde v1.0.228 (*) +│ │ ├── sync_wrapper v0.1.2 +│ │ ├── tokio v1.49.0 (*) +│ │ ├── tokio-rustls v0.24.1 (*) +│ │ ├── tower-service v0.3.3 +│ │ ├── url v2.5.8 +│ │ │ ├── form_urlencoded v1.2.2 (*) +│ │ │ ├── idna v1.1.0 +│ │ │ │ ├── idna_adapter v1.2.1 +│ │ │ │ │ ├── icu_normalizer v2.1.1 +│ │ │ │ │ │ ├── icu_collections v2.1.1 +│ │ │ │ │ │ │ ├── displaydoc v0.2.5 (proc-macro) +│ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.106 (*) +│ │ │ │ │ │ │ │ ├── quote v1.0.43 (*) +│ │ │ │ │ │ │ │ └── syn v2.0.114 (*) +│ │ │ │ │ │ │ ├── potential_utf v0.1.4 +│ │ │ │ │ │ │ │ └── zerovec v0.11.5 +│ │ │ │ │ │ │ │ ├── yoke v0.8.1 +│ │ │ │ │ │ │ │ │ ├── stable_deref_trait v1.2.1 +│ │ │ │ │ │ │ │ │ ├── yoke-derive v0.8.1 (proc-macro) +│ │ │ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.106 (*) +│ │ │ │ │ │ │ │ │ │ ├── quote v1.0.43 (*) +│ │ │ │ │ │ │ │ │ │ ├── syn v2.0.114 (*) +│ │ │ │ │ │ │ │ │ │ └── synstructure v0.13.2 +│ │ │ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.106 (*) +│ │ │ │ │ │ │ │ │ │ ├── quote v1.0.43 (*) +│ │ │ │ │ │ │ │ │ │ └── syn v2.0.114 (*) +│ │ │ │ │ │ │ │ │ └── zerofrom v0.1.6 +│ │ │ │ │ │ │ │ │ └── zerofrom-derive v0.1.6 (proc-macro) +│ │ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.106 (*) +│ │ │ │ │ │ │ │ │ ├── quote v1.0.43 (*) +│ │ │ │ │ │ │ │ │ ├── syn v2.0.114 (*) +│ │ │ │ │ │ │ │ │ └── synstructure v0.13.2 (*) +│ │ │ │ │ │ │ │ ├── zerofrom v0.1.6 (*) +│ │ │ │ │ │ │ │ └── zerovec-derive v0.11.2 (proc-macro) +│ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.106 (*) +│ │ │ │ │ │ │ │ ├── quote v1.0.43 (*) +│ │ │ │ │ │ │ │ └── syn v2.0.114 (*) +│ │ │ │ │ │ │ ├── yoke v0.8.1 (*) +│ │ │ │ │ │ │ ├── zerofrom v0.1.6 (*) +│ │ │ │ │ │ │ └── zerovec v0.11.5 (*) +│ │ │ │ │ │ ├── icu_normalizer_data v2.1.1 +│ │ │ │ │ │ ├── icu_provider v2.1.1 +│ │ │ │ │ │ │ ├── displaydoc v0.2.5 (proc-macro) (*) +│ │ │ │ │ │ │ ├── icu_locale_core v2.1.1 +│ │ │ │ │ │ │ │ ├── displaydoc v0.2.5 (proc-macro) (*) +│ │ │ │ │ │ │ │ ├── litemap v0.8.1 +│ │ │ │ │ │ │ │ ├── tinystr v0.8.2 +│ │ │ │ │ │ │ │ │ ├── displaydoc v0.2.5 (proc-macro) (*) +│ │ │ │ │ │ │ │ │ └── zerovec v0.11.5 (*) +│ │ │ │ │ │ │ │ ├── writeable v0.6.2 +│ │ │ │ │ │ │ │ └── zerovec v0.11.5 (*) +│ │ │ │ │ │ │ ├── writeable v0.6.2 +│ │ │ │ │ │ │ ├── yoke v0.8.1 (*) +│ │ │ │ │ │ │ ├── zerofrom v0.1.6 (*) +│ │ │ │ │ │ │ ├── zerotrie v0.2.3 +│ │ │ │ │ │ │ │ ├── displaydoc v0.2.5 (proc-macro) (*) +│ │ │ │ │ │ │ │ ├── yoke v0.8.1 (*) +│ │ │ │ │ │ │ │ └── zerofrom v0.1.6 (*) +│ │ │ │ │ │ │ └── zerovec v0.11.5 (*) +│ │ │ │ │ │ ├── smallvec v1.15.1 +│ │ │ │ │ │ └── zerovec v0.11.5 (*) +│ │ │ │ │ └── icu_properties v2.1.2 +│ │ │ │ │ ├── icu_collections v2.1.1 (*) +│ │ │ │ │ ├── icu_locale_core v2.1.1 (*) +│ │ │ │ │ ├── icu_properties_data v2.1.2 +│ │ │ │ │ ├── icu_provider v2.1.1 (*) +│ │ │ │ │ ├── zerotrie v0.2.3 (*) +│ │ │ │ │ └── zerovec v0.11.5 (*) +│ │ │ │ ├── smallvec v1.15.1 +│ │ │ │ └── utf8_iter v1.0.4 +│ │ │ └── percent-encoding v2.3.2 +│ │ └── webpki-roots v0.25.4 +│ └── safetensors v0.3.3 +│ ├── serde v1.0.228 (*) +│ └── serde_json v1.0.149 +│ ├── itoa v1.0.17 +│ ├── memchr v2.7.6 +│ ├── serde_core v1.0.228 +│ └── zmij v1.0.16 +├── bootloader v0.11.14 +│ ├── anyhow v1.0.100 +│ ├── bootloader-boot-config v0.11.14 +│ │ └── serde v1.0.228 (*) +│ ├── fatfs v0.3.6 +│ │ ├── bitflags v1.3.2 +│ │ ├── byteorder v1.5.0 +│ │ └── log v0.4.29 +│ ├── serde_json v1.0.149 (*) +│ └── tempfile v3.24.0 +│ ├── fastrand v2.3.0 +│ └── once_cell v1.21.3 +│ [build-dependencies] +│ └── llvm-tools v0.1.1 +├── heapless v0.8.0 (*) +├── libm v0.2.15 +├── multiboot2 v0.24.1 +│ ├── bitflags v2.10.0 +│ ├── log v0.4.29 +│ ├── multiboot2-common v0.3.0 +│ │ ├── ptr_meta v0.3.1 +│ │ │ └── ptr_meta_derive v0.3.1 (proc-macro) +│ │ │ ├── proc-macro2 v1.0.106 (*) +│ │ │ ├── quote v1.0.43 (*) +│ │ │ └── syn v2.0.114 (*) +│ │ └── thiserror v2.0.18 +│ │ └── thiserror-impl v2.0.18 (proc-macro) +│ │ ├── proc-macro2 v1.0.106 (*) +│ │ ├── quote v1.0.43 (*) +│ │ └── syn v2.0.114 (*) +│ ├── ptr_meta v0.3.1 (*) +│ ├── thiserror v2.0.18 (*) +│ └── uefi-raw v0.12.0 +│ ├── bitflags v2.10.0 +│ └── uguid v2.2.1 +├── spin v0.9.8 +├── volatile v0.5.4 +└── x86_64 v0.14.13 + ├── bit_field v0.10.3 + ├── bitflags v2.10.0 + ├── rustversion v1.0.22 (proc-macro) + └── volatile v0.4.6 diff --git a/crates/aether-kernel/tree3.txt b/crates/aether-kernel/tree3.txt new file mode 100644 index 0000000..d8651c0 Binary files /dev/null and b/crates/aether-kernel/tree3.txt differ diff --git a/aether-lang/Cargo.toml b/crates/aether-lang/Cargo.toml similarity index 100% rename from aether-lang/Cargo.toml rename to crates/aether-lang/Cargo.toml diff --git a/crates/aether-lang/output.txt b/crates/aether-lang/output.txt new file mode 100644 index 0000000..a2b61ed Binary files /dev/null and b/crates/aether-lang/output.txt differ diff --git a/aether-lang/src/ascii_render.rs b/crates/aether-lang/src/ascii_render.rs similarity index 89% rename from aether-lang/src/ascii_render.rs rename to crates/aether-lang/src/ascii_render.rs index 249ab0e..c541dbb 100644 --- a/aether-lang/src/ascii_render.rs +++ b/crates/aether-lang/src/ascii_render.rs @@ -6,6 +6,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![allow(dead_code)] use heapless::String; diff --git a/aether-lang/src/ast.rs b/crates/aether-lang/src/ast.rs similarity index 91% rename from aether-lang/src/ast.rs rename to crates/aether-lang/src/ast.rs index 4fb5029..a351d9e 100644 --- a/aether-lang/src/ast.rs +++ b/crates/aether-lang/src/ast.rs @@ -7,12 +7,19 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![allow(dead_code)] extern crate alloc; +use alloc::boxed::Box; use alloc::string::String; use alloc::vec::Vec; -use alloc::boxed::Box; /// Source Span for diagnostics (Line, Column, Length) #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] @@ -44,10 +51,7 @@ impl Spanned { #[derive(Debug, Clone, Copy, PartialEq)] pub enum Number { Int(i64), - Float { - int_part: i64, - frac_part: i64, - }, + Float { int_part: i64, frac_part: i64 }, } impl Number { @@ -82,19 +86,28 @@ pub struct ConfigPair { /// Binary Operators #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BinaryOp { - Add, Sub, Mul, Div, - Eq, Neq, Lt, Gt, Le, Ge, - And, Or, + Add, + Sub, + Mul, + Div, + Mod, + Eq, + Neq, + Lt, + Gt, + Le, + Ge, + And, + Or, } /// Unary Operators #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum UnaryOp { - Neg, Not, + Neg, + Not, } - - // ═══════════════════════════════════════════════════════════════════════════════ // Expression Types // ═══════════════════════════════════════════════════════════════════════════════ @@ -107,13 +120,13 @@ pub type Expr = Spanned; pub enum ExprKind { /// Literal value Literal(Literal), - + /// Identifier: M, data, dim Ident(Ident), /// Binary Operation: a + b BinaryOp(Box, BinaryOp, Box), - + /// Unary Operation: -a UnaryOp(UnaryOp, Box), @@ -187,6 +200,13 @@ pub struct VarDecl { pub value: Expr, } +/// Variable reassignment: count = count + 1 +#[derive(Debug, Clone, PartialEq)] +pub struct AssignStmt { + pub name: Ident, + pub value: Expr, +} + /// Regression statement with configuration #[derive(Debug, Clone, PartialEq)] pub struct RegressStmt { @@ -272,9 +292,10 @@ pub struct ForStmt { pub body: Block, } -/// Seal loop (topological): seal { ... } +/// Seal loop (topological): seal until condition { ... } or seal { ... } #[derive(Debug, Clone, PartialEq)] pub struct LoopStmt { + pub until: Option, pub body: Block, } @@ -324,6 +345,7 @@ pub enum StmtKind { Manifold(ManifoldDecl), Block(BlockDecl), Var(VarDecl), + Assign(AssignStmt), Regress(RegressStmt), Render(RenderStmt), diff --git a/crates/aether-lang/src/interpreter.rs b/crates/aether-lang/src/interpreter.rs new file mode 100644 index 0000000..da66f2e --- /dev/null +++ b/crates/aether-lang/src/interpreter.rs @@ -0,0 +1,1976 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +//! AEGIS Interpreter - Runtime execution of AEGIS programs +// ═══════════════════════════════════════════════════════════════════════════════ +//! +//! Executes parsed AEGIS AST, managing: +//! - 3D manifold workspaces +//! - Block geometry computations +//! - Escalating regression benchmarks +//! - Topological convergence detection +// ═══════════════════════════════════════════════════════════════════════════════ + +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + +#![allow(dead_code)] + +#[cfg(not(feature = "std"))] +extern crate alloc; + +#[cfg(not(feature = "std"))] +use alloc::boxed::Box; +#[cfg(not(feature = "std"))] +use alloc::collections::BTreeMap; +#[cfg(not(feature = "std"))] +use alloc::string::String; +#[cfg(not(feature = "std"))] +use alloc::string::ToString; +#[cfg(not(feature = "std"))] +use alloc::{format, vec}; + +#[cfg(not(feature = "std"))] +macro_rules! println { + ($($arg:tt)*) => {}; +} + +#[cfg(feature = "std")] +use std::boxed::Box; +#[cfg(feature = "std")] +use std::collections::BTreeMap; +#[cfg(feature = "std")] +use std::string::String; +#[cfg(feature = "std")] +use std::vec::Vec; + +use crate::ast::*; +use aether_core::aether::{BlockMetadata, DriftDetector, HierarchicalBlockTree}; +use aether_core::manifold::{ManifoldPoint, TimeDelayEmbedder}; +use aether_core::ml::convolution::Conv2D; +use aether_core::ml::linalg::LossConfig; +use aether_core::ml::tensor::Tensor; +use aether_core::ml::{Activation, KMeans, OptimizerConfig, MLP}; +use aether_core::persistence::{ + persistent_homology, ComplexKind, PersistenceConfig, PersistenceDiagram, +}; +use libm::{fabs, sqrt}; + +#[cfg(not(feature = "std"))] +use alloc::sync::Arc; +#[cfg(feature = "std")] +use safetensors::SafeTensors; +#[cfg(feature = "std")] +use std::sync::Arc; + +#[cfg(feature = "ml")] +use candle_core::{Device, Tensor as CandleTensor}; +#[cfg(feature = "ml")] +use candle_transformers::models::quantized_llama::ModelWeights as LlamaWeights; +#[cfg(feature = "ml")] +use tokenizers::Tokenizer; + +/// Embedding dimension +const DIM: usize = 3; + +// ═══════════════════════════════════════════════════════════════════════════════ +// Runtime Values +// ═══════════════════════════════════════════════════════════════════════════════ + +/// Runtime value types +#[derive(Debug, Clone)] +pub enum Value { + /// Numeric value + Num(f64), + /// Boolean + Bool(bool), + /// String + Str(String), + /// 3D Manifold reference + Manifold(ManifoldHandle), + /// Geometric block reference + Block(BlockHandle), + /// 3D Point + Point([f64; DIM]), + /// Regression result + RegressionResult(RegressionOutput), + /// Persistent homology diagram + Persistence(PersistenceDiagram), + /// Class Definition + Class(ClassHandle), + /// Object Instance + Object(ObjectHandle), + /// Native Function (for Standard Library) + NativeFn(NativeFunction), + /// User-defined function + Function(FnDecl), + /// Dynamic List (Python-like) + List(Vec), + /// ML Types + Mlp(Box), + KMeans(Box>), + Conv2D(Box), + /// Void/Unit + Unit, + /// Module Namespace + Module(String), + /// Dynamic Tensor + Tensor(Tensor), + /// Llama Model (Wrapped) + #[cfg(feature = "ml")] + LlamaModel(Arc), +} + +#[cfg(feature = "ml")] +#[derive(Debug)] +pub struct LlamaContext { + pub model: LlamaWeights, + pub tokenizer: Tokenizer, + pub name: String, +} + +/// Native function pointer type +#[derive(Debug, Clone)] +pub enum NativeFunction { + MathSin, + MathCos, + MathSqrt, + MathExp, + TopoPh, + TopoBetti, + TopoIntervals, + Print, + // ML Constructors + MlpNew, + KMeansNew, + Conv2DNew, + // Seal Functions + SealTrain, + // Tensor Ops + MlLoadWeights, + MlMatMul, + MlAdd, + MlForward, + MlRelu, + MlSoftmax, + MlEmbed, + MlAttention, + MlGpuCheck, + MlBackward, + MlUpdate, + MlLoadLlama, + MlGenerate, +} + +/// Handle to a manifold workspace +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ManifoldHandle(pub usize); + +/// Handle to a geometric block +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct BlockHandle(pub usize); + +/// Handle to a class definition +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ClassHandle(pub usize); + +/// Handle to an object instance +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ObjectHandle(pub usize); + +/// Class Definition Runtime +#[derive(Debug, Clone)] +pub struct ClassDef { + pub name: String, + pub fields: Vec, + pub methods: BTreeMap, +} + +/// Object Instance Runtime +#[derive(Debug, Clone)] +pub struct ObjectInstance { + pub class: ClassHandle, + pub fields: BTreeMap, +} + +/// Regression output with convergence info +#[derive(Debug, Clone)] +pub struct RegressionOutput { + /// Final coefficients + pub coefficients: [f64; 8], + /// Number of epochs to converge + pub epochs: u32, + /// Final error + pub final_error: f64, + /// Converged? + pub converged: bool, + /// Betti numbers at convergence + pub betti: (u32, u32), +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Manifold Workspace +// ═══════════════════════════════════════════════════════════════════════════════ + +/// 3D Manifold workspace containing embedded points +#[derive(Debug)] +pub struct ManifoldWorkspace { + /// Embedded points in 3D + pub points: Vec>, + /// Hierarchical block tree for AETHER + pub block_tree: HierarchicalBlockTree, + /// Drift detector for convergence + pub drift: DriftDetector, + /// Time-delay embedder + pub embedder: TimeDelayEmbedder, + /// Current centroid + pub centroid: [f64; DIM], +} + +impl ManifoldWorkspace { + pub fn new(tau: usize) -> Self { + Self { + points: Vec::new(), + block_tree: HierarchicalBlockTree::new(), + drift: DriftDetector::new(), + embedder: TimeDelayEmbedder::new(tau), + centroid: [0.0; DIM], + } + } + + /// Embed raw data into 3D manifold + pub fn embed_data(&mut self, data: &[f64]) { + self.points.clear(); + self.embedder.reset(); + + for &val in data { + self.embedder.push(val); + if let Some(point) = self.embedder.embed() { + self.points.push(point); + } + } + + self.update_centroid(); + } + + /// Update centroid from points + fn update_centroid(&mut self) { + if self.points.is_empty() { + return; + } + + let mut sum = [0.0; DIM]; + for p in &self.points { + for (d, s) in sum.iter_mut().enumerate().take(DIM) { + *s += p.coords[d]; + } + } + + let n = self.points.len() as f64; + for (d, s) in sum.iter().enumerate().take(DIM) { + self.centroid[d] = s / n; + } + } + + /// Extract block from index range + pub fn extract_block(&self, start: usize, end: usize) -> BlockMetadata { + let end = end.min(self.points.len()); + let start = start.min(end); + + if start >= end { + return BlockMetadata::empty(); + } + + let mut block_points = Vec::new(); + for i in start..end { + block_points.push(self.points[i].coords); + } + + BlockMetadata::from_points(&block_points) + } +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Escalating Regression Engine +// ═══════════════════════════════════════════════════════════════════════════════ + +/// Regression model types +#[derive(Debug, Clone, Copy)] +pub enum RegressionModel { + Linear, + Polynomial { degree: u8 }, + Rbf { gamma: f64 }, +} + +/// Escalating benchmark system +pub struct EscalatingRegressor { + /// Current model complexity + current_level: u32, + /// Target for regression + target: Vec, + /// Predictions + predictions: Vec, + /// Convergence epsilon + epsilon: f64, + /// Betti stability window + betti_history: Vec<(u32, u32)>, +} + +impl EscalatingRegressor { + pub fn new(epsilon: f64) -> Self { + Self { + current_level: 0, + target: Vec::new(), + predictions: Vec::new(), + epsilon, + betti_history: Vec::new(), + } + } + + /// Set target values for regression + pub fn set_target(&mut self, data: &[f64]) { + self.target.clear(); + for &v in data { + self.target.push(v); + } + } + + /// Run escalating regression until convergence + pub fn run_escalating( + &mut self, + manifold: &ManifoldWorkspace, + max_epochs: u32, + ) -> RegressionOutput { + let mut coefficients = [0.0f64; 8]; + let mut error = f64::MAX; + let mut converged = false; + let mut epochs = 0u32; + + for epoch in 0..max_epochs { + epochs = epoch; + let model = self.escalate_model(epoch); + coefficients = self.fit_model(manifold, &model); + error = self.compute_error(manifold, &coefficients, &model); + let betti = self.compute_residual_betti(manifold, &coefficients, &model); + self.betti_history.push(betti); + if self.betti_history.len() > 10 { + self.betti_history.remove(0); + } + if self.is_converged(error, &betti) { + converged = true; + break; + } + } + + RegressionOutput { + coefficients, + epochs, + final_error: error, + converged, + betti: *self.betti_history.last().unwrap_or(&(0, 0)), + } + } + + fn escalate_model(&self, epoch: u32) -> RegressionModel { + match epoch { + 0 => RegressionModel::Linear, + 1 => RegressionModel::Polynomial { degree: 2 }, + 2 => RegressionModel::Polynomial { degree: 3 }, + 3 => RegressionModel::Polynomial { degree: 4 }, + 4..=6 => RegressionModel::Rbf { + gamma: 0.1 * (epoch as f64), + }, + _ => RegressionModel::Rbf { gamma: 1.0 }, + } + } + + fn fit_model(&self, manifold: &ManifoldWorkspace, model: &RegressionModel) -> [f64; 8] { + let mut coeffs = [0.0f64; 8]; + + if manifold.points.is_empty() || self.target.is_empty() { + return coeffs; + } + + match model { + RegressionModel::Linear => { + let n = manifold.points.len().min(self.target.len()) as f64; + let mut sum_x = 0.0; + let mut sum_y = 0.0; + let mut sum_xy = 0.0; + let mut sum_xx = 0.0; + + for (i, p) in manifold.points.iter().enumerate() { + if i >= self.target.len() { + break; + } + let x = p.coords[0]; + let y = self.target[i]; + sum_x += x; + sum_y += y; + sum_xy += x * y; + sum_xx += x * x; + } + + let denom = n * sum_xx - sum_x * sum_x; + if fabs(denom) > 1e-10 { + coeffs[1] = (n * sum_xy - sum_x * sum_y) / denom; + coeffs[0] = (sum_y - coeffs[1] * sum_x) / n; + } + } + RegressionModel::Polynomial { degree } => { + coeffs = self.fit_model(manifold, &RegressionModel::Linear); + coeffs[*degree as usize] = 0.01; + } + RegressionModel::Rbf { .. } => { + coeffs = self.fit_model(manifold, &RegressionModel::Polynomial { degree: 3 }); + } + } + + coeffs + } + + fn compute_error( + &self, + manifold: &ManifoldWorkspace, + coeffs: &[f64; 8], + model: &RegressionModel, + ) -> f64 { + let mut mse = 0.0; + let mut count = 0; + + for (i, p) in manifold.points.iter().enumerate() { + if i >= self.target.len() { + break; + } + let pred = self.predict(p.coords[0], coeffs, model); + let err = pred - self.target[i]; + mse += err * err; + count += 1; + } + + if count > 0 { + mse /= count as f64; + sqrt(mse) + } else { + f64::MAX + } + } + + fn predict(&self, x: f64, coeffs: &[f64; 8], model: &RegressionModel) -> f64 { + match model { + RegressionModel::Linear => coeffs[0] + coeffs[1] * x, + RegressionModel::Polynomial { degree } => { + let mut y = coeffs[0]; + let mut x_pow = x; + for coeff in coeffs.iter().take((*degree as usize).min(7) + 1).skip(1) { + y += coeff * x_pow; + x_pow *= x; + } + y + } + RegressionModel::Rbf { .. } => { + self.predict(x, coeffs, &RegressionModel::Polynomial { degree: 3 }) + } + } + } + + fn compute_residual_betti( + &self, + manifold: &ManifoldWorkspace, + coeffs: &[f64; 8], + model: &RegressionModel, + ) -> (u32, u32) { + let mut sign_changes = 0u32; + let mut oscillations = 0u32; + let mut prev_residual = 0.0; + let mut prev_sign = true; + + for (i, p) in manifold.points.iter().enumerate() { + if i >= self.target.len() { + break; + } + let pred = self.predict(p.coords[0], coeffs, model); + let residual = self.target[i] - pred; + let sign = residual >= 0.0; + if i > 0 && sign != prev_sign { + sign_changes += 1; + } + if i > 1 { + let delta = residual - prev_residual; + let prev_delta = prev_residual; + if (delta > 0.0) != (prev_delta > 0.0) { + oscillations += 1; + } + } + prev_residual = residual; + prev_sign = sign; + } + + (sign_changes / 2 + 1, oscillations / 4) + } + + fn is_converged(&self, error: f64, current_betti: &(u32, u32)) -> bool { + if error < self.epsilon { + return true; + } + if self.betti_history.len() >= 3 { + let recent: Vec<&(u32, u32)> = self.betti_history.iter().rev().take(3).collect(); + if recent.iter().all(|b| **b == *current_betti) { + return true; + } + } + false + } +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Main Interpreter +// ═══════════════════════════════════════════════════════════════════════════════ + +/// Runtime environment +pub struct Interpreter { + /// Variable bindings + pub variables: BTreeMap, // Made public for tests + /// Manifold workspaces + manifolds: Vec, + /// Block geometries + blocks: Vec>, + /// Class definitions + classes: Vec, + /// Object instances + objects: Vec, + /// Sample data (for demo) + sample_data: Vec, +} + +enum RuntimeFlow { + Value(Value), + Return(Value), + Break, + Continue, +} + +impl Interpreter { + pub fn new() -> Self { + let mut data = Vec::new(); + for i in 0..64 { + let x = (i as f64) * 0.1; + data.push(libm::sin(x)); + } + + let mut variables = BTreeMap::new(); + variables.insert( + String::from("print"), + Value::NativeFn(NativeFunction::Print), + ); + + Self { + variables, + manifolds: Vec::new(), + blocks: Vec::new(), + classes: Vec::new(), + objects: Vec::new(), + sample_data: data, + } + } + + /// Execute a program + pub fn execute(&mut self, program: &Program) -> Result { + let mut last_value = Value::Unit; + for stmt in &program.statements { + match self.execute_statement(stmt)? { + RuntimeFlow::Value(value) => last_value = value, + RuntimeFlow::Return(value) => return Ok(value), + RuntimeFlow::Break => return Err(String::from("break outside loop")), + RuntimeFlow::Continue => return Err(String::from("continue outside loop")), + } + } + Ok(last_value) + } + + fn execute_statement(&mut self, stmt: &Statement) -> Result { + match &stmt.node { + StmtKind::Manifold(decl) => self.execute_manifold(decl).map(RuntimeFlow::Value), + StmtKind::Block(decl) => self.execute_block(decl).map(RuntimeFlow::Value), + StmtKind::Var(decl) => self.execute_var(decl).map(RuntimeFlow::Value), + StmtKind::Assign(stmt) => self.execute_assign(stmt).map(RuntimeFlow::Value), + StmtKind::Regress(stmt) => self.execute_regress(stmt).map(RuntimeFlow::Value), + StmtKind::Render(stmt) => self.execute_render(stmt).map(RuntimeFlow::Value), + StmtKind::Class(decl) => self.execute_class(decl).map(RuntimeFlow::Value), + StmtKind::Import(stmt) => self.execute_import(stmt).map(RuntimeFlow::Value), + StmtKind::If(stmt) => self.execute_if(stmt), + StmtKind::While(stmt) => self.execute_while(stmt).map(RuntimeFlow::Value), + StmtKind::Loop(stmt) => self.execute_seal(stmt).map(RuntimeFlow::Value), + StmtKind::For(stmt) => self.execute_for(stmt).map(RuntimeFlow::Value), + StmtKind::Fn(decl) => self.execute_fn_decl(decl).map(RuntimeFlow::Value), + StmtKind::Return(stmt) => self.execute_return(stmt), + StmtKind::Break(_) => Ok(RuntimeFlow::Break), + StmtKind::Continue(_) => Ok(RuntimeFlow::Continue), + StmtKind::Expr(expr) => self.evaluate_expr(expr).map(RuntimeFlow::Value), + StmtKind::Empty => Ok(RuntimeFlow::Value(Value::Unit)), + } + } + + fn execute_class(&mut self, decl: &ClassDecl) -> Result { + let mut methods = BTreeMap::new(); + for m in &decl.methods { + methods.insert(m.name.clone(), m.clone()); + } + + let class_def = ClassDef { + name: decl.name.clone(), + fields: decl.fields.clone(), + methods, + }; + + let handle = ClassHandle(self.classes.len()); + self.classes.push(class_def); + self.variables + .insert(decl.name.clone(), Value::Class(handle)); + Ok(Value::Class(handle)) + } + + fn execute_fn_decl(&mut self, decl: &FnDecl) -> Result { + self.variables + .insert(decl.name.clone(), Value::Function(decl.clone())); + Ok(Value::Unit) + } + + fn execute_return(&mut self, stmt: &ReturnStmt) -> Result { + let value = if let Some(expr) = &stmt.value { + self.evaluate_expr(expr)? + } else { + Value::Unit + }; + Ok(RuntimeFlow::Return(value)) + } + + #[allow(unused_variables)] + fn evaluate_new(&mut self, class_name: &String, args: &[Expr]) -> Result { + let class_handle = if let Some(Value::Class(h)) = self.variables.get(class_name) { + *h + } else { + return Err(format!("Class '{}' not found", class_name)); + }; + + let class_def = self.classes[class_handle.0].clone(); + let mut fields = BTreeMap::new(); + for field in &class_def.fields { + let val = self.evaluate_expr(&field.value)?; + fields.insert(field.name.clone(), val); + } + + let obj_handle = ObjectHandle(self.objects.len()); + self.objects.push(ObjectInstance { + class: class_handle, + fields, + }); + + Ok(Value::Object(obj_handle)) + } + + fn execute_import(&mut self, stmt: &ImportStmt) -> Result { + let mod_name = stmt.module.as_str(); + match mod_name { + "math" => self.import_math(stmt), + "topology" => self.import_topology(stmt), + "ml" | "Ml" => self.import_ml(stmt), + "Seal" => self.import_seal(stmt), + _ => Err(format!("Module '{}' not found", mod_name)), + } + } + + fn import_math(&mut self, stmt: &ImportStmt) -> Result { + if let Some(symbol) = &stmt.symbol { + match symbol.as_str() { + "pi" => { + self.variables + .insert(String::from("pi"), Value::Num(core::f64::consts::PI)); + } + "sin" => { + self.variables.insert( + String::from("sin"), + Value::NativeFn(NativeFunction::MathSin), + ); + } + "cos" => { + self.variables.insert( + String::from("cos"), + Value::NativeFn(NativeFunction::MathCos), + ); + } + "sqrt" => { + self.variables.insert( + String::from("sqrt"), + Value::NativeFn(NativeFunction::MathSqrt), + ); + } + "exp" => { + self.variables.insert( + String::from("exp"), + Value::NativeFn(NativeFunction::MathExp), + ); + } + _ => return Err(format!("Symbol '{}' not found in math", symbol)), + } + } else { + self.variables + .insert(String::from("pi"), Value::Num(core::f64::consts::PI)); + self.variables.insert( + String::from("sin"), + Value::NativeFn(NativeFunction::MathSin), + ); + self.variables.insert( + String::from("cos"), + Value::NativeFn(NativeFunction::MathCos), + ); + self.variables.insert( + String::from("sqrt"), + Value::NativeFn(NativeFunction::MathSqrt), + ); + } + Ok(Value::Unit) + } + + fn import_topology(&mut self, stmt: &ImportStmt) -> Result { + if let Some(symbol) = &stmt.symbol { + match symbol.as_str() { + "ph" => self + .variables + .insert(String::from("ph"), Value::NativeFn(NativeFunction::TopoPh)), + "Betti" => self.variables.insert( + String::from("Betti"), + Value::NativeFn(NativeFunction::TopoBetti), + ), + "betti" => self.variables.insert( + String::from("betti"), + Value::NativeFn(NativeFunction::TopoBetti), + ), + "intervals" => self.variables.insert( + String::from("intervals"), + Value::NativeFn(NativeFunction::TopoIntervals), + ), + _ => return Err(format!("Symbol '{}' not found in topology", symbol)), + }; + } else { + self.variables.insert( + String::from("topology"), + Value::Module(String::from("topology")), + ); + self.variables + .insert(String::from("ph"), Value::NativeFn(NativeFunction::TopoPh)); + self.variables.insert( + String::from("Betti"), + Value::NativeFn(NativeFunction::TopoBetti), + ); + self.variables.insert( + String::from("betti"), + Value::NativeFn(NativeFunction::TopoBetti), + ); + self.variables.insert( + String::from("intervals"), + Value::NativeFn(NativeFunction::TopoIntervals), + ); + } + Ok(Value::Unit) + } + + fn import_ml(&mut self, stmt: &ImportStmt) -> Result { + if let Some(symbol) = &stmt.symbol { + match symbol.as_str() { + "MLP" => { + self.variables + .insert(String::from("MLP"), Value::NativeFn(NativeFunction::MlpNew)); + } + "KMeans" => { + self.variables.insert( + String::from("KMeans"), + Value::NativeFn(NativeFunction::KMeansNew), + ); + } + "Conv2D" => { + self.variables.insert( + String::from("Conv2D"), + Value::NativeFn(NativeFunction::Conv2DNew), + ); + } + _ => return Err(format!("Symbol '{}' not found in ml", symbol)), + }; + } else { + self.variables + .insert(String::from("MLP"), Value::NativeFn(NativeFunction::MlpNew)); + self.variables.insert( + String::from("KMeans"), + Value::NativeFn(NativeFunction::KMeansNew), + ); + self.variables.insert( + String::from("Conv2D"), + Value::NativeFn(NativeFunction::Conv2DNew), + ); + self.variables.insert( + String::from("load_weights"), + Value::NativeFn(NativeFunction::MlLoadWeights), + ); + self.variables.insert( + String::from("matmul"), + Value::NativeFn(NativeFunction::MlMatMul), + ); + self.variables + .insert(String::from("add"), Value::NativeFn(NativeFunction::MlAdd)); + self.variables.insert( + String::from("relu"), + Value::NativeFn(NativeFunction::MlRelu), + ); + self.variables.insert( + String::from("softmax"), + Value::NativeFn(NativeFunction::MlSoftmax), + ); + self.variables.insert( + String::from("attention"), + Value::NativeFn(NativeFunction::MlAttention), + ); + self.variables.insert( + String::from("gpu_check"), + Value::NativeFn(NativeFunction::MlGpuCheck), + ); + self.variables.insert( + String::from("backward"), + Value::NativeFn(NativeFunction::MlBackward), + ); + self.variables.insert( + String::from("update"), + Value::NativeFn(NativeFunction::MlUpdate), + ); + self.variables.insert( + String::from("load_llama"), + Value::NativeFn(NativeFunction::MlLoadLlama), + ); + self.variables.insert( + String::from("generate"), + Value::NativeFn(NativeFunction::MlGenerate), + ); + self.variables + .insert(String::from("Ml"), Value::Module(String::from("Ml"))); + } + Ok(Value::Unit) + } + + fn import_seal(&mut self, stmt: &ImportStmt) -> Result { + if let Some(symbol) = &stmt.symbol { + match symbol.as_str() { + "train" => { + self.variables.insert( + String::from("train"), + Value::NativeFn(NativeFunction::SealTrain), + ); + } + _ => return Err(format!("Symbol '{}' not found in Seal", symbol)), + }; + } else { + self.variables + .insert(String::from("Seal"), Value::Module(String::from("Seal"))); + } + Ok(Value::Unit) + } + + fn execute_manifold(&mut self, decl: &ManifoldDecl) -> Result { + let tau = self.extract_tau(&decl.init).unwrap_or(3); + let mut workspace = ManifoldWorkspace::new(tau); + let data = self.extract_embed_data(&decl.init)?; + workspace.embed_data(data.as_deref().unwrap_or(&self.sample_data)); + let handle = ManifoldHandle(self.manifolds.len()); + self.manifolds.push(workspace); + self.variables + .insert(decl.name.clone(), Value::Manifold(handle)); + Ok(Value::Manifold(handle)) + } + + fn extract_embed_data(&mut self, expr: &Expr) -> Result>, String> { + let ExprKind::Call { name, args } = &expr.node else { + return Ok(None); + }; + if name.as_str() != "embed" { + return Ok(None); + } + + for arg in args { + match arg { + CallArg::Positional(expr) => { + let value = self.evaluate_expr(expr)?; + return self.value_to_f64_vec(value).map(Some); + } + CallArg::Named { name, value } if name.as_str() == "data" => { + let value = self.evaluate_expr(value)?; + return self.value_to_f64_vec(value).map(Some); + } + _ => {} + } + } + + Ok(None) + } + + fn value_to_f64_vec(&self, value: Value) -> Result, String> { + match value { + Value::List(values) => { + let mut out = Vec::with_capacity(values.len()); + for value in values { + match value { + Value::Num(n) => out.push(n), + _ => return Err(String::from("embed data must be a numeric list")), + } + } + Ok(out) + } + _ => Err(String::from("embed expects a numeric list")), + } + } + + fn extract_tau(&self, expr: &Expr) -> Option { + if let ExprKind::Call { args, .. } = &expr.node { + for arg in args { + if let CallArg::Named { name, value } = arg { + if name.as_str() == "tau" { + if let ExprKind::Literal(Literal::Num(n)) = &value.node { + return Some(*n as usize); + } + } + } + } + } + None + } + + fn execute_block(&mut self, decl: &BlockDecl) -> Result { + let (manifold_handle, start, end) = self.extract_block_range(&decl.source)?; + if let Some(workspace) = self.manifolds.get(manifold_handle.0) { + let block = workspace.extract_block(start, end); + let handle = BlockHandle(self.blocks.len()); + self.blocks.push(block); + self.variables + .insert(decl.name.clone(), Value::Block(handle)); + Ok(Value::Block(handle)) + } else { + Err("manifold not found".to_string()) + } + } + + fn extract_block_range(&self, expr: &Expr) -> Result<(ManifoldHandle, usize, usize), String> { + match &expr.node { + ExprKind::MethodCall { object, args, .. } => { + let handle = self.get_manifold_handle(object)?; + let (start, end) = self.extract_range_from_args(args); + Ok((handle, start, end)) + } + ExprKind::Index { object, range } => { + let handle = self.get_manifold_handle(object)?; + let start = range.start.as_f64() as usize; + let end = range.end.as_f64() as usize; + Ok((handle, start, end)) + } + _ => Err("invalid block source".to_string()), + } + } + + fn get_manifold_handle(&self, name: &String) -> Result { + if let Some(Value::Manifold(h)) = self.variables.get(name) { + Ok(*h) + } else { + Err("variable is not a manifold".to_string()) + } + } + + fn extract_range_from_args(&self, args: &[CallArg]) -> (usize, usize) { + let mut start = 0usize; + let mut end = 64usize; + + for (i, arg) in args.iter().enumerate() { + if let CallArg::Positional(expr) = arg { + match &expr.node { + ExprKind::Literal(Literal::Num(n)) => { + if i == 0 { + start = *n as usize; + } + if i == 1 { + end = *n as usize; + } + } + ExprKind::Range(r) => { + start = r.start.as_f64() as usize; + end = r.end.as_f64() as usize; + } + _ => {} + } + } + } + (start, end) + } + + fn execute_var(&mut self, decl: &VarDecl) -> Result { + let value = self.evaluate_expr(&decl.value)?; + self.variables.insert(decl.name.clone(), value.clone()); + Ok(value) + } + + fn execute_assign(&mut self, stmt: &AssignStmt) -> Result { + if !self.variables.contains_key(&stmt.name) { + return Err(format!("cannot assign undefined variable '{}'", stmt.name)); + } + + let value = self.evaluate_expr(&stmt.value)?; + self.variables.insert(stmt.name.clone(), value.clone()); + Ok(value) + } + + fn execute_regress(&mut self, stmt: &RegressStmt) -> Result { + let config = &stmt.config; + let epsilon = match &config.until { + Some(ConvergenceCond::Epsilon(n)) => n.as_f64(), + _ => 1e-6, + }; + let mut regressor = EscalatingRegressor::new(epsilon); + regressor.set_target(&self.sample_data); + if let Some(workspace) = self.manifolds.first() { + let max_epochs = if config.escalate { 100 } else { 10 }; + let result = regressor.run_escalating(workspace, max_epochs); + Ok(Value::RegressionResult(result)) + } else { + Err("no manifold for regression".to_string()) + } + } + + fn execute_render(&mut self, _: &RenderStmt) -> Result { + Ok(Value::Unit) + } + + fn execute_stmt_block(&mut self, block: &Block) -> Result { + let mut last_value = Value::Unit; + for stmt in &block.statements { + match self.execute_statement(stmt)? { + RuntimeFlow::Value(value) => last_value = value, + RuntimeFlow::Return(value) => return Ok(RuntimeFlow::Return(value)), + RuntimeFlow::Break => return Ok(RuntimeFlow::Break), + RuntimeFlow::Continue => return Ok(RuntimeFlow::Continue), + } + } + Ok(RuntimeFlow::Value(last_value)) + } + + fn execute_if(&mut self, stmt: &IfStmt) -> Result { + let cond_val = self.evaluate_expr(&stmt.condition)?; + let is_true = match cond_val { + Value::Bool(b) => b, + _ => return Err(String::from("condition must be boolean")), + }; + if is_true { + self.execute_stmt_block(&stmt.then_branch) + } else if let Some(else_branch) = &stmt.else_branch { + self.execute_stmt_block(else_branch) + } else { + Ok(RuntimeFlow::Value(Value::Unit)) + } + } + + fn execute_while(&mut self, stmt: &WhileStmt) -> Result { + let mut last_value = Value::Unit; + loop { + let cond_val = self.evaluate_expr(&stmt.condition)?; + let is_true = match cond_val { + Value::Bool(b) => b, + _ => return Err(String::from("condition must be boolean")), + }; + if !is_true { + break; + } + match self.execute_stmt_block(&stmt.body)? { + RuntimeFlow::Value(value) => last_value = value, + RuntimeFlow::Return(value) => return Ok(value), + RuntimeFlow::Break => break, + RuntimeFlow::Continue => continue, + } + } + Ok(last_value) + } + + fn execute_for(&mut self, stmt: &ForStmt) -> Result { + let start = stmt.range.start.as_f64() as i64; + let end = stmt.range.end.as_f64() as i64; + let step = if start <= end { 1 } else { -1 }; + let mut current = start; + let mut last_value = Value::Unit; + + while (step > 0 && current < end) || (step < 0 && current > end) { + self.variables + .insert(stmt.iterator.clone(), Value::Num(current as f64)); + match self.execute_stmt_block(&stmt.body)? { + RuntimeFlow::Value(value) => last_value = value, + RuntimeFlow::Return(value) => return Ok(value), + RuntimeFlow::Break => break, + RuntimeFlow::Continue => { + current += step; + continue; + } + } + current += step; + } + + self.variables + .insert(stmt.iterator.clone(), Value::Num(current as f64)); + Ok(last_value) + } + + fn execute_seal(&mut self, stmt: &LoopStmt) -> Result { + let max_iters = 1000; + let mut last_value = Value::Unit; + for _ in 0..max_iters { + if let Some(condition) = &stmt.until { + if self.evaluate_condition(condition)? { + break; + } + } + match self.execute_stmt_block(&stmt.body)? { + RuntimeFlow::Value(value) => last_value = value, + RuntimeFlow::Return(value) => return Ok(value), + RuntimeFlow::Break => break, + RuntimeFlow::Continue => continue, + } + } + Ok(last_value) + } + + fn evaluate_condition(&mut self, expr: &Expr) -> Result { + match self.evaluate_expr(expr)? { + Value::Bool(value) => Ok(value), + _ => Err(String::from("condition must be boolean")), + } + } + + fn evaluate_expr(&mut self, expr: &Expr) -> Result { + match &expr.node { + ExprKind::Literal(lit) => match lit { + Literal::Num(n) => Ok(Value::Num(*n)), + Literal::Bool(b) => Ok(Value::Bool(*b)), + Literal::Str(s) => Ok(Value::Str(s.clone())), + }, + ExprKind::Ident(name) => { + if let Some(v) = self.variables.get(name) { + Ok(v.clone()) + } else { + Ok(Value::Unit) + } + } + ExprKind::FieldAccess { object, field } => self.evaluate_field_access(object, field), + ExprKind::Call { name, args } => self.evaluate_call(name, args), + ExprKind::New { class, args } => self.evaluate_new(class, args), + ExprKind::List(elements) => self.evaluate_list(elements), + ExprKind::MethodCall { + object, + method, + args, + } => self.evaluate_method_call(object, method, args), + ExprKind::Range(_) => Err(String::from( + "Ranges cannot be evaluated directly as values", + )), + ExprKind::BinaryOp(left, op, right) => { + let l = self.evaluate_expr(left)?; + let r = self.evaluate_expr(right)?; + self.evaluate_binary(l, *op, r) + } + ExprKind::UnaryOp(op, expr) => { + let value = self.evaluate_expr(expr)?; + self.evaluate_unary(*op, value) + } + ExprKind::Index { object, range } => { + // Simplified: returns a descriptive string or handle? + // For now, let's treat it as a lookup that returns a sub-manifold or block value + let handle = self.get_manifold_handle(object)?; + let start = range.start.as_f64() as usize; + let end = range.end.as_f64() as usize; + if let Some(workspace) = self.manifolds.get(handle.0) { + let block = workspace.extract_block(start, end); + let block_handle = BlockHandle(self.blocks.len()); + self.blocks.push(block); + Ok(Value::Block(block_handle)) + } else { + Err(format!("Manifold '{}' not found", object)) + } + } + ExprKind::Config(_) => Err(String::from( + "Raw config blocks cannot be evaluated as expressions", + )), + } + } + + fn evaluate_binary(&self, left: Value, op: BinaryOp, right: Value) -> Result { + match (left, op, right) { + (Value::Num(a), BinaryOp::Add, Value::Num(b)) => Ok(Value::Num(a + b)), + (Value::Num(a), BinaryOp::Sub, Value::Num(b)) => Ok(Value::Num(a - b)), + (Value::Num(a), BinaryOp::Mul, Value::Num(b)) => Ok(Value::Num(a * b)), + (Value::Num(a), BinaryOp::Div, Value::Num(b)) => Ok(Value::Num(a / b)), + (Value::Num(a), BinaryOp::Mod, Value::Num(b)) => Ok(Value::Num(a % b)), + (Value::Num(a), BinaryOp::Eq, Value::Num(b)) => Ok(Value::Bool(a == b)), + (Value::Num(a), BinaryOp::Neq, Value::Num(b)) => Ok(Value::Bool(a != b)), + (Value::Num(a), BinaryOp::Lt, Value::Num(b)) => Ok(Value::Bool(a < b)), + (Value::Num(a), BinaryOp::Gt, Value::Num(b)) => Ok(Value::Bool(a > b)), + (Value::Num(a), BinaryOp::Le, Value::Num(b)) => Ok(Value::Bool(a <= b)), + (Value::Num(a), BinaryOp::Ge, Value::Num(b)) => Ok(Value::Bool(a >= b)), + (Value::Bool(a), BinaryOp::Eq, Value::Bool(b)) => Ok(Value::Bool(a == b)), + (Value::Bool(a), BinaryOp::Neq, Value::Bool(b)) => Ok(Value::Bool(a != b)), + (Value::Bool(a), BinaryOp::And, Value::Bool(b)) => Ok(Value::Bool(a && b)), + (Value::Bool(a), BinaryOp::Or, Value::Bool(b)) => Ok(Value::Bool(a || b)), + (Value::Str(a), BinaryOp::Eq, Value::Str(b)) => Ok(Value::Bool(a == b)), + (Value::Str(a), BinaryOp::Neq, Value::Str(b)) => Ok(Value::Bool(a != b)), + _ => Err("Invalid binary operation".into()), + } + } + + fn evaluate_unary(&self, op: UnaryOp, value: Value) -> Result { + match (op, value) { + (UnaryOp::Neg, Value::Num(n)) => Ok(Value::Num(-n)), + (UnaryOp::Not, Value::Bool(b)) => Ok(Value::Bool(!b)), + _ => Err("Invalid unary operation".into()), + } + } + + fn evaluate_list(&mut self, elements: &Vec) -> Result { + let mut values = Vec::new(); + for expr in elements { + values.push(self.evaluate_expr(expr)?); + } + Ok(Value::List(values)) + } + + fn evaluate_call(&mut self, name: &Ident, args: &Vec) -> Result { + if let Some(val) = self.variables.get(name) { + match val.clone() { + Value::NativeFn(func) => self.execute_native_fn(func, args), + Value::Function(func) => self.execute_user_fn(&func, args), + _ => Ok(Value::Unit), + } + } else { + Ok(Value::Unit) + } + } + + fn execute_user_fn(&mut self, func: &FnDecl, args: &[CallArg]) -> Result { + if args.len() != func.params.len() { + return Err(format!( + "function '{}' expected {} arguments, got {}", + func.name, + func.params.len(), + args.len() + )); + } + + let mut frame = self.variables.clone(); + for (param, arg) in func.params.iter().zip(args.iter()) { + let CallArg::Positional(expr) = arg else { + return Err(format!( + "function '{}' does not accept named arguments", + func.name + )); + }; + let value = self.evaluate_expr(expr)?; + frame.insert(param.clone(), value); + } + + let outer = core::mem::replace(&mut self.variables, frame); + let result = match self.execute_stmt_block(&func.body) { + Ok(RuntimeFlow::Return(value)) | Ok(RuntimeFlow::Value(value)) => Ok(value), + Ok(RuntimeFlow::Break) => Err(String::from("break outside loop")), + Ok(RuntimeFlow::Continue) => Err(String::from("continue outside loop")), + Err(err) => Err(err), + }; + self.variables = outer; + result + } + + fn execute_native_fn( + &mut self, + func: NativeFunction, + args: &[CallArg], + ) -> Result { + let mut get_f64 = |args: &[CallArg]| -> Result { + if let Some(CallArg::Positional(expr)) = args.first() { + let val = self.evaluate_expr(expr)?; + if let Value::Num(n) = val { + Ok(n) + } else { + Err(String::from("Expected number")) + } + } else { + Err(String::from("Expected number")) + } + }; + + match func { + NativeFunction::MathSin => Ok(Value::Num(libm::sin(get_f64(args)?))), + NativeFunction::MathCos => Ok(Value::Num(libm::cos(get_f64(args)?))), + NativeFunction::MathSqrt => Ok(Value::Num(libm::sqrt(get_f64(args)?))), + NativeFunction::MathExp => Ok(Value::Num(libm::exp(get_f64(args)?))), + NativeFunction::TopoPh => self.execute_topology_ph(args), + NativeFunction::TopoBetti => self.execute_topology_betti(args), + NativeFunction::TopoIntervals => self.execute_topology_intervals(args), + NativeFunction::Print => { + for arg in args { + if let CallArg::Positional(expr) = arg { + let val = self.evaluate_expr(expr)?; + #[cfg(feature = "std")] + println!("{:?}", val); + } + } + Ok(Value::Unit) + } + NativeFunction::MlpNew => { + let lr = get_f64(args).unwrap_or(0.01); + let config = OptimizerConfig::SGD { + learning_rate: lr, + momentum: 0.9, + }; + Ok(Value::Mlp(Box::new(MLP::new(config, LossConfig::MSE)))) + } + NativeFunction::KMeansNew => { + let k = get_f64(args).unwrap_or(2.0) as usize; + Ok(Value::KMeans(Box::new(KMeans::new(k)))) + } + NativeFunction::Conv2DNew => Ok(Value::Conv2D(Box::new(Conv2D::new( + 1, + 1, + 3, + 1, + 1, + Activation::ReLU, + )))), + + // ML Ops + NativeFunction::MlMatMul => { + let a = self.get_tensor_arg(args, 0)?; + let b = self.get_tensor_arg(args, 1)?; + Ok(Value::Tensor(a.matmul(&b))) + } + NativeFunction::MlAdd => { + let a = self.get_tensor_arg(args, 0)?; + let b = self.get_tensor_arg(args, 1)?; + Ok(Value::Tensor(a.add(&b))) + } + NativeFunction::MlRelu => { + let a = self.get_tensor_arg(args, 0)?; + Ok(Value::Tensor(Activation::ReLU.apply(&a))) + } + NativeFunction::MlSoftmax => { + let a = self.get_tensor_arg(args, 0)?; + Ok(Value::Tensor(Activation::Softmax.apply(&a))) + } + NativeFunction::MlLoadWeights => { + // Acts as "Create Tensor from List" + if let Some(CallArg::Positional(expr)) = args.first() { + let val = self.evaluate_expr(expr)?; + let t = self.value_to_tensor_core(&val)?; + Ok(Value::Tensor(t)) + } else { + Err("Missing argument".into()) + } + } + NativeFunction::MlForward => { + // Map "forward" + // Args: model, input + // Actually this might be MethodCall on Mlp object + Ok(Value::Unit) + } + _ => Ok(Value::Unit), + } + } + + fn get_tensor_arg(&mut self, args: &[CallArg], index: usize) -> Result { + if let Some(CallArg::Positional(expr)) = args.get(index) { + let val = self.evaluate_expr(expr)?; + self.value_to_tensor_core(&val) + } else { + Err(format!("Missing argument {}", index)) + } + } + + fn value_to_tensor_core(&self, val: &Value) -> Result { + match val { + Value::Tensor(t) => Ok(t.clone()), + Value::List(rows) => { + if rows.is_empty() { + return Ok(Tensor::zeros(&[0])); + } + + let mut data = Vec::new(); + + // Check if 2D or 1D + if let Value::List(_) = &rows[0] { + // 2D + let rows_cnt = rows.len(); + let mut cols_cnt = 0; + for (i, row) in rows.iter().enumerate() { + if let Value::List(cols) = row { + if i == 0 { + cols_cnt = cols.len(); + } else if cols.len() != cols_cnt { + return Err("Ragged tensor".into()); + } + for c in cols { + if let Value::Num(n) = c { + data.push(*n); + } else { + return Err("Tensor must contain numbers".into()); + } + } + } else { + return Err("Expected 2D list".into()); + } + } + Ok(Tensor::new(&data, &[rows_cnt, cols_cnt])) + } else { + // 1D + for c in rows { + if let Value::Num(n) = c { + data.push(*n); + } else { + return Err("Tensor must contain numbers".into()); + } + } + Ok(Tensor::new(&data, &[rows.len()])) + } + } + _ => Err("Expected Tensor or List".into()), + } + } + + fn evaluate_method_call( + &mut self, + object_name: &String, + method: &String, + args: &[CallArg], + ) -> Result { + let val = if let Some(v) = self.variables.get(object_name) { + v.clone() + } else { + return Err(format!("Object '{}' not found", object_name)); + }; + match val { + Value::List(mut list) => { + let res = match method.as_str() { + "push" => { + if let Some(CallArg::Positional(expr)) = args.first() { + let val = self.evaluate_expr(expr)?; + list.push(val); + self.variables + .insert(object_name.clone(), Value::List(list)); + Ok(Value::Unit) + } else { + Err(String::from("push requires 1 argument")) + } + } + "pop" => { + let val = list.pop().unwrap_or(Value::Unit); + self.variables + .insert(object_name.clone(), Value::List(list)); + Ok(val) + } + "len" => Ok(Value::Num(list.len() as f64)), + _ => Err(format!("Method '{}' not found on List", method)), + }; + res + } + Value::Mlp(mut mlp) => { + match method.as_str() { + "add_layer" => { + // input, output, activation key string + // Default to Tanh if not string + let input = self.get_arg_num(args, 0)? as usize; + let output = self.get_arg_num(args, 1)? as usize; + let act_str = self.get_arg_str(args, 2).unwrap_or("tanh".to_string()); + let act = match act_str.as_str() { + "relu" => Activation::ReLU, + "sigmoid" => Activation::Sigmoid, + "softmax" => Activation::Softmax, + _ => Activation::Tanh, + }; + mlp.add_layer(input, output, act, None); + self.variables.insert(object_name.clone(), Value::Mlp(mlp)); // Update + Ok(Value::Unit) + } + "train" => { + // inputs (List/Tensor), targets (List/Tensor), epochs + let input = self.get_tensor_arg(args, 0)?; + let target = self.get_tensor_arg(args, 1)?; + let epochs = self.get_arg_num(args, 2).unwrap_or(1.0) as usize; + let res = mlp.fit(&[input], &[target], epochs); // fit expects slice of tensors + Ok(Value::Num(res.final_loss)) + } + "forward" | "predict" => { + let input = self.get_tensor_arg(args, 0)?; + let output = mlp.forward(&input); + Ok(Value::Tensor(output)) + } + _ => Err(format!("Method '{}' not found on MLP", method)), + } + } + Value::Module(mod_name) => match (mod_name.as_str(), method.as_str()) { + ("Ml", "MLP") => self.execute_native_fn(NativeFunction::MlpNew, args), + ("Ml", "KMeans") => self.execute_native_fn(NativeFunction::KMeansNew, args), + ("Ml", "Conv2D") => self.execute_native_fn(NativeFunction::Conv2DNew, args), + ("Seal", "train") => self.execute_native_fn(NativeFunction::SealTrain, args), + ("topology", "ph") => self.execute_native_fn(NativeFunction::TopoPh, args), + ("topology", "betti") | ("topology", "Betti") => { + self.execute_native_fn(NativeFunction::TopoBetti, args) + } + ("topology", "intervals") => { + self.execute_native_fn(NativeFunction::TopoIntervals, args) + } + _ => Err(format!( + "Method '{}' not found in module '{}'", + method, mod_name + )), + }, + _ => Ok(Value::Unit), + } + } + + fn get_arg_num(&mut self, args: &[CallArg], index: usize) -> Result { + if let Some(CallArg::Positional(expr)) = args.get(index) { + let val = self.evaluate_expr(expr)?; + if let Value::Num(n) = val { + Ok(n) + } else { + Err("Expected number".into()) + } + } else { + Err("Missing arg".into()) + } + } + + fn get_arg_str(&mut self, args: &[CallArg], index: usize) -> Result { + if let Some(CallArg::Positional(expr)) = args.get(index) { + let val = self.evaluate_expr(expr)?; + if let Value::Str(s) = val { + Ok(s) + } else { + Err("Expected string".into()) + } + } else { + Err("Missing arg".into()) + } + } + + fn evaluate_field_access(&self, object: &String, field: &String) -> Result { + if let Some(Value::Object(handle)) = self.variables.get(object) { + if let Some(obj) = self.objects.get(handle.0) { + if let Some(val) = obj.fields.get(field) { + return Ok(val.clone()); + } + } + } + if let Some(Value::Module(name)) = self.variables.get(object) { + match (name.as_str(), field.as_str()) { + ("Seal", "train") => Ok(Value::NativeFn(NativeFunction::SealTrain)), + ("Ml", "MLP") => Ok(Value::NativeFn(NativeFunction::MlpNew)), + ("topology", "ph") => Ok(Value::NativeFn(NativeFunction::TopoPh)), + ("topology", "betti") | ("topology", "Betti") => { + Ok(Value::NativeFn(NativeFunction::TopoBetti)) + } + ("topology", "intervals") => Ok(Value::NativeFn(NativeFunction::TopoIntervals)), + _ => Ok(Value::Unit), + } + } else { + Ok(Value::Unit) + } + } + + fn execute_topology_ph(&mut self, args: &[CallArg]) -> Result { + let manifold = self.get_manifold_arg(args, 0)?; + let config = self.persistence_config_from_args(args)?; + let workspace = self + .manifolds + .get(manifold.0) + .ok_or_else(|| String::from("manifold not found"))?; + let diagram = persistent_homology(&workspace.points, config) + .map_err(|err| format!("persistent homology failed: {:?}", err))?; + Ok(Value::Persistence(diagram)) + } + + fn execute_topology_betti(&mut self, args: &[CallArg]) -> Result { + if args.is_empty() { + return Err(String::from( + "Betti requires a persistence diagram or manifold", + )); + } + + let radius = self.get_named_num(args, "radius").unwrap_or(f64::INFINITY); + let first = self.get_arg_value(args, 0)?; + let diagram = match first { + Value::Persistence(diagram) => diagram, + Value::Manifold(handle) => { + let config = self.persistence_config_from_args(args)?; + let workspace = self + .manifolds + .get(handle.0) + .ok_or_else(|| String::from("manifold not found"))?; + persistent_homology(&workspace.points, config) + .map_err(|err| format!("persistent homology failed: {:?}", err))? + } + _ => { + return Err(String::from( + "Betti expects a persistence diagram or manifold", + )) + } + }; + let betti = diagram.betti_at(radius); + Ok(Value::List(vec![ + Value::Num(betti.beta_0 as f64), + Value::Num(betti.beta_1 as f64), + Value::Num(betti.beta_2 as f64), + ])) + } + + fn execute_topology_intervals(&mut self, args: &[CallArg]) -> Result { + let value = self.get_arg_value(args, 0)?; + let diagram = match value { + Value::Persistence(diagram) => diagram, + _ => return Err(String::from("intervals expects a persistence diagram")), + }; + + Ok(Value::List( + diagram + .pairs + .iter() + .map(|pair| { + Value::List(vec![ + Value::Num(pair.dimension as f64), + Value::Num(pair.birth), + Value::Num(pair.death.unwrap_or(-1.0)), + ]) + }) + .collect(), + )) + } + + fn persistence_config_from_args( + &mut self, + args: &[CallArg], + ) -> Result { + let mut config = PersistenceConfig::low_load(); + config.max_homology_dim = self.get_named_num(args, "max_dim").unwrap_or(2.0) as usize; + config.max_radius = self.get_named_num(args, "radius").unwrap_or(f64::INFINITY); + config.max_points = self + .get_named_num(args, "max_points") + .unwrap_or(config.max_points as f64) as usize; + config.max_simplices = self + .get_named_num(args, "max_simplices") + .unwrap_or(config.max_simplices as f64) as usize; + + if let Some(mode) = self.get_named_str(args, "mode")? { + config.complex_kind = match mode.as_str() { + "vr" | "rips" | "vietoris_rips" => ComplexKind::VietorisRips, + "witness" | "landmark" => ComplexKind::Witness { + max_landmarks: self + .get_named_num(args, "landmarks") + .unwrap_or(config.max_points as f64) + as usize, + }, + _ => return Err(format!("unknown topology mode '{}'", mode)), + }; + } + + Ok(config) + } + + fn get_manifold_arg( + &mut self, + args: &[CallArg], + index: usize, + ) -> Result { + match self.get_arg_value(args, index)? { + Value::Manifold(handle) => Ok(handle), + _ => Err(String::from("expected manifold")), + } + } + + fn get_arg_value(&mut self, args: &[CallArg], index: usize) -> Result { + match args.get(index) { + Some(CallArg::Positional(expr)) => self.evaluate_expr(expr), + Some(CallArg::Named { value, .. }) => self.evaluate_expr(value), + None => Err(format!("missing argument {}", index)), + } + } + + fn get_named_num(&mut self, args: &[CallArg], key: &str) -> Option { + args.iter().find_map(|arg| { + let CallArg::Named { name, value } = arg else { + return None; + }; + if name.as_str() != key { + return None; + } + match self.evaluate_expr(value).ok()? { + Value::Num(n) => Some(n), + _ => None, + } + }) + } + + fn get_named_str(&mut self, args: &[CallArg], key: &str) -> Result, String> { + for arg in args { + let CallArg::Named { name, value } = arg else { + continue; + }; + if name.as_str() == key { + return match self.evaluate_expr(value)? { + Value::Str(s) => Ok(Some(s)), + _ => Err(format!("{} must be a string", key)), + }; + } + } + Ok(None) + } +} + +impl Default for Interpreter { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parser::Parser; + + #[test] + fn executes_comparison_logical_unary_and_modulo_expressions() { + let mut parser = Parser::new("let x = 10 % 4~\nlet ok = x == 2 && !false~"); + let program = parser.parse().expect("program should parse"); + let mut interpreter = Interpreter::new(); + + interpreter + .execute(&program) + .expect("program should execute"); + + assert!(matches!( + interpreter.variables.get("x"), + Some(Value::Num(2.0)) + )); + assert!(matches!( + interpreter.variables.get("ok"), + Some(Value::Bool(true)) + )); + } + + #[test] + fn executes_reassignment_statement() { + let mut parser = Parser::new("let count = 0~\ncount = count + 1~"); + let program = parser.parse().expect("program should parse"); + let mut interpreter = Interpreter::new(); + + interpreter + .execute(&program) + .expect("program should execute"); + + assert!(matches!( + interpreter.variables.get("count"), + Some(Value::Num(1.0)) + )); + } + + #[test] + fn executes_while_loop_with_assignment() { + let mut parser = Parser::new("let count = 0~\nwhile count < 3 { count = count + 1~ }"); + let program = parser.parse().expect("program should parse"); + let mut interpreter = Interpreter::new(); + + interpreter + .execute(&program) + .expect("program should execute"); + + assert!(matches!( + interpreter.variables.get("count"), + Some(Value::Num(3.0)) + )); + } + + #[test] + fn executes_for_loop_over_integer_range() { + let mut parser = Parser::new("let sum = 0~\nfor i in 0..4 { sum = sum + i~ }"); + let program = parser.parse().expect("program should parse"); + let mut interpreter = Interpreter::new(); + + interpreter + .execute(&program) + .expect("program should execute"); + + assert!(matches!( + interpreter.variables.get("sum"), + Some(Value::Num(6.0)) + )); + assert!(matches!( + interpreter.variables.get("i"), + Some(Value::Num(4.0)) + )); + } + + #[test] + fn break_exits_loop_and_continue_skips_remaining_body() { + let mut parser = Parser::new( + "let i = 0~ + let sum = 0~ + while i < 5 { + i = i + 1~ + if i == 2 { continue~ } + if i == 4 { break~ } + sum = sum + i~ + }", + ); + let program = parser.parse().expect("program should parse"); + let mut interpreter = Interpreter::new(); + + interpreter + .execute(&program) + .expect("program should execute"); + + assert!(matches!( + interpreter.variables.get("i"), + Some(Value::Num(4.0)) + )); + assert!(matches!( + interpreter.variables.get("sum"), + Some(Value::Num(4.0)) + )); + } + + #[test] + fn seal_until_stops_when_condition_becomes_true() { + let mut parser = + Parser::new("let count = 0~\nseal until count >= 3 { count = count + 1~ }"); + let program = parser.parse().expect("program should parse"); + let mut interpreter = Interpreter::new(); + + interpreter + .execute(&program) + .expect("program should execute"); + + assert!(matches!( + interpreter.variables.get("count"), + Some(Value::Num(3.0)) + )); + } + + #[test] + fn executes_user_defined_function_with_return() { + let mut parser = Parser::new("fn add(a, b) { return a + b~ }\nlet result = add(2, 3)~"); + let program = parser.parse().expect("program should parse"); + let mut interpreter = Interpreter::new(); + + interpreter + .execute(&program) + .expect("program should execute"); + + assert!(matches!( + interpreter.variables.get("result"), + Some(Value::Num(5.0)) + )); + } + + #[test] + fn function_without_explicit_return_uses_last_value() { + let mut parser = Parser::new("fn one() { let x = 1~ x~ }\nlet result = one()~"); + let program = parser.parse().expect("program should parse"); + let mut interpreter = Interpreter::new(); + + interpreter + .execute(&program) + .expect("program should execute"); + + assert!(matches!( + interpreter.variables.get("result"), + Some(Value::Num(1.0)) + )); + } + + #[test] + fn function_parameters_do_not_overwrite_outer_variables() { + let mut parser = Parser::new("let x = 10~\nfn id(x) { return x~ }\nlet y = id(3)~"); + let program = parser.parse().expect("program should parse"); + let mut interpreter = Interpreter::new(); + + interpreter + .execute(&program) + .expect("program should execute"); + + assert!(matches!( + interpreter.variables.get("x"), + Some(Value::Num(10.0)) + )); + assert!(matches!( + interpreter.variables.get("y"), + Some(Value::Num(3.0)) + )); + } + + #[test] + fn manifold_embed_uses_user_numeric_list() { + let mut parser = Parser::new( + "let data = [1.0, 2.0, 3.0, 4.0]~ + manifold M = embed(data, tau=1)~", + ); + let program = parser.parse().expect("program should parse"); + let mut interpreter = Interpreter::new(); + + interpreter + .execute(&program) + .expect("program should execute"); + + let Value::Manifold(handle) = interpreter.variables.get("M").unwrap() else { + panic!("expected manifold handle"); + }; + let workspace = &interpreter.manifolds[handle.0]; + assert_eq!(workspace.points.len(), 2); + assert_eq!(workspace.points[0].coords, [3.0, 2.0, 1.0]); + assert_eq!(workspace.points[1].coords, [4.0, 3.0, 2.0]); + } + + #[test] + fn topology_betti_uses_persistent_homology_engine() { + let mut parser = Parser::new( + "import topology~ + let data = [1.0, 1.0, 1.0, 1.0, 1.0]~ + manifold M = embed(data, tau=1)~ + let diagram = topology.ph(M, max_dim=2, mode=\"vr\", max_points=16)~ + let b = topology.betti(diagram, radius=0.0)~", + ); + let program = parser.parse().expect("program should parse"); + let mut interpreter = Interpreter::new(); + + interpreter + .execute(&program) + .expect("program should execute"); + + let Some(Value::Persistence(diagram)) = interpreter.variables.get("diagram") else { + panic!("expected persistence diagram"); + }; + assert!(!diagram.pairs.is_empty()); + + let Some(Value::List(betti)) = interpreter.variables.get("b") else { + panic!("expected Betti list"); + }; + assert!(matches!( + betti.as_slice(), + [Value::Num(1.0), Value::Num(0.0), Value::Num(0.0)] + )); + } +} + +// Tests helper +fn list_from_u8(bytes: &[u8]) -> Vec { + let mut data = Vec::with_capacity(bytes.len() / 4); + for chunk in bytes.chunks_exact(4) { + let arr: [u8; 4] = chunk.try_into().unwrap(); + data.push(f32::from_le_bytes(arr)); + } + data +} diff --git a/aether-lang/src/lexer.rs b/crates/aether-lang/src/lexer.rs similarity index 90% rename from aether-lang/src/lexer.rs rename to crates/aether-lang/src/lexer.rs index 35fc846..fbd44ac 100644 --- a/aether-lang/src/lexer.rs +++ b/crates/aether-lang/src/lexer.rs @@ -12,6 +12,13 @@ //! - Comments: // single-line //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![allow(dead_code)] extern crate alloc; @@ -133,7 +140,13 @@ pub struct Token { impl Token { pub fn new(kind: TokenKind, line: usize, column: usize, start: usize, end: usize) -> Self { - Self { kind, line, column, start, end } + Self { + kind, + line, + column, + start, + end, + } } } @@ -176,6 +189,10 @@ impl<'a> Lexer<'a> { self.chars.peek() } + fn peek_next_char(&self) -> Option { + self.chars.clone().nth(1) + } + /// Skip whitespace (except newlines which are tokens) fn skip_whitespace(&mut self) { while let Some(&c) = self.peek() { @@ -274,7 +291,12 @@ impl<'a> Lexer<'a> { } // Check for decimal point - if let Some(&'.') = self.peek() { + if self.peek() == Some(&'.') + && self + .peek_next_char() + .map(|c| c.is_ascii_digit()) + .unwrap_or(false) + { self.advance(); let mut frac_part: i64 = 0; let mut frac_digits = 0; @@ -472,4 +494,14 @@ mod tests { let token = lexer.next_token(); assert!(matches!(token.kind, TokenKind::Tilde)); } + + #[test] + fn test_lex_dot_dot_range_after_integer() { + let mut lexer = Lexer::new("1..10"); + let tokens = lexer.tokenize(); + + assert!(matches!(tokens[0].kind, TokenKind::Number(1))); + assert!(matches!(tokens[1].kind, TokenKind::DotDot)); + assert!(matches!(tokens[2].kind, TokenKind::Number(10))); + } } diff --git a/aether-lang/src/lib.rs b/crates/aether-lang/src/lib.rs similarity index 79% rename from aether-lang/src/lib.rs rename to crates/aether-lang/src/lib.rs index d00f555..0df7c19 100644 --- a/aether-lang/src/lib.rs +++ b/crates/aether-lang/src/lib.rs @@ -23,14 +23,19 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![cfg_attr(not(feature = "std"), no_std)] #[cfg(feature = "alloc")] #[macro_use] extern crate alloc; - - // ═══════════════════════════════════════════════════════════════════════════════ // Module Declarations // ═══════════════════════════════════════════════════════════════════════════════ @@ -40,8 +45,8 @@ pub mod ast; pub mod interpreter; pub mod lexer; pub mod parser; -pub mod webgl_export; pub mod vm; +pub mod webgl_export; #[cfg(feature = "python")] pub mod python; diff --git a/aether-lang/src/mod.rs b/crates/aether-lang/src/mod.rs similarity index 71% rename from aether-lang/src/mod.rs rename to crates/aether-lang/src/mod.rs index 9a7a398..c0a853f 100644 --- a/aether-lang/src/mod.rs +++ b/crates/aether-lang/src/mod.rs @@ -22,6 +22,14 @@ //! ``` //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + + #![allow(dead_code)] pub mod lexer; diff --git a/crates/aether-lang/src/parser.rs b/crates/aether-lang/src/parser.rs new file mode 100644 index 0000000..90eed3e --- /dev/null +++ b/crates/aether-lang/src/parser.rs @@ -0,0 +1,1276 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +//! AETHER Parser - Recursive descent parser for .aether scripts +// ═══════════════════════════════════════════════════════════════════════════════ +//! +//! Converts token stream to AST for interpretation. +//! Now produces Spanned nodes for precise error reporting. +//! +//! Grammar (simplified): +//! program → statement* EOF +//! statement → manifold_decl | block_decl | regress_stmt | render_stmt | var_decl +//! manifold_decl → "manifold" IDENT "=" expr +//! regress_stmt → "regress" config_block +//! config_block → "{" (IDENT ":" expr ",")* "}" +// ═══════════════════════════════════════════════════════════════════════════════ + +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + +#![allow(dead_code)] + +extern crate alloc; +use crate::ast::*; +use crate::lexer::{Lexer, Token, TokenKind}; +use alloc::boxed::Box; +use alloc::string::String; +use alloc::vec::Vec; + +#[cfg(not(feature = "std"))] +use alloc::string::ToString; +#[cfg(not(feature = "std"))] +use alloc::{format, vec}; + +#[cfg(not(feature = "std"))] +macro_rules! println { + ($($arg:tt)*) => {}; +} + +/// Parser error +#[derive(Debug, Clone)] +pub struct ParseError { + pub message: String, + pub line: usize, + pub column: usize, +} + +impl ParseError { + pub fn new(msg: &str, line: usize, column: usize) -> Self { + let mut message = String::new(); + message.push_str(msg); + Self { + message, + line, + column, + } + } +} + +/// AEGIS Parser +pub struct Parser<'a> { + tokens: Vec, + current: usize, + _source: &'a str, +} + +impl<'a> Parser<'a> { + /// Create parser from source text + pub fn new(source: &'a str) -> Self { + let mut lexer = Lexer::new(source); + let tokens = lexer.tokenize(); + + Self { + tokens, + current: 0, + _source: source, + } + } + + /// Parse entire program + pub fn parse(&mut self) -> Result { + let mut program = Program::new(); + + while !self.is_at_end() { + // Skip empty lines (though Lexer currently emits Newline tokens, we might consume them) + // Actually, grammar says program -> statement*. + // Our parse_statement handles newline/empty specially. + + // Consume leading newlines strictly + while self.check(TokenKind::Newline) || self.check(TokenKind::Tilde) { + self.advance(); + } + + if self.is_at_end() { + break; + } + + let stmt = self.parse_statement()?; + // We only push non-empty statements + if !matches!(stmt.node, StmtKind::Empty) { + program.push(stmt); + } + } + + Ok(program) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Helpers + // ═══════════════════════════════════════════════════════════════════════════ + + fn make_span(&self, start: &Token, end: &Token) -> Span { + Span { + start: start.start, + end: end.end, + line: start.line, + col: start.column, + } + } + + fn wrap_stmt(&self, kind: StmtKind, start_token: &Token) -> Statement { + let end_token = self.previous(); + let span = self.make_span(start_token, end_token); + Statement { node: kind, span } + } + + fn wrap_expr(&self, kind: ExprKind, start_token: &Token) -> Expr { + let end_token = self.previous(); + let span = self.make_span(start_token, end_token); + Expr { node: kind, span } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Statement Parsing + // ═══════════════════════════════════════════════════════════════════════════ + + fn parse_statement(&mut self) -> Result { + let token = self.peek().clone(); + if let TokenKind::Error(msg) = &token.kind { + return Err(ParseError::new( + &format!("lexer error: {}", msg), + token.line, + token.column, + )); + } + + let kind = match &token.kind { + TokenKind::Manifold => self.parse_manifold_decl()?, + TokenKind::Block => self.parse_block_decl()?, + TokenKind::Regress => self.parse_regress_stmt()?, + TokenKind::Render => self.parse_render_stmt()?, + TokenKind::Identifier(_) => self.parse_ident_start_stmt()?, + + // Class Declaration + TokenKind::Class => self.parse_class_decl()?, + + // Modules + TokenKind::Import => self.parse_import_stmt()?, + TokenKind::From => self.parse_from_import_stmt()?, + + // Control Flow + TokenKind::If => self.parse_if_stmt()?, + TokenKind::While => self.parse_while_stmt()?, + TokenKind::For => self.parse_for_stmt()?, + TokenKind::Seal => self.parse_seal_stmt()?, + TokenKind::Fn => self.parse_fn_decl()?, + TokenKind::Return => self.parse_return_stmt()?, + TokenKind::Break => { + self.advance(); + StmtKind::Break(BreakStmt) + } + TokenKind::Continue => { + self.advance(); + StmtKind::Continue(ContinueStmt) + } + TokenKind::Let => self.parse_let_decl()?, + + TokenKind::Newline | TokenKind::Eof => StmtKind::Empty, + _ => { + return Err(self.unexpected_token_error("statement")); + } + }; + + // Special case: if Empty, just return a dummy empty statement with current token span + if matches!(kind, StmtKind::Empty) { + let _t = self.previous(); // Might be Newline we just consumed or previous + // Doing it properly: + return Ok(Statement { + node: StmtKind::Empty, + span: self.make_span(&token, &token), + }); + } + + self.consume_statement_separators(); + + // For statements that we parsed, we want them wrapped. + // Note: parse_manifold_decl etc currently return StmtKind, need to adapt helper methods. + // Actually, let's make specific parsers return StmtKind and wrap here? + // Wait, parse_ident_start_stmt consumes tokens inside. + // Better to have parse functions return StmtKind. + + Ok(self.wrap_stmt(kind, &token)) + } + + /// manifold_decl → "manifold" IDENT "=" expr + fn parse_manifold_decl(&mut self) -> Result { + self.expect(TokenKind::Manifold)?; + let name = self.expect_ident()?; + self.expect(TokenKind::Equals)?; + let init = self.parse_expr()?; + + Ok(StmtKind::Manifold(ManifoldDecl { name, init })) + } + + /// block_decl → "block" IDENT "=" expr + fn parse_block_decl(&mut self) -> Result { + self.expect(TokenKind::Block)?; + let name = self.expect_ident()?; + self.expect(TokenKind::Equals)?; + let source = self.parse_expr()?; + + Ok(StmtKind::Block(BlockDecl { name, source })) + } + + /// ident_start_stmt → var_decl | expr_stmt + fn parse_ident_start_stmt(&mut self) -> Result { + let first_ident = self.expect_ident()?; + + // 1. Check for type hint: Ident Ident = Expr + if self.check_ident() && self.peek_next_is(TokenKind::Equals) { + let type_hint = Some(first_ident); + let name = self.expect_ident()?; + self.expect(TokenKind::Equals)?; + let value = self.parse_expr()?; + + Ok(StmtKind::Var(VarDecl { + type_hint, + name, + value, + })) + } + // 2. Check for Var Decl without type: Ident = Expr + else if self.check(TokenKind::Equals) { + self.expect(TokenKind::Equals)?; + let value = self.parse_expr()?; + + Ok(StmtKind::Assign(AssignStmt { + name: first_ident, + value, + })) + } + // 3. Expression Statement (e.g., method call) starting with Ident + else { + // We consumed the identifier. Parse the rest as an expression starting with this ident. + let start_token = self.tokens[self.current - 1].clone(); + let kind = self.parse_ident_expr_cont(first_ident, &start_token)?; + Ok(StmtKind::Expr(self.wrap_expr(kind, &start_token))) + } + } + + /// let_decl → "let" IDENT "=" expr + fn parse_let_decl(&mut self) -> Result { + self.expect(TokenKind::Let)?; + let name = self.expect_ident()?; + self.expect(TokenKind::Equals)?; + let value = self.parse_expr()?; + + Ok(StmtKind::Var(VarDecl { + type_hint: None, + name, + value, + })) + } + + /// regress_stmt → "regress" config_block + fn parse_regress_stmt(&mut self) -> Result { + self.expect(TokenKind::Regress)?; + let config = self.parse_regress_config()?; + + Ok(StmtKind::Regress(RegressStmt { config })) + } + + /// render_stmt → "render" IDENT config_block? + fn parse_render_stmt(&mut self) -> Result { + self.expect(TokenKind::Render)?; + let target = self.expect_ident()?; + + let config = if self.check(TokenKind::LBrace) { + self.parse_render_config()? + } else { + RenderConfig::default() + }; + + Ok(StmtKind::Render(RenderStmt { target, config })) + } + + /// class_decl → "class" IDENT "{" (var_decl | fn_decl)* "}" + fn parse_class_decl(&mut self) -> Result { + self.expect(TokenKind::Class)?; + let name = self.expect_ident()?; + self.expect(TokenKind::LBrace)?; + + let mut fields = Vec::new(); + let mut methods = Vec::new(); + + while !self.check(TokenKind::RBrace) && !self.is_at_end() { + if self.check(TokenKind::Newline) { + self.advance(); + continue; + } + + if self.check(TokenKind::Fn) { + // Method + if let StmtKind::Fn(f) = self.parse_fn_decl()? { + methods.push(f); + } + } else if self.check_ident() { + // Field + let field_name = self.expect_ident()?; + let value = if self.check(TokenKind::Equals) { + self.advance(); + self.parse_expr()? + } else { + // Default to false wrapped + let t = self.peek().clone(); // span might be slightly off + self.wrap_expr(ExprKind::Literal(Literal::Bool(false)), &t) + }; + + if self.check(TokenKind::Comma) { + self.advance(); + } + + fields.push(VarDecl { + type_hint: None, + name: field_name, + value, + }); + } else { + let t = self.peek(); + return Err(ParseError::new( + "expected field or method", + t.line, + t.column, + )); + } + } + + self.expect(TokenKind::RBrace)?; + + Ok(StmtKind::Class(ClassDecl { + name, + fields, + methods, + })) + } + + // Modules + fn parse_import_stmt(&mut self) -> Result { + self.expect(TokenKind::Import)?; + let module = self.expect_ident()?; + Ok(StmtKind::Import(ImportStmt { + module, + symbol: None, + })) + } + + fn parse_from_import_stmt(&mut self) -> Result { + self.expect(TokenKind::From)?; + let module = self.expect_ident()?; + self.expect(TokenKind::Import)?; + let symbol = self.expect_ident()?; + + Ok(StmtKind::Import(ImportStmt { + module, + symbol: Some(symbol), + })) + } + + // Control Flow + fn parse_if_stmt(&mut self) -> Result { + self.expect(TokenKind::If)?; + let condition = self.parse_expr()?; + let then_branch = self.parse_block_stmts()?; + + let else_branch = if self.check(TokenKind::Else) { + self.advance(); + Some(self.parse_block_stmts()?) + } else { + None + }; + + Ok(StmtKind::If(IfStmt { + condition, + then_branch, + else_branch, + })) + } + + fn parse_while_stmt(&mut self) -> Result { + self.expect(TokenKind::While)?; + let condition = self.parse_expr()?; + let body = self.parse_block_stmts()?; + Ok(StmtKind::While(WhileStmt { condition, body })) + } + + fn parse_for_stmt(&mut self) -> Result { + self.expect(TokenKind::For)?; + let iterator = self.expect_ident()?; + self.expect(TokenKind::In)?; + + // Currently expecting Range. + // We parse expr, verify range. + let expr = self.parse_expr()?; + let range = match expr.node { + ExprKind::Range(r) => r, + _ => { + return Err(ParseError::new( + "expected range in for loop", + expr.span.line, + expr.span.col, + )); + } + }; + + let body = self.parse_block_stmts()?; + Ok(StmtKind::For(ForStmt { + iterator, + range, + body, + })) + } + + fn parse_seal_stmt(&mut self) -> Result { + self.expect(TokenKind::Seal)?; + let until = if self.check(TokenKind::Until) { + self.advance(); + Some(self.parse_expr()?) + } else { + None + }; + let body = self.parse_block_stmts()?; + Ok(StmtKind::Loop(LoopStmt { until, body })) + } + + fn parse_fn_decl(&mut self) -> Result { + self.expect(TokenKind::Fn)?; + let name = self.expect_ident()?; + self.expect(TokenKind::LParen)?; + + let mut params = Vec::new(); + while !self.check(TokenKind::RParen) && !self.is_at_end() { + params.push(self.expect_ident()?); + if self.check(TokenKind::Comma) { + self.advance(); + } + } + self.expect(TokenKind::RParen)?; + + let body = self.parse_block_stmts()?; + Ok(StmtKind::Fn(FnDecl { name, params, body })) + } + + fn parse_return_stmt(&mut self) -> Result { + self.expect(TokenKind::Return)?; + let value = if self.check(TokenKind::Newline) + || self.check(TokenKind::Tilde) + || self.check(TokenKind::RBrace) + { + None + } else { + Some(self.parse_expr()?) + }; + Ok(StmtKind::Return(ReturnStmt { value })) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Expression Parsing + // ═══════════════════════════════════════════════════════════════════════════ + + // Start with lowest precedence + fn parse_expr(&mut self) -> Result { + self.parse_or() + } + + fn parse_or(&mut self) -> Result { + let mut left = self.parse_and()?; + + while self.check(TokenKind::Or) { + self.advance(); + let right = self.parse_and()?; + left = self.binary_expr(left, BinaryOp::Or, right); + } + + Ok(left) + } + + fn parse_and(&mut self) -> Result { + let mut left = self.parse_equality()?; + + while self.check(TokenKind::And) { + self.advance(); + let right = self.parse_equality()?; + left = self.binary_expr(left, BinaryOp::And, right); + } + + Ok(left) + } + + fn parse_equality(&mut self) -> Result { + let mut left = self.parse_comparison()?; + + while self.check(TokenKind::EqEq) || self.check(TokenKind::NotEq) { + let op_token = self.advance(); + let op = match op_token.kind { + TokenKind::EqEq => BinaryOp::Eq, + TokenKind::NotEq => BinaryOp::Neq, + _ => unreachable!(), + }; + let right = self.parse_comparison()?; + left = self.binary_expr(left, op, right); + } + + Ok(left) + } + + fn parse_comparison(&mut self) -> Result { + let mut left = self.parse_range()?; + + while self.check(TokenKind::Less) + || self.check(TokenKind::Greater) + || self.check(TokenKind::LessEq) + || self.check(TokenKind::GreaterEq) + { + let op_token = self.advance(); + let op = match op_token.kind { + TokenKind::Less => BinaryOp::Lt, + TokenKind::Greater => BinaryOp::Gt, + TokenKind::LessEq => BinaryOp::Le, + TokenKind::GreaterEq => BinaryOp::Ge, + _ => unreachable!(), + }; + let right = self.parse_range()?; + left = self.binary_expr(left, op, right); + } + + Ok(left) + } + + fn parse_range(&mut self) -> Result { + let left = self.parse_arithmetic()?; // Using arithmetic as base for range + + if self.check(TokenKind::Colon) || self.check(TokenKind::DotDot) { + self.advance(); + // range start/end must be numbers, but parse_arithmetic returns Spanned + // We need to extract number values if possible, or return Error + let right = self.parse_arithmetic()?; + + let start_val = self.expr_to_number(&left)?; + let end_val = self.expr_to_number(&right)?; + + let kind = ExprKind::Range(Range { + start: start_val, + end: end_val, + }); + + let span = Span { + start: left.span.start, + end: right.span.end, + line: left.span.line, + col: left.span.col, + }; + return Ok(Expr { node: kind, span }); + } + + Ok(left) + } + + fn parse_arithmetic(&mut self) -> Result { + let mut left = self.parse_term()?; + + while self.check(TokenKind::Plus) || self.check(TokenKind::Minus) { + let op_token = self.advance(); + let op = match op_token.kind { + TokenKind::Plus => BinaryOp::Add, + TokenKind::Minus => BinaryOp::Sub, + _ => unreachable!(), + }; + let right = self.parse_term()?; + + let span = Span { + start: left.span.start, + end: right.span.end, + line: left.span.line, + col: left.span.col, + }; + + let kind = ExprKind::BinaryOp(Box::new(left.clone()), op, Box::new(right)); + left = Expr { node: kind, span }; + } + + Ok(left) + } + + fn parse_term(&mut self) -> Result { + let mut left = self.parse_unary()?; + + while self.check(TokenKind::Star) + || self.check(TokenKind::Slash) + || self.check(TokenKind::Percent) + { + let op_token = self.advance(); + let op = match op_token.kind { + TokenKind::Star => BinaryOp::Mul, + TokenKind::Slash => BinaryOp::Div, + TokenKind::Percent => BinaryOp::Mod, + _ => unreachable!(), + }; + let right = self.parse_unary()?; + + let span = Span { + start: left.span.start, + end: right.span.end, + line: left.span.line, + col: left.span.col, + }; + + let kind = ExprKind::BinaryOp(Box::new(left.clone()), op, Box::new(right)); + left = Expr { node: kind, span }; + } + + Ok(left) + } + + fn parse_unary(&mut self) -> Result { + if self.check(TokenKind::Minus) || self.check(TokenKind::Not) { + let op_token = self.advance(); + let op = match op_token.kind { + TokenKind::Minus => UnaryOp::Neg, + TokenKind::Not => UnaryOp::Not, + _ => unreachable!(), + }; + let expr = self.parse_unary()?; + let span = Span { + start: op_token.start, + end: expr.span.end, + line: op_token.line, + col: op_token.column, + }; + return Ok(Expr { + node: ExprKind::UnaryOp(op, Box::new(expr)), + span, + }); + } + + self.parse_primary() + } + + fn parse_primary(&mut self) -> Result { + let token = self.advance(); + let kind = match token.kind { + TokenKind::Number(n) => ExprKind::Literal(Literal::Num(n as f64)), + TokenKind::Float(int, frac) => { + let val = int as f64 + (frac as f64 / 1_000_000.0); + ExprKind::Literal(Literal::Num(val)) + } + TokenKind::True => ExprKind::Literal(Literal::Bool(true)), + TokenKind::False => ExprKind::Literal(Literal::Bool(false)), + TokenKind::StringLit(ref s) => ExprKind::Literal(Literal::Str(s.clone())), + TokenKind::Self_ => ExprKind::Ident(String::from("self")), + + TokenKind::Identifier(ref name) => { + let name_clone = name.clone(); + // Return result of parse_ident_expr_cont which returns ExprKind + return self.parse_ident_expr_cont(name_clone, &token).map(|kind| { + // Span adjustment needed because parse_ident_expr_cont consumes more + let end_token = self.previous(); + let span = self.make_span(&token, end_token); + Expr { node: kind, span } + }); + } + + // New Object Instantiation + TokenKind::New => { + let class = self.expect_ident()?; + self.expect(TokenKind::LParen)?; + let mut args = Vec::new(); + while !self.check(TokenKind::RParen) && !self.is_at_end() { + args.push(self.parse_expr()?); + if self.check(TokenKind::Comma) { + self.advance(); + } + } + self.expect(TokenKind::RParen)?; + ExprKind::New { class, args } + } + + // List + TokenKind::LBracket => self.parse_list_literal_cont()?, + + // Embed/Convergence keywords used as functions + TokenKind::Embed => { + return self.parse_call_expr_cont(String::from("embed"), &token); + } + TokenKind::Convergence => { + return self.parse_call_expr_cont(String::from("convergence"), &token); + } + + TokenKind::Error(ref msg) => { + return Err(ParseError::new( + &format!("lexer error: {}", msg), + token.line, + token.column, + )) + } + _ => { + return Err(ParseError::new( + &format!("expected expression, found {}", token_label(&token.kind)), + token.line, + token.column, + )) + } + }; + + Ok(self.wrap_expr(kind, &token)) + } + + fn parse_list_literal_cont(&mut self) -> Result { + let mut elements = Vec::new(); + + while !self.check(TokenKind::RBracket) && !self.is_at_end() { + if self.check(TokenKind::Newline) { + self.advance(); + continue; + } + elements.push(self.parse_expr()?); + if self.check(TokenKind::Comma) { + self.advance(); + } + } + self.expect(TokenKind::RBracket)?; + Ok(ExprKind::List(elements)) + } + + fn parse_ident_expr_cont( + &mut self, + name: String, + _start_token: &Token, + ) -> Result { + // Method call: M.cluster(...) + if self.check(TokenKind::Dot) { + self.advance(); + let method = self.expect_flexible_ident()?; + + if self.check(TokenKind::LParen) { + let args = self.parse_call_args()?; + return Ok(ExprKind::MethodCall { + object: name, + method, + args, + }); + } else { + return Ok(ExprKind::FieldAccess { + object: name, + field: method, + }); + } + } + + // Call: embed(...) + if self.check(TokenKind::LParen) { + let args = self.parse_call_args()?; + return Ok(ExprKind::Call { name, args }); + } + + // Index: M[0:64] + if self.check(TokenKind::LBracket) { + self.advance(); + let start = self.parse_number()?; + self.expect(TokenKind::Colon)?; + let end = self.parse_number()?; + self.expect(TokenKind::RBracket)?; + + return Ok(ExprKind::Index { + object: name, + range: Range { start, end }, + }); + } + + Ok(ExprKind::Ident(name)) + } + + fn parse_call_expr_cont( + &mut self, + name: String, + start_token: &Token, + ) -> Result { + let args = self.parse_call_args()?; + let kind = ExprKind::Call { name, args }; + Ok(self.wrap_expr(kind, start_token)) + } + + fn parse_call_args(&mut self) -> Result, ParseError> { + self.expect(TokenKind::LParen)?; + + let mut args = Vec::new(); + + while !self.check(TokenKind::RParen) && !self.is_at_end() { + // Check for named argument + if self.check_flexible_ident() { + let saved_pos = self.current; + let name = self.expect_flexible_ident()?; + + if self.check(TokenKind::Equals) { + self.advance(); + let value = self.parse_expr()?; + args.push(CallArg::Named { name, value }); + } else { + // Backtrack + self.current = saved_pos; + let expr = self.parse_expr()?; + args.push(CallArg::Positional(expr)); + } + } else { + let expr = self.parse_expr()?; + args.push(CallArg::Positional(expr)); + } + + if self.check(TokenKind::Comma) { + self.advance(); + } + } + self.expect(TokenKind::RParen)?; + Ok(args) + } + + // Helper to extract Number from Expr (for Range and Config compatibility) + fn expr_to_number(&self, expr: &Expr) -> Result { + match &expr.node { + ExprKind::Literal(Literal::Num(f)) => { + // Convert f64 back to Number enum just for internal usage in Range? + // Wait, Range struct in ast.rs expects Number enum. + // So I must construct Number. + let int_part = *f as i64; + let frac_part = ((*f - int_part as f64) * 1_000_000.0) as i64; + if frac_part == 0 { + Ok(Number::Int(int_part)) + } else { + Ok(Number::Float { + int_part, + frac_part, + }) + } + } + _ => Err(ParseError::new( + "expected number", + expr.span.line, + expr.span.col, + )), + } + } + + fn parse_number(&mut self) -> Result { + let token = self.advance(); + match token.kind { + TokenKind::Number(n) => Ok(Number::Int(n)), + TokenKind::Float(int, frac) => Ok(Number::Float { + int_part: int, + frac_part: frac, + }), + _ => Err(ParseError::new( + &format!("expected number, found {}", token_label(&token.kind)), + token.line, + token.column, + )), + } + } + + // Config Block Parsers (Simplified for brevity but maintaining logic) + fn parse_regress_config(&mut self) -> Result { + self.expect(TokenKind::LBrace)?; + let mut config = RegressConfig::default(); + while !self.check(TokenKind::RBrace) && !self.is_at_end() { + if self.check(TokenKind::Newline) { + self.advance(); + continue; + } + + let key = self.expect_flexible_ident()?; + self.expect(TokenKind::Colon)?; + + match key.as_str() { + "model" => { + if let ExprKind::Literal(Literal::Str(s)) = self.parse_expr()?.node { + config.model = s; + } + } + "degree" => { + let expr = self.parse_expr()?; + if let Ok(num) = self.expr_to_number(&expr) { + if let Number::Int(n) = num { + config.degree = Some(n as u8); + } + } + } + "target" => config.target = Some(self.parse_expr()?), + "escalate" => { + if let ExprKind::Literal(Literal::Bool(b)) = self.parse_expr()?.node { + config.escalate = b; + } + } + "until" => { + // Parse convergence + // convergence(..) or custom expr + // Look at parse_expr() handling + let expr = self.parse_expr()?; + // Check if it is a call 'convergence' + // Actually convergence is special keyword in lexer but parsed as call + config.until = Some(ConvergenceCond::Custom(expr)); // Simplified + } + _ => { + self.parse_expr()?; + } + } + + if self.check(TokenKind::Comma) { + self.advance(); + } + } + self.expect(TokenKind::RBrace)?; + Ok(config) + } + + fn parse_render_config(&mut self) -> Result { + self.expect(TokenKind::LBrace)?; + let mut config = RenderConfig::default(); + while !self.check(TokenKind::RBrace) && !self.is_at_end() { + if self.check(TokenKind::Newline) { + self.advance(); + continue; + } + + let key = self.expect_flexible_ident()?; + self.expect(TokenKind::Colon)?; + let expr = self.parse_expr()?; + + match key.as_str() { + "color" => { + if let ExprKind::Ident(id) = expr.node { + config.color = Some(id); + } + } + "highlight" => { + if let ExprKind::Ident(id) = expr.node { + config.highlight = Some(id); + } + } + "trajectory" => { + if let ExprKind::Literal(Literal::Bool(b)) = expr.node { + config.trajectory = b; + } + } + "axis" => { + if let Ok(Number::Int(n)) = self.expr_to_number(&expr) { + config.axis = Some(n as u8); + } + } + _ => {} + } + + if self.check(TokenKind::Comma) { + self.advance(); + } + } + self.expect(TokenKind::RBrace)?; + Ok(config) + } + + // Helper Methods + fn parse_block_stmts(&mut self) -> Result { + self.expect(TokenKind::LBrace)?; + let mut statements = Vec::new(); + while !self.check(TokenKind::RBrace) && !self.is_at_end() { + while self.check(TokenKind::Newline) || self.check(TokenKind::Tilde) { + self.advance(); + } + if self.check(TokenKind::RBrace) { + break; + } + let stmt = self.parse_statement()?; + if !matches!(stmt.node, StmtKind::Empty) { + statements.push(stmt); + } + } + self.expect(TokenKind::RBrace)?; + Ok(Block { statements }) + } + + fn binary_expr(&self, left: Expr, op: BinaryOp, right: Expr) -> Expr { + let span = Span { + start: left.span.start, + end: right.span.end, + line: left.span.line, + col: left.span.col, + }; + + Expr { + node: ExprKind::BinaryOp(Box::new(left), op, Box::new(right)), + span, + } + } + + fn consume_statement_separators(&mut self) { + while self.check(TokenKind::Tilde) || self.check(TokenKind::Newline) { + self.advance(); + } + } + + fn peek(&self) -> &Token { + self.tokens + .get(self.current) + .unwrap_or(&self.tokens[self.tokens.len() - 1]) + } + + fn previous(&self) -> &Token { + if self.current == 0 { + return &self.tokens[0]; + } + &self.tokens[self.current - 1] + } + + fn advance(&mut self) -> Token { + if !self.is_at_end() { + self.current += 1; + } + self.previous().clone() + } + + fn is_at_end(&self) -> bool { + matches!(self.peek().kind, TokenKind::Eof) + } + + fn check(&self, kind: TokenKind) -> bool { + if self.is_at_end() { + return false; + } + core::mem::discriminant(&self.peek().kind) == core::mem::discriminant(&kind) + } + + fn check_ident(&self) -> bool { + matches!(self.peek().kind, TokenKind::Identifier(_)) + } + + fn peek_next_is(&self, kind: TokenKind) -> bool { + self.tokens + .get(self.current + 1) + .map(|t| core::mem::discriminant(&t.kind) == core::mem::discriminant(&kind)) + .unwrap_or(false) + } + + fn expect(&mut self, kind: TokenKind) -> Result { + if self.check(kind.clone()) { + Ok(self.advance()) + } else { + Err(self.unexpected_token_error(token_label(&kind).as_str())) + } + } + + fn expect_ident(&mut self) -> Result { + let token = self.advance(); + match token.kind { + TokenKind::Identifier(s) => Ok(s), + _ => Err(ParseError::new( + &format!("expected identifier, found {}", token_label(&token.kind)), + token.line, + token.column, + )), + } + } + + fn check_flexible_ident(&self) -> bool { + matches!( + self.peek().kind, + TokenKind::Identifier(_) + | TokenKind::Dim + | TokenKind::Tau + | TokenKind::Model + | TokenKind::Color + | TokenKind::Axis + | TokenKind::Project + | TokenKind::Cluster + | TokenKind::Center + | TokenKind::Spread + | TokenKind::Format + | TokenKind::Output + | TokenKind::Escalate + | TokenKind::Convergence + ) + } + + fn expect_flexible_ident(&mut self) -> Result { + let token = self.advance(); + match token.kind { + TokenKind::Identifier(s) => Ok(s), + TokenKind::Dim => Ok(String::from("dim")), + TokenKind::Tau => Ok(String::from("tau")), + TokenKind::Model => Ok(String::from("model")), + TokenKind::Color => Ok(String::from("color")), + TokenKind::Axis => Ok(String::from("axis")), + TokenKind::Project => Ok(String::from("project")), + TokenKind::Cluster => Ok(String::from("cluster")), + TokenKind::Center => Ok(String::from("center")), + TokenKind::Spread => Ok(String::from("spread")), + TokenKind::Format => Ok(String::from("format")), + TokenKind::Output => Ok(String::from("output")), + TokenKind::Escalate => Ok(String::from("escalate")), + TokenKind::Convergence => Ok(String::from("convergence")), + _ => Err(ParseError::new( + &format!("expected argument name, found {}", token_label(&token.kind)), + token.line, + token.column, + )), + } + } + + fn unexpected_token_error(&self, expected: &str) -> ParseError { + let token = self.peek(); + match &token.kind { + TokenKind::Error(msg) => { + ParseError::new(&format!("lexer error: {}", msg), token.line, token.column) + } + kind => ParseError::new( + &format!("expected {}, found {}", expected, token_label(kind)), + token.line, + token.column, + ), + } + } +} + +impl core::fmt::Display for ParseError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "line {}, column {}: {}", + self.line, self.column, self.message + ) + } +} + +fn token_label(kind: &TokenKind) -> String { + match kind { + TokenKind::Identifier(name) => format!("identifier '{}'", name), + TokenKind::Number(value) => format!("number {}", value), + TokenKind::Float(int, frac) => format!("float {}.{:06}", int, frac), + TokenKind::StringLit(value) => format!("string {:?}", value), + TokenKind::Error(msg) => format!("lexer error '{}'", msg), + TokenKind::Equals => String::from("="), + TokenKind::Colon => String::from(":"), + TokenKind::Comma => String::from(","), + TokenKind::Dot => String::from("."), + TokenKind::LBrace => String::from("{"), + TokenKind::RBrace => String::from("}"), + TokenKind::LBracket => String::from("["), + TokenKind::RBracket => String::from("]"), + TokenKind::LParen => String::from("("), + TokenKind::RParen => String::from(")"), + TokenKind::Plus => String::from("+"), + TokenKind::Minus => String::from("-"), + TokenKind::Star => String::from("*"), + TokenKind::Slash => String::from("/"), + TokenKind::Percent => String::from("%"), + TokenKind::Less => String::from("<"), + TokenKind::Greater => String::from(">"), + TokenKind::LessEq => String::from("<="), + TokenKind::GreaterEq => String::from(">="), + TokenKind::EqEq => String::from("=="), + TokenKind::NotEq => String::from("!="), + TokenKind::And => String::from("&&"), + TokenKind::Or => String::from("||"), + TokenKind::Not => String::from("!"), + TokenKind::DotDot => String::from(".."), + TokenKind::Tilde => String::from("~"), + TokenKind::Newline => String::from("newline"), + TokenKind::Eof => String::from("end of file"), + other => format!("{:?}", other).to_lowercase(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_tilde_terminated_statements() { + let mut parser = Parser::new("let x = 1~\nlet y = x + 2~"); + let program = parser.parse().expect("program should parse"); + + assert_eq!(program.statements.len(), 2); + } + + #[test] + fn parses_doc_expression_operators() { + let mut parser = Parser::new("let ok = 1 < 2 && !false~"); + let program = parser.parse().expect("program should parse"); + + let StmtKind::Var(var) = &program.statements[0].node else { + panic!("expected variable declaration"); + }; + + assert!(matches!( + var.value.node, + ExprKind::BinaryOp(_, BinaryOp::And, _) + )); + } + + #[test] + fn parses_dot_dot_range_in_for_loop() { + let mut parser = Parser::new("for i in 0..3 { print(i)~ }"); + let program = parser.parse().expect("program should parse"); + + let StmtKind::For(for_stmt) = &program.statements[0].node else { + panic!("expected for statement"); + }; + + assert_eq!(for_stmt.range.start, Number::Int(0)); + assert_eq!(for_stmt.range.end, Number::Int(3)); + } + + #[test] + fn parses_reassignment_as_assignment_statement() { + let mut parser = Parser::new("let count = 0~\ncount = count + 1~"); + let program = parser.parse().expect("program should parse"); + + let StmtKind::Assign(assign) = &program.statements[1].node else { + panic!("expected assignment statement"); + }; + + assert_eq!(assign.name, "count"); + } + + #[test] + fn parses_seal_until_condition() { + let mut parser = Parser::new("seal until count >= 3 { count = count + 1~ }"); + let program = parser.parse().expect("program should parse"); + + let StmtKind::Loop(loop_stmt) = &program.statements[0].node else { + panic!("expected seal loop"); + }; + + assert!(loop_stmt.until.is_some()); + } + + #[test] + fn reports_lexer_error_with_original_message() { + let mut parser = Parser::new("let name = \"unterminated"); + let err = parser.parse().expect_err("program should fail"); + + assert!(err.message.contains("lexer error")); + assert!(err.message.contains("unexpected EOF in string")); + assert_eq!(err.line, 1); + assert_eq!(err.column, 12); + } + + #[test] + fn reports_expected_and_found_token() { + let mut parser = Parser::new("let = 1~"); + let err = parser.parse().expect_err("program should fail"); + + assert!(err.message.contains("expected identifier")); + assert!(err.message.contains("found =")); + assert_eq!(err.line, 1); + assert_eq!(err.column, 5); + } +} diff --git a/aether-lang/src/python.rs b/crates/aether-lang/src/python.rs similarity index 57% rename from aether-lang/src/python.rs rename to crates/aether-lang/src/python.rs index f71ed95..857b018 100644 --- a/aether-lang/src/python.rs +++ b/crates/aether-lang/src/python.rs @@ -1,7 +1,15 @@ use pyo3::prelude::*; -use crate::Interpreter; -use crate::parser::Parser; + +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + use crate::interpreter::Value; +use crate::parser::Parser; +use crate::Interpreter; /// The Aether Interpreter exposed to Python. #[pyclass(unsendable)] @@ -23,7 +31,12 @@ impl AetherInterpreter { let mut parser = Parser::new(&source); let program = match parser.parse() { Ok(p) => p, - Err(e) => return Err(pyo3::exceptions::PyValueError::new_err(format!("Parse error at {}:{}: {}", e.line, e.column, e.message))), + Err(e) => { + return Err(pyo3::exceptions::PyValueError::new_err(format!( + "Parse error at {}:{}: {}", + e.line, e.column, e.message + ))) + } }; match self.inner.execute(&program) { @@ -31,7 +44,7 @@ impl AetherInterpreter { Err(e) => Err(pyo3::exceptions::PyRuntimeError::new_err(e)), } } - + /// Reset the interpreter state. fn reset(&mut self) { self.inner = Interpreter::new(); @@ -42,15 +55,20 @@ impl AetherInterpreter { #[pymodule] fn aether_lang(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; - + /// Convenience function to run a script once #[pyfn(m)] fn run(source: String) -> PyResult { let mut interpreter = Interpreter::new(); - let mut parser = Parser::new(&source); + let mut parser = Parser::new(&source); let program = match parser.parse() { Ok(p) => p, - Err(e) => return Err(pyo3::exceptions::PyValueError::new_err(format!("Parse error at {}:{}: {}", e.line, e.column, e.message))), + Err(e) => { + return Err(pyo3::exceptions::PyValueError::new_err(format!( + "Parse error at {}:{}: {}", + e.line, e.column, e.message + ))) + } }; match interpreter.execute(&program) { diff --git a/crates/aether-lang/src/vm.rs b/crates/aether-lang/src/vm.rs new file mode 100644 index 0000000..06da4d6 --- /dev/null +++ b/crates/aether-lang/src/vm.rs @@ -0,0 +1,910 @@ +//! ═══════════════════════════════════════════════════════════════════════════════ +//! TITAN Cortex: The High-Throughput Virtual Machine +//! ═══════════════════════════════════════════════════════════════════════════════ +//! +//! "The Left Brain of AEGIS." +//! +//! Optimization targets: +//! - Stack-based execution (Cache locality) +//! - Linear bytecode (Predictable branching) +//! - Explicit topological ops (EMBED, ATTEND, PRUNE) +//! +//! ═══════════════════════════════════════════════════════════════════════════════ + +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + +#[cfg(not(feature = "std"))] +use alloc::boxed::Box; +#[cfg(not(feature = "std"))] +use alloc::string::String; +#[cfg(not(feature = "std"))] +use alloc::vec; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + +#[cfg(feature = "std")] +use std::string::String; +#[cfg(feature = "std")] +use std::vec::Vec; + +use crate::ast::{BinaryOp, Expr, ExprKind, Literal, Program, Statement, StmtKind, UnaryOp}; +use crate::interpreter::Value; +use aether_core::memory::ManifoldHeap; // From Phase 1 + +/// Titan Bytecode Instructions +#[derive(Debug, Clone, Copy)] +#[allow(non_camel_case_types)] +pub enum OpCode { + /// Push constant value onto stack + PUSH(f64), + /// Push boolean value onto stack + PUSH_BOOL(bool), + /// Push variable value + LOAD(usize), // Index into constant pool or variable table? Let's use register/slot index + /// Store top of stack to variable + STORE(usize), + + /// Arithmetic + ADD, + SUB, + MUL, + DIV, + MOD, + NEG, + EQ, + NEQ, + LT, + GT, + LE, + GE, + AND, + OR, + NOT, + + /// Topology / Core Logic + /// Embeds the top value into the manifold + EMBED, + /// Checks topological attention/neighbors + ATTEND, + /// Explicit entropy regulation point + PRUNE, + + /// Control Flow + JMP(isize), + JMP_IF_FALSE(isize), + CALL(usize, usize), + RET, + + /// Output + PRINT, + + /// End of program + HALT, +} + +/// The Titan Virtual Machine +pub struct TitanVM { + /// Instruction Pointer + ip: usize, + /// The Bytecode DNA + code: Vec, + /// Operand Stack (Fast, hot memory) + stack: Vec, + // In a real optimized VM, we'd use a primitive stack f64, but for compatibility with AEGIS Value type... + // To achieve the 100x speedup, we should probably strictly stick to f64 for calculations + // and only box when necessary. But let's start safe. + /// The Substrate (Heap) + heap: ManifoldHeap, + + /// Call Frame / Locals (simplified map for now, or vector) + locals: Vec, + frames: Vec, +} + +struct CallFrame { + return_ip: usize, + locals: Vec, +} + +impl TitanVM { + pub fn new() -> Self { + Self { + ip: 0, + code: Vec::new(), + stack: Vec::with_capacity(1024), + heap: ManifoldHeap::new(), + locals: vec![Value::Unit; 256], // Pre-alloc locals slots + frames: Vec::new(), + } + } + + pub fn load_code(&mut self, code: Vec) { + self.code = code; + self.ip = 0; + } + + pub fn run(&mut self) -> Result { + loop { + if self.ip >= self.code.len() { + break; + } + + let op = self.code[self.ip]; + self.ip += 1; + + match op { + OpCode::HALT => break, + + OpCode::PUSH(v) => self.stack.push(Value::Num(v)), + OpCode::PUSH_BOOL(v) => self.stack.push(Value::Bool(v)), + + OpCode::ADD => { + let b = self.pop_num()?; + let a = self.pop_num()?; + self.stack.push(Value::Num(a + b)); + } + OpCode::SUB => { + let b = self.pop_num()?; + let a = self.pop_num()?; + self.stack.push(Value::Num(a - b)); + } + OpCode::MUL => { + let b = self.pop_num()?; + let a = self.pop_num()?; + self.stack.push(Value::Num(a * b)); + } + OpCode::DIV => { + let b = self.pop_num()?; + if b == 0.0 { + return Err("Division by zero".into()); + } + let a = self.pop_num()?; + self.stack.push(Value::Num(a / b)); + } + OpCode::MOD => { + let b = self.pop_num()?; + if b == 0.0 { + return Err("Modulo by zero".into()); + } + let a = self.pop_num()?; + self.stack.push(Value::Num(a % b)); + } + OpCode::NEG => { + let value = self.pop_num()?; + self.stack.push(Value::Num(-value)); + } + OpCode::EQ => { + let b = self.stack.pop().ok_or("Stack underflow")?; + let a = self.stack.pop().ok_or("Stack underflow")?; + self.stack.push(Value::Bool(values_equal(&a, &b))); + } + OpCode::NEQ => { + let b = self.stack.pop().ok_or("Stack underflow")?; + let a = self.stack.pop().ok_or("Stack underflow")?; + self.stack.push(Value::Bool(!values_equal(&a, &b))); + } + OpCode::LT => { + let b = self.pop_num()?; + let a = self.pop_num()?; + self.stack.push(Value::Bool(a < b)); + } + OpCode::GT => { + let b = self.pop_num()?; + let a = self.pop_num()?; + self.stack.push(Value::Bool(a > b)); + } + OpCode::LE => { + let b = self.pop_num()?; + let a = self.pop_num()?; + self.stack.push(Value::Bool(a <= b)); + } + OpCode::GE => { + let b = self.pop_num()?; + let a = self.pop_num()?; + self.stack.push(Value::Bool(a >= b)); + } + OpCode::AND => { + let b = self.pop_truthy()?; + let a = self.pop_truthy()?; + self.stack.push(Value::Bool(a && b)); + } + OpCode::OR => { + let b = self.pop_truthy()?; + let a = self.pop_truthy()?; + self.stack.push(Value::Bool(a || b)); + } + OpCode::NOT => { + let value = self.pop_truthy()?; + self.stack.push(Value::Bool(!value)); + } + + OpCode::PRINT => { + let val = self.stack.pop().ok_or("Stack underflow")?; + // In no_std we might print differently, for now simple debug + #[cfg(feature = "std")] + println!("{:?}", val); + } + + OpCode::EMBED => { + let _val = self.pop_num()?; + // In a real integration, this would push to the TimeDelayEmbedder + // For now, we simulate the 'Action' + // self.heap.alloc(Value::Num(val)); // Store in manifold + } + + OpCode::PRUNE => { + // Trigger Entropy Regulation + self.heap.regulate_entropy(|_h| { + // Mark roots (stack, locals) + // This binding is tricky without referencing self inside closure + // Ideally pass a closure that captures the roots. + // Simplified: + }); + } + + OpCode::LOAD(idx) => { + if idx < self.locals.len() { + self.stack.push(self.locals[idx].clone()); + } else { + return Err("Variable index out of bounds".into()); + } + } + OpCode::STORE(idx) => { + let val = self.stack.pop().ok_or("Stack underflow")?; + if idx >= self.locals.len() { + // Grow locals if needed (simple dynamic growth) + self.locals.resize(idx + 1, Value::Unit); + } + self.locals[idx] = val; + } + + OpCode::JMP(offset) => { + // safer pointer arithmetic + let next = self.ip as isize + offset; + if next < 0 { + return Err("Invalid Jump".into()); + } + self.ip = next as usize; + } + + OpCode::JMP_IF_FALSE(offset) => { + let val = self.stack.pop().ok_or("Stack underflow")?; + let condition = match val { + Value::Bool(b) => b, + Value::Num(n) => n != 0.0, + _ => false, + }; + + if !condition { + let next = self.ip as isize + offset; + if next < 0 { + return Err("Invalid Jump".into()); + } + self.ip = next as usize; + } + } + + OpCode::CALL(target, arity) => { + if target >= self.code.len() { + return Err("Function target out of bounds".into()); + } + + let mut args = Vec::with_capacity(arity); + for _ in 0..arity { + args.push(self.stack.pop().ok_or("Stack underflow")?); + } + args.reverse(); + + let frame = CallFrame { + return_ip: self.ip, + locals: core::mem::replace(&mut self.locals, vec![Value::Unit; 256]), + }; + self.frames.push(frame); + + if self.locals.len() < arity { + self.locals.resize(arity, Value::Unit); + } + for (idx, value) in args.into_iter().enumerate() { + self.locals[idx] = value; + } + + self.ip = target; + } + + OpCode::RET => { + let value = self.stack.pop().unwrap_or(Value::Unit); + let frame = self.frames.pop().ok_or("Return outside function")?; + self.locals = frame.locals; + self.ip = frame.return_ip; + self.stack.push(value); + } + + _ => return Err("Unimplemented OpCode".into()), + } + } + + Ok(self.stack.pop().unwrap_or(Value::Unit)) + } + + fn pop_num(&mut self) -> Result { + match self.stack.pop() { + Some(Value::Num(n)) => Ok(n), + Some(_) => Err("Type Error: Expected Number".into()), + None => Err("Stack Underflow".into()), + } + } + + fn pop_truthy(&mut self) -> Result { + match self.stack.pop() { + Some(Value::Bool(value)) => Ok(value), + Some(Value::Num(value)) => Ok(value != 0.0), + Some(_) => Err("Type Error: Expected Boolean".into()), + None => Err("Stack Underflow".into()), + } + } +} + +fn values_equal(left: &Value, right: &Value) -> bool { + match (left, right) { + (Value::Num(a), Value::Num(b)) => a == b, + (Value::Bool(a), Value::Bool(b)) => a == b, + (Value::Str(a), Value::Str(b)) => a == b, + _ => false, + } +} + +/// The Compiler: AST -> Bytecode +pub struct Compiler { + code: Vec, + /// Simple symbol table: name -> index + locals: Vec, + functions: Vec, + loop_stack: Vec, +} + +struct CompiledFunction { + name: String, + target: usize, + arity: usize, +} + +struct LoopContext { + break_jumps: Vec, + continue_jumps: Vec, +} + +impl Compiler { + pub fn new() -> Self { + Self { + code: Vec::new(), + locals: Vec::new(), + functions: Vec::new(), + loop_stack: Vec::new(), + } + } + + pub fn compile(mut self, program: &Program) -> Vec { + for stmt in &program.statements { + self.compile_stmt(stmt); + } + self.code.push(OpCode::HALT); + self.code + } + + fn resolve_local(&mut self, name: &str) -> usize { + if let Some(idx) = self.locals.iter().position(|r| r == name) { + idx + } else { + let idx = self.locals.len(); + self.locals.push(name.to_string()); + idx + } + } + + fn emit_jump_placeholder(&mut self) -> usize { + let idx = self.code.len(); + self.code.push(OpCode::JMP(0)); + idx + } + + fn patch_jump_to(&mut self, idx: usize, target: usize) { + let offset = (target as isize) - (idx as isize) - 1; + self.code[idx] = OpCode::JMP(offset); + } + + fn push_loop_context(&mut self) { + self.loop_stack.push(LoopContext { + break_jumps: Vec::new(), + continue_jumps: Vec::new(), + }); + } + + fn patch_loop_context(&mut self, continue_target: usize, break_target: usize) { + if let Some(context) = self.loop_stack.pop() { + for idx in context.continue_jumps { + self.patch_jump_to(idx, continue_target); + } + for idx in context.break_jumps { + self.patch_jump_to(idx, break_target); + } + } + } + + fn compile_stmt(&mut self, stmt: &Statement) { + match &stmt.node { + StmtKind::Expr(expr) => { + self.compile_expr(expr); + // Expression statement usually discards result unless it's a specific context + // For now, we leave it on stack or assume explicit print/store + } + StmtKind::Render(stmt) => { + // self.compile_expr(&stmt.data); // Ooops, need to fix RenderStmt access (target currently Ident) + // Actually RenderStmt has 'target' Ident. Access variable. + let idx = self.resolve_local(&stmt.target); + self.code.push(OpCode::LOAD(idx)); + self.code.push(OpCode::PRINT); + } + StmtKind::Var(decl) => { + self.compile_expr(&decl.value); + let idx = self.resolve_local(&decl.name); + self.code.push(OpCode::STORE(idx)); + } + StmtKind::Assign(stmt) => { + self.compile_expr(&stmt.value); + let idx = self.resolve_local(&stmt.name); + self.code.push(OpCode::STORE(idx)); + } + StmtKind::While(stmt) => { + // Label: Start + let start_ip = self.code.len(); + + // Condition + self.compile_expr(&stmt.condition); + + // Jump if False placeholder + let jmp_false_idx = self.code.len(); + self.code.push(OpCode::JMP_IF_FALSE(0)); + + // Body + self.push_loop_context(); + for s in &stmt.body.statements { + self.compile_stmt(s); + } + + // Jump back to Start + let end_ip = self.code.len(); + let back_jump = (start_ip as isize) - (end_ip as isize) - 1; // -1 because IP increments after fetch + self.code.push(OpCode::JMP(back_jump)); + + // Patch Jump If False + let patch_offset = (self.code.len() as isize) - (jmp_false_idx as isize) - 1; + self.code[jmp_false_idx] = OpCode::JMP_IF_FALSE(patch_offset); + self.patch_loop_context(start_ip, self.code.len()); + } + StmtKind::If(stmt) => { + // Condition + self.compile_expr(&stmt.condition); + + // JMP_IF_FALSE to Else or End + let jmp_false_idx = self.code.len(); + self.code.push(OpCode::JMP_IF_FALSE(0)); + + // Then Block + for s in &stmt.then_branch.statements { + self.compile_stmt(s); + } + + // If there's an Else block, we need a Jump over it at end of Then + let mut jmp_end_idx = None; + + if let Some(_else_branch) = &stmt.else_branch { + jmp_end_idx = Some(self.code.len()); + self.code.push(OpCode::JMP(0)); + } + + // Patch False Jump to here (start of Else or End) + let false_dest = self.code.len(); + let patch_false = (false_dest as isize) - (jmp_false_idx as isize) - 1; + self.code[jmp_false_idx] = OpCode::JMP_IF_FALSE(patch_false); + + // Compile Else + if let Some(else_branch) = &stmt.else_branch { + for s in &else_branch.statements { + self.compile_stmt(s); + } + + // Patch End Jump + if let Some(idx) = jmp_end_idx { + let end_dest = self.code.len(); + let patch_end = (end_dest as isize) - (idx as isize) - 1; + self.code[idx] = OpCode::JMP(patch_end); + } + } + } + StmtKind::For(stmt) => { + // 1. Initialize Iterator + let start_val = stmt.range.start.as_f64(); + let end_val = stmt.range.end.as_f64(); + + // PUSH start_val + self.code.push(OpCode::PUSH(start_val)); + // STORE iterator + let iter_idx = self.resolve_local(&stmt.iterator); + self.code.push(OpCode::STORE(iter_idx)); + + // 2. Loop Start Label + let start_ip = self.code.len(); + + // 3. Condition: iterator != end (simplified range loop) + // LOAD iterator + self.code.push(OpCode::LOAD(iter_idx)); + // PUSH end + self.code.push(OpCode::PUSH(end_val)); + // SUB + self.code.push(OpCode::SUB); + + // 4. Jump if False (if 0/Equal) to End + let jmp_false_idx = self.code.len(); + self.code.push(OpCode::JMP_IF_FALSE(0)); + + // 5. Body + self.push_loop_context(); + for s in &stmt.body.statements { + self.compile_stmt(s); + } + + // 6. Increment Iterator + let continue_target = self.code.len(); + // LOAD iterator + self.code.push(OpCode::LOAD(iter_idx)); + // PUSH 1.0 (step) + self.code.push(OpCode::PUSH(1.0)); + // ADD + self.code.push(OpCode::ADD); + // STORE iterator + self.code.push(OpCode::STORE(iter_idx)); + + // 7. Jump back to Start + let end_ip = self.code.len(); + let back_jump = (start_ip as isize) - (end_ip as isize) - 1; + self.code.push(OpCode::JMP(back_jump)); + + // 8. Patch Jump If False + let patch_offset = (self.code.len() as isize) - (jmp_false_idx as isize) - 1; + self.code[jmp_false_idx] = OpCode::JMP_IF_FALSE(patch_offset); + self.patch_loop_context(continue_target, self.code.len()); + } + StmtKind::Loop(stmt) => { + // Label: Start + let start_ip = self.code.len(); + let mut jmp_until_idx = None; + + if let Some(condition) = &stmt.until { + self.compile_expr(condition); + self.code.push(OpCode::NOT); + jmp_until_idx = Some(self.code.len()); + self.code.push(OpCode::JMP_IF_FALSE(0)); + } + + // Body + self.push_loop_context(); + for s in &stmt.body.statements { + self.compile_stmt(s); + } + + // Jump back to Start + let end_ip = self.code.len(); + let back_jump = (start_ip as isize) - (end_ip as isize) - 1; + self.code.push(OpCode::JMP(back_jump)); + + if let Some(idx) = jmp_until_idx { + let patch_offset = (self.code.len() as isize) - (idx as isize) - 1; + self.code[idx] = OpCode::JMP_IF_FALSE(patch_offset); + } + self.patch_loop_context(start_ip, self.code.len()); + } + StmtKind::Fn(decl) => { + let jmp_over_idx = self.code.len(); + self.code.push(OpCode::JMP(0)); + + let target = self.code.len(); + self.functions.push(CompiledFunction { + name: decl.name.clone(), + target, + arity: decl.params.len(), + }); + + let outer_locals = core::mem::replace(&mut self.locals, decl.params.clone()); + for s in &decl.body.statements { + self.compile_stmt(s); + } + self.code.push(OpCode::RET); + self.locals = outer_locals; + + let patch_offset = (self.code.len() as isize) - (jmp_over_idx as isize) - 1; + self.code[jmp_over_idx] = OpCode::JMP(patch_offset); + } + StmtKind::Return(stmt) => { + if let Some(value) = &stmt.value { + self.compile_expr(value); + } + self.code.push(OpCode::RET); + } + StmtKind::Break(_) => { + let idx = self.emit_jump_placeholder(); + if let Some(context) = self.loop_stack.last_mut() { + context.break_jumps.push(idx); + } + } + StmtKind::Continue(_) => { + let idx = self.emit_jump_placeholder(); + if let Some(context) = self.loop_stack.last_mut() { + context.continue_jumps.push(idx); + } + } + _ => { + // TODO: Implement unsupported surface forms in the VM compiler. + } + } + } + + fn compile_expr(&mut self, expr: &Expr) { + match &expr.node { + ExprKind::Literal(l) => match l { + Literal::Num(n) => self.code.push(OpCode::PUSH(*n)), + Literal::Bool(b) => self.code.push(OpCode::PUSH_BOOL(*b)), + _ => {} + }, + ExprKind::Ident(name) => { + let idx = self.resolve_local(name); + self.code.push(OpCode::LOAD(idx)); + } + ExprKind::BinaryOp(left, op, right) => { + self.compile_expr(left); + self.compile_expr(right); + match op { + BinaryOp::Add => self.code.push(OpCode::ADD), + BinaryOp::Sub => self.code.push(OpCode::SUB), + BinaryOp::Mul => self.code.push(OpCode::MUL), + BinaryOp::Div => self.code.push(OpCode::DIV), + BinaryOp::Mod => self.code.push(OpCode::MOD), + BinaryOp::Eq => self.code.push(OpCode::EQ), + BinaryOp::Neq => self.code.push(OpCode::NEQ), + BinaryOp::Lt => self.code.push(OpCode::LT), + BinaryOp::Gt => self.code.push(OpCode::GT), + BinaryOp::Le => self.code.push(OpCode::LE), + BinaryOp::Ge => self.code.push(OpCode::GE), + BinaryOp::And => self.code.push(OpCode::AND), + BinaryOp::Or => self.code.push(OpCode::OR), + } + } + ExprKind::UnaryOp(op, expr) => { + self.compile_expr(expr); + match op { + UnaryOp::Neg => self.code.push(OpCode::NEG), + UnaryOp::Not => self.code.push(OpCode::NOT), + } + } + ExprKind::Call { name, args } => { + let function_idx = self + .functions + .iter() + .position(|function| function.name == *name); + if let Some(function_idx) = function_idx { + for arg in args { + if let crate::ast::CallArg::Positional(expr) = arg { + self.compile_expr(expr); + } + } + let function = &self.functions[function_idx]; + self.code + .push(OpCode::CALL(function.target, function.arity)); + } + } + _ => {} + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parser::Parser; + + fn run_source(source: &str) -> Value { + let mut parser = Parser::new(source); + let program = parser.parse().expect("source should parse"); + let compiler = Compiler::new(); + let code = compiler.compile(&program); + let mut vm = TitanVM::new(); + vm.load_code(code); + vm.run().expect("vm should run") + } + + #[test] + fn test_titan_math() { + let mut vm = TitanVM::new(); + // 5 + 3 * 2 = 11 + let code = vec![ + OpCode::PUSH(5.0), + OpCode::PUSH(3.0), + OpCode::PUSH(2.0), + OpCode::MUL, + OpCode::ADD, + OpCode::HALT, + ]; + + vm.load_code(code); + let res = vm.run().unwrap(); + + if let Value::Num(n) = res { + assert_eq!(n, 11.0); + } else { + panic!("Expected number"); + } + } + + #[test] + fn test_compiler_for_loop() { + use crate::ast::{Block, ForStmt, Number, Range, Span, VarDecl}; + + // for i in 0:3 { accum = accum + i } + // Result should be 0+1+2 = 3. + + let program = Program { + statements: vec![ + // accum = 0 + Statement::new( + StmtKind::Var(VarDecl { + type_hint: None, + name: "accum".to_string(), + value: Expr::new(ExprKind::Literal(Literal::Num(0.0)), Span::default()), + }), + Span::default(), + ), + // for i in 0:3 + Statement::new( + StmtKind::For(ForStmt { + iterator: "i".to_string(), + range: Range { + start: Number::Int(0), + end: Number::Int(3), + }, + body: Block { + statements: vec![ + // accum = accum + i + Statement::new( + StmtKind::Var(VarDecl { + type_hint: None, + name: "accum".to_string(), + value: Expr::new( + ExprKind::BinaryOp( + Box::new(Expr::new( + ExprKind::Ident("accum".to_string()), + Span::default(), + )), + BinaryOp::Add, + Box::new(Expr::new( + ExprKind::Ident("i".to_string()), + Span::default(), + )), + ), + Span::default(), + ), + }), + Span::default(), + ), + ], + }, + }), + Span::default(), + ), + // Expr: accum (to leave result on stack) + Statement::new( + StmtKind::Expr(Expr::new( + ExprKind::Ident("accum".to_string()), + Span::default(), + )), + Span::default(), + ), + ], + }; + + let compiler = Compiler::new(); + let code = compiler.compile(&program); + + let mut vm = TitanVM::new(); + vm.load_code(code); + let res = vm.run().unwrap(); + + if let Value::Num(n) = res { + assert_eq!(n, 3.0); + } else { + panic!("Expected number, got {:?}", res); + } + } + + #[test] + fn test_vm_modulo_comparison_and_boolean_ops() { + let res = run_source("let ok = 10 % 4 == 2 && !false~ ok~"); + + assert!(matches!(res, Value::Bool(true))); + } + + #[test] + fn test_vm_assignment_if_and_while_match_interpreter_surface() { + let res = run_source( + "let i = 0~ + if 1 < 2 { i = i + 1~ } + while i < 3 { i = i + 1~ } + i~", + ); + + assert!(matches!(res, Value::Num(3.0))); + } + + #[test] + fn test_vm_seal_until_condition() { + let res = run_source("let count = 0~ seal until count >= 3 { count = count + 1~ } count~"); + + assert!(matches!(res, Value::Num(3.0))); + } + + #[test] + fn test_vm_user_function_explicit_return() { + let res = run_source("fn add(a, b) { return a + b~ } let result = add(2, 3)~ result~"); + + assert!(matches!(res, Value::Num(5.0))); + } + + #[test] + fn test_vm_user_function_implicit_last_expression_return() { + let res = run_source("fn one() { let x = 1~ x~ } one()~"); + + assert!(matches!(res, Value::Num(1.0))); + } + + #[test] + fn test_vm_user_function_parameters_are_call_frame_local() { + let res = run_source("let x = 10~ fn id(x) { return x~ } let y = id(3)~ let z = x + y~ z~"); + + assert!(matches!(res, Value::Num(13.0))); + } + + #[test] + fn test_vm_break_exits_while_loop() { + let res = run_source( + "let i = 0~ + let sum = 0~ + while i < 10 { + i = i + 1~ + if i == 4 { break~ } + sum = sum + i~ + } + sum~", + ); + + assert!(matches!(res, Value::Num(6.0))); + } + + #[test] + fn test_vm_continue_skips_to_next_while_iteration() { + let res = run_source( + "let i = 0~ + let sum = 0~ + while i < 5 { + i = i + 1~ + if i == 3 { continue~ } + sum = sum + i~ + } + sum~", + ); + + assert!(matches!(res, Value::Num(12.0))); + } +} diff --git a/aether-lang/src/webgl_export.rs b/crates/aether-lang/src/webgl_export.rs similarity index 87% rename from aether-lang/src/webgl_export.rs rename to crates/aether-lang/src/webgl_export.rs index bc5d623..e130dd2 100644 --- a/aether-lang/src/webgl_export.rs +++ b/crates/aether-lang/src/webgl_export.rs @@ -6,6 +6,13 @@ //! //! ═══════════════════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════════════════ +// Aether-Lang — invented by Teerth Sharma +// https://github.com/teerthsharma/Aether-Lang +// Copyright (c) 2026 Teerth Sharma. All Rights Reserved. +// ═══════════════════════════════════════════════════════════════════════════════ +// + #![allow(dead_code)] use heapless::String; diff --git a/docs/AEGIS_LLM_SPECS.md b/docs/AEGIS_LLM_SPECS.md index e1d4908..9ffd7b0 100644 --- a/docs/AEGIS_LLM_SPECS.md +++ b/docs/AEGIS_LLM_SPECS.md @@ -1,48 +1,26 @@ -# AEGIS LLM: Specs & Super Powers +# LLM Runtime Status -If you build an LLM using the AEGIS Bio-Kernel and AETHER ecosystem, you are not building a standard Transformer. You are building a **Living Topological Intelligence**. +This page is retained for existing links. LLM-facing work is not currently an +active public capability claim. -## 🚀 Technical Specifications +## Active Repository Surface -| Component | AEGIS Specification | Standard LLM (PyTorch/Transformers) | -|-----------|--------------------|-------------------------------------| -| **Architecture** | **Geometric Sparse-Event Transformer** | Dense Transformer | -| **Attention Mechanism** | **AETHER Topological Attention** ($O(N)$) | Softmax Dot-Product ($O(N^2)$) | -| **Kernel** | **Bio-Kernel** (Bare metal, `no_std`, Geometric Governor) | Linux Kernel + CUDA Runtime | -| **Optimization Goal** | **Topological Stability** (Betti Numbers) | Loss Minimization (MSE/Cross-Entropy) | -| **Data Representation** | **Manifold Embeddings** (Time-Delay, $\beta_0, \beta_1$ shapes) | High-Dim Vectors | -| **Training Loop** | **Seal-Loop** (Self-stabilizing feedback) | Standard Backpropagation Epochs | -| **Compute Backend** | Hybrid: **Geometric CPU Gatekeeper** + **WGPU Shaders** | CUDA / Metal | +- Optional `ml` feature dependencies exist in `aether-lang`. +- Interpreter value variants and native function names include LLM-related + entries. +- Tensor, neural, and ML helper primitives exist in `aether-core`. ---- +## Gated Surface -## ⚡ Super Powers +Do not claim LLM inference performance, model quality, memory reduction, or +hardware acceleration without: -Building an LLM on AEGIS grants it distinct capabilities that standard models lack: +- a runnable command; +- model name and artifact hash; +- hardware and software environment; +- prompt/input set; +- correctness or output-quality check; +- baseline implementation; +- timing and memory artifact. -### 1. 🧠 Massive Context via Topological Pruning -**The Power:** "Infinite" Context without Quadratic Cost. -**How:** Standard attention looks at *everything* ($N^2$). AEGIS uses **AETHER's Hierarchical Block Tree** to treat data as points in a 3D manifold. It only computes attention for "neighbors" in this geometric space ($\epsilon$-neighborhoods). -**Result:** You can feed whole books or codebases, and the model only "activates" the relevant topological clusters, keeping inference fast ($O(N)$ or $O(N \log N)$). - -### 2. 🔮 Concept Drift "Sixth Sense" -**The Power:** The model knows when the conversation is shifting. -**How:** The **DriftDetector** (in `aether-core`) tracks the trajectory of the data's centroid in the manifold. If the semantic meaning shifts too fast (high velocity), the Bio-Kernel can trigger a "Wake Up" interrupt or adjust learning rates dynamically. -**Result:** An LLM that adapts its "focus" instantly when you switch topics, rather than hallucinating based on old context. - -### 3. 🧬 "Living" Self-Correction (Bio-Seal) -**The Power:** It trains until it "understands," not just until it memorizes. -**How:** The **Seal-Loop** doesn't just check if loss is low. It checks **Topological Convergence** (do the Betti numbers $\beta_0, \beta_1$ stabilize?). If the "shape" of the data representation is still fluctuating, the model keeps learning. -**Result:** More robust generalization. The model stops training only when it has formed a stable "mental model" of the data structure. - -### 4. 📐 The "Shape" of Meaning -**The Power:** Understanding structure, not just statistics. -**How:** AEGIS sees data as **Manifolds**. It calculates **Betti Numbers** (holes and voids) to classify the complexity of data. -**Result:** The model can distinguish between "simple linear" logic (Betti-1 = 0) and "complex circular" reasoning (Betti-1 > 0), potentially detecting circular arguments or paradoxes naturally. - -## 🛠 Status Check -- **Core Implemented**: `SparseAttentionGraph`, `TimeDelayEmbedder`, `DriftDetector`, `Bio-Kernel`. -- **In Progress**: Wiring `Ml.attention` in the interpreter to fully utilize the `SparseAttentionGraph` (currently uses a naive fallback for demo purposes). - -## 💡 Use Case Recommendation -**Do not build a Chatbot.** Build a **Long-Horizon Reasoning Agent** or a **Real-Time System Watchdog**. AEGIS shines where data is massive, streaming, and structured (like logs, biological signals, or massive code repos), where standard Transformers run out of memory or drift into hallucinations. +See [Benchmark Policy](benchmarks/index.md). diff --git a/docs/API.md b/docs/API.md index 254c873..750e371 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,404 +1,6 @@ -# AEGIS API Reference +# API Reference -Complete Rust API documentation for the AEGIS kernel. +The current API map is maintained at [reference/api.md](reference/api.md). ---- - -## Table of Contents - -1. [Language Module (`lang`)](#language-module) -2. [ML Module (`ml`)](#ml-module) -3. [Manifold Module (`manifold`)](#manifold-module) -4. [AETHER Module (`aether`)](#aether-module) -5. [Topology Module (`topology`)](#topology-module) - ---- - -## Language Module - -### `lang::Lexer` - -Tokenizes AEGIS source code. - -```rust -use aether::lang::Lexer; - -let mut lexer = Lexer::new("manifold M = embed(data, dim=3)"); -let tokens = lexer.tokenize(); -``` - -**Methods:** -| Method | Signature | Description | -|--------|-----------|-------------| -| `new` | `fn new(source: &str) -> Lexer` | Create lexer from source | -| `next_token` | `fn next_token(&mut self) -> Token` | Get next token | -| `tokenize` | `fn tokenize(&mut self) -> Vec` | Tokenize entire source | - -### `lang::TokenKind` - -Token types in AEGIS: - -```rust -pub enum TokenKind { - // Keywords - Manifold, Block, Regress, Render, Embed, Until, Escalate, Convergence, - - // Literals - Identifier(String), Number(i64), Float(i64, i64), StringLit(String), - - // Punctuation - Equals, Colon, Comma, Dot, LBrace, RBrace, LBracket, RBracket, LParen, RParen, - - // Special - Newline, Eof, Error(String), -} -``` - -### `lang::Parser` - -Parses token stream into AST. - -```rust -use aether::lang::Parser; - -let mut parser = Parser::new(source); -let program = parser.parse()?; -``` - -**Methods:** -| Method | Signature | Description | -|--------|-----------|-------------| -| `new` | `fn new(source: &str) -> Parser` | Create parser | -| `parse` | `fn parse(&mut self) -> Result` | Parse program | - -### `lang::Interpreter` - -Executes AEGIS programs. - -```rust -use aether::lang::{Parser, Interpreter}; - -let mut parser = Parser::new(source); -let program = parser.parse()?; - -let mut interpreter = Interpreter::new(); -let result = interpreter.execute(&program)?; -``` - -**Methods:** -| Method | Signature | Description | -|--------|-----------|-------------| -| `new` | `fn new() -> Interpreter` | Create interpreter | -| `execute` | `fn execute(&mut self, prog: &Program) -> Result` | Execute program | - ---- - -## ML Module - -### `ml::ManifoldRegressor` - -Non-linear regression on D-dimensional manifolds. - -```rust -use aether::ml::regressor::{ManifoldRegressor, ModelType}; - -let mut regressor: ManifoldRegressor<3> = ManifoldRegressor::new(ModelType::Linear); -regressor.add_point([0.0, 0.5, 0.25], 1.0); -regressor.add_point([0.1, 0.55, 0.28], 1.1); - -let error = regressor.fit(); -let prediction = regressor.predict(&[0.05, 0.52, 0.26]); -``` - -**Methods:** -| Method | Signature | Description | -|--------|-----------|-------------| -| `new` | `fn new(model: ModelType) -> Self` | Create regressor | -| `add_point` | `fn add_point(&mut self, point: [f64; D], target: f64)` | Add training data | -| `fit` | `fn fit(&mut self) -> f64` | Fit model, returns error | -| `predict` | `fn predict(&self, point: &[f64; D]) -> f64` | Predict value | -| `upgrade_model` | `fn upgrade_model(&mut self)` | Escalate to next complexity | -| `error` | `fn error(&self) -> f64` | Get current error | -| `coefficients` | `fn coefficients(&self) -> &Coefficients` | Get fitted coefficients | - -### `ml::ModelType` - -Available regression models: - -```rust -pub enum ModelType { - Linear, - Polynomial(u8), // degree - Rbf { gamma: f64 }, - GaussianProcess { length_scale: f64 }, - GeodesicRegression, -} -``` - -**Complexity levels:** -| Model | Complexity | -|-------|------------| -| `Linear` | 1 | -| `Polynomial(d)` | 1 + d | -| `Rbf` | 5 | -| `GaussianProcess` | 7 | -| `GeodesicRegression` | 9 | - -### `ml::ConvergenceDetector` - -Detects topological convergence. - -```rust -use aether::ml::convergence::{ConvergenceDetector, BettiNumbers}; - -let mut detector = ConvergenceDetector::new(1e-6, 5); - -// Record epoch metrics -detector.record_epoch(BettiNumbers::new(2, 1), 0.1, 0.05); -detector.record_epoch(BettiNumbers::new(1, 0), 0.01, 0.01); - -if detector.is_converged() { - println!("Converged! Score: {}", detector.convergence_score()); -} -``` - -**Methods:** -| Method | Signature | Description | -|--------|-----------|-------------| -| `new` | `fn new(epsilon: f64, window: usize) -> Self` | Create detector | -| `record_epoch` | `fn record_epoch(&mut self, betti: BettiNumbers, drift: f64, error: f64)` | Record metrics | -| `is_converged` | `fn is_converged(&self) -> bool` | Check convergence | -| `convergence_score` | `fn convergence_score(&self) -> f64` | Score 0-1 | -| `reset` | `fn reset(&mut self)` | Reset detector | - -### `ml::BettiNumbers` - -Topological shape signature. - -```rust -use aether::ml::convergence::BettiNumbers; - -let betti = BettiNumbers::new(1, 0); -assert!(betti.is_singular()); // Single component, no loops -``` - -**Methods:** -| Method | Signature | Description | -|--------|-----------|-------------| -| `new` | `fn new(beta_0: u32, beta_1: u32) -> Self` | Create Betti numbers | -| `is_singular` | `fn is_singular(&self) -> bool` | β₀=1, β₁=0 | -| `distance` | `fn distance(&self, other: &Self) -> u32` | L1 distance | - -### `ml::EscalatingBenchmark` - -Auto-escalating benchmark runner. - -```rust -use aether::ml::benchmark::{EscalatingBenchmark, BenchmarkConfig}; - -let config = BenchmarkConfig { - epsilon: 1e-6, - max_epochs: 100, - escalation_patience: 10, - stability_window: 5, - auto_escalate: true, -}; - -let mut benchmark: EscalatingBenchmark<3> = EscalatingBenchmark::new(config); -benchmark.add_data([0.0, 0.5, 0.25], 1.0); - -let result = benchmark.run(); -println!("Converged: {}, Epochs: {}", result.converged, result.epochs); -``` - ---- - -## Manifold Module - -### `manifold::TimeDelayEmbedder` - -Implements Takens' theorem for time-delay embedding. - -```rust -use aether::manifold::TimeDelayEmbedder; - -let mut embedder: TimeDelayEmbedder<3> = TimeDelayEmbedder::new(5); // tau=5 - -embedder.push(1.0); -embedder.push(2.0); -// ... push more values - -if let Some(point) = embedder.embed() { - println!("Embedded point: {:?}", point.coords); -} -``` - -**Methods:** -| Method | Signature | Description | -|--------|-----------|-------------| -| `new` | `fn new(tau: usize) -> Self` | Create with time delay | -| `push` | `fn push(&mut self, value: f64)` | Add sample | -| `embed` | `fn embed(&self) -> Option>` | Get embedded point | -| `reset` | `fn reset(&mut self)` | Clear buffer | - -### `manifold::ManifoldPoint` - -A point in D-dimensional manifold space. - -```rust -use aether::manifold::ManifoldPoint; - -let p1 = ManifoldPoint::<3>::new([1.0, 2.0, 3.0]); -let p2 = ManifoldPoint::<3>::new([1.5, 2.5, 3.5]); - -let dist = p1.distance(&p2); -let is_close = p1.is_neighbor(&p2, 1.0); -``` - -### `manifold::SparseAttentionGraph` - -Sparse attention using geometric locality. - -```rust -use aether::manifold::SparseAttentionGraph; - -let mut graph: SparseAttentionGraph<3> = SparseAttentionGraph::new(0.5); - -graph.add_point(ManifoldPoint::new([0.0, 0.0, 0.0])); -graph.add_point(ManifoldPoint::new([0.1, 0.1, 0.1])); - -let (beta_0, beta_1) = graph.shape(); -``` - ---- - -## AETHER Module - -### `aether::BlockMetadata` - -Geometric metadata for a block of points. - -```rust -use aether::aether::{BlockMetadata, HierarchicalBlockTree}; - -let points = vec![ - [0.0, 0.0, 0.0], - [1.0, 0.0, 0.0], - [0.0, 1.0, 0.0], -]; - -let block = BlockMetadata::<3>::from_points(&points); -println!("Centroid: {:?}", block.centroid); -println!("Radius: {}", block.radius); -``` - -**Fields:** -| Field | Type | Description | -|-------|------|-------------| -| `centroid` | `[f64; D]` | Block center | -| `radius` | `f64` | Max deviation | -| `variance` | `f64` | Point variance | -| `concentration` | `f64` | Angular concentration | -| `count` | `usize` | Number of points | - -### `aether::HierarchicalBlockTree` - -Hierarchical tree for multi-scale analysis. - -```rust -use aether::aether::HierarchicalBlockTree; - -let mut tree: HierarchicalBlockTree<3> = HierarchicalBlockTree::new(); -tree.build_from_blocks(&blocks); - -let active_mask = tree.hierarchical_query(&query, threshold); -let pruning = tree.pruning_ratio(&active_mask); -``` - -### `aether::DriftDetector` - -Track centroid drift for convergence detection. - -```rust -use aether::aether::DriftDetector; - -let mut detector: DriftDetector<3> = DriftDetector::new(); - -let drift = detector.update(&[0.0, 0.0, 0.0]); -let drift = detector.update(&[0.01, 0.01, 0.01]); - -if detector.is_drifting(0.1) { - println!("Still drifting!"); -} -``` - ---- - -## Topology Module - -### `topology::BinaryTopology` - -Compute topology of binary data. - -```rust -use aether::topology::BinaryTopology; - -let data = [0u8; 64]; -let topo = BinaryTopology::analyze(&data); - -println!("β₀ = {}, β₁ = {}", topo.beta_0, topo.beta_1); -``` - -### `topology::TopologicalLoader` - -Verify binary code via topological signature. - -```rust -use aether::loader::TopologicalLoader; - -let loader = TopologicalLoader::new(); -let result = loader.verify(binary_data, reference_shape); -``` - ---- - -## Error Types - -### `lang::ParseError` - -Parser error with location. - -```rust -pub struct ParseError { - pub message: String, - pub line: usize, - pub column: usize, -} -``` - ---- - -## Constants - -```rust -// State dimension for kernel -pub const STATE_DIMENSION: usize = 4; - -// Initial adaptive threshold -pub const INITIAL_EPSILON: f64 = 0.1; - -// Maximum blocks -pub const MAX_BLOCKS: usize = 64; - -// Maximum tree depth -pub const MAX_DEPTH: usize = 16; -``` - ---- - -## See Also - -- [Language Reference](LANGUAGE.md) -- [Tutorial](TUTORIAL.md) -- [Examples](EXAMPLES.md) -- [Architecture](ARCHITECTURE.md) +Use that page as the public contract. It separates crate APIs, CLI commands, and +kernel-facing functions without implying that every internal type is stable. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 8a2c73d..ac12317 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,380 +1,28 @@ -# AETHER-Shield Architecture - -## System Overview - -AETHER-Shield treats the kernel not as a "manager of resources" but as a **Dynamic System on a Manifold**. - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ AETHER-Shield Architecture │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌─────────────────────────────────────────────────────────────────────┐ │ -│ │ Layer 2: Topological Loader │ │ -│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │ │ -│ │ │ ELF Parser │→ │ Sliding │→ │ Shape Verification │ │ │ -│ │ │ │ │ Window (64B) │ │ d(Shape, Ref) ≤ δ │ │ │ -│ │ └──────────────┘ └──────────────┘ └──────────────────────────┘ │ │ -│ └─────────────────────────────────────────────────────────────────────┘ │ -│ ↓ │ -│ ┌─────────────────────────────────────────────────────────────────────┐ │ -# AETHER Architecture - -A deep-dive into the design and implementation of the AETHER Declarative IR for Sparse-Event Execution. - ---- - -## Table of Contents - -1. [Overview](#overview) -2. [Layer Architecture](#layer-architecture) -3. [Language Pipeline](#language-pipeline) -4. [ML Engine](#ml-engine) -5. [Geometric Core](#geomaether-core) -6. [Kernel Layer](#kernel-layer) -7. [Data Flow](#data-flow) -8. [Memory Model](#memory-model) -9. [Extension Points](#extension-points) - ---- - -## Overview - -AETHER is structured as a **layered kernel** where each layer builds on primitives from the layer below: - -``` -┌─────────────────────────────────────────────────────────────┐ -│ AETHER Execution Pipeline │ -├─────────────────────────────────────────────────────────────┤ -│ Layer 4: AETHER Declarative IR │ -├─────────────────────────────────────────────────────────────┤ -│ Layer 3: ML Engine (Manifold Logic) │ -├─────────────────────────────────────────────────────────────┤ -│ Layer 2: Geometric Primitives │ -├─────────────────────────────────────────────────────────────┤ -│ Layer 1: Topological Analysis │ -├─────────────────────────────────────────────────────────────┤ -│ Layer 0: Sparse-Event Microkernel (Execution Target) │ -└─────────────────────────────────────────────────────────────┘ -``` - ---- - -## Layer Architecture - -### Layer 0: Sparse-Event Microkernel - -The foundation layer provides event-driven execution: - -```rust -// Core event loop -loop { - let current_state = get_system_state(); - - if scheduler.should_wake(¤t_state) { - scheduler.handle_event(current_state); - } else { - scheduler.accumulate_entropy(); - } - - hlt(); // Sleep until interrupt -} -``` - -**Components:** -- `SparseScheduler` - Only executes when Δ ≥ ε -- `GeometricGovernor` - PID control for adaptive ε -- `SystemState` - d-dimensional state vector μ(t) - -### Layer 1: Topological Analysis - -Provides topology primitives for shape analysis: - -**Components:** -- `BinaryTopology` - Compute Betti numbers from binary data -- `TopologicalLoader` - Verify code via topological signature -- `BettiNumbers` - (β₀, β₁) shape descriptor - -**Key Insight:** Binary code has a "shape" - NOP sleds, ROP chains, and shellcode have distinct topological signatures. - -### Layer 2: Geometric Primitives - -Provides 3D manifold operations: - -**Components:** -- `TimeDelayEmbedder` - Takens embedding -- `ManifoldPoint` - Point in D-space -- `SparseAttentionGraph` - ε-neighborhood graph -- `GeometricConcentrator` - Streaming PCA - -### Layer 3: ML Engine - -Provides machine learning on manifolds: - -**Components:** -- `ManifoldRegressor` - Non-linear regression -- `EscalatingBenchmark` - Auto-complexity increase -- `ConvergenceDetector` - Topological convergence -- `ResidualAnalyzer` - Residual topology - -### Layer 4: AETHER Declarative IR - -The top-level specification format for orchestrating sparse-event execution: - -**Components:** -- `Lexer` - IR tokenization -- `Parser` - Structural verification -- `IntermediateRepresentation` - Canonical execution graph - ---- - -## IR Consumption Pipeline: The Dual-Target Cortex - -AETHER IR is consumed by a bicameral execution model, targeting different performance profiles: - -### 1. AETHER-Script IR (Dynamic Orchestration) -* **Role:** Rapid prototyping, structural topology, dynamic state declaration. -* **Implementation:** Tree-Walking Interpreter. -* **Architecture:** - ``` - Source -> Lexer -> Parser -> AST -> Tree-Walker - ``` - -### 2. Titan VM (Left Hemisphere) -* **Role:** High-throughput simulation, massive parallelization. -* **Implementation:** Stack-based Linear Bytecode VM. -* **Architecture:** - ``` - AST -> Compiler -> Bytecode -> Titan VM Loop - ``` - -### Unified Memory: The Manifold Heap -Both engines share the **Manifold Heap**, a biologically inspired memory arena. -* **Cyclic Manifold:** A "Bio-Clock" algorithm replaces traditional GC. Memory is treated as a cyclic ring. -* **Entropy Regulation:** Allocation overwrites "High Entropy" (unused) cells on contact. -* **Zero Scan:** We eliminate the O(N) scan entirely. Garbage is treated as passive background entropy. - - -### Lexer Detail - -The lexer recognizes: -- **Keywords**: `manifold`, `block`, `regress`, `render`, `embed`, `until`, `escalate` -- **Operators**: `=`, `:`, `,`, `.`, `{`, `}`, `[`, `]`, `(`, `)` -- **Literals**: Numbers, floats, strings, booleans -- **Identifiers**: Variable names - -### Parser Detail - -Recursive descent parser with grammar: - -```ebnf -program → statement* EOF -statement → manifold_decl | block_decl | regress_stmt | render_stmt -manifold_decl → "manifold" IDENT "=" expr -regress_stmt → "regress" "{" config_pairs "}" -``` - -### Interpreter Detail - -The interpreter maintains: -- **Variables**: Name → Value mapping -- **Manifolds**: Array of ManifoldWorkspace -- **Blocks**: Array of BlockMetadata - ---- - -## ML Engine - -### Regression Pipeline - -``` -Input Data - │ - ▼ -┌──────────────────┐ -│ ManifoldRegressor │ -│ │ -│ ┌──────────────┐ │ -│ │ fit() │ │ ← Least squares / kernel -│ └──────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌──────────────┐ │ -│ │ compute_mse()│ │ ← Error calculation -│ └──────────────┘ │ -└────────┬─────────┘ - │ - ▼ -┌──────────────────────┐ -│ ConvergenceDetector │ -│ │ -│ record_epoch() │ ← Betti, drift, error -│ is_converged() │ ← Check stability -│ convergence_score() │ ← 0-1 score -└──────────────────────┘ -``` - -### Model Escalation - -``` -Level 1: Linear - │ - │ if not converged - ▼ -Level 2: Polynomial(2) - │ - │ if not converged - ▼ -Level 3: Polynomial(3) - │ - │ ... - ▼ -Level N: RBF → GP → Geodesic -``` - -### Convergence Detection - -Three signals combine for convergence: -1. **Betti Stability**: β numbers unchanged for N epochs -2. **Drift Stability**: Centroid movement < threshold -3. **Error Threshold**: MSE < ε - -Convergence score = 0.4×Betti + 0.3×Drift + 0.3×Error - ---- - -## Geometric Core - -### Time-Delay Embedding - -Implements Takens' theorem: - -``` -x(t) → Φ(t) = [x(t), x(t-τ), x(t-2τ), ..., x(t-(d-1)τ)] -``` - -**Implementation:** -```rust -pub struct TimeDelayEmbedder { - buffer: [f64; 256], - head: usize, - tau: usize, -} - -impl TimeDelayEmbedder { - pub fn embed(&self) -> Option> { - // Extract D samples separated by tau - let mut coords = [0.0; D]; - for i in 0..D { - coords[i] = self.buffer[(self.head - i * self.tau) % 256]; - } - Some(ManifoldPoint { coords }) - } -} -``` - -### Block Metadata - -Each block summarizes a region: - -```rust -pub struct BlockMetadata { - pub centroid: [f64; D], // Center of mass - pub radius: f64, // Max deviation - pub variance: f64, // Spread measure - pub concentration: f64, // Angular concentration - pub count: usize, // Point count -} -``` - -### AETHER Hierarchy - -Multi-scale block tree: - -``` -Level 2: Super-clusters (1024 points) - ├── Level 1: Clusters (256 points) - │ ├── Level 0: Blocks (64 points) - │ └── Level 0: Blocks (64 points) - └── Level 1: Clusters (256 points) - ├── Level 0: Blocks (64 points) - └── Level 0: Blocks (64 points) -``` - ---- - -## Kernel Layer - -### Sparse Scheduling - -The kernel only wakes when state deviation exceeds threshold: - -```rust -pub fn should_wake(&self, current: &SystemState) -> bool { - let delta = self.state.deviation(current); - delta >= self.governor.epsilon() -} -``` - -### PID Governor - -Adaptive threshold control: - -```rust -e(t) = R_target - Δ(t)/ε(t) -ε(t+1) = ε(t) + α·e(t) + β·de/dt // PID update -``` - ---- - -## Data Flow - - Level 1 (256 tokens) - ┌───────────┐ ┌───────────┐ ┌───────────┐ - │ Cluster │ │ Cluster │ │ Cluster │ - └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ - │ │ │ - ┌──┬──┼──┬──┐ ┌──┬──┼──┬──┐ ┌──┬──┼──┬──┐ - ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ - Level 0 (64 tokens) - ┌──┐┌──┐┌──┐┌──┐ ┌──┐┌──┐┌──┐┌──┐ ┌──┐┌──┐┌──┐┌──┐ - │B1││B2││B3││B4│ │B5││B6││B7││B8│ │B9││..││..││Bn│ - └──┘└──┘└──┘└──┘ └──┘└──┘└──┘└──┘ └──┘└──┘└──┘└──┘ - -Pruning: If upper_bound(query, cluster) < threshold, - skip entire subtree → O(log n) instead of O(n) -``` - -## Security Model - -### Topological Authentication - -``` -Binary Input - │ - ▼ -┌─────────────────┐ -│ Time-Delay │ Φ(t) = [x(t), x(t-τ), x(t-2τ)] -│ Embedding │ -└─────────────────┘ - │ - ▼ -┌─────────────────┐ -│ Compute Betti │ β₀ = components, β₁ = loops -│ Numbers │ -└─────────────────┘ - │ - ▼ -┌─────────────────┐ -│ Shape Check │ density ∈ [0.1, 0.6]? -└─────────────────┘ - │ - ├── Valid ──▶ Load & Execute - │ - └── Invalid ──▶ Panic(InvalidGeometry) - -Detected Attacks: - • NOP sleds: density ≈ 0 (uniform bytes) - • ROP chains: high β₁ (many loops) - • Encrypted payloads: density > 0.6 -``` +# Architecture + +The current architecture documentation is split by contract: + +- [Runtime Surface](concepts/runtime-surface.md) +- [Language Pipeline](concepts/language-pipeline.md) +- [Execution Model](language/execution-model.md) +- [Persistent Homology](topology/persistent-homology.md) +- [Derivations](topology/derivations.md) +- [Sparse Events](kernel/sparse-events.md) +- [Hardware Boundary](kernel/hardware-boundary.md) + +## System View + +```mermaid +flowchart TB + A["Aether source"] --> B["aether-lang lexer/parser"] + B --> C["AST with spans"] + C --> D["Interpreter"] + C --> E["Titan VM compiler"] + D --> F["aether-core manifolds, topology, ML"] + E --> F + F --> G["aether-kernel sparse-event concepts"] +``` + +The public architecture claim is the composition of these crates. Hardware, +security, and speed claims require the evidence gates listed in +[Evidence Gates](benchmarks/evidence-gates.md). diff --git a/docs/BENCHMARKS.md b/docs/BENCHMARKS.md index be96180..d921ca9 100644 --- a/docs/BENCHMARKS.md +++ b/docs/BENCHMARKS.md @@ -1,36 +1,18 @@ -# ⚡ AETHER Performance Benchmarks +# Benchmarks -Comparisons between AETHER (Candle/Rust) and Python (Torch/Transformers). +The benchmark policy is maintained at [benchmarks/index.md](benchmarks/index.md). -## 1. Large Language Models (LLM) +Legacy speedup tables have been removed from the active docs surface because +they did not include raw artifacts, baselines, environment records, correctness +metrics, or seeds. -**Model:** `TinyLlama/TinyLlama-1.1B-Chat-v1.0` -**Hardware:** Docker Container (CPU) +Use local checks as smoke evidence: -| Metric | Python (Transformers) | AETHER (Native) | Improvement | -|--------|----------------------|----------------|-------------| -| **Load Time** | TBD | TBD | TBD | -| **Inference Speed** | TBD tokens/sec | TBD tokens/sec | TBD | -| **Memory Usage** | TBD | TBD | TBD | - -> *Note: AETHER uses quantized models (GGUF/SafeTensors) optimized for edge deployment, resulting in significantly lower memory footprint and faster startup times.* - -## 2. Geometric Core - -**Task:** Escalating Regression (Sine Wave, 10k points) - -| Implementation | Execution Time | -|----------------|----------------| -| **Python (NumPy)** | 90.1 ms | -| **AETHER (Manifold)** | **0.12 ms** | -| **Speedup** | **~750x** | - -## 3. Topology - -**Task:** Betti Number Calculation (Persistent Homology) - -| Implementation | Execution Time | -|----------------|----------------| -| **GUDHI (Python)** | 50.0 ms | -| **AETHER (Sparse)** | **0.005 ms** | -| **Speedup** | **~10,000x** | +```powershell +cargo fmt --all -- --check +cargo test -p aether-core +cargo test -p aether-lang +cargo test -p aether-cli +cargo check -p aether-core --no-default-features +python -m mkdocs build --strict +``` diff --git a/docs/BIO_CLOCK_PRD.md b/docs/BIO_CLOCK_PRD.md index 83ad662..8dd9ef8 100644 --- a/docs/BIO_CLOCK_PRD.md +++ b/docs/BIO_CLOCK_PRD.md @@ -1,77 +1,15 @@ -# Project: BIO-CLOCK (The Cyclic Manifold) -**Designation:** Architecture Priority Alpha -**Impact Level:** Global / Species-Critical -**Status:** Approved for Implementation +# Bio Clock Status ---- +The Bio Clock material is a design concept for memory and lifecycle behavior. +It is not an active user-facing runtime contract unless backed by code paths and +tests. -## 1. Executive Summary -**"Entropy is not an enemy. It is a fuel."** +Current related surfaces: -Current computing is paralyzed by the "Garbage Collection" fallacy—the idea that memory management is a janitorial task separate from computation. This creates the "Stop-the-World" pause, a fatal flaw for real-time AI and life-critical systems. +- `crates/aegis-core/src/memory.rs`; +- `crates/aether-core/src/memory.rs`; +- [Runtime Surface](concepts/runtime-surface.md); +- [Status Matrix](reference/status.md). -The **Bio-Clock** replaces this with **Algorithmic Homeostasis**. By treating memory as a finite, cyclic resource (the Ouroboros), we eliminate garbage collection entirely. Data is not deleted; it is *metabolized*. - -## 2. The Problem: The "Janitor" Bottleneck -* **Traditional GC (Java/Python):** CPU stops working to scan for dead objects. -* **Result:** Variable latency, energy waste, and inability to guarantee real-time response. -* **Human Cost:** A self-driving car cannot "pause for GC" at 70mph. A pacemaker cannot "sweep the heap" during a heartbeat. - -## 3. The Solution: The Cyclic Manifold -We implement memory as a **Clock**. - -### 3.1. The Mechanism (The Hand of Time) -There is no "free list". There is only the **Current Pointer (CP)**. -1. **Clock Hand:** The CP moves sequentially through the memory ring ($i \rightarrow i+1$). -2. **The Second Chance:** When the Hand touches a cell: - * **If bit=1 (Hot):** Set bit=0 (Cooling) and advance Hand. The cell survives. - * **If bit=0 (Cold):** The cell is dead. **Overwrite it instantly.** -3. **Result:** Allocation is always $O(1)$ amortized. There is no pause. - -### 3.2. Biological Alignment -This mimics ATP cycling in cells. Components are constantly degraded and rebuilt. A cell that stops functioning (stops setting bit=1) is naturally reabsorbed. - -## 4. Technical Specifications - -### 4.1. Struct Definition -```rust -pub struct CyclicHeap { - cycle: [Slot; SIZE], - hand: usize, // The Hand of Time -} - -struct Slot { - data: T, - energy: AtomicBool, // The "Life Force" bit -} -``` - -### 4.2. Performance Guarantees -* **Allocation Complexity:** $O(1)$ (Worst case $O(N)$ only if *everything* is hot, which implies OOM). -* **Deallocation Complexity:** $O(0)$ (Does not exist). -* **Cache Locality:** Perfect (Sequential acccess). - -## 5. Societal & Hardware Impact - -### 5.1. Infinite Uptime (Space & Medical) -A system using Bio-Clock cannot fragment. It is mathematically stable for infinite duration. -* **Use Case:** Voyager-class deep space probes. -* **Use Case:** Neuralink-class brain computer interfaces. - -### 5.2. Thermodynamic Efficiency (Green Computing) -Traditional GC burns electricity to *find* trash. Bio-Clock burns **zero** energy on search. -* **Impact:** If deployed globally, this reduces data center energy consumption by an estimated 15-20%. - -### 5.3. The Interface to Neuromorphics -This algorithm is "Hardware-Ready". It describes the exact behavior of physical **memristor decay**. Implementing this in software now prepares the entire software stack for the transition to biological chips in 2030. - -## 6. Roadmap -1. **Phase 1 (Now):** Implement `CyclicHeap` in `aegis-core/src/memory.rs`. -2. **Phase 2:** Benchmark against standard `Malloc` and `ManifoldHeap`. -3. **Phase 3:** Release as the standard allocator for the AEGIS Kernel. - ---- - -**Signed,** -*AEGIS Architectural Oversight* -*Harvard Laboratory for Advanced Agentic Systems* +Future documentation should state the allocator or memory structure being used, +the invariant it maintains, and the test or benchmark that verifies it. diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md index a16ecff..9710252 100644 --- a/docs/EXAMPLES.md +++ b/docs/EXAMPLES.md @@ -1,524 +1,24 @@ -# AETHER Examples +# Examples -A collection of annotated AETHER scripts for common use cases. +Example documentation should distinguish parser examples from runtime examples. ---- +## Runtime Example -## Table of Contents - -1. [Basic Examples](#basic-examples) -2. [Regression Examples](#regression-examples) -3. [Visualization Examples](#visualization-examples) -4. [Advanced Examples](#advanced-examples) -5. [Complete Applications](#complete-applications) - ---- - -## Basic Examples - -### Example 1: Hello World - -The simplest AETHER program: - -```aegis -// hello_world.aegis -// Create a manifold and render it - -manifold M = embed(data, dim=3, tau=5) -render M -``` - -### Example 2: Block Extraction - -Extract and analyze a geometric block: - -```aegis -// block_basics.aegis - -manifold M = embed(data, dim=3, tau=5) - -// Extract first 64 points as a block -block B = M.cluster(0:64) - -// Get geometric properties -centroid C = B.center // Center point -radius R = B.spread // Max distance from center - -// Render with block highlighted -render M { - highlight: B -} -``` - -### Example 3: Multiple Blocks - -Compare different regions of the manifold: - -```aegis -// multiple_blocks.aegis - -manifold M = embed(time_series, dim=3, tau=7) - -// Extract three time periods -block morning = M[0:100] -block afternoon = M[100:200] -block evening = M[200:300] - -// Get centers -c1 = morning.center -c2 = afternoon.center -c3 = evening.center - -render M { - color: by_cluster -} -``` - ---- - -## Regression Examples - -### Example 4: Simple Polynomial - -Fit a polynomial to data: - -```aegis -// polynomial_fit.aegis - -manifold M = embed(measurements, dim=3, tau=5) - -regress { - model: "polynomial", - degree: 3 -} -``` - -### Example 5: Escalating Regression - -Automatically find the right model: - -```aegis -// escalating.aegis - -manifold M = embed(sensor_data, dim=3, tau=7) - -// Start simple, escalate until convergence -regress { - model: "linear", // Start with linear - escalate: true, // Auto-escalate - until: convergence(1e-6) -} - -// Output will show: -// Linear → Poly(2) → Poly(3) → RBF → Converged! -``` - -### Example 6: RBF Kernel Regression - -For complex non-linear patterns: - -```aegis -// rbf_regression.aegis - -manifold M = embed(complex_data, dim=3, tau=10) - -regress { - model: "rbf", - escalate: true, - until: convergence(1e-5) -} -``` - -### Example 7: Gaussian Process - -With uncertainty quantification: - -```aegis -// gp_regression.aegis - -manifold M = embed(noisy_data, dim=3, tau=5) - -regress { - model: "gp", - until: convergence(1e-4) -} -``` - ---- - -## Visualization Examples - -### Example 8: Density Coloring - -Color by point density: - -```aegis -// density_viz.aegis - -manifold M = embed(data, dim=3, tau=5) - -render M { - color: by_density -} -``` - -### Example 9: Cluster Coloring - -Color by cluster assignment: - -```aegis -// cluster_viz.aegis - -manifold M = embed(data, dim=3, tau=5) - -// Define clusters -block A = M[0:100] -block B = M[100:200] - -render M { - color: by_cluster, - highlight: A -} -``` - -### Example 10: Trajectory Visualization - -Show time evolution: - -```aegis -// trajectory_viz.aegis - -manifold M = embed(time_series, dim=3, tau=5) - -render M { - color: gradient, - trajectory: on // Show temporal path -} +```aether +let data = [1.0, 2.0, 3.0, 4.0]~ +manifold M = embed(data, tau=1)~ +print("embedded")~ ``` -### Example 11: Axis Projection - -Project to 2D for specific views: +## Topology Example -```aegis -// projection_viz.aegis - -manifold M = embed(data, dim=3, tau=5) - -// X-Y projection (axis=2 for Z projection) -render M { - color: by_density, - axis: 2 -} +```aether +import topology~ +let data = [1.0, 1.0, 1.0, 1.0, 1.0]~ +manifold M = embed(data, tau=1)~ +let diagram = topology.ph(M, max_dim=2, mode="vr", max_points=16)~ +let b = topology.betti(diagram, radius=0.0)~ ``` ---- - -## Advanced Examples - -### Example 12: Anomaly Detection - -Detect outliers geometrically: - -```aegis -// anomaly_detection.aegis - -manifold M = embed(sensor_readings, dim=3, tau=10) - -// Normal operating range -block normal = M[0:500] -normal_center = normal.center -normal_radius = normal.spread - -// Check recent data -block recent = M[500:550] -recent_center = recent.center - -// If distance(recent_center, normal_center) > 2*normal_radius -// then we have an anomaly - -render M { - highlight: recent, - color: by_density -} -``` - -### Example 13: Change Point Detection - -Find where patterns change: - -```aegis -// change_detection.aegis - -manifold M = embed(process_data, dim=3, tau=5) - -// Sliding window analysis -block w1 = M[0:50] -block w2 = M[50:100] -block w3 = M[100:150] -block w4 = M[150:200] - -// Compute centroids -c1 = w1.center -c2 = w2.center -c3 = w3.center -c4 = w4.center - -// Large centroid jumps indicate change points -render M { - trajectory: on -} -``` - -### Example 14: Multi-Scale Analysis - -Analyze at multiple granularities: - -```aegis -// multiscale.aegis - -manifold M = embed(data, dim=3, tau=5) - -// Fine scale (16 point blocks) -block fine_1 = M[0:16] -block fine_2 = M[16:32] -block fine_3 = M[32:48] -block fine_4 = M[48:64] - -// Medium scale (64 point blocks) -block medium = M[0:64] - -// Coarse scale (256 point blocks) -block coarse = M[0:256] - -// Compare hierarchical centroids -``` - -### Example 15: Streaming Analysis - -Process data in windows: - -```aegis -// streaming.aegis - -// Initialize with historical data -manifold M = embed(historical, dim=3, tau=5) - -// Reference block -block reference = M[0:100] -ref_center = reference.center - -// As new data arrives, compare to reference -// (In practice, this would be in a loop) -block window = M[100:150] -window_center = window.center -``` - ---- - ---- - -## ML Library Examples - -### Example 16: Neural Network (MLP) - -Train a neural network on the manifold: - -```aegis -// mlp_demo.aegis -import ml - -// 1. Embed data -manifold M = embed(data, dim=3, tau=2) - -// 2. Create Network -let nn = MLP(0.01) // LR=0.01 -nn.add_layer(3, 8) // Input (3D embedding) -> Hidden -nn.add_layer(8, 1) // Hidden -> Output - -// 3. Train Loop -seal { - let loss = nn.train() - // Stops when loss stabilizes -} -``` - -### Example 17: Clustering (K-Means) - -Group similar manifold regions: - -```aegis -// kmeans_demo.aegis -import ml - -manifold M = embed(data, dim=3, tau=5) - -// Create K-Means with K=3 -let kmeans = KMeans(3) - -// Fit to manifold points -let result = kmeans.fit(M) -``` - -### Example 18: Image Convolution - -Process a 2D grid/image: - -```aegis -// conv_demo.aegis -import ml - -let conv = Conv2D() // Default 3x3 kernel - -// Forward pass on dummy data (simulated as list) -let output = conv.forward(image_data) -``` - ---- - -## Complete Applications - -### Application 1: Sensor Fusion - -Fuse multiple sensor streams: - -```aegis -// sensor_fusion.aegis -// Combine temperature, pressure, and humidity sensors - -manifold M = embed(fused_sensors, dim=3, tau=7) - -// Normal operating envelope -block normal_ops = M[0:200] - -// Current state -block current = M[200:210] - -// Check if current is within normal envelope -regress { - model: "rbf", - escalate: true, - until: convergence(1e-4) -} - -render M { - color: by_density, - highlight: current -} -``` - -### Application 2: Predictive Maintenance - -Predict equipment failures: - -```aegis -// predictive_maintenance.aegis - -manifold M = embed(vibration_data, dim=3, tau=10) - -// Known good state -block healthy = M[0:500] -healthy_center = healthy.center -healthy_spread = healthy.spread - -// Known failure precursor -block pre_failure = M[500:600] - -// Current readings -block current = M[600:650] -current_center = current.center - -// If current is closer to pre_failure than healthy -// schedule maintenance! - -render M { - color: by_cluster, - trajectory: on -} -``` - -### Application 3: Financial Analysis - -Detect market regime changes: - -```aegis -// market_regimes.aegis - -manifold M = embed(price_returns, dim=3, tau=5) - -// Bull market period -block bull = M[0:250] - -// Bear market period -block bear = M[250:500] - -// Current market -block now = M[500:520] - -// Classify current regime based on proximity -regress { - model: "gp", - escalate: true, - until: convergence(1e-5) -} - -render M { - color: by_cluster -} -``` - -### Application 4: Signal Processing - -Denoise and analyze signals: - -```aegis -// signal_processing.aegis - -manifold M = embed(noisy_signal, dim=3, tau=7) - -// Regression smooths the manifold -regress { - model: "polynomial", - degree: 5, - escalate: true, - until: convergence(1e-6) -} - -// The fitted coefficients represent the denoised signal - -render M { - color: gradient, - trajectory: on -} -``` - ---- - -## Running Examples - -### With Docker - -```bash -# Run any example -docker run -v $(pwd)/examples:/scripts teerthsharma/aegis run /scripts/hello_world.aegis -``` - -### With REPL - -```bash -docker run -it teerthsharma/aegis repl -aegis> load examples/escalating.aegis -``` - ---- - -## Contributing Examples - -Have a cool AETHER script? Contribute it! - -1. Fork the repo -2. Add your script to `examples/` -3. Add documentation here -4. Submit a PR - -See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines. +See [Language syntax](language/syntax.md), [Module contracts](language/modules.md), +and [Status matrix](reference/status.md) for claim boundaries. diff --git a/docs/FAQ.md b/docs/FAQ.md index d711749..dbed958 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -1,308 +1,31 @@ -# Frequently Asked Questions (FAQ) +# FAQ -Common questions about AEGIS, the 3D ML Language Kernel. +## Is Aether a replacement for Python ML frameworks? ---- +No. The current repository is a Rust language/runtime and systems experiment +with internal ML primitives. Framework replacement, speed, and model-quality +claims require external baselines and artifacts. -## General Questions +## What is active today? -### What is AEGIS? +See [Status Matrix](reference/status.md). -AEGIS is a **domain-specific language** for machine learning on geometric manifolds. It embeds data into 3D space where patterns become visible shapes, and uses topological methods to detect when models have truly converged. +## Where are the derivations? -### Why "AEGIS"? +See [Derivations](topology/derivations.md). -AEGIS means "shield" in Greek mythology. The name reflects the project's origins as a security-focused microkernel with topological code authentication. It has evolved into a full ML language while retaining its geometric foundations. +## How do I run a script? -### Is AEGIS a replacement for Python/TensorFlow/PyTorch? - -No. AEGIS is complementary - it excels at: -- Non-linear pattern detection -- Anomaly detection via geometry -- When you need to "see" your data in 3D -- Topological convergence (not arbitrary loss thresholds) - -Use traditional ML frameworks for standard supervised/unsupervised learning. - -### What's "topological convergence"? - -Instead of stopping when `loss < threshold`, AEGIS stops when the **shape** of the residuals stabilizes. This is measured via Betti numbers (β₀ = connected components, β₁ = loops). When β stabilizes and drift → 0, we've truly converged. - ---- - -## Installation Questions - -### How do I install AEGIS? - -**Docker (recommended):** -```bash -docker pull teerthsharma/aether -docker run -it teerthsharma/aether repl -``` - -**From source:** -```bash -rustup install nightly -git clone https://github.com/teerthsharma/aether.git -cd aether && cargo build --release -``` - -### Do I need Rust installed? - -Only if building from source. Docker users don't need Rust. - -### What platforms are supported? - -- **Docker**: Linux, macOS, Windows (via Docker Desktop) -- **Native**: Linux, macOS, Windows (with nightly Rust) - -### Why nightly Rust? - -AEGIS uses unstable features: -- `abi_x86_interrupt` for interrupt handlers -- `no_std` / `no_main` for bare-metal operation -- `build-std` for custom target compilation - ---- - -## Language Questions - -### What's the file extension for AEGIS scripts? - -`.aether` - for example: `my_script.aether` - -### How do I run an AEGIS script? - -```bash -# Docker -docker run -v $(pwd):/scripts teerthsharma/aether run /scripts/script.aether - -# REPL -aether> load script.aether -``` - -### What does `tau` mean? - -`tau` (τ) is the **time delay** for Takens embedding. It controls how far apart samples are in the embedding: - -```aether -// tau=1: adjacent samples -manifold M = embed(data, dim=3, tau=1) - -// tau=5: samples 5 apart (smoother patterns) -manifold M = embed(data, dim=3, tau=5) -``` - -**Rule of thumb:** Start with tau=5, increase for smoother patterns. - -### What does `dim=3` mean? - -The embedding dimension. AEGIS typically uses 3D because: -1. Takens' theorem shows 3D is sufficient for many systems -2. 3D is visualizable (humans can understand it) -3. Higher dimensions add computation without much benefit - -### What's a "block"? - -A block is a geometric region of the manifold - a subset of points. Think of it as selecting a piece of a 3D point cloud: - -```aether -block B = M[0:64] // Points 0-63 -block B = M.cluster(0:64) // Same thing -``` - ---- - -## Regression Questions - -### What regression models are available? - -| Model | Syntax | Best For | -|-------|--------|----------| -| Linear | `"linear"` | Simple trends | -| Polynomial | `"polynomial"` + `degree` | Smooth curves | -| RBF | `"rbf"` | Complex local patterns | -| Gaussian Process | `"gp"` | Uncertainty estimation | -| Geodesic | `"geodesic"` | True manifold structure | - -### What does "escalate" do? - -When `escalate: true`, AEGIS automatically increases model complexity if the current model doesn't converge: - -``` -Linear → Poly(2) → Poly(3) → ... → RBF → GP → Geodesic -``` - -### How do I know when regression converged? - -Look for: -``` -Converged! → β stable, drift → 0 ✓ -``` - -Or check the output: -- Betti numbers stable (e.g., `β = (1, 0)` for 3+ epochs) -- Error below epsilon -- "The Answer Has Come" - -### My regression isn't converging. What do I do? - -1. **Increase max epochs**: Default may be too low -2. **Relax epsilon**: `convergence(1e-4)` instead of `1e-6` -3. **Check tau**: Try different values (3, 5, 7, 10) -4. **Use `escalate: true`**: Let AEGIS find the right model - ---- - -## Visualization Questions - -### How do I visualize my manifold? - -```aether -render M { - color: by_density -} -``` - -### What color modes are available? - -| Mode | Effect | -|------|--------| -| `by_density` | Color by local point density | -| `by_cluster` | Color by cluster assignment | -| `gradient` | Gradient along trajectory | - -### Can I export to 3D formats? - -Currently, AEGIS outputs ASCII visualization. WebGL and OBJ export are planned features. - -### How do I highlight specific blocks? - -```aether -block B = M[0:64] -render M { - color: by_density, - highlight: B -} -``` - ---- - -## Performance Questions - -### How fast is AEGIS? - -Benchmarks on standard hardware: -- 64 points: < 1ms -- 256 points: < 10ms -- 1024 points: < 100ms -- Regression convergence: varies (typically 10-100 epochs) - -### How much memory does AEGIS use? - -Minimal - AEGIS is designed for bare-metal operation: -- Fixed-size allocations (no heap in kernel mode) -- `heapless` collections with compile-time bounds -- Typical: < 1MB for most scripts - -### Can I run on embedded systems? - -Yes! AEGIS is `no_std` compatible and can run on: -- x86_64 bare metal -- ARM (with modifications) -- Any system with a Rust nightly target - ---- - -## Docker Questions - -### How do I mount local files in Docker? - -```bash -docker run -v $(pwd):/scripts teerthsharma/aether run /scripts/your_file.aether -``` - -### What Docker commands are available? - -- `docker run teerthsharma/aether repl` - Interactive REPL -- `docker run teerthsharma/aegis run ` - Execute script -- `docker run teerthsharma/aegis benchmark` - Run benchmarks -- `docker run teerthsharma/aegis --help` - Show help - -### How do I save output from Docker? - -```bash -docker run -v $(pwd)/output:/output teerthsharma/aegis run script.aegis > /output/results.txt -``` - ---- - -## Troubleshooting - -### "Command not found: aegis" - -You're not using Docker. Run: -```bash -docker run -it teerthsharma/aegis repl -``` - -### "File not found" - -Mount your directory: -```bash -docker run -v $(pwd):/scripts teerthsharma/aegis run /scripts/file.aegis -``` - -### "Parse error: unexpected token" - -Check your syntax. Common issues: -- Missing colons in config blocks: `model: "polynomial"` ✓ -- Missing commas between config items -- Unclosed braces `{}` - -### "Build failed: serde_core errors" - -This is a nightly toolchain issue, not your code. Solutions: -1. Update nightly: `rustup update nightly` -2. Pin to a specific nightly in `rust-toolchain.toml` -3. Use Docker instead - -### "Convergence not reached" - -See [regression troubleshooting](#my-regression-isnt-converging-what-do-i-do). - ---- - -## Contributing Questions - -### How do I contribute? - -See [CONTRIBUTING.md](../CONTRIBUTING.md). Quick start: -```bash -git clone https://github.com/teerthsharma/aegis.git -docker-compose run dev -cargo test --lib +```powershell +cargo run -p aether-cli -- run examples/simple.aegis ``` -### What should I work on? - -Check [GitHub Issues](https://github.com/teerthsharma/aegis/issues) for: -- `good first issue` - Beginner friendly -- `help wanted` - Community input needed -- `enhancement` - New features - -### How do I report bugs? - -Open a [GitHub Issue](https://github.com/teerthsharma/aegis/issues) with: -- AEGIS version -- Rust version (if building from source) -- Minimal reproducible script -- Expected vs actual behavior +## What does `topology.ph` do? ---- +It calls the bounded persistent-homology engine in `aether-core`. See +[Persistent Homology](topology/persistent-homology.md). -## Still Have Questions? +## Are security claims active? -- 📖 [Full Documentation](../README.md#-documentation) -- 💬 [GitHub Discussions](https://github.com/teerthsharma/aegis/discussions) -- 🐛 [Report Issue](https://github.com/teerthsharma/aegis/issues) +No production security claim is active from documentation alone. The binary +shape code is documented as a heuristic gate. See [Shape Gates](topology/shape-gates.md). diff --git a/docs/FORMAL_CORE.md b/docs/FORMAL_CORE.md new file mode 100644 index 0000000..076adbc --- /dev/null +++ b/docs/FORMAL_CORE.md @@ -0,0 +1,693 @@ +# Aether Formal Core + +This document defines the current proof-facing core of Aether-Lang for Lean 4 +formalization. It describes the executable subset shared by the parser, +interpreter, and Titan VM as of the current implementation. + +## Purpose + +The formal core is the stable language fragment intended to receive a Lean 4 +syntax, static well-formedness relation, and operational semantics. Surface +forms outside this file may remain experimental until they are lowered into this +core or given their own proof rules. + +## Lexical Surface + +The core lexer recognizes: + +- Identifiers: ASCII identifiers used for variables, parameters, and functions. +- Numbers: integer literals and fixed micro-precision decimal float literals. +- Booleans: `true`, `false`. +- Unit: `unit` as the source spelling for the unit value. +- Strings: string literals for proof-core values and equality checks, with + `\"`, `\\`, `\n`, `\r`, and `\t` escapes. +- Comments: line comments beginning with `//` and block comments delimited by + `/*` and `*/`; block comments may nest, and unterminated block comments are + lexer errors. +- Lists: bracketed list literals whose elements are proof-core expressions; + newlines may separate elements and a trailing comma before `]` is accepted. +- Statement separators: newline, `~`, and `;`. Newline accepts LF, CRLF, or CR + as one logical line break. +- Range separator: `..` for integer ranges. +- Seal alias: `seal` and `🦭` both tokenize to the same control-flow keyword. +- Operators: `+`, `-`, `*`, `/`, `%`, `==`, `!=`, `<`, `>`, `<=`, `>=`, `&&`, + `||`, `!`. + +The lexer must preserve `1..10` as `Number(1)`, `DotDot`, `Number(10)` rather +than treating it as a malformed float. Decimal literals such as `1.5` parse as +fixed micro-precision proof-core numeric expressions. + +## Core Syntax + +The proof core is: + +```text +program ::= stmt* + +stmt ::= "let" ident (":" ann-ty)? "=" newline* expr + | ident "=" newline* expr + | "if" newline* expr block ("else" block)? + | "while" newline* expr block + | "for" ident "in" signed-int ".." signed-int block + | "seal" ("until" newline* expr)? block + | "fn" ident newline* "(" params? ")" (":" ann-ty)? block + | "return" expr? + | "break" + | "continue" + | expr + +block ::= separator* "{" stmt* "}" +separator ::= newline | "~" | ";" +params ::= ident ("," newline* ident)* ","? newline* + | ident ":" ann-ty ("," newline* ident ":" ann-ty)* ","? newline* +ann-ty ::= "num" | "bool" | "str" | "unit" + | "list" "[" newline* ann-ty newline* "]" + +expr ::= literal + | ident + | ident newline* "(" call-args? ")" + | "[" list-items? "]" + | "(" newline* expr newline* ")" + | expr "[" newline* expr newline* "]" + | expr "." newline* ident + | expr "." newline* ident "(" call-args? ")" + | "-" newline* expr + | "!" newline* expr + | expr binop newline* expr + +call-args ::= arg ("," newline* arg)* ","? newline* +arg ::= expr | ident "=" expr +list-items ::= expr ("," newline* expr)* ","? newline* +binop ::= "+" | "-" | "*" | "/" | "%" + | "==" | "!=" | "<" | ">" | "<=" | ">=" + | "&&" | "||" +literal ::= number | float | bool | string | unit +signed-int ::= number | "-" number +``` + +Parser precedence, from low to high, is: + +1. `||` +2. `&&` +3. `==`, `!=` +4. `<`, `>`, `<=`, `>=` +5. `..` +6. `+`, `-` +7. `*`, `/`, `%` +8. unary `-`, `!` +9. postfix `expr[expr]`, `expr.ident`, `expr.ident(args?)` +10. primary expressions + +## Runtime Values + +The proof core values are: + +```text +Value ::= Num int | Float int micros | Bool bool | Str string | List Value* | Unit +``` + +The interpreter supports additional host values such as manifolds, classes, +tensors, and native functions. Those are outside the first Lean 4 core unless +explicitly modeled as opaque external values. `Float int micros` stores decimal +numeric values in fixed micro-precision, matching lexer float tokens. + +## Big-Step Statement Semantics + +Statement execution produces a flow: + +```text +Flow ::= Value Value | Return Value | Break | Continue +``` + +The environment maps identifiers to runtime values. Function calls execute in a +call frame whose parameter bindings are local to the call. The VM currently +restores the caller locals after `RET`; the interpreter clones and restores the +variable environment around user function execution. + +Core rules to encode first: + +- `let x = e` evaluates `e` and binds `x`. +- `x = e` requires an existing binding in interpreter semantics, then updates + `x`. The VM slot model currently creates a slot if one is missing; the formal + core should choose the interpreter rule as the stricter source semantics. +- `if c { t } else { f }` executes `t` when `c` is truthy, otherwise `f`. +- `while c { b }` repeatedly executes `b` while `c` is truthy. +- `for i in a..b { body }` binds `i` to each integer value `a <= i < b`; + `a` and `b` may be signed integer literals. +- `seal until c { body }` checks `c` before each iteration and stops when true. +- `fn f(params) { body }` is a top-level declaration in the proof-core static + semantics; nested function declarations are rejected before bytecode lowering. +- `return e` produces `Return(v)` and unwinds to the nearest function call. +- `break` and `continue` are handled by the nearest loop. +- An expression statement evaluates the expression and leaves its value as the + statement result. + +Truthiness in the current runtime is: + +- `Bool(false)` is false. +- `Num(0.0)` is false. +- Empty strings and empty lists are false. +- `Unit` and unsupported values are false. +- Other booleans, nonzero numbers, non-empty strings, and non-empty lists are true. + +The proof-core static checker is stricter for control flow: `if`, `while`, and +`seal until` conditions must have type `bool` when known. Logical `&&` and +`||` operands must also be boolean when known. Conditions and logical operands +whose type is still `unknown` are accepted until more precise annotations or +inference are available. Function signatures retain arity and a conservative +inferred result type from visible `return` statements and final expression +statements; calls to functions with concrete inferred returns use that type, +while imprecise returns remain `unknown`. Unary logical `!` has the same known-boolean requirement. Equality +operators require compatible known operand types, while still allowing +`unknown` on either side. The `unit` literal has type `unit`. List literals carry a static element type when +homogeneous; empty, mixed, and otherwise imprecise lists use `list[unknown]` +while preserving the dynamic runtime list value. List indexing requires a +list-like target and a numeric index when known; successful indexing returns +the known element type for homogeneous lists and `unknown` for imprecise lists. +String indexing also requires a numeric index and returns a one-character +string when the index is in range. +Field access currently supports `.length` for known strings and lists, +producing `num`; unsupported concrete fields are rejected statically, while +fields on `unknown` targets remain `unknown`. Pure method calls currently +support zero-argument `.len()` for known strings and lists, producing `num`, +and zero-argument `.is_empty()` for known strings and lists, producing `bool`; +strings also support zero-argument `.first()` and `.last()`, which return the +first or last character as `str` when present, zero-argument `.tail()`, which +returns the remaining string after the first character when present, +`.take(count)` and `.drop(count)`, which require numeric counts and return the +prefix or suffix string, zero-argument `.reverse()`, which returns the +characters in reverse order, `.at(index)`, which requires a numeric index and returns `str`, plus +`.contains(value)`, `.starts_with(prefix)`, and `.ends_with(suffix)`, which +require string arguments and return `bool`; lists also support zero-argument `.first()` and `.last()`, producing the known +element type statically and the corresponding runtime element when present, +zero-argument `.tail()`, producing `list[T]` statically and the remaining +runtime list for non-empty lists, `.take(count)` and `.drop(count)`, which +require numeric counts and return lists with the same element type, +zero-argument `.reverse()`, producing `list[T]` statically and the reversed +runtime list, `.append(value)`, which requires a value compatible with the list +element type and returns a new list with that value at the end, +`.prepend(value)`, which requires a value compatible with the list element type +and returns a new list with that value at the beginning, +`.concat(other)`, which requires a list argument with compatible element type +and returns a new concatenated list, `.join(separator)`, which requires string +list elements and a string separator and returns `str`, plus `.at(index)`, which requires a numeric index argument and returns the known +element type, and `.contains(value)`, which also requires a value compatible +with the list element type and returns `bool`; +unsupported concrete methods are rejected statically, while methods on +`unknown` targets remain `unknown`. +Annotated local declarations such as `let count: num = 1` and +`let xs: list[num] = [1]` bind the declared type only when the initializer is +compatible with that annotation; incompatible initializers are rejected before +runtime or bytecode execution. + +## Bytecode Correspondence + +The VM core lowers supported syntax into stack bytecode: + +- Numeric constants: `PUSH`. +- Boolean constants: `PUSH_BOOL`. +- Locals: `LOAD`, `STORE`. +- Arithmetic and logic: `ADD`, `SUB`, `MUL`, `DIV`, `MOD`, `NEG`, `EQ`, `NEQ`, + `LT`, `GT`, `LE`, `GE`, `AND`, `OR`, `NOT`. +- Unit constants: `PUSH Unit`. +- Lists and strings: dynamic list construction plus list and string indexing. +- Fields: postfix field access with executable `.length` on strings and lists. +- Methods: pure postfix `.len()` and `.is_empty()` method calls on strings and + lists, string `.first()`, `.tail()`, `.last()`, `.take(count)`, `.drop(count)`, `.reverse()`, `.at(index)`, `.contains(value)`, `.starts_with(prefix)`, and `.ends_with(suffix)`, plus list `.first()`, `.tail()`, `.last()`, `.at(index)`, + `.take(count)`, `.drop(count)`, `.reverse()`, `.append(value)`, `.prepend(value)`, `.concat(other)`, `.join(separator)`, and `.contains(value)`. +- Branching: `JMP`, `JMP_IF_FALSE`. +- Loop flow: `break` and `continue` are lowered by the compiler into patched + `JMP` instructions targeting the loop exit or continuation point. +- Functions: `CALL(target, arity)` and `RET`. +- Program end: `HALT`. + +The first VM proof target should be stack preservation for well-formed bytecode: +if bytecode is produced by the compiler for a well-formed core program, runtime +stack underflow does not occur. + +## Current Boundaries + +The following are parsed or represented elsewhere but are not yet in the formal +core: + +- Classes, methods, object creation, modules, imports. +- Manifold, block, render, regress, topology-specific host operations. +- Named function arguments in user-defined calls. +- General object/class field access beyond proof-core `.length`. +- Mutating or host/object method calls beyond proof-core pure `.len()`. +- Tensor and ML model handles. +- Forward function references before declaration in VM lowering. +- Global variable capture inside VM user functions. + +These features should either lower into the core above or receive separate +semantics before being included in Lean 4 proofs. + +## Lean 4 Formalization + +The checked Lean 4 scaffold lives in: + +- `lakefile.lean`: Lake package definition for `aether-formal`. +- `lean-toolchain`: Lean toolchain pin. +- `Aether.lean`: top-level module import. +- `Aether/Lexer.lean`: token-kind model and executable lexical scanner for + the proof DSL surface. +- `Aether/Core.lean`: core syntax, values, environments, expression evaluation, + executable function-call evaluation, single-statement and block-flow + relations, and initial sanity theorems/examples. +- `Aether/Static.lean`: executable static well-formedness and lightweight type + checking for the current unannotated proof core. +- `Aether/Parser.lean`: executable token-to-core parser for the current + proof-core expression subset and simple statements. +- `Aether/VM.lean`: core stack-machine bytecode, VM state, one-step execution, + bounded execution, expression-to-bytecode compilation for the closed + literal/unary/binary subset plus slot-aware variable expression compilation, + straight-line statement compilation for `let`, assignment, and expression + statements, straight-line block compilation, branch-aware `if` compilation, + bounded `while`, integer-range `for`, and `seal` compilation, and checked + examples for arithmetic, locals, conditional branch behavior, loop behavior, + call-frame behavior, and evaluator/compiler agreement examples. +- `Aether/Pipeline.lean`: stage-aware source diagnostics over the executable + lexer, parser, static checker, checked compiler, and frame VM runner. + +Run: + +```text +lake build +``` + +The first formalization target is the executable proof core, not the full host +runtime. `Aether.Core` models exact integer literals and fixed micro-precision +decimal float literals as the proof-friendly numeric subset of the Rust +runtime's `f64` values. Integer-only arithmetic preserves integer results; +mixed integer/float arithmetic converts through micro-units and returns +`Value.float`. + +`Aether.Lexer` mirrors the Rust lexer token-kind surface in Lean and provides +an executable `tokenize` scanner for keywords, identifiers, integers, fixed +micro-precision float tokens, strings, line comments, block comments, statement +separators, range tokens, arithmetic/comparison/logical operators, delimiters, +newline, EOF, and lexical errors. Checked examples cover the important `1.5` +versus `1..10` split, keyword/operator scanning, the `🦭` seal alias, comment +handling, nested block comments, tilde separators, portable LF/CRLF/CR line +endings, and string termination errors. `tokenizeLocated` preserves each token's starting and +ending line/column as a `SourceSpan` while keeping the existing `TokenKind` +parser API unchanged. The `tokenKinds` projection maps a located token stream +back to its parser-facing token kinds, and checked examples verify that this +projection agrees with `tokenize` for representative core inputs including +comments, newlines, ranges, strings, lexical errors, and the `🦭` alias. +Checked examples cover ordinary token ranges, newline ranges for LF/CRLF/CR, +the seal emoji alias range, `1..10` token spans, and lexical error ranges. +String escape scanning translates supported escapes to runtime characters and +reports unsupported escape sequences as lexer errors whose located spans point +at the offending escape. Block-comment scanning is depth-aware, advances +line/column positions across newlines, and reports unterminated block comments +with spans from the opening slash through EOF. AST-level source spans and +lexer/parser correctness theorems remain future formalization work. + +`Aether.Parser` consumes the Lean token stream and produces `Aether.Core` +syntax for the proof subset. It currently covers precedence-aware expressions +for integer numeric, fixed micro-precision float, boolean, string, unit, and list +literals, postfix list indexing, postfix field access, postfix method calls, +variables including `self`, unary negation/not, +multiplicative/additive, +comparison, equality, logical `&&`, and logical `||`, plus parenthesized +expressions, multiline parenthesized expressions, function calls with positional or named arguments, multiline and +trailing-comma argument lists, and Rust-compatible keyword call syntax for +`embed(...)` and `convergence(...)`. Statement parsing covers `let`, +assignment, `return`, `break`, `continue`, expression statements, newline +separators, tilde separators, EOF termination, brace-delimited blocks, +`if`/`else`, `while`, signed integer-range `for`, conditional `seal until`, +unconditional `seal`, annotated local declarations, untyped `fn` declarations, +`fn` declarations with basic parameter type annotations such as `x: num` and +`xs: list[num]`, untyped-parameter function declarations with declared return +types such as `fn id(x): num`, typed function declarations with declared +return types such as `fn id(x: num): num`, and multiline function parameter +lists with optional trailing commas. `list[...]` type annotations may place +newlines after `[` and before `]`, including in typed parameters and declared +return types. Block-bearing forms may place a statement separator between the +header and opening `{`. +Parser diagnostics for malformed multiline `list[...]` annotations skip +annotation-internal newlines and point at the offending token. +Parenthesized expressions may place newlines after `(` and before `)`. +Postfix index expressions may place newlines after `[` and before `]`. +Postfix field and method expressions may place newlines after `.`. +Function-call expressions may place newlines between the callee name and `(`. +Function declarations may place newlines between the function name and `(`. +Unary expressions may place newlines after `-` or `!`. +Binary expressions may place newlines after the operator before the right-hand +operand. +Let declarations and assignment statements may place newlines after `=` before +the right-hand expression. +Control-flow condition forms may place newlines after `if`, `while`, or +`seal until` before the condition expression. +Checked examples verify arithmetic +precedence, parenthesized boolean expressions, positional call parsing, +multiline binary right-hand-side parsing, +multiline assignment right-hand-side parsing, +multiline control-flow condition parsing, +multiline unary expression parsing, +multiline parenthesized expression parsing, +multiline postfix index parsing, +multiline postfix member parsing, +multiline call opening parsing, +multiline function declaration opening parsing, +multiple tilde-separated statements, newline-separated loop-control statements, +block parsing, `if`/`else` parsing, `while` parsing, signed integer `for` parsing, +both conditional and unconditional `seal` parsing, untyped and typed function declaration +parsing, line-broken block opening parsing, `self` expression parsing, reserved domain keyword field/method names, +keyword call parsing, named argument preservation, multiline function parameter +parsing, multiline call argument parsing, multiline list type annotation +parsing, list literal parsing, and decimal float literal parsing. Method calls beyond proof-core pure `.len()`, +broader object/class field semantics, source spans, full compiler/VM +correspondence proofs, and parser correctness theorems remain future +formalization work. +`parseProgramDetailed` wraps the executable parser with `Except ParseError`, +currently recording the broad context that failed plus a diagnostic token. For +malformed expression starts in `let`, assignment, `return`, `if`, `while`, and +conditional `seal`, the diagnostic token is the offending expression-start +token. The classifier treats `self`, decimal float literals, and `embed` or +`convergence` followed by `(` as valid expression starts, matching the +executable parser's `self` variable, float literal, and keyword-call expression +support while rejecting bare keyword-call names as malformed starts. Incomplete +expressions that end at a binary operator before a statement terminator point +at that trailing operator as the missing-right-operand site. Condition +expressions before `if`, `while`, and `seal until` body blocks use the same +rule when the trailing operator appears before `{`. Other incomplete expressions +whose first token is valid still use the broader statement-start diagnostic +until the parser carries recursive failure locations. Checked examples cover +expression, trailing binary operators in statements and control-flow conditions, +malformed `self`-started and float-started expressions, bare keyword-call names, +malformed integer-ranges, complete `for` ranges missing body blocks, missing +`if`/`while` condition expressions, stray `else` tokens that require a preceding +parsed `if` statement, conditional `seal until` expression, function-parameter +parse failures, malformed type annotations in local declarations, function +parameters, and function return positions after both untyped and typed parameter +lists, and function declarations with valid parameters but missing body blocks. +Malformed nested `list[...]` type annotations recursively point at the token +where an element type or closing bracket is missing, so local, parameter, and +return annotation diagnostics do not collapse to the outer `list` token. +`Aether.Pipeline` attaches the corresponding +`SourceSpan` from the located token stream when surfacing parse diagnostics. +The pipeline walks located statement boundaries so failures after earlier +valid statements point at the later failed statement rather than the beginning +of the file, and invalid expression-start failures point at the offending +token's range without requiring a fully spanned AST. + +`Aether.Static` adds the first checked static gate for proof-core programs. +It models `num`, `bool`, `str`, element-aware `list[...]`, `unit`, and +`unknown` types; `unknown` is used for unannotated function parameters, +imprecise function call results, and imprecise list element types. Integer +literals and fixed micro-precision float literals both check as `num`. The +checker validates known arithmetic/comparison/logical/equality operand shapes, +unary operator operands, boolean control-flow conditions, declaration-before-use, +assignment compatibility with refinement of `unknown` assignment targets, +function arity, annotated function parameter argument types, declared function +return types, inferred function call result types, +named function-call argument validation against declared parameter names, +conservative `if`/`else` branch environment joins, `return` placement inside functions, +top-level-only function declarations, and `break`/`continue` placement inside +loops. It checks function bodies after collecting top-level function +signatures, so forward calls by name, arity, and parameter names are +represented. Signature +collection also infers conservative result types from visible return +statements and final expression statements, threading local `let` bindings +and conservative `if`/`else` branch joins through the function body while +merging disagreement to `unknown`. Checked +examples cover valid and invalid expressions, list element checking, list +indexing success and index operand mismatch, supported field access and +unsupported concrete field diagnostics, supported method calls and unsupported +concrete method diagnostics, +undeclared assignment rejection, +top-level `break`/`return` rejection, loop control inside loops, valid function +calls, explicit and implicit inferred call result typing, and arity mismatch +rejection. +`checkProgramDetailed` mirrors the executable checker with +`Except CheckError`, preserving static failure reasons such as undeclared +variables/functions, unary and binary operand mismatches, concrete non-boolean +control-flow conditions, assignment mismatches, arity mismatches, duplicate +top-level function names, duplicate function parameters, unknown or duplicate +named arguments, nested function declarations, and invalid `return`, `break`, +or `continue` placement. The option-returning `checkProgram` wrapper delegates +to the detailed checker and erases the error payload, so both checker APIs +enforce the same duplicate-signature, argument, placement, and declared-return +contracts. Declared-return checking also walks explicit `return` paths and +final-expression returns inside branch and loop bodies before consulting the +merged return summary, so a concrete mismatch in one branch cannot be hidden by +an inferred `unknown` merge. An `if` without an `else` contributes an implicit +unit path only when the conditional is the end of the current block; when later +statements exist, the missing branch falls through to those statements. Thus +non-unit functions must cover both branches explicitly at function-exit points. +A `while` body is also checked with a zero-iteration +fallthrough path, so a non-unit function cannot rely on a loop body as its only +return source. Integer-range `for` loops have the same fallthrough rule because +the range may be empty. Conditional `seal until` bodies also have a skip path +when the exit condition is already true; bare `seal` remains body-only in the +current declared-return checker. +Checked examples cover each major diagnostic class. +Richer source-language type syntax, loop-carried environment joins, and +preservation/progress theorems remain future work. + +For `if`/`else`, the static checker joins variables introduced by both branches +only when their types are compatible. Variables introduced by just one branch, +or introduced with incompatible branch types, are not exposed after the +conditional. Existing `unknown` variables assigned compatible concrete values +in both branches are refined by the join; incompatible branch assignments leave +the original imprecise type. Function result inference uses the same join rule +before checking final expression statements. + +On successful assignment, the static checker updates the variable environment: +assigning a concrete value to an `unknown` variable refines that variable to +the concrete type for later statements, including nested `list[unknown]` +element refinement. Existing concrete assignment targets keep their declared +static type. + +`StepBlock` models ordered statement execution. Value-producing statements +continue to the next statement, the final statement's value is preserved as the +block value, and `return`, `break`, or `continue` stop the block immediately. +`StepStmt` and `StepBlock` are mutually defined so structured `if` statements +can evaluate the selected branch as a block: truthy conditions step through the +then branch, falsey conditions step through the `else` branch when present, and +a falsey condition without `else` produces `unit` without changing the +environment. The same big-step relation includes `while`: falsey conditions +produce `unit`, value-producing bodies recurse, `return` propagates, `break` +exits with `unit`, and `continue` recurses to the next condition check. +Big-step `forRange` binds the iterator to each ascending integer value, +rebinds it to the stop value on normal completion, recurses after ordinary +values or `continue`, exits with `unit` on `break`, and propagates `return`. +Big-step `seal until` stops when its condition is truthy, otherwise executes +the body and recurses after ordinary values or `continue`; bare `seal` recurses +after ordinary values or `continue`. Both forms exit with `unit` on `break` and +propagate `return`. In this env-only big-step relation, `fnDecl` is a +unit-producing statement with no variable-environment effect. Full +function-environment behavior is modeled by the bounded executable `FnEnv` +semantics below and remains a target for a future Prop relation that carries +function bindings explicitly. + +`EvalExprWithFnsRel`, `EvalArgsWithFnsRel`, `StepStmtWithFns`, and +`StepBlockWithFns` are the first Prop-level function-environment semantics. +They carry `FnEnv` through statement and block stepping, bind `fn` +declarations into that environment, evaluate positional call arguments, bind +parameters into a call frame, and treat either an ordinary function-body value +or an explicit `return` as the call expression result. Checked call witnesses +cover both explicit `return` and implicit final-expression results. The current +checked slice covers literals, variables, list construction, list/string indexing, +field access through the shared field evaluator, pure `len`, `is_empty`, +string `first`/`tail`/`last`/`take`/`drop`/`reverse`/`at`/`contains`/`starts_with`/`ends_with`, and list `first`/`tail`/`last`/`at`/`take`/`drop`/`reverse`/`append`/`prepend`/`concat`/`join`/`contains` method calls through the shared method evaluator, unary and binary operators through the shared operator +evaluators, function calls, `let`, expression statements, `return`, +declaration sequencing, assignment to existing variable bindings, and +structured `if`/`else` branching with both variable and function +environments threaded through the selected branch. It also covers `while`: +falsey conditions produce `unit`, value-producing bodies recurse, `return` +propagates, `break` exits with `unit`, and `continue` recurses to the next +condition check while preserving both environments. `break` and `continue` +statements now propagate through function-aware blocks. Function-aware +`forRange` binds the iterator to each ascending integer value, rebinds it to +the stop value on normal completion, recurses after ordinary values or +`continue`, exits with `unit` on `break`, and propagates `return` while +threading `FnEnv`. Function-aware `seal until` stops on a truthy condition, +otherwise executes the body and recurses after ordinary values or `continue`; +bare `seal` recurses after ordinary values or `continue`. Both `seal` forms +exit with `unit` on `break` and propagate `return` while preserving both +environments. Initial executable correspondence witnesses check that selected +`EvalExprWithFnsRel` facts for numeric literals, booleans, variables, unary +operators, binary operators, list construction, indexed access, field access, +method calls including `is_empty`, string `first`/`tail`/`last`/`take`/`drop`/`reverse`/`at`/`contains`/`starts_with`/`ends_with`, and list `first`/`tail`/`last`/`at`/`take`/`drop`/`reverse`/`append`/`prepend`/`concat`/`join`/`contains`, explicit-return function calls, and implicit final-expression +function calls agree with `evalExprWithFns` on concrete examples; full +inductive correspondence remains future work. +Initial statement-level executable witnesses check that selected +`StepStmtWithFns` facts for `let`, assignment, expression, `fn` declaration, +`return`, `break`, and `continue` statements agree with projected +`execStmtWithFns` results on concrete examples. Statement checks compare +observable environments and flow, with function declarations checking that a +function binding is added without requiring equality over the function body +payload. +Initial block-level executable witnesses check selected `StepBlockWithFns` +facts for empty blocks, single-statement blocks, ordinary value sequencing, and +early `return`/`break`/`continue` propagation against projected +`execBlockWithFns` results. +Structured statement executable witnesses now also check selected +`StepStmtWithFns` `if` facts for true branches, false branches with `else`, and +false branches without `else` against projected `execStmtWithFns` results. +Loop statement executable witnesses currently check selected non-recursive +`while` facts: false conditions exit with `unit`, body `return` propagates, and +body `break` exits with `unit`. +They also check selected non-recursive `forRange` facts: completed ranges bind +the iterator to the stop value, body `return` propagates, and body `break` +exits with `unit`. +Recursive `forRange` witnesses now additionally cover ordinary value-body +iteration into the completed range case and body `continue` advancing to the +next range value. +`seal until` executable witnesses cover already-satisfied conditions, ordinary +body-value recursion into completion, and body `break` exiting with `unit`. +They also cover body `return` propagation and body `continue` rechecking the +condition before completing. +Bare `seal` executable witnesses cover ordinary value-body recursion into a +later `break`, direct body `return` propagation, and body `break` exiting with +`unit`; they also cover body `continue` advancing to the next bare-seal +iteration. + +`evalExprWithFns` and `execBlockWithFns` extend the executable core with a +bounded function environment. `Stmt.fnDecl` binds a function definition, +`Expr.call` preserves positional and named argument nodes, evaluates argument +payload expressions in source order in the caller environment, binds named +arguments to matching function parameters while preserving positional call +behavior, executes the function body, and treats either explicit `return` or +the body's final value as the call result. Checked examples +cover explicit return, implicit final-expression return, arity mismatch, and +parameter shadowing that preserves the caller's outer binding. The bounded +statement executor also evaluates `if`/`else` by running the selected branch as +a block and returning `unit` for a falsey condition without `else`. Bounded +`while` execution rechecks the condition each iteration, returns `unit` on +normal completion or `break`, treats `continue` as the next iteration, and +preserves `return` flow for enclosing function calls. Bounded `forRange` +execution binds the iterator for each ascending integer value `start <= i < +stop`, rebinds the iterator to `stop` on normal completion, treats `continue` +as the next integer, exits with `unit` on `break`, and preserves `return` flow. +Bounded `seal until` execution checks the condition before each iteration and +stops with `unit` when it becomes truthy; bare `seal` repeats until fuel is +exhausted or control flow exits. Both forms treat `continue` as the next +iteration, exit with `unit` on `break`, and preserve `return` flow. + +`Aether.VM` is the first formal bytecode model. It currently covers the +proof-core stack instructions needed for constants, locals, arithmetic, +unary operations, dynamic list construction, list indexing, unconditional jumps, +conditional false jumps, and halt. +It also includes `compileExpr` for closed literal, unary, and binary +expressions, with checked examples showing that running compiled bytecode +produces the same value as direct expression evaluation for representative +integer arithmetic, float arithmetic, boolean, string, and list cases. +`compileExprWithSlots` adds explicit variable to +local-slot lookup for expression compilation against VM locals, with checked +examples for successful variable loading and missing-slot failure. Stack and +frame expression compilation both lower indexing as target bytecode followed by +index bytecode and an index opcode, and lower field access as target bytecode +followed by a field opcode. Pure method calls lower target and argument +bytecode followed by a method opcode. Checked examples include zero-argument +`is_empty` calls on strings and lists, string `first`/`tail`/`last`/`take`/`drop`/`reverse`/`at`/`contains`/`starts_with`/`ends_with` calls, and list `first`/`tail`/`last`/`at`/`take`/`drop`/`reverse`/`append`/`prepend`/`concat`/`join`/`contains` calls. `FrameOp`, +`CallFrame`, `FrameState`, `frameStep`, and `runFrame` add a Lean call-frame VM +surface for direct bytecode with `CALL`/`RET` behavior. Checked examples cover +argument passing, explicit return values, implicit unit return, and restoring +caller locals after a function call. `compileFrameProgram` hoists top-level +`fn` declarations after main bytecode, compiles calls to absolute +`CALL target arity` instructions after normalizing named function-call +arguments into parameter order, compiles `return` to `RET`, and runs the +resulting program with `runCompiledFrameProgram`. `FrameOp.jmp` and +`FrameOp.jmpIfFalse` support structured `if`/`else` and bounded `while` +compilation inside frame-compiled functions. Checked examples verify emitted +bytecode, function return values stored by callers, parameter shadowing that +preserves caller locals, branch execution inside a function, and loop execution +inside a function. Frame compilation also supports integer-range `for` loops, +conditional `seal until`, and bare `seal` using the same relative jump scheme +as the non-frame VM; checked examples cover `for` accumulation and conditional +`seal` execution inside functions. Frame compilation carries pending +`break`/`continue` jump sites through nested blocks and branches, then patches +them at the nearest `while`, integer-range `for`, or `seal` loop boundary. +Checked examples cover `break` exiting a compiled function loop and `continue` +skipping the rest of the current loop body. `compileCheckedFrameProgram` +composes `Aether.Static.checkProgramDetailed` with frame compilation, giving +the first checked AST-to-bytecode entrypoint. A Lean theorem records that +successful checked compilation implies the detailed static checker accepted the +source program. `compileCheckedFrameSource`, `runCheckedFrameSource`, and +`checkedFrameSourceLocal?` add the corresponding source-string pipeline: +tokenize, parse, statically check, lower to frame bytecode, and run. Checked +examples show that valid function source code runs through the pipeline while +statically invalid numeric/boolean arithmetic, non-boolean control-flow +conditions, function arity mismatches, and malformed source are rejected before +bytecode execution. Duplicate functions +and duplicate parameters are rejected by the checked compiler path as well as +the diagnostic source pipeline. Checked source examples also verify that the +`🦭 until` alias parses, statically checks, lowers, and runs like `seal until`, +and that string/list `.is_empty()` calls execute through the checked frame +compiler, as do list `.first()`, `.tail()`, `.last()`, `.at(index)`, +`.take(count)`, `.drop(count)`, `.reverse()`, `.append(value)`, `.prepend(value)`, `.concat(other)`, `.join(separator)`, and `.contains(value)` calls, plus string +`.first()`, `.tail()`, `.last()`, `.take(count)`, `.drop(count)`, `.reverse()`, `.at(index)`, `.contains(value)`, `.starts_with(prefix)`, and `.ends_with(suffix)` calls. +General object method calls and full compiler/VM correspondence proofs remain +future work. + +`Aether.Pipeline` wraps the source pipeline in `Except Pipeline.Error` so +failures keep their phase. The current phases are `lex message SourceSpan`, +`parse ParseError SourceSpan`, +`static CheckError (Option SourceSpan)`, `compile`, and `runtime`. `parseSource` tokenizes first +with `tokenizeLocated` and reports the first lexer error token with its source +span before invoking the parser. Parser failures carry both the parser +context and the source span of the failed statement start. The located +pipeline replays statement parsing over `LocatedToken` values, preserving the +parser's token-kind API while still reporting later statement failures at their +own line and column range. Parser failures for malformed type annotations point +at the token where the type should begin. Static failures are also rendered +with best-effort source ranges by matching checker errors back to located +tokens for variables, functions, operators, named arguments, duplicate names, +and invalid control-flow conditions. Non-boolean `if`, `while`, and +`seal until` diagnostics prefer the offending condition token when its concrete +type can be matched. +Declared return mismatches prefer the mismatched return expression token, +including explicit `unit` literals; if a non-unit function implicitly returns +unit because no return value is present, the diagnostic points at the closing +brace of the function body when that brace can be matched. +The variable matcher treats the reserved `self` token as the variable name +`self`, and treats `embed`/`convergence` keyword-call tokens as function names, +so undeclared diagnostics for those names retain a source range. +Duplicate function diagnostics use `fn name` token structure, and duplicate +parameter diagnostics search within function parameter lists, so repeated names +in function bodies do not steal the diagnostic range. +Field and method mismatch diagnostics prefer the member identifier after `.` +when it can be matched, falling back to the dot token only if the member name +cannot be recovered from the located token stream. +`compileSource` then parses, runs `checkProgramDetailed`, and lowers to frame +bytecode; `runSource` executes the lowered program; `sourceLocal?` exposes a +checked local value for examples. Checked examples distinguish lexical string +termination, string literal execution, positioned parse failure, concrete static +numeric/boolean/string misuse, concrete static arity mismatch, successful +execution, and fuel-limited runtime state. `errorString`, +`parseSourceErrorString`, `checkSourceErrorString`, +`compileSourceErrorString`, `runSourceErrorString`, and +`sourceLocalErrorString` provide deterministic string rendering for pipeline +errors, including positioned lexical and parser messages, concrete static +diagnostics, compile failures, and runtime/local-access failures. Lexical and +parse renderers print half-open source ranges such as `1:1-1:4`; static +renderers include a range when token lookup can identify a stable source +location. Parser diagnostic rendering names the proof-core lexer keyword +tokens, including domain and object/module keywords, instead of collapsing them +to a generic token label. + +`compileStmtWithSlots` covers the +straight-line statement subset: declarations allocate or reuse a local slot and +emit `STORE`, assignments require an existing slot, and expression statements +leave the expression value on the stack. Branching, loops, and functions remain +separate proof targets for the straight-line compiler. `compileBlockWithSlots` +threads the slot table through a sequence of supported straight-line statements +and concatenates their bytecode; unsupported statements fail compilation. +`compileStmtWithBranches` +and `compileBlockWithBranches` add `if` lowering with `JMP_IF_FALSE` and `JMP`, +with checked examples for both true and false branch execution. The same +compiler layer lowers bounded `while` execution to condition bytecode, +`JMP_IF_FALSE` over the body and back jump, body bytecode, and a negative `JMP` +back to the condition, with checked examples for zero-iteration and +multi-iteration execution. It also lowers integer-range `for` loops by +initializing the iterator slot, checking `iterator < end`, running the body, +incrementing the iterator, and jumping back to the condition; checked examples +verify emitted bytecode and final locals for `0..3`. `seal until` lowers to a +pre-body exit check using boolean negation plus `JMP_IF_FALSE`, while bare +`seal` lowers to an unconditional body/back-jump loop; checked examples cover +conditional execution and bare-loop bytecode. Loop-control patching for +`break`/`continue`, functions, and call frames remain separate proof targets. diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 75f33d4..db35ea3 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -1,301 +1,42 @@ -# Getting Started with AEGIS +# Getting Started -Welcome to AEGIS, the 3D ML Language Kernel! This guide will help you get up and running quickly. +This page is retained for existing links. The current getting-started flow is +documented in the MkDocs site: -## Table of Contents +- [Home](index.md) +- [Language syntax](language/syntax.md) +- [Execution model](language/execution-model.md) +- [Status matrix](reference/status.md) -1. [Installation](#installation) -2. [Your First Script](#your-first-script) -3. [Understanding Manifolds](#understanding-manifolds) -4. [Running Regression](#running-regression) -5. [Next Steps](#next-steps) +## Build And Run ---- - -## Installation - -### Option 1: Docker (Recommended) - -The easiest way to get started is with Docker: - -```bash -# Pull the AEGIS image -docker pull teerthsharma/aether - -# Start the REPL -docker run -it teerthsharma/aether repl -``` - -### Option 2: From Source - -```bash -# Install Rust nightly -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -rustup install nightly -rustup default nightly -rustup component add rust-src llvm-tools-preview - -# Clone AEGIS -git clone https://github.com/teerthsharma/aether.git -cd aether - -# Build -cargo build --release -``` - ---- - -## Your First Script - -Create a file called `hello.aether`: - -```aether -// hello.aether - Your first AETHER program - -// Step 1: Create a 3D manifold from data -// dim=3 means 3D space -// tau=5 is the time delay for embedding -manifold M = embed(data, dim=3, tau=5) - -// Step 2: Extract a geometric block (first 64 points) -block B = M.cluster(0:64) - -// Step 3: Get the block's centroid (center point) -centroid C = B.center - -// Step 4: Render the manifold -render M { - color: by_density -} -``` - -Run it: - -```bash -# With Docker -docker run -v $(pwd):/scripts teerthsharma/aether run /scripts/hello.aether - -# Or in the REPL -aether> load hello.aether -``` - -**Expected output:** +```powershell +cargo build -p aether-cli +cargo run -p aether-cli -- check examples/simple.aegis +cargo run -p aether-cli -- run examples/simple.aegis +cargo run -p aether-cli -- repl ``` -[Lexing] Tokenizing script... -[Parsing] Building AST... -[Interpreting] Executing statements... -Execution Summary: - Manifolds created: 1 - Blocks extracted: 1 - Regressions run: 0 - Renders: 1 +The standard script extensions are `.aether` and `.ae`. Some repository examples +use legacy extensions and may print an extension warning. -✓ Completed successfully -``` - ---- - -## Understanding Manifolds - -### What is a Manifold? - -In AEGIS, a **manifold** is a 3D geometric space where your data lives. Instead of treating data as flat numbers, we embed it into 3D space where: - -- **Similar points are close together** -- **Patterns become visible shapes** -- **Anomalies are geometrically distant** - -### Time-Delay Embedding - -AEGIS uses **Takens' theorem** to create manifolds from 1D time-series: - -``` -Original data: [x₁, x₂, x₃, x₄, x₅, x₆, ...] - -With tau=2: -Point 1: (x₁, x₃, x₅) -Point 2: (x₂, x₄, x₆) -... -``` - -This transforms temporal patterns into geometric shapes! - -### Creating a Manifold +## Minimal Script ```aether -// Basic manifold (3D, tau=1) -manifold M = embed(data, dim=3) - -// Custom time delay -manifold M = embed(data, dim=3, tau=5) - -// From specific data source -manifold M = embed(sensor_readings, dim=3, tau=7) +let data = [1.0, 2.0, 3.0, 4.0]~ +manifold M = embed(data, tau=1)~ +print("done")~ ``` ---- - -## Running Regression - -### Basic Regression +## Topology Script ```aether -manifold M = embed(data, dim=3, tau=5) - -// Simple polynomial regression -regress { - model: "polynomial", - degree: 3 -} +import topology~ +let data = [1.0, 1.0, 1.0, 1.0, 1.0]~ +manifold M = embed(data, tau=1)~ +let diagram = topology.ph(M, max_dim=2, mode="vr", max_points=16)~ +let b = topology.betti(diagram, radius=0.0)~ ``` -### Escalating Regression - -The magic of AEGIS is **escalating regression** - automatically increasing model complexity until convergence: - -```aether -manifold M = embed(data, dim=3, tau=5) - -regress { - model: "polynomial", - degree: 2, - escalate: true, // Enable auto-escalation - until: convergence(1e-6) // Stop when error < 1e-6 -} -``` - -**What happens:** -1. Starts with polynomial degree 2 -2. If error doesn't converge, escalates to degree 3 -3. Continues escalating: Poly → RBF → Gaussian Process -4. Stops when **topological convergence** is detected - -### Model Types - -| Model | Description | Complexity | -|-------|-------------|------------| -| `"linear"` | y = a + bx | O(1) | -| `"polynomial"` | y = Σaᵢxⁱ | O(d) | -| `"rbf"` | Radial Basis Function | O(n) | -| `"gp"` | Gaussian Process | O(n²) | -| `"geodesic"` | Manifold regression | O(n² log n) | - ---- - -## Working with Blocks - -### What is a Block? - -A **block** is a geometric region of the manifold - a cluster of nearby points. - -```aether -manifold M = embed(data, dim=3, tau=5) - -// Extract block by index range -block B = M[0:64] - -// Or use cluster method -block B = M.cluster(0:64) -``` - -### Block Properties - -```aether -block B = M[0:64] - -// Centroid (center point) -centroid C = B.center - -// Radius (max distance from center) -radius R = B.spread - -// Use in computations -render M { - highlight: B // Highlight this block -} -``` - ---- - -## Understanding Convergence - -### What is Topological Convergence? - -Instead of arbitrary loss thresholds, AEGIS detects convergence through **topology**: - -1. **Betti Numbers (β₀, β₁)** - Count connected components and loops -2. **Centroid Drift** - How much the solution is moving -3. **Residual Collapse** - Whether errors are shrinking to a point - -### Convergence Conditions - -```aether -// Error threshold -until: convergence(1e-6) - -// Betti stability (advanced) -until: betti_stable(10) // Stable for 10 epochs -``` - -### Reading Convergence Output - -``` -Epoch 1: Linear → Error: 0.15, β = (3, 1) -Epoch 5: Polynomial(3) → Error: 0.03, β = (2, 1) ↑ escalate -Epoch 12: RBF → Error: 0.008, β = (1, 0) ↑ escalate -Epoch 15: Converged! → β stable, drift → 0 ✓ - -The Answer Has Come ✓ - Coefficients: [0.9987, -0.0234, 0.0012, ...] -``` - ---- - -## Next Steps - -Now that you understand the basics: - -1. **📖 Read the [Language Reference](LANGUAGE.md)** - Complete syntax guide -2. **💡 Explore [Examples](EXAMPLES.md)** - More complex scripts -3. **🔬 Study [Mathematics](MATHEMATICS.md)** - The theory behind AEGIS -4. **🏗️ Learn [Architecture](ARCHITECTURE.md)** - How AEGIS works internally -5. **🤝 [Contribute](../CONTRIBUTING.md)** - Help improve AEGIS! - ---- - -## Common Issues - -### "Command not found: aether" - -Make sure you're using Docker: -```bash -docker run -it teerthsharma/aether repl -``` - -### "File not found" - -Mount your directory when running scripts: -```bash -docker run -v $(pwd):/scripts teerthsharma/aether run /scripts/yourfile.aether -``` - -### "Convergence not reached" - -Try increasing max epochs or using a more flexible model: -```aether -regress { - model: "rbf", - escalate: true, - until: convergence(1e-4) // Less strict threshold -} -``` - ---- - -## Getting Help - -- 📖 [Full Documentation](../README.md#-documentation) -- ❓ [FAQ](FAQ.md) -- 🐛 [Report Issues](https://github.com/teerthsharma/aether/issues) -- 💬 [Discussions](https://github.com/teerthsharma/aether/discussions) +This calls the active bounded persistent-homology path described in +[Persistent Homology](topology/persistent-homology.md). diff --git a/docs/HARDWARE_SPEC.md b/docs/HARDWARE_SPEC.md index 7ead1c3..dd35975 100644 --- a/docs/HARDWARE_SPEC.md +++ b/docs/HARDWARE_SPEC.md @@ -1,63 +1,15 @@ -# AEGIS Physical Specification: The "Bio-Chip" Architecture +# Hardware Specification Status -**Designation:** AEGIS-PPU (Physical Processing Unit) -**Class:** Non-Von Neumann / Neuromorphic -**Target Process:** 3nm Analog-Digital Hybrid -**Authored By:** AEGIS Architectural Oversight Committee +This compatibility page points to [Hardware Boundary](kernel/hardware-boundary.md). ---- +Any hardware target must document: -## 1. Core Philosophy: The Physical Manifold -The software AEGIS simulates a "Living Manifold" on dead silicon. The Hardware AEGIS *is* the manifold. +- CPU architecture; +- boot path; +- required toolchain; +- run command; +- expected output; +- failure modes; +- captured artifact. -### The Von Neumann Bottleneck -* **Traditional:** CPU fetches data -> Process -> Store. (Bus bandwidth limits performance). -* **AEGIS-PPU:** Data is never fetched. Logic gates are distributed *inside* the memory cells. The memory *is* the processor. - -## 2. Architecture Overview - -### 2.1. The Synaptic Lattice (Memory) -Instead of DRAM (Capacitors), AEGIS uses **Memristive Crossbars**. -* **Component:** Hafnium Oxide (HfO2) Memristor. -* **Function:** Stores state as resistance (Analog float value). -* **Active Decay:** The hardware is biased to leak charge over time ($V_{leak}$). - * **Result:** "Garbage Collection" is passive. If a cell is not reinforced (read/written), its resistance drifts to $\infty$ (Death). - * **Power Cost:** Negative (uses leakage current). - -### 2.2. The Geometric Core (Compute) -Distributed across the lattice are **Topological Processing Units (TPUs)**. -* **Density:** 1 TPU per 64KB of Lattice. -* **Operation:** - * **Summation:** Kirchhoff’s Law (Current addition is instant). - * **Embedding:** Time-delay embeddings are performed by delay-lines in the circuit routing itself. - * **Betti-Scan:** Hardware implementation of Homology. A wavefront propagates through the lattice; "holes" in the data obstruct the wave, creating a unique interference pattern (The Shape). - -## 3. Instruction Set Architecture (ISA): "Bio-RISC" - -The chip does not execute sequential instructions. It executes **Impulses**. - -| Opcode | Mnemonic | Biological Equivalent | Function | -|:---|:---|:---|:---| -| `0x01` | `IMPULSE` | Action Potential | Propagate signal through connected manifold neighbors. | -| `0x02` | `PLASTIC` | Hebbian Learning | Adjust resistance of path based on correlation ($R = R - \delta$). | -| `0x03` | `DECAY` | Entropy | Force accelerate leakage in target cluster. | -| `0x04` | `MITOSIS` | Cell Division | Copy active cluster to adjacent free region. | - -## 4. Performance Scaling - -| Metric | Intel Core i9 (Von Neumann) | AEGIS Bio-Chip (Neuromorphic) | -|:---|:---|:---| -| **Memory Access** | 100ns (Latency) | **0ns** (In-Memory) | -| **GC Overhead** | 20% CPU Cycles | **0%** (Physics) | -| **Parallelism** | 24 Threads | **10,000+ Active Clusters** | -| **Energy/Op** | 100 pJ | **0.1 pJ** | - -## 5. Manufacturing Feasibility -* **Simulation:** Can be emulated on FPGA (Xilinx Versal). -* **Fabrication:** Requires Backend-of-Line (BEOL) integration of RRAM on standard CMOS. -* **Status:** Theoretical / Prototype Phase. - ---- - -**Officer's Note:** -This hardware specification represents the "Absolute Limit" of the architecture. By moving to hardware, we transition from *simulating* life to *creating* artificial life. The "Software Memory Problem" is solved not by code, but by physics. +Until those artifacts exist, hardware support should be treated as roadmap. diff --git a/docs/LANGUAGE.md b/docs/LANGUAGE.md index c516373..a517d23 100644 --- a/docs/LANGUAGE.md +++ b/docs/LANGUAGE.md @@ -1,229 +1,15 @@ -# AETHER Declarative IR Specification +# Language Reference -> Version 0.1.0 +This compatibility page points to the current language documentation: -## 1. Overview +- [Syntax](language/syntax.md) +- [Execution Model](language/execution-model.md) +- [Module Contracts](language/modules.md) +- [Language Pipeline](concepts/language-pipeline.md) -AETHER is a declarative intermediate representation for event-driven sparse execution, where declarations specify terminal states in 3D geometric manifolds. +The active language surface includes variables, assignment, arithmetic, +comparison, logical operators, lists, functions, `if`, `while`, `for`, +`seal until`, imports, manifolds, blocks, and topology module calls. -## 2. Syntax - -### 2.1 Metadata and Annotations - -```aegis -// Information annotation -``` - -### 2.2 Manifold Declaration - -```aegis -manifold = embed(, dim=, tau=) -``` - -**Parameters:** -- `source`: Input data source (e.g., `data`, file path) -- `dim`: Embedding dimension (1-16, default: 3) -- `tau`: Time delay for Takens embedding (default: 1) - -**Example:** -```aegis -manifold M = embed(data, dim=3, tau=5) -``` - -### 2.3 Block Declaration - -```aegis -block = .cluster(:) -block = [:] -``` - -**Example:** -```aegis -block B = M.cluster(0:64) -block B2 = M[64:128] -``` - -### 2.4 Variable Assignment - -```aegis - = - = -``` - -**Example:** -```aegis -centroid C = B.center -radius R = B.spread -x = 42 -``` - -### 2.5 Regression Statement - -```aegis -regress { - model: , - degree: , // optional - target: , // optional - escalate: , - until: -} -``` - -**Model Types:** -- `"linear"` - Linear regression -- `"polynomial"` - Polynomial regression -- `"rbf"` - Radial Basis Function -- `"gp"` - Gaussian Process -- `"geodesic"` - Manifold geodesic regression - -**Convergence Conditions:** -- `convergence()` - Error threshold -- `betti_stable()` - Betti number stability - -**Example:** -```aegis -regress { - model: "polynomial", - degree: 3, - escalate: true, - until: convergence(1e-6) -} -``` - -### 2.6 Render Statement - -```aegis -render { - color: , - highlight: , - trajectory: , - axis: -} -``` - -**Color Modes:** -- `by_density` - Color by point density -- `by_cluster` - Color by cluster assignment -- `gradient` - Gradient along axis - -**Example:** -```aegis -render M { - color: by_density, - highlight: B, - trajectory: on -} -``` - -## 3. Data Types - -| Type | Description | Example | -|------|-------------|---------| -| `int` | Integer | `42`, `-7` | -| `float` | Floating point | `3.14159` | -| `bool` | Boolean | `true`, `false` | -| `string` | Text | `"polynomial"` | -| `manifold` | 3D embedded space | `M` | -| `block` | Geometric region | `B` | -| `point` | D-dimensional point | `C` | - -## 4. Built-in Functions - -### embed(source, dim, tau) -Embed 1D time-series into D-dimensional manifold using Takens' theorem. - -### convergence(epsilon) -Convergence condition based on error threshold. - -### betti_stable(epochs) -Convergence when Betti numbers stable for N epochs. - -## 5. Properties - -### Block Properties - -| Property | Type | Description | -|----------|------|-------------| -| `.center` | point | Centroid of block | -| `.spread` | float | Radius (max deviation) | -| `.variance` | float | Variance of points | -| `.count` | int | Number of points | - -### Manifold Properties - -| Property | Type | Description | -|----------|------|-------------| -| `.center` | point | Global centroid | -| `.betti` | (int, int) | Betti numbers (β₀, β₁) | -| `.dim` | int | Embedding dimension | - -## 6. Grammar (EBNF) - -```ebnf -program = { statement } ; -statement = manifold_decl | block_decl | var_decl | regress_stmt | render_stmt ; - -manifold_decl = "manifold" IDENT "=" expr ; -block_decl = "block" IDENT "=" expr ; -var_decl = [ IDENT ] IDENT "=" expr ; -regress_stmt = "regress" config_block ; -render_stmt = "render" IDENT [ config_block ] ; - -config_block = "{" { config_pair } "}" ; -config_pair = IDENT ":" expr [ "," ] ; - -expr = primary { "." IDENT [ call_args ] } ; -primary = NUMBER | STRING | BOOL | IDENT | call_expr | index_expr ; -call_expr = IDENT call_args ; -call_args = "(" [ arg { "," arg } ] ")" ; -arg = expr | IDENT "=" expr ; -index_expr = IDENT "[" NUMBER ":" NUMBER "]" ; - -IDENT = letter { letter | digit | "_" } ; -NUMBER = digit { digit } [ "." digit { digit } ] ; -STRING = '"' { char } '"' ; -BOOL = "true" | "false" ; -``` - -## 7. Examples - -### Hello World - -```aegis -// Create manifold -manifold M = embed(data, dim=3) - -// Render it -render M { - color: gradient -} -``` - -### Escalating Regression - -```aegis -manifold M = embed(sensor_data, dim=3, tau=7) - -regress { - model: "polynomial", - escalate: true, - until: convergence(1e-8) -} -``` - -### Cluster Analysis - -```aegis -manifold M = embed(data, dim=3, tau=5) - -block A = M[0:50] -block B = M[50:100] - -centroid_a = A.center -centroid_b = B.center - -render M { - color: by_cluster, - trajectory: on -} -``` +Parsed syntax and runtime behavior are separate claims. A feature should be +documented as active only when the interpreter or Titan VM path has evidence. diff --git a/docs/MATHEMATICS.md b/docs/MATHEMATICS.md index 74b7b6e..03f8a57 100644 --- a/docs/MATHEMATICS.md +++ b/docs/MATHEMATICS.md @@ -1,167 +1,12 @@ -# AETHER-Shield Mathematical Specification +# Mathematics -## 1. State Space Formulation +The mathematical derivations are maintained in: -### 1.1 System State Vector +- [Topology derivations](topology/derivations.md) +- [Persistent homology](topology/persistent-homology.md) +- [Sparse events](kernel/sparse-events.md) +- [Topological convergence](ml/topological-convergence.md) -The kernel state is represented as a point on a d-dimensional manifold: - -$$\mu(t) \in \mathbb{R}^d$$ - -For AETHER-Shield with d=4: - -$$\mu(t) = \begin{bmatrix} m(t) \\ i(t) \\ q(t) \\ e(t) \end{bmatrix}$$ - -Where: -- $m(t)$ = Memory Pressure ∈ [0, 1] -- $i(t)$ = IRQ Rate (normalized) -- $q(t)$ = Thread Queue Depth (normalized) -- $e(t)$ = Entropy Pool Level ∈ [0, 1] - -### 1.2 Deviation Metric - -The "Action Potential" measures trajectory deviation using the L2 norm: - -$$\Delta(t) = ||\mu(t) - \mu(t_{last})||_2 = \sqrt{\sum_{k=1}^{d} (\mu_k(t) - \mu_k(t_{last}))^2}$$ - -### 1.3 Execution Condition - -The sparse trigger activates if and only if: - -$$\Delta(t) \geq \epsilon(t)$$ - -## 2. Geometric Governor (PID-on-Manifold) - -### 2.1 Error Signal - -$$e(t) = R_{target} - \frac{\Delta(t)}{\epsilon(t)}$$ - -Where $R_{target}$ = 1000 Hz (target kernel tick rate). - -### 2.2 Control Law - -$$\epsilon(t+1) = \epsilon(t) + \alpha \cdot e(t) + \beta \cdot \frac{de}{dt}$$ - -With: -- $\alpha$ = 0.01 (proportional gain) -- $\beta$ = 0.05 (derivative gain) - -### 2.3 Stability Bounds - -$$\epsilon(t) \in [0.001, 10.0]$$ - -## 3. Topological Data Analysis - -### 3.1 Time-Delay Embedding (Takens' Theorem) - -For a 1D signal $x(t)$, embed into $\mathbb{R}^D$: - -$$\Phi(t) = [x(t), x(t-\tau), x(t-2\tau), ..., x(t-(D-1)\tau)]$$ - -This reconstructs the attractor of the underlying dynamical system. - -### 3.2 Betti Numbers - -**β₀ (0-dimensional homology)**: Number of connected components - -$$\beta_0 = |H_0(X)|$$ - -Computed via 1D clustering: count "gaps" where consecutive byte difference > threshold. - -**β₁ (1-dimensional homology)**: Number of loops/cycles - -$$\beta_1 = |H_1(X)|$$ - -Approximated by detecting oscillation patterns in byte stream. - -### 3.3 Shape Signature - -$$Shape(B) = (\beta_0, \beta_1)$$ - -### 3.4 Authentication Criterion - -$$d_{Wasserstein}(Shape(B), Shape_{ref}) \leq \delta$$ - -Simplified to density-based heuristic: - -$$density = \frac{\beta_0}{|B|} \in [0.1, 0.6] \implies \text{Valid}$$ - -## 4. AETHER Geometric Primitives - -### 4.1 Block Metadata - -For a block of embeddings $\{k_1, ..., k_n\}$: - -**Centroid:** -$$\mu_{block} = \frac{1}{n}\sum_{i=1}^{n} k_i$$ - -**Radius:** -$$r_{block} = \max_i ||k_i - \mu_{block}||_2$$ - -**Variance:** -$$\sigma^2_{block} = \frac{1}{n}\sum_{i=1}^{n} ||k_i - \mu_{block}||_2^2 - \left(\frac{1}{n}\sum_{i=1}^{n} ||k_i - \mu_{block}||_2\right)^2$$ - -**Concentration:** -$$c_{block} = \frac{1}{n}\sum_{i=1}^{n} \frac{k_i \cdot \mu_{block}}{||k_i|| \cdot ||\mu_{block}||}$$ - -### 4.2 Upper-Bound Scoring (Cauchy-Schwarz) - -For query $q$ and block with centroid $\mu$ and radius $r$: - -$$score(q, \text{block}) \leq ||q|| \cdot (||\mu|| + r)$$ - -If upper bound < threshold, the entire block can be skipped. - -### 4.3 Hierarchical Complexity - -| Approach | Scoring Complexity | -|----------|-------------------| -| Dense Attention | O(n²) | -| AETHER (flat) | O(n/b) | -| Hierarchical | O(log(n/b)) | - -Where $b$ = block size (64 tokens). - -## 5. Sparse Attention Graph - -### 5.1 ε-Neighborhood - -Two points are neighbors if: - -$$d(p_i, p_j) < \epsilon$$ - -### 5.2 Sparse Adjacency - -$$A_{ij} = \begin{cases} 1 & \text{if } ||p_i - p_j||_2 < \epsilon \\ 0 & \text{otherwise} \end{cases}$$ - -### 5.3 Euler Characteristic Approximation - -$$\chi = V - E + F$$ - -For planar graphs: -$$\beta_0 - \beta_1 + \beta_2 = \chi$$ - -Simplified: -$$\beta_1 \approx E - V + \beta_0$$ - -## 6. Convergence Properties - -### 6.1 Governor Stability - -The PID controller converges to steady state where: - -$$e(t) \rightarrow 0 \implies \frac{\Delta(t)}{\epsilon(t)} \rightarrow R_{target}$$ - -### 6.2 Lyapunov Analysis - -For the error dynamics with Lyapunov candidate $V = e^2/2$: - -$$\dot{V} = e \cdot \dot{e}$$ - -With appropriate gains, $\dot{V} < 0$ ensures asymptotic stability. - -## References - -1. Takens, F. (1981). Detecting strange attractors in turbulence. -2. Edelsbrunner, H. & Harer, J. (2010). Computational Topology. -3. AETHER Geometric Extensions. DOI: 10.13141/RG.2.2.14811.27684 +Every formula should name its implementation surface and claim boundary. A +formula in documentation is not evidence that the full system property has been +proved for all runtime paths. diff --git a/docs/ML_AETHER.md b/docs/ML_AETHER.md index d93c15f..cd7a26f 100644 --- a/docs/ML_AETHER.md +++ b/docs/ML_AETHER.md @@ -1,478 +1,13 @@ -# AEGIS AETHER Integration Guide +# Aether ML -> **Geometric Intelligence for Machine Learning** +The current ML documentation is split into: -AETHER (Adaptive Efficient Topological Hierarchical Embedding Representations) provides geometric primitives that enable O(log n) ML operations through hierarchical pruning and sparse attention. +- [ML primitives](ml/primitives.md) +- [Topological convergence](ml/topological-convergence.md) +- [Benefit emergence](concepts/benefit-emergence.md) +- [Benchmark policy](benchmarks/index.md) ---- - -## Table of Contents - -1. [AETHER Overview](#aether-overview) -2. [Block Metadata for ML](#block-metadata-for-ml) -3. [Hierarchical Block Trees](#hierarchical-block-trees) -4. [Sparse Attention Mechanisms](#sparse-attention-mechanisms) -5. [Drift Detection for Online Learning](#drift-detection) -6. [Practical Applications](#practical-applications) - ---- - -## AETHER Overview - -### The AETHER Advantage - -| Traditional ML | AETHER ML | -|---------------|-----------| -| Dense attention O(n²) | **Sparse attention O(n)** | -| Full matrix scan | **Hierarchical pruning O(log n)** | -| Fixed batch retraining | **Drift-aware adaptation** | -| Scalar convergence criteria | **Topological convergence** | - -### Core Primitives - -``` -BlockMetadata → Geometric summary of data blocks -HierarchicalBlockTree → Multi-scale tree for pruning -DriftDetector → Semantic drift tracking -SparseAttentionGraph → O(n) locality-based attention -``` - ---- - -## Block Metadata for ML - -### Computing Block Summaries - -A `BlockMetadata` captures the geometric essence of a data block: - -```aegis -// Create block metadata from points -let points = [[0.0, 0.0], [1.0, 0.0], [0.5, 0.5]]~ -block B = BlockMetadata.from_points(points)~ - -print("Centroid: " + B.centroid)~ // [0.5, 0.167] -print("Radius: " + B.radius)~ // Max distance from centroid -print("Variance: " + B.variance)~ // Distance variance -print("Concentration: " + B.concentration)~ // Angular alignment -print("Count: " + B.count)~ // 3 -``` - -### Using Metadata for Classification - -```aegis -// Block-based k-NN -fn block_classify(query, class_blocks) { - let best_class = 0~ - let best_score = -1e10~ - - for i in 0..len(class_blocks) { - let B = class_blocks[i]~ - - // Score = similarity to block centroid - let score = -distance(query, B.centroid)~ - - // Adjust by concentration (high concentration = more confident) - score = score * B.concentration~ - - if score > best_score { - best_score = score~ - best_class = i~ - } - } - - return best_class~ -} -``` - -### Upper-Bound Pruning - -AETHER's key insight: compute bounds before full calculation. - -```aegis -// Cauchy-Schwarz upper bound -// score(q, B) ≤ ||q|| × (||centroid|| + radius) - -fn can_skip_block(query, block, threshold) { - let q_norm = norm(query)~ - let c_norm = norm(block.centroid)~ - let upper_bound = q_norm * (c_norm + block.radius)~ - - // If upper bound is below threshold, skip this block entirely - return upper_bound < threshold~ -} -``` - ---- - -## Hierarchical Block Trees - -### Tree Structure - -``` -Level 2: [████████] Super-cluster (1024 tokens) - ↓ -Level 1: [████][████] Clusters (256 tokens each) - ↓ ↓ -Level 0: [██][██][██][██] Blocks (64 tokens each, finest) -``` - -### Building the Tree - -```aegis -// Create hierarchical block tree -manifold M = embed(data, dim=3, tau=5)~ - -// Extract fine-level blocks -let blocks = []~ -for i in 0..len(M)/64 { - let start = i * 64~ - let end = min(start + 64, len(M))~ - blocks.push(BlockMetadata.from_points(M[start:end]))~ -} - -// Build hierarchy -let tree = HierarchicalBlockTree.new()~ -tree.build_from_blocks(blocks)~ -``` - -### Hierarchical Query (O(log n) Search) - -```aegis -// Find relevant blocks with early pruning -fn hierarchical_search(tree, query, threshold) { - // Start at coarsest level (Level 2) - let active_l2 = []~ - for block in tree.level(2) { - if not can_skip_block(query, block, threshold) { - active_l2.push(block.id)~ - } - } - // Often 50-90% pruned at this level! - - // Check children of active Level 2 blocks (Level 1) - let active_l1 = []~ - for parent_id in active_l2 { - for child in tree.children(parent_id) { - if not can_skip_block(query, child, threshold) { - active_l1.push(child.id)~ - } - } - } - - // Finally, check Level 0 (finest blocks) - let active_l0 = []~ - for parent_id in active_l1 { - for child in tree.children(parent_id) { - if not can_skip_block(query, child, threshold) { - active_l0.push(child.id)~ - } - } - } - - return active_l0~ // Only these blocks need full attention -} -``` - -### Pruning Efficiency - -```aegis -// Measure pruning ratio -let active_mask = tree.hierarchical_query(query, threshold)~ -let pruning_ratio = tree.pruning_ratio(active_mask)~ - -print("Pruned " + (pruning_ratio * 100) + "% of blocks!")~ -// Typical: 60-90% pruning for sparse queries -``` - ---- - -## Sparse Attention Mechanisms - -### Geometric Locality - -Traditional attention: every token attends to every other token (O(n²)) -AETHER attention: tokens only attend to geometric neighbors (O(n)) - -```aegis -// Sparse attention via epsilon-neighborhood -fn sparse_attention(points, epsilon) { - let n = len(points)~ - let attention = zeros_matrix(n, n)~ - - for i in 0..n { - for j in 0..n { - if distance(points[i], points[j]) < epsilon { - attention[i][j] = 1.0~ - } - } - } - - // Normalize rows - for i in 0..n { - let row_sum = sum(attention[i])~ - if row_sum > 0 { - attention[i] = vscale(attention[i], 1.0 / row_sum)~ - } - } - - return attention~ -} -``` - -### Computing Betti Numbers - -```aegis -// Extract topological signature from attention graph -fn attention_topology(points, epsilon) { - let graph = SparseAttentionGraph.new(epsilon)~ - - for p in points { - graph.add_point(p)~ - } - - let beta_0 = graph.compute_betti_0()~ // Connected components - let beta_1 = graph.estimate_betti_1()~ // Cycles (Euler estimate) - - return { beta_0: beta_0, beta_1: beta_1 }~ -} -``` - -### Multi-Scale Attention - -```aegis -// Attention at different epsilon scales -fn multiscale_attention(points, epsilon_scales) { - let results = []~ - - for eps in epsilon_scales { - let topo = attention_topology(points, eps)~ - results.push({ - epsilon: eps, - beta_0: topo.beta_0, - beta_1: topo.beta_1 - })~ - } - - // Persistence: features stable across scales are important - return find_persistent_features(results)~ -} -``` - ---- - -## Drift Detection - -### DriftDetector for Online Learning - -```aegis -// Initialize drift detector -let detector = DriftDetector.new()~ - -// Process streaming batches -for batch in data_stream.batches(64) { - let centroid = batch.center~ - let drift_score = detector.update(centroid)~ - - print("Drift score: " + drift_score)~ - print("Velocity: " + detector.velocity_magnitude())~ - - if detector.is_drifting(0.1) { - alert("Concept drift detected!")~ - trigger_retraining()~ - } -} -``` - -### Adaptive Learning Rate - -```aegis -// Adjust learning rate based on drift -fn adaptive_lr(base_lr, drift_detector) { - let velocity = drift_detector.velocity_magnitude()~ - - if velocity > 0.5 { - // High drift = increase learning rate - return base_lr * 2.0~ - } else if velocity < 0.1 { - // Stable = decrease learning rate - return base_lr * 0.5~ - } else { - return base_lr~ - } -} -``` - -### Drift-Aware Model Update - -```aegis -// Full online learning loop with drift adaptation -fn online_train(initial_model, data_stream) { - let model = initial_model~ - let drift_detector = DriftDetector.new()~ - let lr = 0.01~ - - for batch in data_stream.batches(32) { - // Check for drift - let centroid = batch.center~ - let drift = drift_detector.update(centroid)~ - - if drift_detector.is_drifting(0.2) { - // Major drift: reset model - print("Major drift! Resetting model.")~ - model = fresh_model()~ - lr = 0.05~ // High learning rate for new regime - } else if drift_detector.is_drifting(0.1) { - // Minor drift: increase learning rate - lr = lr * 1.5~ - } else { - // Stable: decay learning rate - lr = max(0.001, lr * 0.99)~ - } - - // Update model - model = online_update(model, batch, lr)~ - } - - return model~ -} -``` - ---- - -## Practical Applications - -### Fast Nearest Neighbor Search - -```aegis -// O(log n) nearest neighbor via hierarchical blocks -fn fast_nn(tree, query, k) { - // First pass: hierarchical pruning - let threshold = initial_threshold()~ - let candidates = hierarchical_search(tree, query, threshold)~ - - // Second pass: exact search within candidates - let distances = []~ - for block_id in candidates { - for point in tree.block_points(block_id) { - let d = distance(query, point)~ - distances.push({ point: point, distance: d })~ - } - } - - // Return k nearest - distances.sort_by(|a, b| a.distance - b.distance)~ - return distances[0:k]~ -} -``` - -### Block-Based Anomaly Detection - -```aegis -// Anomaly = point far from all block centroids -fn block_anomaly_detection(tree, test_points, threshold) { - let anomalies = []~ - - for point in test_points { - let min_dist = 1e10~ - - for block in tree.level(0) { - let d = distance(point, block.centroid)~ - // Account for block spread - let normalized_d = d / (block.radius + 1e-6)~ - min_dist = min(min_dist, normalized_d)~ - } - - if min_dist > threshold { - anomalies.push(point)~ - } - } - - return anomalies~ -} -``` - -### Geometric Compression - -```aegis -// Choose compression strategy based on block properties -fn compress_block(block) { - if block.variance < 0.1 { - // Low variance: store centroid + deltas - return centroid_delta_compress(block)~ - } else if block.concentration > 0.9 { - // High concentration: aggressive quantization - return int4_quantize(block)~ - } else { - // Dispersed: full precision - return full_precision(block)~ - } -} - -// Estimate compression ratio -fn estimated_ratio(block) { - if block.variance < 0.1 { - return 4.0~ // 4x compression - } else if block.concentration > 0.9 { - return 4.0~ // 16-bit → 4-bit - } else { - return 1.0~ // No compression - } -} -``` - -### Seal Loop with AETHER Convergence - -```aegis -// Training with AETHER-based convergence -fn aether_train(model, data) { - let tree = build_block_tree(data)~ - let drift_detector = DriftDetector.new()~ - - 🦭 until convergence(1e-6) { - // Train step - let loss = model.train_step(data)~ - - // Compute block centroids of predictions - let pred_blocks = compute_blocks(model.predict(data))~ - - // Track drift of prediction manifold - for block in pred_blocks { - drift_detector.update(block.centroid)~ - } - - // Converge when predictions stabilize geometrically - if not drift_detector.is_drifting(0.01) { - let (beta_0, beta_1) = pred_topology(pred_blocks)~ - if beta_0 == 1 and beta_1 == 0 { - break~ // Single connected component, no holes = converged! - } - } - } - - return model~ -} -``` - ---- - -## Summary - -AETHER provides the geometric foundation for efficient ML: - -| Component | ML Application | Complexity | -|-----------|---------------|------------| -| `BlockMetadata` | Fast classification, anomaly detection | O(1) per block | -| `HierarchicalBlockTree` | Nearest neighbor, range queries | O(log n) | -| `SparseAttentionGraph` | Transformer attention, clustering | O(n) vs O(n²) | -| `DriftDetector` | Online learning, concept drift | O(1) per update | - -**Key Insight**: By operating on geometric summaries instead of individual points, AETHER achieves logarithmic complexity for operations that are traditionally linear or quadratic. - ---- - -## See Also - -- [ML Library Reference](ML_LIBRARY.md) - Core ML API -- [ML Tasks Encyclopedia](ML_TASKS.md) - All ML tasks -- [ML From Scratch](ML_FROM_SCRATCH.md) - Building from zero -- [Research Paper](paper/) - Mathematical foundations +The active claim is that Aether contains internal Rust ML primitives and exposes +a subset through the interpreter. Claims about logarithmic behavior, model +quality, framework replacement, or speedups require benchmark artifacts and +baseline comparisons. diff --git a/docs/ML_FROM_SCRATCH.md b/docs/ML_FROM_SCRATCH.md index 0166e18..224deb5 100644 --- a/docs/ML_FROM_SCRATCH.md +++ b/docs/ML_FROM_SCRATCH.md @@ -1,912 +1,13 @@ -# AEGIS ML From Scratch +# ML From Scratch Compatibility Page -> **Building Complete Machine Learning Algorithms from Zero in AEGIS** +This page is retained for existing links. The current documentation avoids +large tutorial claims that are not tied to interpreter tests. -This guide proves AEGIS can implement ML algorithms entirely from first principles, using only basic operations and the `~` statement terminator. No external libraries. No pre-built functions. Pure AEGIS. +Use: ---- +- [ML primitives](ml/primitives.md) +- [Module contracts](language/modules.md) +- [Evidence gates](benchmarks/evidence-gates.md) -## Table of Contents - -1. [Linear Algebra Foundation](#linear-algebra-foundation) -2. [Loss Functions](#loss-functions) -3. [Gradient Computation](#gradient-computation) -4. [Optimizers](#optimizers) -5. [Complete Models](#complete-models) -6. [Neural Networks](#neural-networks) -7. [Topological Extensions](#topological-extensions) - ---- - -## Linear Algebra Foundation - -### Basic Operations - -```aegis -// ═══════════════════════════════════════════════════ -// Vector Operations from Scratch -// ═══════════════════════════════════════════════════ - -// Vector addition -fn vadd(a, b) { - let result = zeros(len(a))~ - for i in 0..len(a) { - result[i] = a[i] + b[i]~ - } - return result~ -} - -// Scalar multiplication -fn vscale(v, s) { - let result = zeros(len(v))~ - for i in 0..len(v) { - result[i] = v[i] * s~ - } - return result~ -} - -// Dot product -fn dot(a, b) { - let sum = 0.0~ - for i in 0..len(a) { - sum = sum + a[i] * b[i]~ - } - return sum~ -} - -// Euclidean norm -fn norm(v) { - return sqrt(dot(v, v))~ -} - -// Normalize vector -fn normalize(v) { - let n = norm(v)~ - if n < 1e-10 { - return v~ - } - return vscale(v, 1.0 / n)~ -} - -// L2 distance -fn distance(a, b) { - let sum = 0.0~ - for i in 0..len(a) { - let diff = a[i] - b[i]~ - sum = sum + diff * diff~ - } - return sqrt(sum)~ -} - -// Cosine similarity -fn cosine(a, b) { - let na = norm(a)~ - let nb = norm(b)~ - if na < 1e-10 or nb < 1e-10 { - return 0.0~ - } - return dot(a, b) / (na * nb)~ -} -``` - -### Matrix Operations - -```aegis -// ═══════════════════════════════════════════════════ -// Matrix Operations from Scratch -// ═══════════════════════════════════════════════════ - -// Create zero matrix -fn zeros_matrix(rows, cols) { - let M = []~ - for i in 0..rows { - M[i] = zeros(cols)~ - } - return M~ -} - -// Matrix-vector multiply -fn matvec(A, v) { - let rows = len(A)~ - let result = zeros(rows)~ - for i in 0..rows { - result[i] = dot(A[i], v)~ - } - return result~ -} - -// Matrix-matrix multiply -fn matmul(A, B) { - let rows_a = len(A)~ - let cols_a = len(A[0])~ - let cols_b = len(B[0])~ - let C = zeros_matrix(rows_a, cols_b)~ - - for i in 0..rows_a { - for j in 0..cols_b { - let sum = 0.0~ - for k in 0..cols_a { - sum = sum + A[i][k] * B[k][j]~ - } - C[i][j] = sum~ - } - } - return C~ -} - -// Transpose -fn transpose(A) { - let rows = len(A)~ - let cols = len(A[0])~ - let T = zeros_matrix(cols, rows)~ - for i in 0..rows { - for j in 0..cols { - T[j][i] = A[i][j]~ - } - } - return T~ -} - -// Outer product -fn outer(a, b) { - let m = len(a)~ - let n = len(b)~ - let O = zeros_matrix(m, n)~ - for i in 0..m { - for j in 0..n { - O[i][j] = a[i] * b[j]~ - } - } - return O~ -} -``` - -### Statistical Functions - -```aegis -// ═══════════════════════════════════════════════════ -// Statistics from Scratch -// ═══════════════════════════════════════════════════ - -// Mean -fn mean(data) { - let sum = 0.0~ - for x in data { - sum = sum + x~ - } - return sum / len(data)~ -} - -// Variance -fn variance(data) { - let m = mean(data)~ - let sum = 0.0~ - for x in data { - let diff = x - m~ - sum = sum + diff * diff~ - } - return sum / len(data)~ -} - -// Standard deviation -fn std(data) { - return sqrt(variance(data))~ -} - -// Covariance -fn covariance(x, y) { - let mx = mean(x)~ - let my = mean(y)~ - let sum = 0.0~ - for i in 0..len(x) { - sum = sum + (x[i] - mx) * (y[i] - my)~ - } - return sum / len(x)~ -} - -// Correlation -fn correlation(x, y) { - return covariance(x, y) / (std(x) * std(y))~ -} -``` - ---- - -## Loss Functions - -```aegis -// ═══════════════════════════════════════════════════ -// Loss Functions from Scratch -// ═══════════════════════════════════════════════════ - -// Mean Squared Error -fn mse(y_true, y_pred) { - let sum = 0.0~ - for i in 0..len(y_true) { - let diff = y_true[i] - y_pred[i]~ - sum = sum + diff * diff~ - } - return sum / len(y_true)~ -} - -// Mean Absolute Error -fn mae(y_true, y_pred) { - let sum = 0.0~ - for i in 0..len(y_true) { - sum = sum + abs(y_true[i] - y_pred[i])~ - } - return sum / len(y_true)~ -} - -// Root Mean Squared Error -fn rmse(y_true, y_pred) { - return sqrt(mse(y_true, y_pred))~ -} - -// Binary Cross-Entropy -fn binary_cross_entropy(y_true, y_pred) { - let sum = 0.0~ - for i in 0..len(y_true) { - let p = clip(y_pred[i], 1e-7, 1.0 - 1e-7)~ - sum = sum - (y_true[i] * log(p) + (1 - y_true[i]) * log(1 - p))~ - } - return sum / len(y_true)~ -} - -// Categorical Cross-Entropy -fn categorical_cross_entropy(y_true, y_pred) { - let sum = 0.0~ - for i in 0..len(y_true) { - for k in 0..len(y_true[i]) { - if y_true[i][k] > 0 { - let p = clip(y_pred[i][k], 1e-7, 1.0)~ - sum = sum - y_true[i][k] * log(p)~ - } - } - } - return sum / len(y_true)~ -} - -// Hinge Loss (SVM) -fn hinge_loss(y_true, y_pred) { - let sum = 0.0~ - for i in 0..len(y_true) { - let margin = 1 - y_true[i] * y_pred[i]~ - sum = sum + max(0, margin)~ - } - return sum / len(y_true)~ -} - -// Huber Loss (robust to outliers) -fn huber_loss(y_true, y_pred, delta) { - let sum = 0.0~ - for i in 0..len(y_true) { - let diff = abs(y_true[i] - y_pred[i])~ - if diff <= delta { - sum = sum + 0.5 * diff * diff~ - } else { - sum = sum + delta * (diff - 0.5 * delta)~ - } - } - return sum / len(y_true)~ -} -``` - ---- - -## Gradient Computation - -```aegis -// ═══════════════════════════════════════════════════ -// Gradient Computation from Scratch -// ═══════════════════════════════════════════════════ - -// Numerical gradient (finite differences) -fn numerical_gradient(f, x, epsilon) { - let grad = zeros(len(x))~ - for i in 0..len(x) { - let x_plus = copy(x)~ - let x_minus = copy(x)~ - x_plus[i] = x_plus[i] + epsilon~ - x_minus[i] = x_minus[i] - epsilon~ - grad[i] = (f(x_plus) - f(x_minus)) / (2 * epsilon)~ - } - return grad~ -} - -// MSE gradient (analytical) -fn mse_gradient(X, y, weights) { - let n = len(y)~ - let d = len(weights)~ - let grad = zeros(d)~ - - for i in 0..n { - let pred = dot(X[i], weights)~ - let error = pred - y[i]~ - for j in 0..d { - grad[j] = grad[j] + 2 * error * X[i][j] / n~ - } - } - - return grad~ -} - -// Binary cross-entropy gradient -fn bce_gradient(X, y, weights) { - let n = len(y)~ - let d = len(weights)~ - let grad = zeros(d)~ - - for i in 0..n { - let z = dot(X[i], weights)~ - let pred = sigmoid(z)~ - let error = pred - y[i]~ - for j in 0..d { - grad[j] = grad[j] + error * X[i][j] / n~ - } - } - - return grad~ -} -``` - ---- - -## Optimizers - -```aegis -// ═══════════════════════════════════════════════════ -// Optimizers from Scratch -// ═══════════════════════════════════════════════════ - -// Vanilla Gradient Descent -fn gradient_descent(f, x0, lr, epsilon) { - let x = copy(x0)~ - - 🦭 until convergence(epsilon) { - let grad = numerical_gradient(f, x, 1e-5)~ - for i in 0..len(x) { - x[i] = x[i] - lr * grad[i]~ - } - } - - return x~ -} - -// Stochastic Gradient Descent -fn sgd(X, y, batch_size, lr, epsilon) { - let weights = zeros(len(X[0]))~ - let n = len(X)~ - - 🦭 until convergence(epsilon) { - // Random batch - let indices = sample_indices(n, batch_size)~ - let X_batch = select(X, indices)~ - let y_batch = select(y, indices)~ - - // Gradient on batch - let grad = mse_gradient(X_batch, y_batch, weights)~ - - // Update - weights = vadd(weights, vscale(grad, -lr))~ - } - - return weights~ -} - -// SGD with Momentum -fn sgd_momentum(X, y, lr, momentum, epsilon) { - let weights = zeros(len(X[0]))~ - let velocity = zeros(len(X[0]))~ - - 🦭 until convergence(epsilon) { - let grad = mse_gradient(X, y, weights)~ - - // Update velocity - velocity = vadd(vscale(velocity, momentum), grad)~ - - // Update weights - weights = vadd(weights, vscale(velocity, -lr))~ - } - - return weights~ -} - -// Adam Optimizer -fn adam(X, y, lr, beta1, beta2, epsilon) { - let weights = zeros(len(X[0]))~ - let m = zeros(len(X[0]))~ // First moment - let v = zeros(len(X[0]))~ // Second moment - let t = 0~ - - 🦭 until convergence(epsilon) { - t = t + 1~ - let grad = mse_gradient(X, y, weights)~ - - // Update biased moments - for i in 0..len(weights) { - m[i] = beta1 * m[i] + (1 - beta1) * grad[i]~ - v[i] = beta2 * v[i] + (1 - beta2) * grad[i] * grad[i]~ - } - - // Bias correction - let m_hat = vscale(m, 1 / (1 - pow(beta1, t)))~ - let v_hat = vscale(v, 1 / (1 - pow(beta2, t)))~ - - // Update weights - for i in 0..len(weights) { - weights[i] = weights[i] - lr * m_hat[i] / (sqrt(v_hat[i]) + 1e-8)~ - } - } - - return weights~ -} - -// RMSprop -fn rmsprop(X, y, lr, decay, epsilon) { - let weights = zeros(len(X[0]))~ - let cache = zeros(len(X[0]))~ - - 🦭 until convergence(epsilon) { - let grad = mse_gradient(X, y, weights)~ - - // Update cache - for i in 0..len(weights) { - cache[i] = decay * cache[i] + (1 - decay) * grad[i] * grad[i]~ - weights[i] = weights[i] - lr * grad[i] / (sqrt(cache[i]) + 1e-8)~ - } - } - - return weights~ -} -``` - ---- - -## Complete Models - -### Linear Regression - -```aegis -// Complete Linear Regression from Scratch -fn linear_regression(X, y) { - let n = len(X)~ - let d = len(X[0])~ - - // Add bias column - let X_bias = zeros_matrix(n, d + 1)~ - for i in 0..n { - X_bias[i][0] = 1.0~ // Bias term - for j in 0..d { - X_bias[i][j + 1] = X[i][j]~ - } - } - - // Solve via gradient descent - let weights = zeros(d + 1)~ - let lr = 0.01~ - - 🦭 until convergence(1e-6) { - let grad = mse_gradient(X_bias, y, weights)~ - weights = vadd(weights, vscale(grad, -lr))~ - } - - return { - bias: weights[0], - coefficients: weights[1:] - }~ -} -``` - -### Logistic Regression - -```aegis -// Logistic Regression from Scratch -fn sigmoid(z) { - return 1.0 / (1.0 + exp(-z))~ -} - -fn logistic_regression(X, y, lr) { - let weights = zeros(len(X[0]))~ - let bias = 0.0~ - - 🦭 until convergence(1e-6) { - let total_grad_w = zeros(len(weights))~ - let total_grad_b = 0.0~ - - for i in 0..len(X) { - let z = dot(X[i], weights) + bias~ - let pred = sigmoid(z)~ - let error = pred - y[i]~ - - for j in 0..len(weights) { - total_grad_w[j] = total_grad_w[j] + error * X[i][j]~ - } - total_grad_b = total_grad_b + error~ - } - - // Update - let n = len(X)~ - for j in 0..len(weights) { - weights[j] = weights[j] - lr * total_grad_w[j] / n~ - } - bias = bias - lr * total_grad_b / n~ - } - - return { weights: weights, bias: bias }~ -} - -fn logistic_predict(model, x) { - let z = dot(x, model.weights) + model.bias~ - return if sigmoid(z) >= 0.5 { 1 } else { 0 }~ -} -``` - -### K-Means Clustering - -```aegis -// K-Means from Scratch -fn kmeans(X, k) { - let n = len(X)~ - let d = len(X[0])~ - - // Initialize centroids randomly - let indices = sample_indices(n, k)~ - let centroids = []~ - for i in 0..k { - centroids[i] = copy(X[indices[i]])~ - } - - let labels = zeros(n)~ - - 🦭 until convergence(1e-6) { - let changed = 0~ - - // Assignment step - for i in 0..n { - let best_cluster = 0~ - let best_dist = 1e10~ - - for j in 0..k { - let d = distance(X[i], centroids[j])~ - if d < best_dist { - best_dist = d~ - best_cluster = j~ - } - } - - if labels[i] != best_cluster { - labels[i] = best_cluster~ - changed = changed + 1~ - } - } - - // Update step - for j in 0..k { - let sum = zeros(d)~ - let count = 0~ - - for i in 0..n { - if labels[i] == j { - sum = vadd(sum, X[i])~ - count = count + 1~ - } - } - - if count > 0 { - centroids[j] = vscale(sum, 1.0 / count)~ - } - } - - if changed == 0 { - break~ - } - } - - return { centroids: centroids, labels: labels }~ -} -``` - -### Decision Stump (for boosting) - -```aegis -// Decision Stump from Scratch -fn decision_stump(X, y, weights) { - let best_feature = 0~ - let best_threshold = 0~ - let best_error = 1e10~ - let best_direction = 1~ - - for f in 0..len(X[0]) { - // Get unique values for this feature - let values = unique(column(X, f))~ - - for v in values { - for direction in [-1, 1] { - let error = 0.0~ - - for i in 0..len(X) { - let pred = if direction * X[i][f] < direction * v { 1 } else { -1 }~ - if pred != y[i] { - error = error + weights[i]~ - } - } - - if error < best_error { - best_error = error~ - best_feature = f~ - best_threshold = v~ - best_direction = direction~ - } - } - } - } - - return { - feature: best_feature, - threshold: best_threshold, - direction: best_direction, - error: best_error - }~ -} -``` - ---- - -## Neural Networks - -### Activation Functions - -```aegis -// ═══════════════════════════════════════════════════ -// Activation Functions from Scratch -// ═══════════════════════════════════════════════════ - -fn relu(x) { - let result = zeros(len(x))~ - for i in 0..len(x) { - result[i] = if x[i] > 0 { x[i] } else { 0 }~ - } - return result~ -} - -fn relu_derivative(x) { - let result = zeros(len(x))~ - for i in 0..len(x) { - result[i] = if x[i] > 0 { 1 } else { 0 }~ - } - return result~ -} - -fn sigmoid_vec(x) { - let result = zeros(len(x))~ - for i in 0..len(x) { - result[i] = 1.0 / (1.0 + exp(-x[i]))~ - } - return result~ -} - -fn sigmoid_derivative(x) { - let s = sigmoid_vec(x)~ - let result = zeros(len(x))~ - for i in 0..len(x) { - result[i] = s[i] * (1 - s[i])~ - } - return result~ -} - -fn tanh_vec(x) { - let result = zeros(len(x))~ - for i in 0..len(x) { - let e_pos = exp(x[i])~ - let e_neg = exp(-x[i])~ - result[i] = (e_pos - e_neg) / (e_pos + e_neg)~ - } - return result~ -} - -fn softmax(x) { - let max_x = max(x)~ - let exp_x = zeros(len(x))~ - let sum_exp = 0.0~ - - for i in 0..len(x) { - exp_x[i] = exp(x[i] - max_x)~ - sum_exp = sum_exp + exp_x[i]~ - } - - for i in 0..len(x) { - exp_x[i] = exp_x[i] / sum_exp~ - } - - return exp_x~ -} -``` - -### Multi-Layer Perceptron - -```aegis -// Complete MLP from Scratch -fn mlp_train(X, y, hidden_sizes, lr) { - // Initialize layers - let layers = []~ - let prev_size = len(X[0])~ - - for size in hidden_sizes { - layers.push({ - weights: random_matrix(prev_size, size, -0.5, 0.5), - bias: zeros(size) - })~ - prev_size = size~ - } - // Output layer - layers.push({ - weights: random_matrix(prev_size, 1, -0.5, 0.5), - bias: zeros(1) - })~ - - 🦭 until convergence(1e-4) { - let total_loss = 0.0~ - - for i in 0..len(X) { - // ═══ Forward Pass ═══ - let activations = [X[i]]~ - let z_values = []~ - - for l in 0..len(layers) { - let z = vadd(matvec(transpose(layers[l].weights), activations[-1]), layers[l].bias)~ - z_values.push(z)~ - - if l < len(layers) - 1 { - activations.push(relu(z))~ - } else { - activations.push(z)~ // Linear output - } - } - - let output = activations[-1][0]~ - let error = output - y[i]~ - total_loss = total_loss + error * error~ - - // ═══ Backward Pass ═══ - let delta = [error]~ - - for l in reverse(0..len(layers)) { - // Gradient for this layer - if l < len(layers) - 1 { - delta = hadamard(delta, relu_derivative(z_values[l]))~ - } - - // Update weights - let grad_w = outer(activations[l], delta)~ - layers[l].weights = matrix_sub(layers[l].weights, matrix_scale(grad_w, lr))~ - layers[l].bias = vadd(layers[l].bias, vscale(delta, -lr))~ - - // Propagate delta - if l > 0 { - delta = matvec(layers[l].weights, delta)~ - } - } - } - - if total_loss / len(X) < 1e-6 { - break~ - } - } - - return layers~ -} - -fn mlp_predict(layers, x) { - let current = x~ - for l in 0..len(layers) { - let z = vadd(matvec(transpose(layers[l].weights), current), layers[l].bias)~ - if l < len(layers) - 1 { - current = relu(z)~ - } else { - current = z~ - } - } - return current[0]~ -} -``` - ---- - -## Topological Extensions - -### Betti-Regularized Training - -```aegis -// Neural network with topological regularization -fn topo_train(X, y, hidden_sizes, lr, topo_weight) { - let layers = init_layers(X, hidden_sizes)~ - - 🦭 until convergence(1e-5) { - // Standard forward pass - let predictions = []~ - for i in 0..len(X) { - predictions.push(mlp_forward(layers, X[i]))~ - } - - // Standard MSE loss - let mse_loss = mse(y, predictions)~ - - // ═══ Topological Regularization ═══ - // Embed predictions into manifold - manifold M = embed(predictions, dim=3, tau=1)~ - let (beta_0, beta_1) = M.shape()~ - - // Penalty for fragmented predictions (beta_0 > 1) - let topo_loss = topo_weight * max(0, beta_0 - 1)~ - - // Total loss - let total_loss = mse_loss + topo_loss~ - - // Backprop with modified gradient - layers = backprop(layers, total_loss, lr)~ - } - - return layers~ -} -``` - -### Seal-Loop Hyperparameter Tuning - -```aegis -// Hyperparameter tuning via topological convergence -fn seal_tune(X, y, param_grid) { - let best_params = {}~ - let best_score = -1e10~ - - 🦭 until convergence(0.001) { - // Sample random hyperparameters - let params = { - lr: random(param_grid.lr_min, param_grid.lr_max), - hidden_size: random_int(param_grid.hidden_min, param_grid.hidden_max), - batch_size: random_choice(param_grid.batch_sizes) - }~ - - // Train with these params - let model = train_with_params(X, y, params)~ - let score = cross_validate(model, X, y, 5)~ - - if score > best_score { - best_score = score~ - best_params = params~ - } - - // Narrow search space around best - param_grid = narrow_grid(param_grid, best_params)~ - } - - return best_params~ -} -``` - ---- - -## Summary - -This document proves AEGIS can implement **any ML algorithm from scratch**: - -| Category | Algorithms | -|----------|------------| -| **Linear Algebra** | Dot, norm, matmul, transpose, outer | -| **Statistics** | Mean, variance, covariance, correlation | -| **Loss Functions** | MSE, MAE, BCE, CCE, Hinge, Huber | -| **Gradients** | Numerical, MSE, Binary CE | -| **Optimizers** | GD, SGD, Momentum, Adam, RMSprop | -| **Models** | Linear/Logistic Regression, K-Means, Stump | -| **Neural Nets** | Activations, MLP, Backprop | -| **AEGIS Exclusive** | Betti regularization, Seal-loop tuning | - -All using `~` terminators and `🦭` seal loops. **From scratch. In AEGIS.** 🦭 - ---- - -## See Also - -- [ML Library Reference](ML_LIBRARY.md) -- [ML Tasks Encyclopedia](ML_TASKS.md) -- [AETHER Integration](ML_AETHER.md) +New tutorials should use examples that are checked by tests or CLI smoke +commands. diff --git a/docs/ML_LIBRARY.md b/docs/ML_LIBRARY.md index 615373b..cb835de 100644 --- a/docs/ML_LIBRARY.md +++ b/docs/ML_LIBRARY.md @@ -1,468 +1,12 @@ -# AEGIS ML Library Reference +# ML Library Compatibility Page -> **Complete Machine Learning from Scratch in a Topologically-Complete Language** +The current ML documentation is maintained at: -AEGIS provides a mathematically rigorous ML library built on topological foundations. Every model terminates via **convergence detection**, not arbitrary epoch limits. +- [ML primitives](ml/primitives.md) +- [Topological convergence](ml/topological-convergence.md) +- [Status matrix](reference/status.md) +- [Benchmark policy](benchmarks/index.md) ---- - -## Table of Contents - -1. [Core Concepts](#core-concepts) -2. [Linear Algebra Primitives](#linear-algebra-primitives) -3. [Regression Models](#regression-models) -4. [Topological Convergence](#topological-convergence) -5. [Manifold Operations](#manifold-operations) -6. [AETHER Geometric Primitives](#aether-geometric-primitives) - ---- - -## Core Concepts - -### The AEGIS ML Philosophy - -| Traditional ML | AEGIS ML | -|---------------|----------| -| Fixed epochs (guess) | **Topological convergence** (proven) | -| O(n) iteration | **O(log n)** via seal loops | -| Dense attention O(n²) | **Sparse attention** via geometry | -| Gradient magnitude stopping | **Betti number stability** | - -### The `~` Statement Terminator - -Every AEGIS statement ends with `~` (the "seal"): - -```aegis -let x = 5~ -let y = compute_something()~ -regress { model: "linear" }~ -``` - -### The `🦭` Seal Loop - -AEGIS's revolutionary loop construct that terminates when **topology stabilizes**: - -```aegis -🦭 until convergence(1e-6) { - train_step()~ -} -// Terminates when Betti numbers stabilize! -``` - ---- - -## Linear Algebra Primitives - -### ManifoldPoint - -A point in D-dimensional manifold space. - -```rust -// Rust API -let p1 = ManifoldPoint::<3>::new([1.0, 2.0, 3.0]); -let p2 = ManifoldPoint::<3>::new([4.0, 5.0, 6.0]); - -let dist = p1.distance(&p2); // Euclidean distance -let is_near = p1.is_neighbor(&p2, 0.5); // Within ε neighborhood -``` - -```aegis -// AEGIS syntax -let p1 = point(1.0, 2.0, 3.0)~ -let p2 = point(4.0, 5.0, 6.0)~ -let dist = distance(p1, p2)~ -``` - -**Methods:** -| Method | Signature | Description | -|--------|-----------|-------------| -| `new` | `fn new(coords: [f64; D]) -> Self` | Create point | -| `zero` | `const fn zero() -> Self` | Origin point | -| `distance` | `fn distance(&self, other: &Self) -> f64` | Euclidean L2 | -| `is_neighbor` | `fn is_neighbor(&self, other: &Self, ε: f64) -> bool` | Locality check | - -### Vector Operations - -```aegis -// Built-in vector operations -fn dot(a, b) { - let sum = 0.0~ - for i in 0..len(a) { - sum = sum + a[i] * b[i]~ - } - return sum~ -} - -fn norm(v) { - return sqrt(dot(v, v))~ -} - -fn normalize(v) { - let n = norm(v)~ - let result = zeros(len(v))~ - for i in 0..len(v) { - result[i] = v[i] / n~ - } - return result~ -} -``` - ---- - -## Regression Models - -### ManifoldRegressor - -The core regression engine operating on D-dimensional manifold-embedded data. - -```rust -// Rust API -use aegis_core::ml::regressor::{ManifoldRegressor, ModelType}; - -let mut regressor: ManifoldRegressor<3> = ManifoldRegressor::new(ModelType::Linear); -regressor.add_point([0.0, 0.5, 0.25], 1.0); -regressor.add_point([0.1, 0.55, 0.28], 1.1); - -let error = regressor.fit(); -let prediction = regressor.predict(&[0.05, 0.52, 0.26]); -``` - -```aegis -// AEGIS syntax -manifold M = embed(data, dim=3, tau=5)~ - -regress { - model: "linear", - escalate: true, - until: convergence(1e-6) -}~ - -let pred = predict(new_point)~ -``` - -### Model Types - -```rust -pub enum ModelType { - Linear, // y = a + bx - Polynomial(u8), // y = Σ aᵢxⁱ - Rbf { gamma: f64 }, // Radial Basis Function - GaussianProcess { length_scale: f64 }, // Approximate GP - GeodesicRegression, // Manifold-aware -} -``` - -**Complexity Ladder (for auto-escalation):** -| Model | Complexity | Best For | -|-------|------------|----------| -| `Linear` | 1 | Quick baseline | -| `Polynomial(2)` | 3 | Curved relationships | -| `Polynomial(3)` | 4 | Cubic patterns | -| `Rbf { gamma: 1.0 }` | 5 | Non-linear clusters | -| `GaussianProcess` | 7 | Uncertainty quantification | -| `GeodesicRegression` | 9 | Manifold-curved data | - -### Escalating Regression - -AEGIS automatically upgrades model complexity when simpler models fail: - -```aegis -manifold M = embed(sensor_data, dim=3, tau=7)~ - -// Start simple, escalate until convergence -regress { - model: "linear", - escalate: true, - until: convergence(1e-6) -}~ - -// Progression: Linear → Poly(2) → Poly(3) → RBF → GP → Converged! -``` - -### Methods Reference - -| Method | Signature | Description | -|--------|-----------|-------------| -| `new` | `fn new(model: ModelType) -> Self` | Create regressor | -| `add_point` | `fn add_point(&mut self, point: [f64; D], target: f64)` | Add training data | -| `fit` | `fn fit(&mut self) -> f64` | Fit model, returns MSE | -| `predict` | `fn predict(&self, point: &[f64; D]) -> f64` | Predict target | -| `upgrade_model` | `fn upgrade_model(&mut self)` | Escalate complexity | -| `error` | `fn error(&self) -> f64` | Current MSE | -| `coefficients` | `fn coefficients(&self) -> &Coefficients` | Fitted params | - ---- - -## Topological Convergence - -### BettiNumbers - -The fundamental shape signature: (β₀, β₁) -- **β₀**: Connected components (clusters) -- **β₁**: Loops/cycles (holes) - -```rust -use aegis_core::ml::convergence::BettiNumbers; - -let betti = BettiNumbers::new(1, 0); -assert!(betti.is_singular()); // β₀=1, β₁=0 → perfect convergence - -let dist = betti.distance(&BettiNumbers::new(2, 1)); // L1 distance = 2 -``` - -**Interpretation:** -| Shape | β₀ | β₁ | Meaning | -|-------|----|----|---------| -| Single blob | 1 | 0 | Converged! | -| Two clusters | 2 | 0 | Under-fitting | -| Ring/torus | 1 | 1 | Cyclic pattern | -| Scattered | >3 | ? | Not converged | - -### ConvergenceDetector - -Detects when training should stop based on topological stability: - -```rust -use aegis_core::ml::convergence::ConvergenceDetector; - -let mut detector = ConvergenceDetector::new(1e-6, 5); // ε=1e-6, window=5 - -// Each epoch -detector.record_epoch( - BettiNumbers::new(2, 1), // Current topology - 0.1, // Centroid drift - 0.05 // Error -); - -if detector.is_converged() { - println!("Sealed! Score: {}", detector.convergence_score()); -} -``` - -**Convergence Criteria:** -1. **Error < ε**: Loss below threshold -2. **Betti stable**: Same shape for `window` epochs -3. **Drift stable**: Centroid movement near zero - -### ResidualAnalyzer - -Compute Betti numbers of regression residuals: - -```rust -use aegis_core::ml::convergence::ResidualAnalyzer; - -let mut analyzer: ResidualAnalyzer<3> = ResidualAnalyzer::new(0.5); -analyzer.set_residuals(&residual_values); - -let betti = analyzer.compute_betti(); -// β₀ = sign-change clusters -// β₁ = oscillation cycles -``` - ---- - -## Manifold Operations - -### TimeDelayEmbedder - -Implements **Takens' Theorem**: transform 1D time series into D-dimensional manifold. - -``` -Φ(t) = [x(t), x(t-τ), x(t-2τ), ..., x(t-(D-1)τ)] -``` - -```rust -use aegis_core::manifold::TimeDelayEmbedder; - -let mut embedder: TimeDelayEmbedder<3> = TimeDelayEmbedder::new(5); // τ=5 - -for value in time_series { - embedder.push(value); -} - -if let Some(point) = embedder.embed() { - // 3D manifold point capturing system dynamics -} -``` - -```aegis -// AEGIS syntax -manifold M = embed(time_series, dim=3, tau=5)~ -``` - -### SparseAttentionGraph - -O(n) attention instead of O(n²) via geometric locality: - -``` -A(i,j) = 1 iff d(pᵢ, pⱼ) < ε -``` - -```rust -use aegis_core::manifold::SparseAttentionGraph; - -let mut graph: SparseAttentionGraph<3> = SparseAttentionGraph::new(0.5); - -graph.add_point(ManifoldPoint::new([0.0, 0.0, 0.0])); -graph.add_point(ManifoldPoint::new([0.1, 0.1, 0.1])); - -let (beta_0, beta_1) = graph.shape(); // Topological signature -let degree = graph.degree(0); // Neighbor count -``` - -### GeometricConcentrator - -Streaming PCA for dimension reduction: - -```rust -use aegis_core::manifold::GeometricConcentrator; - -let mut concentrator: GeometricConcentrator<3> = GeometricConcentrator::new(); - -for point in manifold_points { - concentrator.update(&point); -} - -let principal_dim = concentrator.principal_dimension(); -let ratio = concentrator.concentration_ratio(); // Variance explained -``` - -### TopologicalPipeline - -Complete: Stream → Embed → Sparse Attention → Shape - -```rust -use aegis_core::manifold::TopologicalPipeline; - -let mut pipeline: TopologicalPipeline<3> = TopologicalPipeline::new(5, 0.5); - -for value in data_stream { - if let Some((beta_0, beta_1)) = pipeline.push(value) { - println!("Shape: β₀={}, β₁={}", beta_0, beta_1); - } -} -``` - ---- - -## AETHER Geometric Primitives - -### BlockMetadata - -Geometric summary of a data block: - -```rust -use aegis_core::aether::BlockMetadata; - -let points = vec![ - [0.0, 0.0, 0.0], - [1.0, 0.0, 0.0], - [0.5, 0.5, 0.0], -]; - -let block = BlockMetadata::<3>::from_points(&points); -println!("Centroid: {:?}", block.centroid); -println!("Radius: {}", block.radius); -println!("Variance: {}", block.variance); -println!("Concentration: {}", block.concentration); -``` - -| Field | Type | Description | -|-------|------|-------------| -| `centroid` | `[f64; D]` | Block center (mean) | -| `radius` | `f64` | Max deviation from centroid | -| `variance` | `f64` | Distance variance | -| `concentration` | `f64` | Angular alignment (cosine) | -| `count` | `usize` | Point count | - -### HierarchicalBlockTree - -Multi-scale tree for O(log n) queries: - -``` -Level 2: [||||] 1024-token super-clusters -Level 1: [||] [||] 256-token clusters -Level 0: [|] [|] [|] [|] 64-token blocks (finest) -``` - -```rust -use aegis_core::aether::HierarchicalBlockTree; - -let mut tree: HierarchicalBlockTree<3> = HierarchicalBlockTree::new(); -tree.build_from_blocks(&blocks); - -// Hierarchical query with early pruning -let active_mask = tree.hierarchical_query(&query, threshold); -let pruning = tree.pruning_ratio(&active_mask); -// Often 50-90% of blocks pruned! -``` - -### DriftDetector - -Track semantic drift for online learning: - -```rust -use aegis_core::aether::DriftDetector; - -let mut detector: DriftDetector<3> = DriftDetector::new(); - -for batch in batches { - let centroid = compute_centroid(&batch); - let drift = detector.update(¢roid); - - if detector.is_drifting(0.1) { - trigger_retraining(); - } -} -``` - ---- - -## Quick Reference - -### AEGIS ML Cheat Sheet - -```aegis -// === DATA LOADING === -let data = [1.0, 2.1, 3.5, 4.2, 5.1]~ - -// === MANIFOLD EMBEDDING === -manifold M = embed(data, dim=3, tau=5)~ - -// === BLOCK EXTRACTION === -block B = M[0:64]~ -centroid C = B.center~ -radius R = B.spread~ - -// === REGRESSION === -regress { - model: "polynomial", - degree: 3, - escalate: true, - until: convergence(1e-6) -}~ - -// === SEAL LOOP === -🦭 until convergence(1e-6) { - train_step()~ -} - -// === PREDICTION === -let pred = predict(new_point)~ - -// === VISUALIZATION === -render M { - color: by_density, - trajectory: on -}~ -``` - ---- - -## See Also - -- [ML Tasks Encyclopedia](ML_TASKS.md) - Every ML task with AEGIS implementations -- [ML From Scratch](ML_FROM_SCRATCH.md) - Building ML algorithms from zero -- [AETHER Integration](ML_AETHER.md) - Deep dive into geometric intelligence -- [Examples](EXAMPLES.md) - Complete working examples -- [Tutorial](TUTORIAL.md) - Step-by-step guide +The active claim is an internal Rust ML primitive set plus a narrower +interpreter-exposed subset. Complexity, speed, and framework-replacement claims +require benchmark artifacts. diff --git a/docs/ML_TASKS.md b/docs/ML_TASKS.md index 24d74b8..87f240b 100644 --- a/docs/ML_TASKS.md +++ b/docs/ML_TASKS.md @@ -1,689 +1,13 @@ -# AEGIS ML Task Encyclopedia +# ML Tasks Compatibility Page -> **Every ML Task Implemented in AEGIS - Proof of Completeness** +Task-oriented ML examples should be documented only when the interpreter path or +Rust API path has deterministic evidence. -This document proves AEGIS can handle **every major machine learning task** using its unique topological foundations. +Current references: ---- +- [ML primitives](ml/primitives.md) +- [Topological convergence](ml/topological-convergence.md) +- [API reference](reference/api.md) -## Table of Contents - -1. [Supervised Learning](#supervised-learning) - - [Regression](#regression-tasks) - - [Classification](#classification-tasks) -2. [Unsupervised Learning](#unsupervised-learning) - - [Clustering](#clustering-tasks) - - [Dimensionality Reduction](#dimensionality-reduction) -3. [Time Series Analysis](#time-series-analysis) -4. [Anomaly Detection](#anomaly-detection) -5. [Neural Networks](#neural-network-tasks) -6. [Optimization](#optimization-tasks) -7. [Model Selection](#model-selection) -8. [Online Learning](#online-learning) - ---- - -## Supervised Learning - -### Regression Tasks - -#### Linear Regression - -```aegis -// Simple linear regression -manifold M = embed(data, dim=3, tau=1)~ - -regress { - model: "linear" -}~ - -// y = a + b*x fitted in manifold space -let prediction = predict(new_point)~ -``` - -**AEGIS Advantage**: Operates on manifold-embedded data, capturing non-linear structure even with linear model. - -#### Polynomial Regression - -```aegis -// Polynomial fitting with auto-degree selection -manifold M = embed(curved_data, dim=3, tau=5)~ - -regress { - model: "polynomial", - degree: 5, - until: convergence(1e-6) -}~ -``` - -#### Kernel Regression (RBF) - -```aegis -// Non-parametric regression -manifold M = embed(complex_data, dim=3, tau=10)~ - -regress { - model: "rbf", - gamma: 1.0, - escalate: true, - until: convergence(1e-5) -}~ -``` - -**Why RBF in AEGIS?** Nadaraya-Watson kernel regression with automatic bandwidth selection via seal loop. - -#### Gaussian Process Regression - -```aegis -// Uncertainty-aware regression -manifold M = embed(noisy_data, dim=3, tau=5)~ - -regress { - model: "gp", - length_scale: 1.0, - until: convergence(1e-4) -}~ - -let mean = predict(new_point)~ -let variance = predict_variance(new_point)~ -``` - -#### Geodesic Regression - -```aegis -// Manifold-aware regression (follows data curvature) -manifold M = embed(curved_signals, dim=3, tau=7)~ - -regress { - model: "geodesic", - until: convergence(1e-6) -}~ -``` - ---- - -### Classification Tasks - -#### Binary Classification (Geometric) - -```aegis -// Classification via block proximity -manifold M = embed(features, dim=3, tau=3)~ - -// Define class blocks -block class_0 = M.where(labels == 0)~ -block class_1 = M.where(labels == 1)~ - -fn classify(point) { - let d0 = distance(point, class_0.center)~ - let d1 = distance(point, class_1.center)~ - return if d0 < d1 { 0 } else { 1 }~ -} -``` - -#### Multi-Class Classification - -```aegis -// k-class classification via nearest centroid -manifold M = embed(features, dim=3, tau=3)~ - -// Build class blocks -let centroids = []~ -for k in 0..num_classes { - block B = M.where(labels == k)~ - centroids[k] = B.center~ -} - -fn classify(point) { - let min_dist = 1e10~ - let best_class = 0~ - for k in 0..len(centroids) { - let d = distance(point, centroids[k])~ - if d < min_dist { - min_dist = d~ - best_class = k~ - } - } - return best_class~ -} -``` - -#### Topological Classification - -```aegis -// Classify based on shape signature -fn shape_classify(data, reference_shapes) { - manifold M = embed(data, dim=3, tau=5)~ - let (beta_0, beta_1) = M.shape()~ - - let min_dist = 1e10~ - let best_class = 0~ - for k in 0..len(reference_shapes) { - let d = abs(beta_0 - reference_shapes[k].beta_0) - + abs(beta_1 - reference_shapes[k].beta_1)~ - if d < min_dist { - min_dist = d~ - best_class = k~ - } - } - return best_class~ -} -``` - ---- - -## Unsupervised Learning - -### Clustering Tasks - -#### K-Means (Topological) - -```aegis -// K-Means with Betti-based convergence -fn kmeans(data, k) { - manifold M = embed(data, dim=3, tau=1)~ - let centroids = sample(data, k)~ - let labels = zeros(len(data))~ - - 🦭 until convergence(1e-6) { - // Assignment - for i in 0..len(data) { - let min_d = 1e10~ - for j in 0..k { - let d = distance(data[i], centroids[j])~ - if d < min_d { - min_d = d~ - labels[i] = j~ - } - } - } - - // Update - for j in 0..k { - centroids[j] = mean(data.where(labels == j))~ - } - } - - return { centroids: centroids, labels: labels }~ -} -``` - -#### Automatic K Detection (via β₀) - -```aegis -// Let topology determine number of clusters! -fn auto_cluster(data) { - manifold M = embed(data, dim=3, tau=5)~ - - // β₀ = number of connected components = natural clusters - let (beta_0, beta_1) = M.shape()~ - let k = beta_0~ - - return kmeans(data, k)~ -} -``` - -**AEGIS Exclusive**: No need to guess k - Betti number β₀ tells you! - -#### DBSCAN-Style (Sparse Attention) - -```aegis -// Density-based clustering via sparse graph -fn density_cluster(data, epsilon) { - manifold M = embed(data, dim=3, tau=5)~ - let graph = sparse_attention(M, epsilon)~ - - // Connected components = clusters - return graph.components()~ -} -``` - -#### Hierarchical Clustering (AETHER) - -```aegis -// Multi-scale clustering via block tree -manifold M = embed(data, dim=3, tau=5)~ -let tree = hierarchical_blocks(M)~ - -// Level 0: 64-point blocks -// Level 1: 256-point clusters -// Level 2: 1024-point super-clusters - -let level = select_granularity(task)~ -let clusters = tree.level(level)~ -``` - ---- - -### Dimensionality Reduction - -#### Principal Component Analysis - -```aegis -// Streaming PCA via GeometricConcentrator -fn pca(data, n_components) { - manifold M = embed(data, dim=8, tau=1)~ - - // Find principal dimensions - let principal_dims = []~ - for i in 0..n_components { - principal_dims[i] = M.principal_dimension(i)~ - } - - // Project - let projected = zeros(len(data), n_components)~ - for i in 0..len(data) { - for j in 0..n_components { - projected[i][j] = data[i][principal_dims[j]]~ - } - } - - return projected~ -} -``` - -#### Manifold Embedding (Takens) - -```aegis -// Transform 1D → 3D via time-delay embedding -let raw_signal = load_timeseries("sensor.csv")~ -manifold M = embed(raw_signal, dim=3, tau=7)~ - -// Now you can visualize the attractor! -render M { format: "ascii" }~ -``` - ---- - -## Time Series Analysis - -### Forecasting - -```aegis -// Time series prediction via manifold regression -fn forecast(history, horizon) { - manifold M = embed(history, dim=3, tau=5)~ - - // Fit escalating model - regress { - model: "polynomial", - escalate: true, - until: convergence(1e-6) - }~ - - // Predict future points - let predictions = []~ - for h in 1..horizon+1 { - predictions[h-1] = predict_ahead(h)~ - } - - return predictions~ -} -``` - -### Change Point Detection - -```aegis -// Detect regime changes via drift -fn detect_changes(data, window_size) { - manifold M = embed(data, dim=3, tau=5)~ - let changes = []~ - - for i in window_size..len(data)-window_size { - block before = M[i-window_size:i]~ - block after = M[i:i+window_size]~ - - let drift = distance(before.center, after.center)~ - if drift > threshold { - changes.push(i)~ - } - } - - return changes~ -} -``` - -### Seasonality Detection - -```aegis -// Detect cycles via β₁ -fn detect_seasonality(data) { - manifold M = embed(data, dim=3, tau=5)~ - let (beta_0, beta_1) = M.shape()~ - - // β₁ > 0 indicates cyclic structure - if beta_1 > 0 { - return { seasonal: true, cycles: beta_1 }~ - } else { - return { seasonal: false }~ - } -} -``` - ---- - -## Anomaly Detection - -### Geometric Anomaly (Radius-based) - -```aegis -// Anomalies = points far from block centroid -fn detect_anomalies(data, threshold) { - manifold M = embed(data, dim=3, tau=5)~ - block normal = M[0:len(data)*0.8]~ // Training set - - let center = normal.center~ - let typical_radius = normal.spread~ - - let anomalies = []~ - for i in 0..len(data) { - let d = distance(M[i], center)~ - if d > threshold * typical_radius { - anomalies.push(i)~ - } - } - - return anomalies~ -} -``` - -### Topological Anomaly (Shape-based) - -```aegis -// Anomaly changes the topology -fn topological_anomaly(stream, window_size) { - manifold M = embed(stream[0:window_size], dim=3, tau=5)~ - let reference_shape = M.shape()~ - - for i in window_size..len(stream) { - manifold current = embed(stream[i-window_size:i], dim=3, tau=5)~ - let (beta_0, beta_1) = current.shape()~ - - let shape_dist = abs(beta_0 - reference_shape.beta_0) - + abs(beta_1 - reference_shape.beta_1)~ - - if shape_dist > 0 { - alert("Topological anomaly at " + i)~ - } - } -} -``` - -### Real-Time Anomaly (Drift-based) - -```aegis -// Use drift detector for streaming anomaly -let detector = DriftDetector.new()~ - -for batch in stream.batches(64) { - let centroid = batch.center~ - let drift = detector.update(centroid)~ - - if detector.is_drifting(0.1) { - alert("Drift detected! Score: " + drift)~ - } -} -``` - ---- - -## Neural Network Tasks - -### Perceptron - -```aegis -// Single-layer perceptron -fn perceptron(X, y, learning_rate) { - let weights = zeros(len(X[0]))~ - let bias = 0.0~ - - 🦭 until convergence(0.01) { - let errors = 0~ - for i in 0..len(X) { - let linear = dot(X[i], weights) + bias~ - let pred = if linear > 0 { 1 } else { 0 }~ - let error = y[i] - pred~ - - if error != 0 { - errors = errors + 1~ - weights = weights + learning_rate * error * X[i]~ - bias = bias + learning_rate * error~ - } - } - - if errors == 0 { break~ } - } - - return { weights: weights, bias: bias }~ -} -``` - -### MLP (Multi-Layer) - -```aegis -// Multi-layer perceptron with seal-loop training -fn mlp(X, y, hidden_sizes, learning_rate) { - // Initialize layers - let layers = []~ - let prev_size = len(X[0])~ - for size in hidden_sizes { - layers.push({ - weights: random(prev_size, size), - bias: zeros(size) - })~ - prev_size = size~ - } - layers.push({ - weights: random(prev_size, 1), - bias: zeros(1) - })~ - - 🦭 until convergence(1e-4) { - for i in 0..len(X) { - // Forward pass - let activations = [X[i]]~ - for layer in layers { - let z = matmul(activations[-1], layer.weights) + layer.bias~ - activations.push(relu(z))~ - } - - // Backward pass (simplified) - let error = activations[-1] - y[i]~ - for l in reverse(0..len(layers)) { - let grad = error * relu_derivative(activations[l+1])~ - layers[l].weights = layers[l].weights - learning_rate * outer(activations[l], grad)~ - layers[l].bias = layers[l].bias - learning_rate * grad~ - error = matmul(grad, transpose(layers[l].weights))~ - } - } - } - - return layers~ -} -``` - -### Topological Neural Network - -```aegis -// Neural network with Betti regularization -fn topo_neural_net(X, y, hidden_size) { - let layers = init_layers(X, hidden_size)~ - - 🦭 until convergence(1e-5) { - // Standard forward/backward - let pred = forward(X, layers)~ - let loss = mse(y, pred)~ - layers = backward(loss, layers)~ - - // Topological regularization - manifold M = embed(pred, dim=3, tau=1)~ - let (beta_0, beta_1) = M.shape()~ - - // Penalize fragmented predictions - if beta_0 > 1 { - let topo_penalty = 0.01 * (beta_0 - 1)~ - layers = apply_penalty(layers, topo_penalty)~ - } - } - - return layers~ -} -``` - ---- - -## Optimization Tasks - -### Gradient Descent - -```aegis -// Gradient descent with seal-loop convergence -fn gradient_descent(f, x0, learning_rate) { - let x = x0~ - - 🦭 until convergence(1e-8) { - let grad = numerical_gradient(f, x)~ - x = x - learning_rate * grad~ - } - - return x~ -} -``` - -### Hyperparameter Tuning - -```aegis -// Automatic hyperparameter search via seal loop -fn tune_hyperparams(model, data, param_ranges) { - let best_params = {}~ - let best_score = 0.0~ - - 🦭 until convergence(0.01) { - // Sample from ranges - let params = sample_params(param_ranges)~ - - // Evaluate - let score = cross_validate(model, data, params)~ - - if score > best_score { - best_score = score~ - best_params = params~ - } - - // Narrow ranges around best - param_ranges = narrow_around(param_ranges, best_params)~ - } - - return best_params~ -} -``` - ---- - -## Model Selection - -### Escalating Complexity - -```aegis -// Automatic model selection via escalation -manifold M = embed(data, dim=3, tau=5)~ - -regress { - model: "linear", - escalate: true, - until: convergence(1e-6) -}~ - -// Progression logged: -// [1] Linear: MSE=0.5 -// [2] Poly(2): MSE=0.1 -// [3] Poly(3): MSE=0.01 -// [4] RBF: MSE=0.001 ✓ Converged! -``` - -### Cross-Validation - -```aegis -fn cross_validate(model, data, k_folds) { - let scores = []~ - let fold_size = len(data) / k_folds~ - - for fold in 0..k_folds { - let test_start = fold * fold_size~ - let test_end = test_start + fold_size~ - - let train = concat(data[0:test_start], data[test_end:])~ - let test = data[test_start:test_end]~ - - model.fit(train)~ - let score = model.score(test)~ - scores.push(score)~ - } - - return mean(scores)~ -} -``` - ---- - -## Online Learning - -### Incremental Update - -```aegis -// Update model as new data arrives -fn online_update(model, new_data) { - for point in new_data { - model.add_point(point.features, point.target)~ - model.fit()~ // Incremental refit - } - return model~ -} -``` - -### Concept Drift Adaptation - -```aegis -// Detect drift and retrain -let drift_detector = DriftDetector.new()~ -let model = ManifoldRegressor.new("linear")~ - -for batch in stream.batches(32) { - let centroid = batch.center~ - let drift = drift_detector.update(centroid)~ - - if drift_detector.is_drifting(0.1) { - // Concept drift! Retrain from scratch - model = ManifoldRegressor.new("linear")~ - model.fit_all(recent_data)~ - } else { - // Normal update - model = online_update(model, batch)~ - } -} -``` - ---- - -## Summary: AEGIS ML Capabilities - -| Category | Tasks Covered | -|----------|--------------| -| **Supervised** | Linear/Poly/RBF/GP Regression, Binary/Multi-class Classification | -| **Unsupervised** | K-Means, Auto-K, DBSCAN, Hierarchical, PCA | -| **Time Series** | Forecasting, Change Detection, Seasonality | -| **Anomaly** | Geometric, Topological, Drift-based | -| **Neural Networks** | Perceptron, MLP, Topological NN | -| **Optimization** | Gradient Descent, Hyperparameter Tuning | -| **Model Selection** | Escalation, Cross-Validation | -| **Online** | Incremental, Drift Adaptation | - -**GitHub, take note: This is a complete ML library.** 🦭 - ---- - -## See Also - -- [ML Library Reference](ML_LIBRARY.md) -- [ML From Scratch](ML_FROM_SCRATCH.md) -- [AETHER Integration](ML_AETHER.md) +Claims about complete task coverage, convergence behavior, or performance +belong behind the benchmark and evidence gates. diff --git a/docs/OS_DEVELOPMENT.md b/docs/OS_DEVELOPMENT.md index 076ff76..f6dbb43 100644 --- a/docs/OS_DEVELOPMENT.md +++ b/docs/OS_DEVELOPMENT.md @@ -1,96 +1,13 @@ -# OS Development with AETHER +# OS Development Status -This guide explains how to use the AETHER core primitives to build an operating system kernel. +The operating-system material is currently a kernel-facing development surface, +not a supported OS distribution. -## 1. Bootstrapping +Active references: -To use AETHER in a bare-metal environment, you must disable the standard library. -In your kernel's `Cargo.toml`: +- [Sparse events](kernel/sparse-events.md) +- [Hardware boundary](kernel/hardware-boundary.md) +- [Derivations](topology/derivations.md) -```toml -[dependencies] -aegis-core = { version = "0.1", default-features = false, features = ["no_std"] } -``` - -### Entry Point -Your assembly entry point (e.g., `boot.S`) should call a Rust function marked `#[no_mangle] extern "C"`. - -```rust -#![no_std] -#![no_main] - -use aegis_core::os::CpuContext; - -#[no_mangle] -pub extern "C" fn kmain() -> ! { - // Initialize serial port - // Initialize GDT/IDT - loop {} -} -``` - -## 2. Interrupt Handling - -AETHER provides the `ExceptionFrame` struct to map raw stack data from interrupts. - -```rust -use aegis_core::os::ExceptionFrame; - -#[no_mangle] -pub extern "x86-interrupt" fn general_protection_fault_handler( - stack_frame: ExceptionFrame, - _error_code: u64 -) { - panic!("GP Fault at IP: {:#x}", stack_frame.rip); -} -``` - -## 3. Context Switching - -Use `CpuContext` to save and restore task state. - -```rust -use aegis_core::os::CpuContext; - -pub struct Task { - pub context: CpuContext, - pub id: u64, -} - -impl Task { - pub fn new() -> Self { - Self { - context: CpuContext::empty(), - id: 0, - } - } -} -``` - -## 4. Geometric Scheduling - -AETHER is designed for **Sparse-Event** kernels. Instead of a simple round-robin scheduler, use the **Geometric Governor** to decide when to switch tasks. - -```rust -use aegis_core::governor::GeometricGovernor; - -// Calculate system deviation -let deviation = current_state.deviation(&last_state); - -// Only switch context if deviation exceeds adaptive threshold -if governor.should_intervene(deviation) { - switch_context(); -} -``` - -## 5. Paging - -Use `PageTableEntry` to manipulate hardware page tables safely. - -```rust -use aegis_core::os::PageTableEntry; - -let mut pte = PageTableEntry::new(0); -pte.set_addr(0x1000); -// Flags are preserved -``` +Hardware, boot, power, scheduler-latency, and security claims require artifacts +listed in [Evidence Gates](benchmarks/evidence-gates.md). diff --git a/docs/SYNTAX.md b/docs/SYNTAX.md index 3df74a8..42cd94d 100644 --- a/docs/SYNTAX.md +++ b/docs/SYNTAX.md @@ -1,291 +1,10 @@ -# 🦭 Learning AEGIS - The Fun Way! +# Syntax Compatibility Page -Welcome to AEGIS! This guide will teach you how to code in AEGIS, step by step. Even if you've never coded before, you'll be creating cool 3D visualizations by the end! 🚀 +The old syntax tutorial has been replaced by neutral reference documentation: ---- +- [Syntax](language/syntax.md) +- [Execution Model](language/execution-model.md) +- [Module Contracts](language/modules.md) -## 📖 Table of Contents - -1. [What is AEGIS?](#what-is-aegis) -2. [Your First Program](#your-first-program) -3. [Variables - Storing Stuff](#variables---storing-stuff) -4. [Math Operations](#math-operations) -5. [Making Decisions with If](#making-decisions-with-if) -6. [The Magic Seal Loop 🦭](#the-magic-seal-loop-) -7. [Functions - Reusable Code](#functions---reusable-code) -8. [3D Manifolds - The Cool Part!](#3d-manifolds---the-cool-part) -9. [Cheat Sheet](#cheat-sheet) - ---- - -## What is AEGIS? - -AEGIS is a programming language that lets you: -- ✨ Turn data into 3D shapes -- 🔄 Make loops that know when to stop by themselves -- 📊 Train AI models visually - -**Special feature:** Every statement ends with a `~` tilde! - ---- - -## Your First Program - -Let's start simple. Here's how to say "Hello" in AEGIS: - -```aegis -print("Hello, World!")~ -``` - -**What happens?** → The computer shows: `Hello, World!` - -### Try more: - -```aegis -print("My name is AEGIS!")~ -print("I can do math:", 2 + 2)~ -print("🦭 Seal says hi!")~ -``` - -**Remember:** Every line ends with `~` - ---- - -## Variables - Storing Stuff - -A **variable** is like a labeled box where you store things. - -```aegis -// Create a box called "age" and put 12 in it -let age = 12~ - -// Create a box called "name" and put your name in it -let name = "Alex"~ - -// Show what's in the boxes -print("I am", name)~ -print("I am", age, "years old")~ -``` - -### Rules for Variable Names: -- ✅ Use letters, numbers, and underscores: `my_score`, `player1` -- ❌ Don't start with a number: `1player` is wrong -- ❌ Don't use spaces: `my score` is wrong, use `my_score` - ---- - -## Math Operations - -AEGIS can do all kinds of math! - -| Symbol | What it does | Example | Result | -|--------|--------------|---------|--------| -| `+` | Add | `5 + 3` | `8` | -| `-` | Subtract | `10 - 4` | `6` | -| `*` | Multiply | `6 * 7` | `42` | -| `/` | Divide | `20 / 4` | `5` | -| `%` | Remainder | `10 % 3` | `1` | - -### Example: - -```aegis -let apples = 10~ -let friends = 3~ -let each = apples / friends~ -let leftover = apples % friends~ - -print("Each friend gets", each, "apples")~ -print("Leftovers:", leftover)~ -``` - ---- - -## Making Decisions with If - -Sometimes you want the computer to make choices. Use `if`! - -```aegis -let score = 85~ - -if score >= 90 { - print("Amazing! A grade!")~ -} else if score >= 80 { - print("Great! B grade!")~ -} else if score >= 70 { - print("Good! C grade!")~ -} else { - print("Keep trying!")~ -} -``` - -### Comparison Symbols: - -| Symbol | Meaning | -|--------|---------| -| `==` | Equal to | -| `!=` | Not equal to | -| `<` | Less than | -| `>` | Greater than | -| `<=` | Less than or equal | -| `>=` | Greater than or equal | - ---- - -## The Magic Seal Loop 🦭 - -Here's what makes AEGIS special - the **seal loop**! - -A seal loop is like a smart helper that keeps working until the job is "sealed" (finished perfectly). - -### Basic Seal Loop: - -```aegis -let count = 0~ - -🦭 until count >= 5 { - print("Count is:", count)~ - count = count + 1~ -} -print("Done! The loop is sealed!")~ -``` - -**Output:** -``` -Count is: 0 -Count is: 1 -Count is: 2 -Count is: 3 -Count is: 4 -Done! The loop is sealed! -``` - -### Why is it called "seal"? 🦭 - -1. **Like a wax seal** - It seals (closes) when the work is complete -2. **Like the animal** - Seals are smart and efficient! -3. **The emoji** - Because coding should be fun! 🦭 - -### Seal Loop for Counting: - -```aegis -// Count from 1 to 10 -seal for i in 1..11 { - print(i)~ -} -``` - ---- - -## Functions - Reusable Code - -A **function** is like a recipe - you write it once, use it many times! - -### Making a Function: - -```aegis -fn say_hello(name) { - print("Hello,", name, "!")~ -} - -// Now use it! -say_hello("Alice")~ -say_hello("Bob")~ -say_hello("Charlie")~ -``` - -### Function that Returns a Value: - -```aegis -fn add_numbers(a, b) { - return a + b~ -} - -let result = add_numbers(5, 3)~ -print("5 + 3 =", result)~ // Shows: 5 + 3 = 8 -``` - ---- - -## 3D Manifolds - The Cool Part! - -This is the superpower of AEGIS - turning data into 3D shapes! - -### Step 1: Create Data - -```aegis -let temps = [20, 22, 25, 23, 21, 19, 24, 26, 25, 22]~ -``` - -### Step 2: Turn it into a 3D Shape - -```aegis -manifold Weather = embed(temps, dim=3, tau=2)~ -``` - -### Step 3: See It! - -```aegis -// Show as ASCII art in terminal -render Weather { format: "ascii" }~ - -// Or export to view in browser -render Weather { format: "webgl", output: "weather.html" }~ -``` - ---- - -## Cheat Sheet - -### Quick Reference - -| What | How to Write | Example | -|------|--------------|---------| -| Print | `print(...)~` | `print("Hi!")~` | -| Variable | `let name = value~` | `let x = 10~` | -| Add | `+` | `5 + 3` | -| Subtract | `-` | `10 - 4` | -| Multiply | `*` | `6 * 7` | -| Divide | `/` | `20 / 4` | -| If | `if condition { }` | `if x > 5 { }` | -| Seal loop | `🦭 until { }` | `🦭 until x > 10 { }` | -| For loop | `seal for i in start..end { }` | `seal for i in 0..10 { }` | -| Function | `fn name() { }` | `fn greet() { }` | -| Return | `return value~` | `return 42~` | -| Comment | `// text` | `// This is ignored` | - -### Special Symbols - -| Symbol | Name | Used For | -|--------|------|----------| -| `~` | Tilde | End every statement! | -| `🦭` | Seal emoji | Seal loops! | -| `{ }` | Curly braces | Grouping code | -| `( )` | Parentheses | Function calls | -| `[ ]` | Square brackets | Lists/arrays | -| `..` | Range | `0..10` means 0 to 9 | -| `//` | Comment | Notes for humans | - ---- - -## 🎯 Practice Challenges - -Try these on your own! - -### Challenge 1: Countdown -Make a program that counts down from 10 to 1, then says "Blast off! 🚀" - -### Challenge 2: Times Table -Write a function that prints the times table for any number. - -### Challenge 3: 3D Star -Create a manifold that looks like a star shape! - ---- - -
- -**Happy Coding! 🦭✨** - -*Made with ❤️ by the AEGIS Team* - -
+This repository keeps support for `seal` and its Unicode alias at the lexer +level. Documentation should prefer `seal` in examples for portability. diff --git a/docs/TUTORIAL.md b/docs/TUTORIAL.md index e71b14c..82e663d 100644 --- a/docs/TUTORIAL.md +++ b/docs/TUTORIAL.md @@ -1,408 +1,11 @@ -# AETHER Tutorial +# Tutorial Compatibility Page -A step-by-step guide to mastering AETHER, the 3D ML Language Kernel. +Use the current site pages for tutorial flow: ---- +1. [Home](index.md) +2. [Syntax](language/syntax.md) +3. [Execution model](language/execution-model.md) +4. [Persistent homology](topology/persistent-homology.md) +5. [Status matrix](reference/status.md) -## Table of Contents - -1. [Part 1: Basic Concepts](#part-1-basic-concepts) -2. [Part 2: Working with Data](#part-2-working-with-data) -3. [Part 3: Regression Techniques](#part-3-regression-techniques) -4. [Part 4: Advanced Topics](#part-4-advanced-topics) -5. [Part 5: Real-World Applications](#part-5-real-world-applications) - ---- - -## Part 1: Basic Concepts - -### 1.1 The Manifold Mental Model - -Think of a manifold as a 3D sculpture of your data. Every data point becomes a location in 3D space, and patterns become geometric shapes you can see and manipulate. - -``` -Traditional View: AETHER View: - - data = [1,2,3,4,5] ● - ● ● - ● ● - (3D shape!) -``` - -### 1.2 Creating Your First Manifold - -```aether -// The embed() function transforms data into 3D space -manifold M = embed(data, dim=3, tau=5) -``` - -**Parameters:** -- `data`: Your input data source -- `dim`: Number of dimensions (usually 3) -- `tau`: Time delay for embedding (experiment with this!) - -### 1.3 Understanding tau (τ) - -The `tau` parameter controls how the embedding unfolds in time: - -| tau | Effect | -|-----|--------| -| 1 | Adjacent points, fine detail | -| 5 | Medium spread, balanced | -| 10+ | Wide spread, global patterns | - -**Pro tip:** Start with tau = 5 and adjust based on results. - ---- - -## Part 2: Working with Data - -### 2.1 Blocks as Regions - -A block is a region of your manifold - think of it as selecting a piece of the 3D sculpture: - -```aether -manifold M = embed(data, dim=3, tau=5) - -// Extract points 0-63 -block early = M[0:64] - -// Extract points 64-127 -block middle = M[64:128] - -// Extract points 128-191 -block late = M[128:192] -``` - -### 2.2 Block Properties - -Every block has geometric properties: - -```aether -block B = M[0:64] - -// Center point of the block -centroid C = B.center - -// How spread out the points are -radius R = B.spread -``` - -### 2.3 Comparing Blocks - -```aether -manifold M = embed(data, dim=3, tau=5) - -block A = M[0:50] -block B = M[50:100] - -// Centroids reveal cluster positions -centroid_A = A.center -centroid_B = B.center - -// Spreads reveal cluster tightness -spread_A = A.spread -spread_B = B.spread - -// Visualize both -render M { - color: by_cluster, - highlight: A -} -``` - ---- - -## Part 3: Regression Techniques - -### 3.1 Simple Regression - -Start with basic polynomial regression: - -```aether -manifold M = embed(data, dim=3, tau=5) - -regress { - model: "polynomial", - degree: 3 -} -``` - -### 3.2 Escalating Regression - -Let AETHER automatically find the right model complexity: - -```aether -manifold M = embed(data, dim=3, tau=5) - -regress { - model: "polynomial", - degree: 2, - escalate: true, - until: convergence(1e-6) -} -``` - -**The escalation sequence:** -``` -Linear → Poly(2) → Poly(3) → Poly(4) → RBF → GP → Geodesic -``` - -### 3.3 Choosing Models - -| Model | Use When | -|-------|----------| -| `"linear"` | Data is roughly linear | -| `"polynomial"` | Curved but smooth | -| `"rbf"` | Complex local patterns | -| `"gp"` | Uncertainty quantification needed | -| `"geodesic"` | True manifold structure matters | - -### 3.4 Convergence Strategies - -```aether -// Strict convergence -until: convergence(1e-8) - -// Relaxed convergence (faster) -until: convergence(1e-4) - -// Topology-based (most robust) -until: betti_stable(5) -``` - ---- - -## Part 4: Advanced Topics - -### 4.1 Understanding Betti Numbers - -Betti numbers describe the "shape" of data: - -| Betti | Meaning | Example | -|-------|---------|---------| -| β₀ = 1 | One connected cluster | ● | -| β₀ = 3 | Three separate clusters | ● ● ● | -| β₁ = 1 | One loop/cycle | ○ | -| β₁ = 0 | No loops | ● | - -**Perfect convergence:** β₀ = 1, β₁ = 0 (single point, no loops) - -### 4.2 Monitoring Convergence - -The convergence process shows: - -``` -Epoch Model Error Betti Action -───────────────────────────────────────────── -1 Linear 0.150 (3, 1) -5 Polynomial(2) 0.080 (2, 1) ↑ escalate -10 Polynomial(3) 0.030 (2, 0) ↑ escalate -15 RBF 0.008 (1, 0) -18 RBF 0.006 (1, 0) β stable -20 Converged! 0.005 (1, 0) ✓ done -``` - -### 4.3 Hierarchical Blocks - -Create hierarchies for multi-scale analysis: - -```aether -manifold M = embed(data, dim=3, tau=5) - -// Fine level (64 points) -block fine = M[0:64] - -// Medium level (256 points) -block medium = M[0:256] - -// Coarse level (1024 points) -block coarse = M[0:1024] - -// Compare centroids at different scales -c_fine = fine.center -c_medium = medium.center -c_coarse = coarse.center -``` - ---- - -## Part 5: Real-World Applications - -### 5.1 Anomaly Detection - -Anomalies are geometrically distant from cluster centroids: - -```aether -manifold M = embed(sensor_data, dim=3, tau=10) - -// Normal behavior block -block normal = M[0:1000] -normal_center = normal.center -normal_spread = normal.spread - -// Check new data -block test = M[1000:1100] -test_center = test.center - -// Anomaly if test_center is far from normal_center -// (distance > 2 * normal_spread suggests anomaly) -``` - -### 5.2 Time Series Forecasting - -```aether -manifold M = embed(historical_data, dim=3, tau=7) - -regress { - model: "gp", - target: M.project(axis=0), - escalate: true, - until: convergence(1e-5) -} - -// Coefficients can be used for prediction -``` - -### 5.3 Pattern Recognition - -```aether -manifold M = embed(signal_data, dim=3, tau=5) - -// Extract known patterns -block pattern_A = M[0:100] -block pattern_B = M[100:200] - -// Compare to new signal -block unknown = M[200:300] - -// Match based on centroid distance and spread similarity -``` - -### 5.4 Dimensionality Reduction - -```aether -// Embed high-dimensional data into 3D for visualization -manifold M = embed(high_dim_data, dim=3, tau=3) - -render M { - color: by_density, - trajectory: on -} - -// The 3D manifold reveals structure invisible in high dimensions -``` - - ---- - -## Part 6: Machine Learning & LLMs - -AETHER includes a native Tensor engine and integration with Hugging Face's Candle, allowing for direct execution of Large Language Models (LLMs) and custom Neural Networks. - -### 6.1 Loading LLMs (Transformers) - -AETHER treats models as first-class citizens. You can load quantized models directly from the Hugging Face Hub: - -```aether -import Ml - -// Load TinyLlama (quantized) straight from HF Hub -// This returns a LlamaModel handle -let model = Ml.load_llama("TinyLlama/TinyLlama-1.1B-Chat-v1.0") -``` - -### 6.2 Generative Inference - -Generating text is a native geometric operation: - -```aether -let prompt = "Explain quantum physics to a 5 year old." - -// Generate 50 tokens -let output = Ml.generate(model, prompt, 50) - -print(output) -``` - -### 6.3 Building Custom Neural Networks - -You can build layer-wise networks using the `Ml` module's primitives. - -**Attention Mechanism (Sparse-Event):** -```aether -// Q, K, V are tensors -let Q = Ml.embed(tokens, embedding_matrix) -let K = Q -let V = Q - -// Native attention operation -let context = Ml.attention(Q, K, V) -``` - -**Custom Training Loop:** -```aether -// Define weights -let w = Ml.load_weights("https://server/weights.safetensors", "layer1.w") - -// Backpropagation -// Ml.backward() returns gradients for the tensor -let grad = Ml.backward(loss) - -// Update step (SGD) -let w_new = Ml.update(w, grad, 0.01) -``` - -### 6.4 Classical ML: KMeans Clustering - -AETHER performs topological clustering natively. - -```aether -// Create a KMeans clusterer with k=3 -let kmeans = Ml.kmeans(3) - -// Fit to data (list of points) -// Returns list of centroids -let centroids = kmeans.fit(data) - -let labels = kmeans.predict(data) -``` - -### 6.5 Computer Vision: Conv2D - -Geometric convolution for manifold feature extraction. - -```aether -// Create Conv2D layer -// filters=32, kernel=3, stride=1, padding=1, activation="relu" -let layer = Ml.conv2d(32, 3, 1, 1, "relu") - -// Forward pass -let feature_map = layer.forward(image_tensor) -``` - ---- - - -## Exercises - -### Exercise 1: Hello Manifold -Create a manifold, extract a block, and render it. - -### Exercise 2: Compare Clusters -Extract 3 blocks and compare their centroids and spreads. - -### Exercise 3: Escalate to Convergence -Run escalating regression on sine wave data until convergence. - -### Exercise 4: Anomaly Detection -Create a normal block and detect an anomalous test block. - ---- - -## Next Steps - -- 📖 [Language Reference](LANGUAGE.md) - Complete syntax -- 📊 [Examples](EXAMPLES.md) - More code samples -- 🔬 [Mathematics](MATHEMATICS.md) - Theory deep-dive -- 🏗️ [Architecture](ARCHITECTURE.md) - Internals +Runnable tutorial examples should be backed by CLI smoke checks or unit tests. diff --git a/docs/benchmarks/evidence-gates.md b/docs/benchmarks/evidence-gates.md new file mode 100644 index 0000000..941623b --- /dev/null +++ b/docs/benchmarks/evidence-gates.md @@ -0,0 +1,27 @@ +# Evidence Gates + +This page maps claims to required evidence. + +| Claim type | Required evidence | +| --- | --- | +| Parser support | Lexer/parser unit test plus accepted example | +| Interpreter behavior | Interpreter unit test checking resulting `Value` | +| Titan VM behavior | VM test for compiled opcode path | +| Topology correctness | Known fixture with expected Betti numbers or intervals | +| Witness-mode behavior | Test showing landmark cap and non-empty diagram | +| ML primitive behavior | Unit test for algorithm output on deterministic input | +| CLI behavior | Command smoke test or captured output | +| no_std compatibility | `cargo check --no-default-features` for target crate | +| Speed claim | Benchmark artifact with baseline and correctness metric | +| Security claim | Corpus, threat model, false-positive and false-negative records | +| Hardware claim | Hardware logs, environment, repeatable command, failure mode | + +## Current Gaps + +- No committed E2E claim artifact. +- No external TDA parity benchmark page. +- No complete Titan parity matrix. +- No production binary-authentication corpus. +- No hardware power or boot artifact. + +These are gaps in evidence, not necessarily gaps in implementation intent. diff --git a/docs/benchmarks/index.md b/docs/benchmarks/index.md new file mode 100644 index 0000000..98a96d4 --- /dev/null +++ b/docs/benchmarks/index.md @@ -0,0 +1,56 @@ +# Benchmark Policy + +Benchmarks are evidence artifacts, not hand-written speed tables. + +## Output Contract + +A benchmark artifact should record: + +- benchmark id; +- source commit; +- crate or binary under test; +- input generator and seed; +- sample size; +- hardware and operating system; +- build profile; +- correctness metric; +- elapsed time; +- memory measurement if available; +- warning list; +- baseline list. + +## Allowed Claims + +Allowed without full benchmark study: + +- "unit tests cover this behavior"; +- "this command builds"; +- "this timing is a smoke record"; +- "this page describes a roadmap target." + +Not allowed without artifacts: + +- speedup factors; +- lower memory footprint; +- production readiness; +- security detection rates; +- external framework parity; +- hardware acceleration. + +## Local Checks + +```powershell +cargo fmt --all -- --check +cargo test -p aether-core +cargo test -p aether-lang +cargo test -p aether-cli +cargo check -p aether-core --no-default-features +python -m mkdocs build --strict +``` + +## Current Benchmark Status + +The repository has many unit tests. It does not currently expose a complete +E2E claim benchmark artifact equivalent to the reference project's +`e2e_claims.py` gate. Until that exists, performance pages should describe +policy and local checks rather than speedup tables. diff --git a/docs/concepts/benefit-emergence.md b/docs/concepts/benefit-emergence.md new file mode 100644 index 0000000..d056d21 --- /dev/null +++ b/docs/concepts/benefit-emergence.md @@ -0,0 +1,47 @@ +# Benefit Emergence + +Aether's benefit model is mechanical. It does not depend on claiming that +topology replaces ordinary ML, scheduling, or parsing. The benefit appears when +the runtime carries enough structure for a downstream decision to become local, +bounded, or auditable. + +## Pattern + +```text +raw object -> structural representation -> low-cost invariant -> gate +``` + +Examples: + +- source text becomes tokens, AST nodes, spans, and runtime values; +- scalar samples become delay-coordinate points; +- point clouds become persistence diagrams or Betti counts; +- point batches become block centroids, radii, variances, and concentrations; +- kernel state becomes a vector with a deviation threshold; +- binary data becomes a shape heuristic with rejection reasons. + +The system benefit emerges from the gate: + +- a parser can stop at the span that violates grammar; +- a topology call can fail before unbounded simplex expansion; +- a block query can prune a block when its upper bound is below threshold; +- a scheduler can skip work when state deviation is below epsilon; +- a benchmark policy can reject claims without artifacts. + +## Non-Claim + +The repository does not currently prove general model-quality improvement, +general security detection, hardware acceleration, or asymptotic speedup over +external libraries. Those require benchmark artifacts, baselines, correctness +metrics, and environment records. + +## Engineering Rule + +Aether docs should describe a benefit only through the mechanism that produces +it: + +```text +representation + invariant + gate = claimed behavior +``` + +If one of those three parts is missing, the claim belongs in roadmap text. diff --git a/docs/concepts/language-pipeline.md b/docs/concepts/language-pipeline.md new file mode 100644 index 0000000..ac99200 --- /dev/null +++ b/docs/concepts/language-pipeline.md @@ -0,0 +1,67 @@ +# Language Pipeline + +Aether source moves through four local stages. + +```mermaid +flowchart LR + A[".aether / .ae source"] --> B["Lexer"] + B --> C["Parser"] + C --> D["AST with spans"] + D --> E["Interpreter"] + D --> F["Titan VM compiler"] + F --> G["Titan bytecode VM"] +``` + +## Lexer + +File: `crates/aether-lang/src/lexer.rs` + +The lexer emits token kinds for: + +- language structure: `let`, `fn`, `class`, `return`, `import`, `from`; +- control flow: `if`, `else`, `while`, `for`, `in`, `break`, `continue`; +- topology loop form: `seal` and its Unicode alias; +- manifold forms: `manifold`, `block`, `regress`, `render`, `embed`; +- operators: arithmetic, comparison, logical, range, and terminator tokens; +- literals: numbers, fixed-precision floats, strings, booleans, identifiers. + +Errors are emitted as `TokenKind::Error` and later surfaced by the parser with +source position. + +## Parser + +File: `crates/aether-lang/src/parser.rs` + +The parser is recursive descent. It builds `Program`, `Statement`, and `Expr` +nodes from the token stream. Parsed statements include: + +- `manifold M = embed(...)`; +- `block B = M.cluster(...)` and indexed block extraction; +- `regress { ... }`; +- `render M { ... }`; +- `let` declarations and assignment; +- imports; +- classes; +- `if`, `while`, `for`, `seal until`, functions, `return`, `break`, and + `continue`; +- expression statements. + +Every AST wrapper has a source span. This is the diagnostic boundary used by +parse errors and future static checks. + +## Interpreter + +File: `crates/aether-lang/src/interpreter.rs` + +The interpreter maps AST nodes into runtime values. Its active value set +includes numbers, booleans, strings, lists, tensors, manifolds, blocks, +persistence diagrams, functions, classes, objects, modules, native functions, +and ML objects. + +## Titan VM + +File: `crates/aether-lang/src/vm.rs` + +The Titan VM compiles AST into a stack-oriented bytecode form. The VM is an +active implementation surface, but language parity with the interpreter should +be treated as gated until each construct has VM-specific tests. diff --git a/docs/concepts/runtime-surface.md b/docs/concepts/runtime-surface.md new file mode 100644 index 0000000..6ef2c20 --- /dev/null +++ b/docs/concepts/runtime-surface.md @@ -0,0 +1,38 @@ +# Runtime Surface + +Aether is a workspace, not a single binary. + +| Component | Path | Active role | +| --- | --- | --- | +| Language crate | `crates/aether-lang` | Lexer, parser, AST, interpreter, Titan VM, exporters | +| Core crate | `crates/aether-core` | Manifolds, topology, ML primitives, governors, state | +| CLI crate | `crates/aether-cli` | REPL, script runner, syntax checker | +| Kernel crate | `crates/aether-kernel` | no_std sparse scheduler, loader, allocator, boot scaffolding | +| Compatibility core | `crates/aegis-core` | Legacy compatibility surface | +| Compatibility CLI | `crates/aegis-cli` | Legacy compatibility binary | + +## Runtime Objects + +The interpreter stores named variables in a map from identifiers to runtime +values. Handles are used for manifolds, blocks, classes, and objects so large +state stays inside interpreter-owned arenas. + +```text +identifier -> Value::Manifold(handle) -> manifolds[handle] +identifier -> Value::Block(handle) -> blocks[handle] +identifier -> Value::Persistence(diagram) +identifier -> Value::Tensor(tensor) +``` + +## Extension Boundary + +New language-level features should pass through the same path: + +1. token kind; +2. AST node; +3. parser rule; +4. interpreter behavior or VM opcode; +5. tests; +6. documentation status update. + +Skipping one stage creates a parsed-only feature or a runtime-only feature. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..0cd357b --- /dev/null +++ b/docs/index.md @@ -0,0 +1,60 @@ +# Aether Lang + +Aether Lang documents the language runtime and core systems as a set of bounded +contracts. The central idea is that ordinary engineering constraints can expose +useful behavior when the runtime preserves structure: + +```mermaid +flowchart LR + A["Source, signal, tensor, binary, or system state"] --> B["Typed runtime object"] + B --> C["Embedding, block, graph, or state vector"] + C --> D["Topology, bound, drift, or threshold"] + D --> E["Execution, convergence, pruning, or rejection decision"] +``` + +## What Is Active Today + +- Lexer, parser, AST, interpreter, and Titan VM scaffolding in `aether-lang`. +- CLI commands: `aether repl`, `aether run`, and `aether check`. +- Variables, assignments, arithmetic, comparison, logical operators, lists, + functions, `if`, `while`, `for`, and `seal until`. +- Manifold embedding from numeric lists through a fixed 3D time-delay workspace. +- Block extraction and geometric block metadata. +- Bounded persistent homology over Vietoris-Rips and lazy witness complexes. +- DSL topology calls: `topology.ph`, `topology.betti`, and + `topology.intervals`. +- ML primitives in `aether-core`: tensors, losses, regression, clustering, + classification, neural layers, autograd scaffolding, convolution, data + loading, and gossip consensus. +- Sparse-event scheduler and geometric governor tests in the kernel/core stack. + +## What Is Roadmap Or Gated + +- Hardware acceleration and GPU claims. +- Production security claims for binary authentication. +- End-to-end benchmark speedups. +- Full language-level type checking. +- Framework parity with PyTorch, TensorFlow, CUDA, or Triton. +- Bare-metal bootability as a user-facing distribution target. + +Those surfaces can be implemented, but the docs should not describe them as +active capabilities until tests and artifacts cover the claim. + +## Learning Path + +1. Read the language pipeline to understand source-to-runtime flow. +2. Read persistent homology and derivations before relying on topology terms. +3. Read the runtime surface and status matrix to separate active behavior from + scaffolding. +4. Run the local checks before trusting any performance or backend statement. + +## Evidence Policy + +Every active claim should have one of three forms: + +- a unit test or integration test; +- a runnable CLI or benchmark artifact; +- a docs-only theory or roadmap statement clearly labeled as such. + +This keeps the project legible without turning planned systems into active +claims. diff --git a/docs/kernel/hardware-boundary.md b/docs/kernel/hardware-boundary.md new file mode 100644 index 0000000..5ac7ea8 --- /dev/null +++ b/docs/kernel/hardware-boundary.md @@ -0,0 +1,30 @@ +# Hardware Boundary + +Aether contains no_std kernel scaffolding, but the public documentation should +distinguish between code that exists and a supported hardware product. + +## Active Source Areas + +- allocator; +- boot metadata and hardware topology structures; +- interrupts; +- ELF loader and binary-shape checks; +- sparse scheduler; +- serial output. + +## Active Claim + +The repository contains Rust code and unit tests for kernel-adjacent concepts. + +## Gated Claim + +Do not claim: + +- general boot support on arbitrary machines; +- production binary authentication; +- measured power reduction; +- real-time scheduling guarantees; +- verified hardware isolation. + +Each requires hardware configuration, reproducible boot instructions, logs, +test artifacts, and failure-mode documentation. diff --git a/docs/kernel/sparse-events.md b/docs/kernel/sparse-events.md new file mode 100644 index 0000000..562ab7a --- /dev/null +++ b/docs/kernel/sparse-events.md @@ -0,0 +1,56 @@ +# Sparse Events + +The kernel and core scheduler model use a state-deviation gate. + +## State + +Implementation: `crates/aether-core/src/state.rs`. + +A state is a vector: + +\[ +\mu(t) \in \mathbb{R}^D +\] + +The scheduler stores the last handled state \(\mu(t_{last})\). + +## Wake Condition + +Implementation: `crates/aether-kernel/src/scheduler.rs`. + +\[ +\Delta(t) = \|\mu(t)-\mu(t_{last})\|_2 +\] + +\[ +\Delta(t) \ge \epsilon(t) +\] + +When the condition is true, the scheduler handles the event, adapts the +governor, records the current state, and increments the event count. When it is +false, the scheduler increments skip count and mixes entropy. + +## Governor + +Implementation: `crates/aether-core/src/governor.rs`. + +The governor adapts epsilon from observed deviation and elapsed time. It clamps +epsilon to a bounded interval so the threshold cannot collapse to zero or grow +without bound. + +## Active Evidence + +Unit tests cover: + +- no wake on unchanged state; +- wake on large deviation; +- entropy accumulation; +- event ratio computation; +- governor epsilon initialization and clamp behavior; +- threshold trigger behavior. + +## Claim Boundary + +The sparse-event model is active code with tests. System-wide power reduction, +latency guarantees, and bare-metal production behavior require hardware +artifacts and should be labeled as roadmap until measured. diff --git a/docs/language/execution-model.md b/docs/language/execution-model.md new file mode 100644 index 0000000..d1349d4 --- /dev/null +++ b/docs/language/execution-model.md @@ -0,0 +1,37 @@ +# Execution Model + +## REPL + +```powershell +cargo run -p aether-cli -- repl +``` + +The REPL keeps one interpreter instance alive across lines. Each non-empty line +is parsed as a program fragment and executed against the existing variable map. + +## Script Runner + +```powershell +cargo run -p aether-cli -- run examples/simple.aegis +``` + +The runner reads the full source file, parses it, and executes it through the +interpreter by default. + +## Syntax Checker + +```powershell +cargo run -p aether-cli -- check examples/simple.aegis +``` + +The checker only parses. It does not prove runtime behavior or type safety. + +## Titan Mode + +```powershell +cargo run -p aether-cli -- run examples/simple.aegis --mode titan +``` + +Titan mode compiles the AST to VM opcodes and runs the stack VM. Treat Titan +parity as an explicit test requirement. A construct documented as active in the +interpreter is not automatically active in Titan mode. diff --git a/docs/language/modules.md b/docs/language/modules.md new file mode 100644 index 0000000..a8780d8 --- /dev/null +++ b/docs/language/modules.md @@ -0,0 +1,42 @@ +# Module Contracts + +The interpreter exposes modules through `Value::Module` and native functions. + +## `math` + +Active names: + +- `sin`; +- `cos`; +- `sqrt`; +- `exp`; +- `pi`. + +## `topology` + +Active names: + +- `topology.ph(manifold, ...)`; +- `topology.betti(diagram_or_manifold, radius=...)`; +- `topology.intervals(diagram)`; +- `topology.Betti(...)` alias path. + +## `Ml` + +Active construction and helper surface includes: + +- `Ml.MLP(...)`; +- `Ml.KMeans(...)`; +- `Ml.Conv2D(...)`; +- tensor helpers such as matrix multiply, add, ReLU, and softmax through native + function dispatch. + +Individual ML methods must be documented from tests, not from constructor +presence alone. A constructible object is not the same as a complete algorithmic +contract. + +## `Seal` + +`Seal.train` is exposed as a native function entrypoint. Treat training-quality +or topological-stop behavior as gated unless the exact call path has a test or +benchmark artifact. diff --git a/docs/language/syntax.md b/docs/language/syntax.md new file mode 100644 index 0000000..dcd6965 --- /dev/null +++ b/docs/language/syntax.md @@ -0,0 +1,88 @@ +# Syntax + +Aether source is statement-oriented. Newlines and `~` can terminate statements. + +## Literals And Variables + +```aether +let x = 10~ +let y = 3.14~ +let ok = true~ +let name = "aether"~ +let values = [1.0, 2.0, 3.0]~ +``` + +The parser accepts optional type-hint style declarations: + +```aether +point C = [1.0, 2.0, 3.0]~ +``` + +Current type hints are parsed as syntax. They are not a full static type system. + +## Expressions + +Active expression operators: + +- arithmetic: `+`, `-`, `*`, `/`, `%`; +- comparison: `<`, `>`, `<=`, `>=`, `==`, `!=`; +- logical: `&&`, `||`, `!`; +- ranges: `0..4` for `for` loops and `0:64` for slices. + +## Control Flow + +```aether +if count == 0 { + print("empty")~ +} else { + print("nonempty")~ +} + +while count < 3 { + count = count + 1~ +} + +for i in 0..4 { + total = total + i~ +} +``` + +`break` and `continue` are active inside loops. + +## Seal Loop + +```aether +seal until count >= 3 { + count = count + 1~ +} +``` + +The active implementation evaluates the `until` expression. Topological +convergence syntax can be parsed in regression configuration, but full +language-level convergence semantics should be treated as gated unless a test +covers the exact path. + +## Functions + +```aether +fn add(a, b) { + return a + b~ +} + +let result = add(2, 3)~ +``` + +Functions return explicit `return` values or the last value produced by the +body. + +## Manifolds And Blocks + +```aether +let data = [1.0, 2.0, 3.0, 4.0]~ +manifold M = embed(data, tau=1)~ +block B = M.cluster(0:2)~ +``` + +The active interpreter uses a fixed 3D embedding workspace. `tau` is used. +`dim` can be parsed but is not the runtime dimension selector in the current +interpreter path. diff --git a/docs/ml/primitives.md b/docs/ml/primitives.md new file mode 100644 index 0000000..f16610a --- /dev/null +++ b/docs/ml/primitives.md @@ -0,0 +1,41 @@ +# ML Primitives + +Aether's ML code lives under `crates/aether-core/src/ml`. The documentation +describes the module inventory and claim boundaries rather than presenting it as +a benchmarked replacement for external ML frameworks. + +## Module Inventory + +| Module | Active surface | +| --- | --- | +| `tensor` | Owned tensor data, shapes, indexing, map, add, sub, mul, scale, transpose, matmul, reductions | +| `linalg` | Loss functions, distances, RBF kernel, numerical gradients | +| `regressor` | Linear, polynomial, RBF-style, Gaussian-process-labeled, and geodesic-labeled model enum paths | +| `convergence` | Betti records, drift/error windows, residual analysis | +| `benchmark` | Escalating benchmark runner over internal test functions | +| `clustering` | KMeans, DBSCAN, agglomerative clustering, auto-k helper | +| `classification` | Logistic regression, KNN, perceptron, Gaussian naive Bayes, decision stump, AdaBoost, nearest centroid | +| `neural` | Dense layers, activations, optimizer config, MLP training loop | +| `autograd` | Tape and variable scaffolding for differentiable tensor operations | +| `convolution` | Conv2D forward path | +| `dataloader` | Batch iteration over tensors | +| `gossip` | Local centroid and consensus propagation | + +## Language Boundary + +The DSL exposes a narrower surface than the Rust crate inventory. Constructors +such as `Ml.MLP`, `Ml.KMeans`, and `Ml.Conv2D` are available through native +function dispatch. Individual methods should be documented as active only when +the interpreter path is implemented and tested. + +## Claim Boundary + +The ML module can be described as internal Rust ML primitives. It should not be +documented as: + +- faster than PyTorch, TensorFlow, sklearn, GUDHI, or ripser; +- production-ready for all model families; +- equivalent to external framework semantics; +- hardware accelerated. + +Those claims require benchmark artifacts and parity tests. diff --git a/docs/ml/topological-convergence.md b/docs/ml/topological-convergence.md new file mode 100644 index 0000000..dab9435 --- /dev/null +++ b/docs/ml/topological-convergence.md @@ -0,0 +1,43 @@ +# Topological Convergence + +Topological convergence in Aether means that model or residual behavior is +observed through shape signals, not only scalar loss. + +## Current Internal Signals + +The convergence modules use: + +- scalar error; +- Betti-number history; +- centroid drift; +- residual sign-change and oscillation heuristics; +- fixed windows and thresholds. + +## Internal Convergence Shape + +For a residual sequence \(r_i = y_i - \hat{y_i}\), the interpreter-level +escalating regressor estimates shape using sign changes and oscillation counts. +That is a lightweight residual heuristic, not persistent homology. + +The persistent-homology path is separate: + +```aether +let diagram = topology.ph(M, max_dim=2)~ +let b = topology.betti(diagram, radius=0.5)~ +``` + +## Claim Boundary + +It is accurate to say: + +- Aether exposes topology and residual-shape signals for convergence logic. +- Some tests verify parser and interpreter paths for `seal until` and topology + calls. +- The core crate contains convergence and residual-analysis structures. + +It is not yet accurate to say: + +- every training loop terminates by persistent homology; +- topology improves model quality on external datasets; +- topological convergence replaces validation metrics; +- convergence behavior is benchmarked across model classes. diff --git a/docs/paper/aegis-language.tex b/docs/paper/aegis-language.tex index d06c74f..d637c19 100644 --- a/docs/paper/aegis-language.tex +++ b/docs/paper/aegis-language.tex @@ -1,291 +1,45 @@ -% AEGIS: A Topologically-Convergent Programming Language with Seal Loops -% IEEE Format Research Paper +\documentclass{article} +\usepackage{amsmath} +\usepackage{amssymb} +\usepackage{geometry} +\geometry{margin=1in} -\documentclass[conference]{IEEEtran} -\usepackage{cite} -\usepackage{amsmath,amssymb,amsfonts} -\usepackage{algorithmic} -\usepackage{graphicx} -\usepackage{textcomp} -\usepackage{xcolor} -\usepackage{listings} -\usepackage{tikz} - -% Custom AEGIS language highlighting -\lstdefinelanguage{aegis}{ - keywords={manifold, block, regress, render, seal, for, while, if, else, fn, return, let, until, in, embed, convergence, true, false}, - keywordstyle=\color{blue}\bfseries, - ndkeywords={dim, tau, model, format, escalate}, - ndkeywordstyle=\color{purple}, - identifierstyle=\color{black}, - sensitive=true, - comment=[l]{//}, - morecomment=[s]{/*}{*/}, - commentstyle=\color{gray}\ttfamily, - stringstyle=\color{red}\ttfamily, - morestring=[b]", -} -\lstset{ - language=aegis, - basicstyle=\footnotesize\ttfamily, - frame=single, - numbers=left, - numberstyle=\tiny, -} +\title{Aether Language Notes} +\author{Aether Lang} +\date{} \begin{document} - -\title{AEGIS: A Topologically-Convergent Programming Language\\with Geometric Seal Loops} - -\author{ -\IEEEauthorblockN{Teerth Sharma} -\IEEEauthorblockA{Independent Researcher\\ -Email: teerthsharma@example.com} -} - \maketitle -\begin{abstract} -We present AEGIS, a novel programming language that introduces topologically-convergent control flow through its unique \texttt{seal} loop construct. Unlike traditional iteration which terminates on explicit conditions, AEGIS seal loops terminate when the underlying manifold topology stabilizes—detected via Betti number convergence and centroid drift analysis. The language embeds data into geometric manifolds, enabling patterns to be ``seen'' as 3D shapes rather than abstract tensors. We demonstrate that seal loops provide natural termination guarantees for optimization problems without requiring manual threshold tuning. Benchmarks show competitive performance with Python while offering superior expressiveness for machine learning workloads. -\end{abstract} - -\begin{IEEEkeywords} -programming languages, topological data analysis, manifold learning, convergent loops, geometric computing -\end{IEEEkeywords} - -\section{Introduction} - -Traditional programming languages treat iteration as a fundamentally syntactic concern—loops terminate when a boolean condition evaluates to false. This approach, while general, creates a semantic gap in scientific computing where the ``natural'' termination point is often defined by convergence of some underlying property rather than an explicit counter or threshold. - -Consider training a neural network: practitioners set arbitrary epoch counts or loss thresholds, yet the mathematically correct termination occurs when the model has genuinely ``learned'' the data—a fundamentally topological property of the loss landscape. - -AEGIS addresses this through the \textbf{seal loop}, a control structure that monitors the geometric/topological properties of the computation and terminates when these properties stabilize. The name evokes both: -\begin{itemize} - \item A \emph{wax seal}—the loop ``seals'' when the work is complete - \item The marine mammal—efficient, intelligent, and perfectly adapted -\end{itemize} - -\section{Related Work} - -\subsection{Topological Data Analysis} -Persistent homology \cite{edelsbrunner2010computational} provides tools for extracting topological features from data. AEGIS integrates these concepts at the language level. - -\subsection{Manifold Learning} -Time-delay embeddings via Takens' theorem \cite{takens1981detecting} allow reconstruction of dynamical system attractors. AEGIS makes this a first-class primitive. - -\subsection{Termination Analysis} -While traditional approaches focus on proving loop termination \cite{cook2011proving}, AEGIS takes a different path: loops that \emph{intrinsically} terminate when their mathematical purpose is fulfilled. - -\section{Language Design} - -\subsection{Core Primitives} - -AEGIS provides four geometric primitives: - -\begin{lstlisting}[caption={Geometric primitives}] -manifold M = embed(data, dim=3, tau=5) -block B = M.extract(0:64) -regress { model: "polynomial", escalate: true } -render M { format: "ascii" } -\end{lstlisting} - -\subsection{The Seal Loop} - -The seal loop has two forms: - -\subsubsection{Until Form} -\begin{lstlisting}[caption={Seal until convergence}] -seal until convergence(1e-6) { - regress { model: "rbf" } -} -\end{lstlisting} - -\subsubsection{For Form} -\begin{lstlisting}[caption={Seal for with range}] -seal for i in 0..N { - // Body executes until topology seals -} -\end{lstlisting} - -The Unicode variant using 🦭 is also supported: - -\begin{lstlisting}[caption={Emoji syntax}] -let x = 0 -// Emoji variant -seal until x >= 10 { - x = x + 1 -} -\end{lstlisting} - -\subsection{Convergence Detection} - -Seal loops monitor: - -\begin{enumerate} - \item \textbf{Betti number stability}: $\beta_0(t) = \beta_0(t-1)$ for $k$ iterations - \item \textbf{Centroid drift}: $\|\mu(t) - \mu(t-1)\|_2 < \epsilon$ - \item \textbf{Residual topology}: The residual manifold collapses to a point -\end{enumerate} - -\begin{equation} -\text{sealed} \Leftrightarrow \Delta\beta_k = 0 \land \text{drift} < \epsilon -\end{equation} - -\section{Formal Syntax} +\section*{Status} -\subsection{EBNF Grammar} +This file is retained as an archived language-note entrypoint. The active +language documentation is maintained in: -\begin{verbatim} -program ::= statement* -statement ::= manifold_decl | block_decl - | seal_loop | if_stmt | fn_decl - | var_decl | expr_stmt - -seal_loop ::= 'seal' ('until' expr)? - ('for' IDENT 'in' range)? - block - -range ::= expr '..' expr -block ::= '{' statement* '}' - -manifold_decl ::= 'manifold' IDENT '=' - 'embed' '(' expr (',' param)* ')' - -param ::= IDENT '=' expr - -fn_decl ::= 'fn' IDENT '(' params? ')' block -params ::= IDENT (',' IDENT)* - -expr ::= primary (binop primary)* -binop ::= '+' | '-' | '*' | '/' | '<' | '>' - | '<=' | '>=' | '==' | '!=' -\end{verbatim} - -\subsection{Type System} - -AEGIS uses a structural type system with principal types: - -\begin{itemize} - \item \texttt{Number}: 64-bit floating point - \item \texttt{Bool}: Boolean - \item \texttt{String}: UTF-8 string - \item \texttt{Manifold}: D-dimensional manifold - \item \texttt{Block}: Geometric block from manifold - \item \texttt{List}: Homogeneous list -\end{itemize} - -\section{Implementation} - -\subsection{Architecture} - -\begin{figure}[h] -\centering -\begin{tikzpicture}[scale=0.8] - \node[draw, rectangle] (lexer) at (0,0) {Lexer}; - \node[draw, rectangle] (parser) at (2.5,0) {Parser}; - \node[draw, rectangle] (ast) at (5,0) {AST}; - \node[draw, rectangle] (interp) at (7.5,0) {Interpreter}; - - \draw[->] (lexer) -- (parser); - \draw[->] (parser) -- (ast); - \draw[->] (ast) -- (interp); - - \node[draw, rectangle, dashed] (tda) at (7.5,-1.5) {TDA Engine}; - \draw[->] (interp) -- (tda); -\end{tikzpicture} -\caption{AEGIS compilation pipeline} -\end{figure} - -\subsection{Seal Loop Implementation} - -The interpreter maintains a \texttt{DriftDetector} that tracks centroid trajectory: - -\begin{lstlisting}[language=Rust, caption={Drift detection}] -fn execute_seal_loop(&mut self, seal: &SealLoop) { - let mut drift = DriftDetector::new(); - - for _ in 0..MAX_ITER { - self.execute_body(&seal.body); - - let centroid = self.manifold.centroid(); - if drift.update(¢roid) < EPSILON { - return; // Sealed! - } - } -} -\end{lstlisting} - -\section{Evaluation} - -\subsection{Benchmark Setup} - -We compare AEGIS against Python 3.11 on: \begin{itemize} - \item Fibonacci(40): Recursive computation - \item Matrix multiplication: 100×100 matrices - \item Manifold embedding: 1000 points → 3D - \item Convergent regression: Until $\epsilon < 10^{-8}$ + \item \texttt{docs/language/syntax.md} + \item \texttt{docs/language/execution-model.md} + \item \texttt{docs/language/modules.md} + \item \texttt{docs/concepts/language-pipeline.md} + \item \texttt{docs/reference/status.md} \end{itemize} -\subsection{Results} - -\begin{table}[h] -\centering -\caption{Performance Comparison (seconds)} -\begin{tabular}{|l|r|r|r|} -\hline -\textbf{Benchmark} & \textbf{Python} & \textbf{AEGIS} & \textbf{Speedup} \\ -\hline -Fibonacci(40) & 45.2 & 12.3 & 3.7× \\ -MatMul(100) & 2.1 & 0.8 & 2.6× \\ -Embed(1000) & N/A & 0.05 & — \\ -Convergence & Manual & Auto & Natural \\ -\hline -\end{tabular} -\end{table} - -\subsection{Convergence Guarantee} - -Unlike threshold-based termination, seal loops provide a \emph{semantic} guarantee: the loop terminates when the computation has converged mathematically, not when an arbitrary condition is met. - -\section{Case Study: ML Training} +\section*{Pipeline} -\begin{lstlisting}[caption={Seal loop for training}] -manifold Loss = embed(loss_history, dim=3, tau=5) +Aether source is processed through: -seal until convergence(1e-8) { - regress { - model: "geodesic", - escalate: true - } -} - -// Loop exits when loss manifold stabilizes -render Loss { format: "webgl" } -\end{lstlisting} - -The escalating regression automatically increases model complexity (Linear → Polynomial → RBF → Geodesic) until topological convergence. - -\section{Conclusion} - -AEGIS introduces a paradigm shift in loop semantics: rather than explicit termination conditions, seal loops terminate when their mathematical purpose is fulfilled. This provides: - -\begin{itemize} - \item Natural convergence without threshold tuning - \item Geometric intuition for optimization problems - \item Competitive performance with expressive syntax - \item First-class topological data analysis -\end{itemize} +\[ + source \rightarrow lexer \rightarrow parser \rightarrow AST + \rightarrow interpreter\ |\ Titan\ VM . +\] -Future work includes JIT compilation, GPU acceleration, and formal verification of convergence properties. +The active implementation supports variables, assignments, arithmetic, +comparison and logical expressions, lists, functions, loops, manifold embedding, +and topology module calls as described by the MkDocs pages. -\section*{Acknowledgments} -We thank the Rust community for excellent no\_std support and the TDA research community for foundational algorithms. +\section*{Claim Boundary} -\begin{thebibliography}{00} -\bibitem{takens1981detecting} F. Takens, ``Detecting strange attractors in turbulence,'' in \emph{Dynamical Systems and Turbulence}, Springer, 1981. -\bibitem{edelsbrunner2010computational} H. Edelsbrunner and J. Harer, \emph{Computational Topology: An Introduction}, AMS, 2010. -\bibitem{cook2011proving} B. Cook et al., ``Proving program termination,'' \emph{CACM}, vol. 54, no. 5, 2011. -\end{thebibliography} +Parsed syntax is not automatically runtime support. Runtime support is active +only when the interpreter or VM path has tests and documentation. \end{document} diff --git a/docs/paper/aegis-paper.tex b/docs/paper/aegis-paper.tex index 73e5be0..f824441 100644 --- a/docs/paper/aegis-paper.tex +++ b/docs/paper/aegis-paper.tex @@ -1,231 +1,54 @@ -% AEGIS: Geometric Sparse-Event Microkernel with Topological Code Authentication -% IEEE Conference Paper Format +\documentclass{article} +\usepackage{amsmath} +\usepackage{amssymb} +\usepackage{geometry} +\geometry{margin=1in} -\documentclass[conference]{IEEEtran} - -\usepackage{amsmath,amssymb,amsfonts} -\usepackage{algorithmic} -\usepackage{graphicx} -\usepackage{textcomp} -\usepackage{xcolor} -\usepackage{booktabs} -\usepackage{hyperref} - -\def\BibTeX{{\rm B\kern-.05em{\sc i\kern-.025em b}\kern-.08em - T\kern-.1667em\lower.7ex\hbox{E}\kern-.125emX}} +\title{Aether Sparse-Event Kernel Notes} +\author{Aether Lang} +\date{} \begin{document} - -\title{AEGIS: Geometric Sparse-Event Microkernel with Topological Code Authentication} - -\author{ -\IEEEauthorblockN{AEGIS Research Team} -\IEEEauthorblockA{\textit{Topological Systems Engineering}} -} - \maketitle -\begin{abstract} -We present AEGIS, a formally verified, event-driven microkernel that executes tasks only upon significant system state deviation ($\Delta \geq \epsilon$) and authenticates binary code via topological signatures. Unlike traditional fixed-interval schedulers, AEGIS treats the kernel as a dynamic system on a manifold, using PID-on-Manifold control for adaptive threshold adjustment. Our Topological Gatekeeper computes Betti numbers ($\beta_0$, $\beta_1$) via persistent homology to detect malicious code patterns including NOP sleds and ROP chains with 100\% true positive rate and 0\% false positive rate on legitimate code. The AETHER geometric extension provides hierarchical sparse attention achieving O(log n) query complexity. Rigorous benchmarks demonstrate Lyapunov stability of the governor controller and verify all mathematical properties. AEGIS achieves near-zero idle power consumption while maintaining robust security guarantees through topological data analysis. -\end{abstract} - -\begin{IEEEkeywords} -microkernel, topology, sparse-attention, persistent homology, event-driven, security -\end{IEEEkeywords} - -\section{Introduction} - -Modern operating system kernels face a fundamental tension: they must be responsive to events while minimizing resource consumption. Traditional approaches rely on fixed-interval scheduling (typically 100Hz-1000Hz), leading to unnecessary CPU wake-ups during idle periods and potential response latency during high activity. - -We propose a paradigm shift: treating the kernel not as a manager of resources, but as a \textbf{dynamic system on a manifold}. In this framework, the kernel's behavior emerges from the geometry of its state space rather than from arbitrary timer intervals. - -\subsection{Contributions} - -\begin{enumerate} -\item \textbf{Sparse Triggering}: A mathematically principled wake condition based on L2 deviation in state space -\item \textbf{PID-on-Manifold Governor}: Adaptive threshold control with proven Lyapunov stability -\item \textbf{Topological Gatekeeper}: Binary authentication via Betti numbers achieving 100\% detection of NOP sleds -\item \textbf{AETHER Extensions}: Hierarchical block trees for O(log n) sparse attention -\end{enumerate} - -\section{Mathematical Foundations} - -\subsection{State Space Formulation} - -The kernel state is represented as a point on a d-dimensional manifold: -\begin{equation} -\mu(t) \in \mathbb{R}^d -\end{equation} - -For AEGIS with $d=4$: -\begin{equation} -\mu(t) = \begin{bmatrix} m(t) \\ i(t) \\ q(t) \\ e(t) \end{bmatrix} -\end{equation} - -Where $m(t)$ is memory pressure, $i(t)$ is IRQ rate, $q(t)$ is thread queue depth, and $e(t)$ is entropy pool level. - -\subsection{Sparse Trigger Condition} - -The deviation metric measures trajectory distance: -\begin{equation} -\Delta(t) = ||\mu(t) - \mu(t_{last})||_2 -\end{equation} - -Execution occurs if and only if: -\begin{equation} -\Delta(t) \geq \epsilon(t) -\end{equation} - -\subsection{Geometric Governor (PID Control)} - -The error signal is defined as: -\begin{equation} -e(t) = R_{target} - \frac{\Delta(t)}{\epsilon(t)} -\end{equation} - -The control law adapts threshold: -\begin{equation} -\epsilon(t+1) = \epsilon(t) + \alpha \cdot e(t) + \beta \cdot \frac{de}{dt} -\end{equation} - -with stability bounds $\epsilon \in [0.001, 10.0]$. - -\subsection{Topological Data Analysis} - -We compute Betti numbers via persistent homology: -\begin{itemize} -\item $\beta_0$: Number of connected components (gaps in byte stream) -\item $\beta_1$: Number of loops/cycles (oscillation patterns) -\end{itemize} - -Shape signature: -\begin{equation} -Shape(B) = (\beta_0, \beta_1) -\end{equation} +\section*{Status} -\section{Architecture} - -AEGIS employs a three-layer architecture: - -\subsection{Layer 0: Math-Metal HAL} -System state vector, deviation metric computation, and interrupt handlers. - -\subsection{Layer 1: Sparse-Event Scheduler} -Geometric Governor (PID control), SparseScheduler, and entropy pool management. - -\subsection{Layer 2: Topological Loader} -ELF parser with sliding window analysis, Betti number computation, and shape verification against reference signatures. - -\section{Experimental Evaluation} - -We conducted rigorous benchmarks on the AEGIS implementation. All tests were executed in a Rust test harness targeting x86\_64-pc-windows-msvc. - -\subsection{Governor Stability} - -\begin{table}[h] -\caption{Governor Convergence Results} -\begin{center} -\begin{tabular}{lcc} -\toprule -\textbf{Target Rate} & \textbf{Final $\epsilon$} & \textbf{Bounded} \\ -\midrule -10 Hz & 10.0000 & \checkmark \\ -1000 Hz & 0.1000 & \checkmark \\ -\bottomrule -\end{tabular} -\end{center} -\end{table} - -Lyapunov analysis confirmed 50\% energy decreasing iterations, demonstrating asymptotic stability. Stress testing (low load $\rightarrow$ spike $\rightarrow$ recovery) showed all epsilon values remained within $[0.001, 10.0]$ bounds. - -\subsection{Topological Gatekeeper} - -\begin{table}[h] -\caption{Detection Accuracy} -\begin{center} -\begin{tabular}{lc} -\toprule -\textbf{Pattern Type} & \textbf{Rate} \\ -\midrule -NOP Sled TPR & 100.0\% \\ -Legitimate Code FPR & 0.0\% \\ -\bottomrule -\end{tabular} -\end{center} -\end{table} - -The Topological Gatekeeper achieved perfect detection of NOP sled patterns while producing zero false positives on legitimate compiled code samples. - -\subsection{AETHER Hierarchical Attention} - -\begin{table}[h] -\caption{AETHER Pruning Ratios by Threshold} -\begin{center} -\begin{tabular}{lc} -\toprule -\textbf{Threshold} & \textbf{Blocks Pruned} \\ -\midrule -0.1 & 5.3\% \\ -0.3 & 6.6\% \\ -0.5 & 9.4\% \\ -0.7 & 13.8\% \\ -0.9 & 19.1\% \\ -\bottomrule -\end{tabular} -\end{center} -\end{table} - -Higher thresholds correctly result in more aggressive pruning, validating the Cauchy-Schwarz upper bound scoring mechanism. - -\subsection{Manifold Embedding} - -Time-delay embedding of sine wave signals achieved 100\% quality score, demonstrating correct Takens' theorem implementation for attractor reconstruction. - -\subsection{ML Convergence Detection} - -Topological convergence detection achieved 50\% accuracy on synthetic test cases (clear convergence vs. oscillating), validating the Betti stability detection mechanism. - -\section{Discussion} - -\subsection{Energy Efficiency} - -By only waking the CPU when $\Delta(t) \geq \epsilon(t)$, AEGIS achieves near-zero idle power. The adaptive threshold prevents both: -\begin{itemize} -\item \textbf{Thrashing}: $\epsilon$ too low $\rightarrow$ excessive wakes -\item \textbf{Oversleeping}: $\epsilon$ too high $\rightarrow$ missed events -\end{itemize} - -\subsection{Security Implications} - -The topological authentication approach detects malicious patterns by their geometric ``shape'' rather than signatures: -\begin{itemize} -\item NOP sleds: Very low density ($\beta_0/|B| \approx 0$) -\item ROP chains: High loop count (elevated $\beta_1$) -\item Encrypted payloads: High entropy (density $> 0.6$) -\end{itemize} +This file is retained as an archived research-note entrypoint. It is not the +active source of public claims for Aether Lang. -\subsection{Limitations} +The active documentation standard is maintained in: \begin{itemize} -\item Lyapunov stability at 50\% suggests room for gain tuning -\item Benchmarks run in test harness, not bare-metal -\item Topological convergence detection could be improved + \item \texttt{docs/index.md} + \item \texttt{docs/kernel/sparse-events.md} + \item \texttt{docs/kernel/hardware-boundary.md} + \item \texttt{docs/benchmarks/index.md} + \item \texttt{docs/reference/status.md} \end{itemize} -\section{Related Work} +\section*{Sparse-Event Model} -Event-driven kernels have been explored in embedded systems (TinyOS, Contiki), but none utilize topological data analysis for code authentication. Persistent homology has been applied to malware classification but not integrated into kernel loaders. Sparse attention mechanisms (BigBird, Longformer) inspired AETHER but operate at transformer level rather than kernel scheduling. +The scheduler model represents system state as a vector +\[ + \mu(t) \in \mathbb{R}^D . +\] -\section{Conclusion} +The wake condition is +\[ + \Delta(t) = \|\mu(t)-\mu(t_{last})\|_2, + \qquad + \Delta(t) \ge \epsilon(t). +\] -AEGIS demonstrates that treating kernels as dynamic systems on manifolds yields both efficiency and security benefits. The PID-on-Manifold governor maintains bounded behavior under stress, while the Topological Gatekeeper provides robust binary authentication. Future work includes formal verification of Lyapunov stability, integration with hardware trust mechanisms, and extension to multi-core scheduling. +The governor adapts \(\epsilon(t)\) from the observed deviation and elapsed +time. Claims about hardware power, latency, security behavior, or production +boot support require artifacts described in the benchmark and evidence-gate +documentation. -\section*{References} +\section*{Claim Boundary} -\begin{enumerate} -\item Takens, F. (1981). Detecting strange attractors in turbulence. -\item Edelsbrunner, H. \& Harer, J. (2010). Computational Topology. -\item AETHER Geometric Extensions. DOI: 10.13141/RG.2.2.14811.27684 -\end{enumerate} +This note does not claim measured speedup, production binary authentication, +power behavior, or formal verification for the complete repository. +Those claims require current artifacts, baselines, and reproducible commands. \end{document} diff --git a/docs/reference/api.md b/docs/reference/api.md new file mode 100644 index 0000000..ac259cc --- /dev/null +++ b/docs/reference/api.md @@ -0,0 +1,62 @@ +# API Reference + +This page is a compact public map. It is not generated Rustdoc. + +## `aether-lang` + +| Type | Path | Role | +| --- | --- | --- | +| `Lexer` | `lexer.rs` | Converts source text into tokens | +| `TokenKind` | `lexer.rs` | Token vocabulary | +| `Parser` | `parser.rs` | Builds AST from token stream | +| `ParseError` | `parser.rs` | Parse error with line and column | +| `Program` | `ast.rs` | Top-level statement list | +| `ExprKind` | `ast.rs` | Expression node variants | +| `StmtKind` | `ast.rs` | Statement node variants | +| `Interpreter` | `interpreter.rs` | Executes AST programs | +| `Value` | `interpreter.rs` | Runtime value enum | +| `TitanVM` | `vm.rs` | Stack-based VM | +| `Compiler` | `vm.rs` | AST-to-opcode compiler | +| `AsciiRenderer` | `ascii_render.rs` | ASCII point-cloud rendering | +| `WebGLExporter` | `webgl_export.rs` | HTML/WebGL point-cloud export | + +## `aether-core` + +| Type or function | Path | Role | +| --- | --- | --- | +| `ManifoldPoint` | `manifold.rs` | Point in D-dimensional space | +| `TimeDelayEmbedder` | `manifold.rs` | Delay-coordinate embedding | +| `SparseAttentionGraph` | `manifold.rs` | Epsilon-neighborhood graph | +| `TopologicalPipeline` | `manifold.rs` | Streaming topology pipeline | +| `BlockMetadata` | `aether.rs` | Centroid/radius/variance/concentration | +| `HierarchicalBlockTree` | `aether.rs` | Multi-level block summary tree | +| `DriftDetector` | `aether.rs` | Centroid drift tracking | +| `persistent_homology` | `persistence.rs` | Bounded PH engine | +| `time_delay_persistence` | `persistence.rs` | Samples to PH diagram | +| `PersistenceConfig` | `persistence.rs` | PH bounds and complex selection | +| `ComplexKind` | `persistence.rs` | Vietoris-Rips or witness mode | +| `PersistenceDiagram` | `persistence.rs` | Persistence pair collection | +| `BettiNumbers3` | `persistence.rs` | `beta_0`, `beta_1`, `beta_2` | +| `TopologicalShape` | `topology.rs` | Binary-shape heuristic result | +| `verify_shape` | `topology.rs` | Binary-shape gate | +| `GeometricGovernor` | `governor.rs` | Adaptive epsilon controller | +| `SystemState` | `state.rs` | Scheduler state vector | + +## `aether-cli` + +| Command | Role | +| --- | --- | +| `aether repl` | Interactive interpreter session | +| `aether run ` | Parse and execute a script | +| `aether run --mode titan` | Compile and run with Titan VM | +| `aether check ` | Parse-only syntax check | + +## `aether-kernel` + +| Type or function | Role | +| --- | --- | +| `SparseScheduler` | State-deviation wake gate | +| `verify_elf` | ELF header validation | +| `verify_binary_topology` | Loader-facing topology heuristic | +| `init_heap` | Allocator initialization | +| `HardwareTopology` | Boot-time hardware topology record | diff --git a/docs/reference/contributing.md b/docs/reference/contributing.md new file mode 100644 index 0000000..d543674 --- /dev/null +++ b/docs/reference/contributing.md @@ -0,0 +1,46 @@ +# Contribution Standard + +A small language/runtime repository still needs claim discipline. + +## Code + +- Public Rust APIs should return typed errors or documented `Result` values + when failure is expected. +- `unwrap()` in production paths needs a local invariant that is clear from the + surrounding code. +- Unsafe code needs a `Safety` comment and a test or review note for the + boundary. +- Optional dependencies must stay optional for default builds unless the crate + contract changes. +- Parser features need lexer, parser, interpreter or VM coverage as applicable. + +## Docs + +Every concept page should include: + +- the active implementation path; +- the formula or state transition if there is one; +- plain mechanical meaning; +- the active claim; +- the gated or roadmap claim; +- a failure mode. + +## Benchmarks + +Every speed claim needs: + +- raw artifact; +- baseline list; +- hardware/software environment; +- correctness metric; +- seed or determinism note. + +## Tone + +Use neutral systems language. Prefer "the implementation does X under Y +condition" over broad promotional phrasing. Benefits should emerge from the +mechanism being documented: representation, invariant, and gate. + +## Commits + +Group related work into coherent commits that can be reviewed independently. diff --git a/docs/reference/status.md b/docs/reference/status.md new file mode 100644 index 0000000..f82d909 --- /dev/null +++ b/docs/reference/status.md @@ -0,0 +1,42 @@ +# Status Matrix + +## Active + +| Surface | Evidence | +| --- | --- | +| Lexer tokens for current syntax | `lexer.rs` tests | +| Parser for statements and operators | `parser.rs` tests | +| Interpreter assignments, loops, functions | `interpreter.rs` tests | +| Numeric-list manifold embedding | `interpreter.rs` test | +| `topology.ph` and `topology.betti` | `interpreter.rs` topology test | +| Bounded persistent homology H0/H1/H2 | `persistence.rs` tests | +| Lazy witness mode | `persistence.rs` test | +| Block metadata and compression selection | `aether.rs` tests | +| Drift detector | `aether.rs` test | +| Sparse graph and pipeline | `manifold.rs` tests | +| Geometric governor | `governor.rs` tests | +| Sparse scheduler | `scheduler.rs` tests | +| CLI parse-error formatting | `aether-cli` test | + +## Partial Or Gated + +| Surface | Gate | +| --- | --- | +| Titan VM language parity | VM tests per construct | +| Full static type checking | Static checker and diagnostics | +| Complete class/object semantics | Interpreter tests and docs | +| Render as a user-facing graphics command | CLI artifact or exported file test | +| `Seal.train` semantic contract | Interpreter test and training artifact | +| ML model quality | Deterministic datasets and baseline metrics | +| External TDA parity | ripser/GUDHI-style fixtures | +| no_std workspace claim | `cargo check --no-default-features` in CI | +| Bare-metal product claim | Boot logs and hardware matrix | +| Security detection claim | Threat model, corpus, metrics | + +## Removed From Active Claims + +- Unverified speedup factors. +- Placeholder benchmark rows. +- Broad "verified full trust chain" language for this repository. +- Production security guarantees. +- Hardware acceleration claims. diff --git a/docs/superpowers/plans/2026-06-21-aether-language-completion.md b/docs/superpowers/plans/2026-06-21-aether-language-completion.md new file mode 100644 index 0000000..5b95f3d --- /dev/null +++ b/docs/superpowers/plans/2026-06-21-aether-language-completion.md @@ -0,0 +1,7665 @@ +# Aether Language Completion Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Bring the Aether/Aegis DSL from a partial parser/runtime toward a complete, testable language implementation. + +**Architecture:** Build from the compiler front end outward: lexer correctness, parser grammar, AST/runtime semantics, diagnostics, then compiler/VM lowering. Each slice must add failing tests first and keep `cargo test -p aether-lang` green before moving to the next slice. + +**Tech Stack:** Rust workspace, `aether-lang` lexer/parser/AST/interpreter/VM, existing `aether-core` runtime primitives. + +## Global Constraints + +- Preserve the existing crate layout and public exports. +- Keep changes focused in `crates/aether-lang` unless a task explicitly needs `aether-core`. +- Use TDD: every new language behavior gets a failing test before implementation. +- Maintain `no_std` compatibility where current modules already support it. +- Do not remove existing AEGIS/Aether syntax aliases unless a compatibility task explicitly does so. + +--- + +### Task 1: Implement Documented Expression Surface + +**Files:** +- Modify: `crates/aether-lang/src/lexer.rs` +- Modify: `crates/aether-lang/src/parser.rs` +- Modify: `crates/aether-lang/src/ast.rs` +- Modify: `crates/aether-lang/src/interpreter.rs` + +**Interfaces:** +- Consumes: `Lexer::tokenize`, `Parser::parse`, `Interpreter::execute` +- Produces: parsed and executable `~`, `..`, `%`, comparisons, logical operators, and unary `!`/`-` + +- [x] **Step 1: Write failing tests for doc-backed syntax** + +Add tests proving `1..10` tokenizes as `Number`, `DotDot`, `Number`; `~` separates statements; `1 < 2 && !false` parses; and `10 % 4 == 2` executes. + +- [x] **Step 2: Run test to verify it fails** + +Run: `cargo test -p aether-lang` +Expected: FAIL in lexer/parser/interpreter tests for the missing operators and separators. + +- [x] **Step 3: Implement minimal lexer/parser/interpreter support** + +Keep `1.5` float handling intact while preserving `1..10` as a range; add precedence layers for `||`, `&&`, equality, comparison, range, additive, multiplicative, unary, and primary expressions. + +- [x] **Step 4: Run test to verify it passes** + +Run: `cargo test -p aether-lang` +Expected: PASS. + +### Task 2: Add Assignment and Mutable Runtime State + +**Files:** +- Modify: `crates/aether-lang/src/ast.rs` +- Modify: `crates/aether-lang/src/parser.rs` +- Modify: `crates/aether-lang/src/interpreter.rs` + +**Interfaces:** +- Consumes: `StmtKind::Var`, `ExprKind::Ident` +- Produces: `StmtKind::Assign { name, value }` or equivalent, with runtime variable update behavior + +- [x] **Step 1: Write failing parser/runtime tests** + +```rust +let mut parser = Parser::new("let count = 0~\ncount = count + 1~"); +let program = parser.parse().expect("program should parse"); +let mut interpreter = Interpreter::new(); +interpreter.execute(&program).expect("program should execute"); +assert!(matches!(interpreter.variables.get("count"), Some(Value::Num(1.0)))); +``` + +- [x] **Step 2: Implement assignment parsing** + +When a statement starts with an identifier followed by `=`, parse it as assignment rather than only declaration. + +- [x] **Step 3: Implement assignment execution** + +Evaluate the right-hand expression and update `Interpreter::variables`. + +- [x] **Step 4: Verify** + +Run: `cargo test -p aether-lang` +Expected: PASS. + +### Task 3: Make Loops Semantically Useful + +**Files:** +- Modify: `crates/aether-lang/src/parser.rs` +- Modify: `crates/aether-lang/src/interpreter.rs` + +**Interfaces:** +- Consumes: assignment from Task 2, existing `IfStmt`, `WhileStmt`, `ForStmt`, `LoopStmt` +- Produces: executable `while`, `for i in start..end`, and bounded `seal until condition { ... }` + +- [x] **Step 1: Write failing runtime tests** + +Cover `while count < 3`, `for i in 0..3`, `break`, and `continue`. + +- [x] **Step 2: Add loop control flow result type** + +Introduce an internal execution-flow enum such as `RuntimeFlow::Value(Value)`, `Break`, `Continue`, `Return(Value)` so loop control does not collapse to `Unit`. + +- [x] **Step 3: Execute `for` ranges** + +Bind the iterator name for each integer step from range start to range end, execute the body, and restore/overwrite the variable predictably. + +- [x] **Step 4: Add `seal until` parsing and execution** + +Support `seal until expr { ... }` and `🦭 until expr { ... }`, preserving the current bare `seal { ... }` fallback. + +- [x] **Step 5: Verify** + +Run: `cargo test -p aether-lang` +Expected: PASS. + +### Task 4: Function Calls and Returns + +**Files:** +- Modify: `crates/aether-lang/src/interpreter.rs` + +**Interfaces:** +- Consumes: existing `FnDecl`, `ReturnStmt`, `ExprKind::Call` +- Produces: user-defined function storage, local call frames, parameter binding, return propagation + +- [x] **Step 1: Write failing tests** + +Test `fn add(a, b) { return a + b~ } let result = add(2, 3)~`. + +- [x] **Step 2: Store function declarations** + +Add a function table to `Interpreter` or store functions in `variables` through a new `Value::Function`. + +- [x] **Step 3: Implement call frames** + +Bind arguments to parameters in a temporary scope and restore previous bindings after execution. + +- [x] **Step 4: Verify** + +Run: `cargo test -p aether-lang` +Expected: PASS. + +### Task 5: Diagnostics and Check Mode + +**Files:** +- Modify: `crates/aether-lang/src/lexer.rs` +- Modify: `crates/aether-lang/src/parser.rs` +- Modify: `crates/aether-cli/src/main.rs` + +**Interfaces:** +- Consumes: `Token::line`, `Token::column`, `Span` +- Produces: stable error messages with source location and expected token/context + +- [x] **Step 1: Write failing tests** + +Assert parser errors include the unexpected token, expected grammar construct, line, and column. + +- [x] **Step 2: Preserve lexer errors as parser failures** + +Do not let `TokenKind::Error` reach generic unexpected-token paths. + +- [x] **Step 3: Surface diagnostics in CLI check/run** + +Format errors consistently for `aether check` and `aether run`. + +- [x] **Step 4: Verify** + +Run: `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 10: Lean Block Flow Semantics + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 9 single-statement relation and statement-flow model +- Produces: checked Lean relation for ordered block execution and control-flow propagation + +- [x] **Step 1: Add block execution relation** + +Define `StepBlock` for empty blocks, single statements, value-sequencing, and early stop on `return`, `break`, or `continue`. + +- [x] **Step 2: Add checked block examples** + +Cover `let` followed by expression, assignment followed by expression, early `return`, early `break`, and early `continue`. + +- [x] **Step 3: Document block-flow semantics** + +Record that final expression values are preserved and control-flow signals stop a block immediately. + +- [x] **Step 4: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 161: Proof-Core String `first()` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, argument-aware static method + compatibility, stack/frame method opcodes, checked source frame compilation, + and source diagnostic rendering. +- Produces: zero-argument `.first()` support for proof-core strings, returning + the first character as `str` at runtime when present and rejecting + non-zero-arity concrete calls statically. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `"open".first()` through `evalExpr`, `checkExpr`, +closed expression bytecode execution, frame expression compilation, +`checkedFrameSourceLocal?`, and `sourceLocal?`. Include an empty-string +runtime failure and a one-argument static diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because string method evaluation and static method +compatibility do not yet support `.first()`. + +- [x] **Step 3: Implement shared runtime support** + +Wire string `.first()` through `evalIndex` so first-character extraction shares +the same runtime behavior as string indexing. + +- [x] **Step 4: Implement static method typing** + +Extend static method compatibility so zero-argument `str.first()` returns +`str`. + +- [x] **Step 5: Document the proof-core method** + +Record string `.first()` in the proof-core method surface and checked compiler +coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 162: Proof-Core String `last()` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, argument-aware static method + compatibility, stack/frame method opcodes, checked source frame compilation, + and source diagnostic rendering. +- Produces: zero-argument `.last()` support for proof-core strings, returning + the final character as `str` at runtime when present and rejecting + non-zero-arity concrete calls statically. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `"open".last()` through `evalExpr`, `checkExpr`, +closed expression bytecode execution, frame expression compilation, +`checkedFrameSourceLocal?`, and `sourceLocal?`. Include an empty-string +runtime failure and a one-argument static diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because string method evaluation and static method +compatibility do not yet support `.last()`. + +- [x] **Step 3: Implement shared runtime support** + +Wire string `.last()` through character-list evaluation so it returns the last +character under the same character semantics as string indexing. + +- [x] **Step 4: Implement static method typing** + +Extend static method compatibility so zero-argument `str.last()` returns +`str`. + +- [x] **Step 5: Document the proof-core method** + +Record string `.last()` in the proof-core method surface and checked compiler +coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 163: Proof-Core String `tail()` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, argument-aware static method + compatibility, stack/frame method opcodes, checked source frame compilation, + and source diagnostic rendering. +- Produces: zero-argument `.tail()` support for proof-core strings, returning + the remaining string after the first character at runtime when present and + rejecting non-zero-arity concrete calls statically. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `"open".tail()` through `evalExpr`, `checkExpr`, +closed expression bytecode execution, frame expression compilation, +`checkedFrameSourceLocal?`, and `sourceLocal?`. Include an empty-string +runtime failure and a one-argument static diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because string method evaluation and static method +compatibility do not yet support `.tail()`. + +- [x] **Step 3: Implement shared runtime support** + +Wire string `.tail()` through character-list evaluation so it returns the +remaining characters under the same character semantics as string indexing. + +- [x] **Step 4: Implement static method typing** + +Extend static method compatibility so zero-argument `str.tail()` returns +`str`. + +- [x] **Step 5: Document the proof-core method** + +Record string `.tail()` in the proof-core method surface and checked compiler +coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 164: Proof-Core String `take(count)` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, numeric argument static compatibility, + stack/frame method opcodes, checked source frame compilation, and source + diagnostic rendering. +- Produces: `.take(count)` support for proof-core strings, returning the + prefix string for non-negative numeric counts and rejecting non-numeric + concrete count arguments statically. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `"open".take(2)` through `evalExpr`, `checkExpr`, +closed expression bytecode execution, frame expression compilation, +`checkedFrameSourceLocal?`, and `sourceLocal?`. Include negative-count +runtime failure and a boolean-count static diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because string method evaluation and static method +compatibility do not yet support `.take(count)`. + +- [x] **Step 3: Implement shared runtime support** + +Add character-list prefix evaluation and wire string `.take(count)` through it, +rejecting negative runtime counts like list `.take(count)`. + +- [x] **Step 4: Implement static method typing** + +Extend static method compatibility so `str.take(count)` returns `str` only +when the count argument checks as numeric. + +- [x] **Step 5: Document the proof-core method** + +Record string `.take(count)` in the proof-core method surface and checked +compiler coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 165: Proof-Core String `drop(count)` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, numeric argument static compatibility, + stack/frame method opcodes, checked source frame compilation, and source + diagnostic rendering. +- Produces: `.drop(count)` support for proof-core strings, returning the + suffix string after dropping a non-negative numeric count and rejecting + non-numeric concrete count arguments statically. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `"open".drop(2)` through `evalExpr`, `checkExpr`, +closed expression bytecode execution, frame expression compilation, +`checkedFrameSourceLocal?`, and `sourceLocal?`. Include negative-count +runtime failure and a boolean-count static diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because string method evaluation and static method +compatibility do not yet support `.drop(count)`. + +- [x] **Step 3: Implement shared runtime support** + +Add character-list suffix evaluation and wire string `.drop(count)` through it, +rejecting negative runtime counts like list `.drop(count)`. + +- [x] **Step 4: Implement static method typing** + +Extend static method compatibility so `str.drop(count)` returns `str` only +when the count argument checks as numeric. + +- [x] **Step 5: Document the proof-core method** + +Record string `.drop(count)` in the proof-core method surface and checked +compiler coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 166: Proof-Core String `starts_with(prefix)` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, string argument static compatibility, + stack/frame method opcodes, checked source frame compilation, and source + diagnostic rendering. +- Produces: `.starts_with(prefix)` support for proof-core strings, returning + `bool` for prefix membership and rejecting non-string concrete prefix + arguments statically. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `"open".starts_with("op")` through `evalExpr`, +`checkExpr`, closed expression bytecode execution, frame expression +compilation, `checkedFrameSourceLocal?`, and `sourceLocal?`. Include a false +runtime prefix case and a non-string argument static diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because string method evaluation and static method +compatibility do not yet support `.starts_with(prefix)`. + +- [x] **Step 3: Implement shared runtime support** + +Wire string `.starts_with(prefix)` through the character-list prefix helper. + +- [x] **Step 4: Implement static method typing** + +Extend static method compatibility so `str.starts_with(prefix)` returns +`bool` only when the prefix argument checks as `str`. + +- [x] **Step 5: Document the proof-core method** + +Record string `.starts_with(prefix)` in the proof-core method surface and +checked compiler coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 167: Proof-Core String `ends_with(suffix)` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, string argument static compatibility, + stack/frame method opcodes, checked source frame compilation, and source + diagnostic rendering. +- Produces: `.ends_with(suffix)` support for proof-core strings, returning + `bool` for suffix membership and rejecting non-string concrete suffix + arguments statically. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `"open".ends_with("en")` through `evalExpr`, +`checkExpr`, closed expression bytecode execution, frame expression +compilation, `checkedFrameSourceLocal?`, and `sourceLocal?`. Include a false +runtime suffix case and a non-string argument static diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because string method evaluation and static method +compatibility do not yet support `.ends_with(suffix)`. + +- [x] **Step 3: Implement shared runtime support** + +Wire string `.ends_with(suffix)` through reverse character-list prefix +matching. + +- [x] **Step 4: Implement static method typing** + +Extend static method compatibility so `str.ends_with(suffix)` returns `bool` +only when the suffix argument checks as `str`. + +- [x] **Step 5: Document the proof-core method** + +Record string `.ends_with(suffix)` in the proof-core method surface and +checked compiler coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 168: Proof-Core String `reverse()` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, stack/frame method opcodes, checked + source frame compilation, and source diagnostic rendering. +- Produces: zero-argument `.reverse()` support for proof-core strings, + returning a character-reversed `str` and rejecting non-zero-arity concrete + calls statically. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `"open".reverse()` through `evalExpr`, `checkExpr`, +closed expression bytecode execution, frame expression compilation, +`checkedFrameSourceLocal?`, and `sourceLocal?`. Include a one-argument static +diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because string method evaluation and static method +compatibility do not yet support `.reverse()`. + +- [x] **Step 3: Implement shared runtime support** + +Wire string `.reverse()` through character-list reversal. + +- [x] **Step 4: Implement static method typing** + +Extend static method compatibility so zero-argument `str.reverse()` returns +`str`. + +- [x] **Step 5: Document the proof-core method** + +Record string `.reverse()` in the proof-core method surface and checked +compiler coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 169: Proof-Core List `reverse()` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, static method compatibility, stack/frame + method opcodes, checked source frame compilation, and source diagnostic + rendering. +- Produces: zero-argument `.reverse()` support for proof-core lists, returning + a reversed `list[T]` and rejecting non-zero-arity concrete calls statically. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `[7, 9].reverse()` through `evalExpr`, `checkExpr`, +closed expression bytecode execution, frame expression compilation, +`checkedFrameSourceLocal?`, and `sourceLocal?`. Include an empty-list runtime +example and a one-argument static diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because list method evaluation and static method compatibility +do not yet support `.reverse()`. + +- [x] **Step 3: Implement shared runtime support** + +Wire list `.reverse()` through structural list reversal. + +- [x] **Step 4: Implement static method typing** + +Extend static method compatibility so zero-argument `list[T].reverse()` +returns `list[T]`. + +- [x] **Step 5: Document the proof-core method** + +Record list `.reverse()` in the proof-core method surface and checked compiler +coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 170: Proof-Core List `append(value)` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, static method compatibility, stack/frame + method opcodes, checked source frame compilation, and source diagnostic + rendering. +- Produces: one-argument pure `.append(value)` support for proof-core lists, + returning a new `list[T]` with the value at the end and rejecting + incompatible concrete element types statically. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `[7].append(9)` through `evalExpr`, `checkExpr`, +closed expression bytecode execution, frame expression compilation, +`checkedFrameSourceLocal?`, and `sourceLocal?`. Include an empty-list runtime +example and an incompatible-element static diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because list method evaluation and static method compatibility +do not yet support `.append(value)`. + +- [x] **Step 3: Implement shared runtime support** + +Wire list `.append(value)` through structural list append without mutating the +receiver. + +- [x] **Step 4: Implement static method typing** + +Extend static method compatibility so `list[T].append(value)` returns +`list[T]` when `value` is compatible with `T`. + +- [x] **Step 5: Document the proof-core method** + +Record list `.append(value)` in the proof-core method surface and checked +compiler coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 171: Proof-Core List `concat(other)` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, static method compatibility, stack/frame + method opcodes, checked source frame compilation, and source diagnostic + rendering. +- Produces: one-argument pure `.concat(other)` support for proof-core lists, + returning a new list with the receiver values followed by the argument list + values and rejecting incompatible concrete list element types statically. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `[7].concat([9])` through `evalExpr`, `checkExpr`, +closed expression bytecode execution, frame expression compilation, +`checkedFrameSourceLocal?`, and `sourceLocal?`. Include an empty-receiver +runtime example and an incompatible-list static diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because list method evaluation and static method compatibility +do not yet support `.concat(other)`. + +- [x] **Step 3: Implement shared runtime support** + +Wire list `.concat(other)` through structural list concatenation without +mutating the receiver or argument. + +- [x] **Step 4: Implement static method typing** + +Extend static method compatibility so `list[T].concat(list[U])` returns +`list[T]` when `T` and `U` are compatible. + +- [x] **Step 5: Document the proof-core method** + +Record list `.concat(other)` in the proof-core method surface and checked +compiler coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 172: Proof-Core List `prepend(value)` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, static method compatibility, stack/frame + method opcodes, checked source frame compilation, and source diagnostic + rendering. +- Produces: one-argument pure `.prepend(value)` support for proof-core lists, + returning a new `list[T]` with the value at the beginning and rejecting + incompatible concrete element types statically. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `[9].prepend(7)` through `evalExpr`, `checkExpr`, +closed expression bytecode execution, frame expression compilation, +`checkedFrameSourceLocal?`, and `sourceLocal?`. Include an empty-list runtime +example and an incompatible-element static diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because list method evaluation and static method compatibility +do not yet support `.prepend(value)`. + +- [x] **Step 3: Implement shared runtime support** + +Wire list `.prepend(value)` through structural cons/list construction without +mutating the receiver. + +- [x] **Step 4: Implement static method typing** + +Extend static method compatibility so `list[T].prepend(value)` returns +`list[T]` when `value` is compatible with `T`. + +- [x] **Step 5: Document the proof-core method** + +Record list `.prepend(value)` in the proof-core method surface and checked +compiler coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 173: Proof-Core List `join(separator)` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, static method compatibility, stack/frame + method opcodes, checked source frame compilation, and source diagnostic + rendering. +- Produces: one-argument pure `.join(separator)` support for proof-core string + lists, returning a `str` with separator text between elements and rejecting + incompatible concrete element or separator types statically. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `["a", "b"].join(",")` through `evalExpr`, +`checkExpr`, closed expression bytecode execution, frame expression +compilation, `checkedFrameSourceLocal?`, and `sourceLocal?`. Include an empty +list runtime example, a non-string runtime failure, and a concrete non-string +list static diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because list method evaluation and static method compatibility +do not yet support `.join(separator)`. + +- [x] **Step 3: Implement shared runtime support** + +Wire list `.join(separator)` through recursive string concatenation that fails +if any runtime list element is not a string. + +- [x] **Step 4: Implement static method typing** + +Extend static method compatibility so `list[str].join(str)` returns `str` and +incompatible concrete list or separator types are rejected. + +- [x] **Step 5: Document the proof-core method** + +Record list `.join(separator)` in the proof-core method surface and checked +compiler coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 174: Lean Semicolon Statement Separators + +**Files:** +- Modify: `Aether/Lexer.lean` +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core tokenization, located tokenization, parser terminator + skipping, source pipeline parsing, and diagnostic rendering. +- Produces: `;` as a proof-core statement separator equivalent to newline and + `~`, with stable located spans and source execution through the checked + compiler pipeline. + +- [x] **Step 1: Add failing lexer/parser/source examples** + +Add checked examples proving `;` tokenizes as its own token, located +tokenization preserves a half-open semicolon span, `parseProgram` accepts +semicolon-separated statements, and `sourceLocal?` executes a semicolon- +separated source program. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the lexer does not yet emit a semicolon separator and +the parser does not yet treat it as a terminator. + +- [x] **Step 3: Implement semicolon tokenization** + +Add `TokenKind.semicolon`, scan `;` in plain and located lexers, and render it +in diagnostics. + +- [x] **Step 4: Implement semicolon terminator handling** + +Treat `TokenKind.semicolon` like newline and `~` in parser terminator skipping +and located pipeline terminator skipping. + +- [x] **Step 5: Document the lexical surface** + +Record semicolon as a proof-core statement separator in the formal-core docs. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 175: Lean Multiline List Literal Separators + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core list literal parsing, newline tokens, comma tokens, and + source-pipeline execution. +- Produces: list literals that accept newlines between elements and a trailing + comma before `]`, matching the existing Rust parser surface while preserving + the same `Expr.list` core. + +- [x] **Step 1: Add failing parser/source examples** + +Add checked examples proving `parseProgram` accepts a multiline list literal +with a trailing comma and that `sourceLocal?` executes a source program using +that literal. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `parseListLiteral` currently treats newline after comma +and trailing comma before `]` as parse failures. + +- [x] **Step 3: Implement list literal separator handling** + +Teach `parseListLiteral` to skip newline tokens within list literals and accept +`]` immediately after a comma as a trailing-comma terminator. + +- [x] **Step 4: Document the list literal surface** + +Record multiline and trailing-comma list literal syntax in the formal-core +syntax and parser coverage docs. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 176: Lean Multiline Call Argument Separators + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core function-call and method-call argument parsing, newline + tokens, comma tokens, and source-pipeline execution. +- Produces: call and method argument lists that accept newlines between + arguments and a trailing comma before `)`, preserving positional and named + `Arg` nodes. + +- [x] **Step 1: Add failing parser/source examples** + +Add checked examples proving `parseExpr` accepts a multiline function call with +a trailing comma and that `sourceLocal?` executes a method call using the same +argument-list separator rules. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `parseArgList` currently treats newline after comma and +trailing comma before `)` as parse failures. + +- [x] **Step 3: Implement argument-list separator handling** + +Teach `parseArgList` to skip newline tokens within argument lists and accept +`)` immediately after a comma as a trailing-comma terminator. + +- [x] **Step 4: Document the argument-list surface** + +Record multiline and trailing-comma call/method argument syntax in the +formal-core grammar and parser coverage docs. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 177: Lean Multiline Function Parameter Separators + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core untyped and typed function parameter parsing, newline + tokens, comma tokens, and source-pipeline execution. +- Produces: function declarations whose parameter lists accept newlines between + parameters and a trailing comma before `)`, preserving the same `Stmt.fnDecl` + and typed function declaration core nodes. + +- [x] **Step 1: Add failing parser/source examples** + +Add checked examples proving `parseProgram` accepts multiline untyped and typed +function parameter lists with trailing commas, and that `sourceLocal?` executes +a source program using the untyped form. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `parseParamList` and `parseTypedParamList` currently +treat newline after comma and trailing comma before `)` as parse failures. + +- [x] **Step 3: Implement parameter-list separator handling** + +Teach untyped and typed parameter-list parsers to skip newline tokens inside +parameter lists and accept `)` immediately after a comma as a trailing-comma +terminator. + +- [x] **Step 4: Document the parameter-list surface** + +Record multiline and trailing-comma function parameter syntax in the formal-core +grammar and parser coverage docs. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 178: Lean Multiline List Type Annotations + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core annotated local declarations, typed parameters, declared + return types, and nested `list[...]` type annotation parsing. +- Produces: `list[...]` annotations that accept newlines after `[`, before + nested element types, and before `]`, preserving the same `AnnTy.list` core. + +- [x] **Step 1: Add failing parser/source examples** + +Add checked examples proving multiline `list[...]` annotations parse in local +declarations, typed parameters, and declared return types, and that +`sourceLocal?` executes a source program using the local annotation. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `parseAnnTy` currently treats newline tokens inside +`list[...]` annotations as parse failures. + +- [x] **Step 3: Implement type-annotation newline handling** + +Teach `parseAnnTy` to skip newline tokens at annotation boundaries and inside +`list[...]` before parsing the element type and closing bracket. + +- [x] **Step 4: Document multiline list type annotations** + +Record newline-tolerant `list[...]` annotations in the formal-core grammar and +parser coverage docs. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 179: Lean Line-Broken Block Openings + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core block parsing, statement terminator skipping, structured + statements, function declarations, and source-pipeline execution. +- Produces: block-bearing forms that may place a statement separator between + the header and `{`, preserving the same structured statement core nodes. + +- [x] **Step 1: Add failing parser/source examples** + +Add checked examples proving `parseProgram` accepts a line-broken `if` block +opening and that `sourceLocal?` executes a function declaration whose `{` starts +after a newline. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `parseBlock` currently requires `{` immediately at the +current token. + +- [x] **Step 3: Implement block opening terminator skipping** + +Teach `parseBlock` to skip statement terminators before matching `{`, so all +block-bearing forms share the same line-broken opening behavior. + +- [x] **Step 4: Document block opening separators** + +Record that a separator may appear between a block header and opening brace in +the formal-core syntax and parser coverage docs. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 180: Lean Multiline Type Diagnostic Offsets + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: newline-tolerant `list[...]` type annotation parsing, parser + diagnostic offsets, and source diagnostic rendering. +- Produces: malformed multiline type annotations whose diagnostics point at the + offending token after skipped newlines rather than at the outer annotation. + +- [x] **Step 1: Add failing parser/source diagnostics** + +Add checked examples proving `let xs: list[\n = [1]` reports the `=` token as +the type error, with a rendered source span on line 2. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `annTyFailureOffset?` does not yet skip newline tokens +while walking malformed `list[...]` annotations. + +- [x] **Step 3: Implement newline-aware type diagnostic offsets** + +Teach the type diagnostic offset walker to skip newline tokens consistently at +annotation boundaries and inside `list[...]`. + +- [x] **Step 4: Document multiline type diagnostics** + +Record that parser diagnostics for malformed multiline list annotations point +at the offending token after skipped newlines. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 181: Lean Multiline Parenthesized Expressions + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core parenthesized expression parsing, newline tokens, and + source-pipeline execution. +- Produces: parenthesized expressions that accept newlines after `(` and before + `)`, preserving the grouped expression as the same core `Expr`. + +- [x] **Step 1: Add failing parser/source examples** + +Add checked examples proving `parseExpr` accepts a line-broken parenthesized +expression and that `sourceLocal?` executes a source program using that grouped +expression. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `parsePrimary` currently passes newline tokens directly +to `parseExpr` after `(` and requires `)` immediately after the expression. + +- [x] **Step 3: Implement parenthesis newline handling** + +Teach parenthesized-expression parsing to skip newline tokens after `(` and +before `)`. + +- [x] **Step 4: Document multiline parenthesized expressions** + +Record newline-tolerant parenthesized expressions in the formal-core grammar +and parser coverage docs. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 182: Lean Multiline Postfix Indexing + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core postfix index parsing, newline tokens, and checked + source-pipeline execution. +- Produces: postfix index expressions that accept newlines after `[` and before + `]`, preserving the same `Expr.index` core node. + +- [x] **Step 1: Add failing parser/source examples** + +Add checked examples proving `parseExpr` accepts a multiline postfix index and +that `sourceLocal?` executes a source program using that index form. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `parsePostfixLoop` currently passes newline tokens +directly to `parseExpr` after `[` and requires `]` immediately after the index. + +- [x] **Step 3: Implement index newline handling** + +Teach postfix index parsing to skip newline tokens after `[` and before `]`. + +- [x] **Step 4: Document multiline index expressions** + +Record newline-tolerant postfix indexing in the formal-core grammar and parser +coverage docs. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 183: Lean Multiline Postfix Member Access + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core postfix field/method parsing, newline tokens, and checked + source-pipeline execution. +- Produces: postfix field and method expressions that accept newlines after `.`, + preserving the same `Expr.field` and `Expr.method` core nodes. + +- [x] **Step 1: Add failing parser/source examples** + +Add checked examples proving `parseExpr` accepts multiline postfix field and +method access, and that `sourceLocal?` executes source programs using those +member forms. + +- [x] **Step 2: Verify red** + +Run `lake build`. + +Expected: FAIL before implementation on the new multiline member examples. + +- [x] **Step 3: Implement post-dot newline skipping** + +Add a parser helper that skips newline tokens after `.`, then use it before +matching the member identifier and optional method-call argument list. + +- [x] **Step 4: Document grammar and coverage** + +Update the formal grammar and parser coverage notes for newline-tolerant +postfix member access. + +- [x] **Step 5: Verify green** + +Run: +- `lake build` +- `cargo test -p aether-lang -p aether-cli` + +Expected: PASS. + +### Task 184: Lean Multiline Function Call Opening + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core function-call parsing, newline tokens, and checked + source-pipeline execution. +- Produces: function-call expressions that accept newlines between a callee name + and `(` without consuming ordinary statement-separator newlines when no call + follows. + +- [x] **Step 1: Add failing parser/source examples** + +Add checked examples proving `parseExpr` accepts a line-broken call opening and +that `sourceLocal?` executes a user-defined function call using that form. + +- [x] **Step 2: Verify red** + +Run `lake build`. + +Expected: FAIL before implementation on the new multiline call-opening example. + +- [x] **Step 3: Implement guarded call-opening newline skipping** + +Add a parser helper that skips newline tokens only while checking whether an +identifier or keyword-call name is followed by `(`. Fall back to the original +token tail for non-call identifiers so statement separators remain observable. + +- [x] **Step 4: Document grammar and coverage** + +Update the formal grammar and parser coverage notes for newline-tolerant +function-call openings. + +- [x] **Step 5: Verify green** + +Run: +- `lake build` +- `cargo test -p aether-lang -p aether-cli` + +Expected: PASS. + +### Task 185: Lean Multiline Unary Operands + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core unary parsing, newline tokens, and checked + source-pipeline execution. +- Produces: unary `-` and `!` expressions that accept newlines before their + operands while preserving the same `Expr.unary` core nodes. + +- [x] **Step 1: Add failing parser/source examples** + +Add checked examples proving `parseExpr` accepts line-broken unary operands and +that `sourceLocal?` executes source programs using both boolean and numeric +forms. + +- [x] **Step 2: Verify red** + +Run `lake build`. + +Expected: FAIL before implementation on the new multiline unary examples. + +- [x] **Step 3: Implement unary operand newline skipping** + +Add a parser helper that skips newline tokens after unary `-` and `!`, then use +it before recursively parsing each unary operand. + +- [x] **Step 4: Document grammar and coverage** + +Update the formal grammar and parser coverage notes for newline-tolerant unary +expressions. + +- [x] **Step 5: Verify green** + +Run: +- `lake build` +- `cargo test -p aether-lang -p aether-cli` + +Expected: PASS. + +### Task 186: Lean Multiline Binary Right-Hand Sides + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core binary operator parsing, newline tokens, and checked + source-pipeline execution. +- Produces: binary expressions that accept newlines after a recognized operator + before the right-hand operand while leaving newline statement separators + observable before operators. + +- [x] **Step 1: Add failing parser/source examples** + +Add checked examples proving `parseExpr` accepts line-broken arithmetic and +logical binary right-hand sides, and that `sourceLocal?` executes a source +program using that form. + +- [x] **Step 2: Verify red** + +Run `lake build`. + +Expected: FAIL before implementation on the new multiline binary examples. + +- [x] **Step 3: Implement binary RHS newline skipping** + +Add a parser helper that skips newline tokens after a recognized binary +operator, then call it before parsing the right-hand side in left-associative +operator parsing. + +- [x] **Step 4: Document grammar and coverage** + +Update the formal grammar and parser coverage notes for newline-tolerant binary +right-hand sides. + +- [x] **Step 5: Verify green** + +Run: +- `lake build` +- `cargo test -p aether-lang -p aether-cli` + +Expected: PASS. + +### Task 187: Lean Multiline Assignment Right-Hand Sides + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core let declarations, assignment statements, newline tokens, + and checked source-pipeline execution. +- Produces: statement-level `=` forms that accept newlines before the + right-hand expression for typed lets, untyped lets, and reassignments. + +- [x] **Step 1: Add failing parser/source examples** + +Add checked examples proving `parseProgram` accepts line-broken assignment +right-hand sides for untyped lets, typed lets, and reassignments, and that +`sourceLocal?` executes source programs using those forms. + +- [x] **Step 2: Verify red** + +Run `lake build`. + +Expected: FAIL before implementation on the new multiline assignment examples. + +- [x] **Step 3: Implement assignment RHS newline skipping** + +Add a parser helper that skips newline tokens after statement-level `=`, then +call it before parsing RHS expressions for typed lets, untyped lets, and +reassignments. + +- [x] **Step 4: Document grammar and coverage** + +Update the formal grammar and parser coverage notes for newline-tolerant +assignment right-hand sides. + +- [x] **Step 5: Verify green** + +Run: +- `lake build` +- `cargo test -p aether-lang -p aether-cli` + +Expected: PASS. + +### Task 188: Lean Multiline Control-Flow Conditions + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core `if`, `while`, conditional `seal until`, newline tokens, + and checked source-pipeline execution. +- Produces: control-flow condition forms that accept newlines after the + condition introducer before the condition expression. + +- [x] **Step 1: Add failing parser/source examples** + +Add checked examples proving `parseProgram` accepts line-broken conditions for +`if`, `while`, and `seal until`, and that `sourceLocal?` executes source using +those forms. + +- [x] **Step 2: Verify red** + +Run `lake build`. + +Expected: FAIL before implementation on the new multiline condition examples. + +- [x] **Step 3: Implement condition newline skipping** + +Add a parser helper that skips newline tokens after condition introducers, then +call it before parsing `if`, `while`, and `seal until` condition expressions. + +- [x] **Step 4: Document grammar and coverage** + +Update the formal grammar and parser coverage notes for newline-tolerant +control-flow conditions. + +- [x] **Step 5: Verify green** + +Run: +- `lake build` +- `cargo test -p aether-lang -p aether-cli` + +Expected: PASS. + +### Task 189: Lean Multiline Function Declaration Opening + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core function declarations, newline tokens, parameter-list + parsing, and checked source-pipeline execution. +- Produces: function declarations that accept newlines between the function name + and `(` before the parameter list. + +- [x] **Step 1: Add failing parser/source examples** + +Add checked examples proving `parseProgram` accepts line-broken function +declaration openings for untyped and typed declarations, and that `sourceLocal?` +executes a source program using that declaration form. + +- [x] **Step 2: Verify red** + +Run `lake build`. + +Expected: FAIL before implementation on the new multiline function declaration +opening examples. + +- [x] **Step 3: Implement function declaration opening newline skipping** + +Add a parser helper that skips newline tokens after a function declaration name, +then use it before matching the parameter-list opening `(`. Keep parse +diagnostic classification aligned with the same declaration shape. + +- [x] **Step 4: Document grammar and coverage** + +Update the formal grammar and parser coverage notes for newline-tolerant +function declaration openings. + +- [x] **Step 5: Verify green** + +Run: +- `lake build` +- `cargo test -p aether-lang -p aether-cli` + +Expected: PASS. + +### Task 64: Lean List Literal Core Support + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: lexer `[` and `]` tokens and the existing Rust list literal + surface. +- Produces: first-class proof-core list literals that parse, type-check, + evaluate, compare structurally, lower to VM list-construction opcodes, and + run through the source pipeline. + +- [x] **Step 1: Add failing checked examples** + +Add Lean examples for parsing empty and non-empty list literals, evaluating +lists, type-checking them as `list`, rejecting numeric operators on lists, +compiling dynamic list construction in stack and frame VMs, and running list +source through the pipeline. + +- [x] **Step 2: Extend core syntax and values** + +Add `Expr.list` and `Value.list`, with structural equality support. + +- [x] **Step 3: Extend parser and static checker** + +Parse bracketed comma-separated expressions into `Expr.list`, treat list +literals as `Ty.list`, and still check element expressions for undeclared +variables and other errors. + +- [x] **Step 4: Extend VM and pipeline rendering** + +Add stack and frame VM list-construction opcodes so list elements are evaluated +before constructing `Value.list`; render `Ty.list` diagnostics. + +- [x] **Step 5: Document and verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 65: Lean List Indexing Support + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core `Expr.list`/`Value.list` and lexer bracket tokens. +- Produces: postfix `expr[index]` syntax that parses, checks list/numeric + operands, evaluates and lowers through both VMs, and reports source spans. + +- [x] **Step 1: Add failing checked examples** + +Add Lean examples for core list indexing, parser postfix syntax, static +success/error paths, stack and frame VM lowering, and source-pipeline runtime +and diagnostic behavior. + +- [x] **Step 2: Extend core syntax and evaluation** + +Add `Expr.index` and runtime evaluation for dynamic list indexing, returning +`none` for non-list targets, non-numeric indexes, negative indexes, and +out-of-bounds indexes. + +- [x] **Step 3: Extend parser and static checker** + +Parse bracket postfix indexing after primary expressions and calls. Check that +the target is list-like, the index is num-like, and return `Ty.unknown` because +current lists are heterogeneous. + +- [x] **Step 4: Extend VM and pipeline rendering** + +Add stack and frame VM index opcodes, lower target/index bytecode in order, and +surface precise static index diagnostics. + +- [x] **Step 5: Document and verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 66: Lean Fixed Micro-Precision Float Literals + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: lexer `float int fracMicros` tokens. +- Produces: proof-core float expressions and values using fixed + micro-precision arithmetic where possible, with parser, evaluator, static + checker, bytecode, and source pipeline coverage. + +- [x] **Step 1: Add failing checked examples** + +Add Lean examples for parsing float literals, evaluating float arithmetic and +comparisons, type-checking floats as `num`, lowering float constants to both +VMs, and running float source through the pipeline. + +- [x] **Step 2: Extend core syntax and numeric evaluation** + +Add `Expr.float` and `Value.float`, preserve existing integer behavior for +integer-only operations, and evaluate mixed numeric operations through +micro-unit conversion. + +- [x] **Step 3: Extend parser and static checker** + +Parse `Lexer.TokenKind.float` into `Expr.float` and keep static type `Ty.num` +for both integer and fixed micro-precision literals. + +- [x] **Step 4: Extend VM and pipeline behavior** + +Lower float literals to `PUSH` values in stack and frame VMs and add source +pipeline examples proving float programs run rather than parse-fail. + +- [x] **Step 5: Document and verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 67: Lean Postfix Field Access + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: lexer `.` tokens and postfix expression parsing. +- Produces: proof-core `expr.field` syntax with executable `.length` support + for strings and lists, static rejection for unsupported concrete fields, VM + lowering, and source diagnostics. + +- [x] **Step 1: Add failing checked examples** + +Add Lean examples for parsing field access, evaluating list and string +`length`, statically typing supported fields, rejecting unsupported concrete +field access, compiling field bytecode in stack/frame VMs, and running source +field access through the pipeline. + +- [x] **Step 2: Extend core syntax and evaluation** + +Add `Expr.field` and evaluate `.length` for `Value.list` and `Value.str`, +returning `none` for unsupported fields or target values. + +- [x] **Step 3: Extend parser and static checker** + +Parse `.` identifier as a postfix expression after primaries, calls, and +indexing. Type supported concrete fields and return `Ty.unknown` for unknown +targets. + +- [x] **Step 4: Extend VM and pipeline diagnostics** + +Add stack and frame VM field opcodes and render unsupported field diagnostics +with useful source spans. + +- [x] **Step 5: Document and verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 68: Lean Postfix Method Calls + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: lexer `.` tokens, parenthesized argument parsing, and postfix + expression parsing. +- Produces: proof-core `expr.method(args)` syntax with executable pure + `.len()` support for strings and lists, static rejection for unsupported + concrete methods, VM lowering, and source diagnostics. + +- [x] **Step 1: Add failing checked examples** + +Add Lean examples for parsing postfix method calls, evaluating list and string +`.len()`, statically typing supported method calls, rejecting unsupported +concrete method calls, compiling method bytecode in stack/frame VMs, and +running method calls through the source pipeline. + +- [x] **Step 2: Extend core syntax and evaluation** + +Add `Expr.method` and evaluate `.len()` with zero arguments for `Value.list` +and `Value.str`, returning `none` for unsupported methods or arities. + +- [x] **Step 3: Extend parser and static checker** + +Parse `.` identifier followed by parentheses as a postfix method call. Check +argument expressions and type supported concrete methods. + +- [x] **Step 4: Extend VM and pipeline diagnostics** + +Add stack and frame VM method opcodes and render unsupported method diagnostics +with useful source spans. + +- [x] **Step 5: Document and verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 69: Lean String Escape Lexing + +**Files:** +- Modify: `Aether/Lexer.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: lexer string literal scanning and located-token spans. +- Produces: proof-core string escapes for `\"`, `\\`, `\n`, and `\t`, + plus deterministic invalid-escape diagnostics. + +- [x] **Step 1: Add failing checked examples** + +Add Lean examples for escaped quote/backslash/newline/tab tokenization, +source-pipeline execution of escaped strings, and invalid escape diagnostic +rendering. + +- [x] **Step 2: Extend `readString`** + +Teach the Lean string scanner to consume valid escape sequences into their +runtime characters while preserving source consumption for located spans. + +- [x] **Step 3: Add invalid escape diagnostics** + +Return a lexer error for unsupported escape sequences and ensure located-token +spans point at the offending escape. + +- [x] **Step 4: Document and verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 70: Lean Block Comment Lexing + +**Files:** +- Modify: `Aether/Lexer.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: lexer slash/star character scanning and located-token position + tracking. +- Produces: `/* ... */` block comments in the Lean proof-core lexer, including + newline-aware located scanning and unterminated-comment diagnostics. + +- [x] **Step 1: Add failing checked examples** + +Add Lean examples proving block comments are skipped in plain tokenization, +located tokenization keeps following token positions correct across comment +newlines, and unterminated block comments report deterministic lexer errors. + +- [x] **Step 2: Implement plain block-comment skipping** + +Teach `scanFuel` to recognize `/*`, consume through the next `*/`, and emit an +unterminated block-comment lexer error when EOF arrives first. + +- [x] **Step 3: Implement located block-comment skipping** + +Teach `scanLocatedFuel` to skip block comments while advancing line/column +positions for all consumed characters and to span unterminated block comments +from the opening slash to EOF. + +- [x] **Step 4: Document and verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 71: Lean Static List Element Typing + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: existing Lean `Ty.list`, list literal checking, and index + expression checking. +- Produces: element-aware list static types for homogeneous proof-core lists, + precise indexed expression types, and preserved `unknown` element typing for + mixed or otherwise imprecise lists. + +- [x] **Step 1: Add failing checked examples** + +Add Lean examples proving numeric and string list literals infer `list[num]` +and `list[str]`, mixed literals remain `list[unknown]`, indexing a homogeneous +list returns the element type, and assigning a homogeneous indexed result to an +incompatible existing variable is rejected. + +- [x] **Step 2: Extend static type representation** + +Represent list types with an element type while keeping `unknown` available for +mixed lists, empty lists, function results, and imprecise values. + +- [x] **Step 3: Infer list and index types** + +Thread element type inference through both the `Option` checker and detailed +checker, update compatibility helpers for fields, methods, assignment, and +diagnostic rendering. + +- [x] **Step 4: Document and verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 72: Lean Static Function Result Inference + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Lean function declarations, return statements, and static function + signature collection. +- Produces: function signatures that retain an inferred result type when + visible from the body, so calls to concrete-return functions no longer always + type as `unknown`. + +- [x] **Step 1: Add failing checked examples** + +Add Lean examples proving a function returning a numeric literal makes +`one()` type as `num`, a `let` initialized from that call records `num`, and a +later assignment of `str` to that binding is rejected. + +- [x] **Step 2: Extend function signatures** + +Add a result type to `FnSig` while preserving arity checking and duplicate +function diagnostics. + +- [x] **Step 3: Infer result types during signature collection** + +Infer result types from statically visible `return` statements using unknown +parameter bindings, merging multiple concrete returns conservatively to +`unknown` when they disagree. + +- [x] **Step 4: Document and verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 73: Lean Static Implicit Function Result Inference + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: existing function result inference and the runtime rule that a + function body without explicit `return` uses the final expression value. +- Produces: static function signatures that infer result types from final + expression statements when no explicit return determines the result. + +- [x] **Step 1: Add failing checked examples** + +Add Lean examples proving a function whose body ends in an expression infers +that expression's type, records concrete types for variables initialized from +such calls, and rejects incompatible assignments after such calls. + +- [x] **Step 2: Infer final expression types** + +Teach the signature inference pass to use a final `Stmt.expr` as the implicit +function result while preserving explicit `return` inference and conservative +`unknown` merging for disagreement. + +- [x] **Step 3: Document and verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 74: Lean Static If-Branch Environment Joins + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: static block checking for `if`/`else` branches and the existing + variable environment model. +- Produces: conservative static environment joins for variables introduced in + both branches with compatible types. + +- [x] **Step 1: Add failing checked examples** + +Add Lean examples proving a variable introduced as the same type in both +branches is available after the `if`, and a variable introduced with +incompatible branch types is not joined and therefore remains unavailable to +later statements. + +- [x] **Step 2: Implement branch joins** + +Compute variables newly introduced by both branch result environments, merge +compatible types, and return the joined state from `if` checking. Preserve the +existing behavior for `if` without `else`. + +- [x] **Step 3: Document and verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 75: Lean Function Result Inference Uses If-Branch Joins + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 74 branch environment joins and Task 73 implicit + final-expression function result inference. +- Produces: function signature inference that can use variables introduced by + compatible `if`/`else` branches before a final expression. + +- [x] **Step 1: Add failing checked examples** + +Add Lean examples proving a function with `if cond { let x = 1 } else { let x = +2 }; x` infers `num`, and incompatible branch bindings keep the final variable +unavailable so the call result remains imprecise. + +- [x] **Step 2: Thread branch joins through signature inference** + +Teach the signature inference environment pass to compute the same conservative +join for `if`/`else` statements as the main static checker. + +- [x] **Step 3: Document and verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 76: Lean Static Assignment Type Refinement + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: static assignment compatibility, `unknown` types from function + parameters/results, and the variable environment model. +- Produces: assignment checking that refines `unknown` targets to concrete + assigned types for later statements. + +- [x] **Step 1: Add failing checked examples** + +Add Lean examples proving an `unknown` variable assigned a numeric value is +tracked as `num`, and a later incompatible assignment is rejected with an +assignment mismatch. + +- [x] **Step 2: Implement assignment refinement** + +Update the static variable environment on successful assignment, refining +`unknown` targets and nested `list[unknown]` targets while preserving existing +concrete types. + +- [x] **Step 3: Document and verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 77: Lean If-Branch Assignment Refinement Joins + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 74 branch joins and Task 76 assignment type refinement. +- Produces: branch joins that can refine existing `unknown` variables when both + branches assign compatible concrete types. + +- [x] **Step 1: Add failing checked examples** + +Add Lean examples proving an existing `unknown` variable assigned `num` in both +branches is refined to `num` after the `if`, and incompatible branch assignments +do not refine the variable. + +- [x] **Step 2: Extend branch joins** + +Update the join helper to refine existing `unknown` variables from compatible +then/else branch environments while keeping incompatible branch updates +imprecise. + +- [x] **Step 3: Document and verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 63: Lean String Literal Core Support + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: lexer `stringLit` tokens and the existing Rust string literal + surface. +- Produces: first-class proof-core string literals that parse, type-check, + evaluate, compare for equality/inequality, lower to VM push instructions, + and run through the source pipeline. + +- [x] **Step 1: Add failing checked examples** + +Add Lean examples for parsing a string literal, type-checking it as `str`, +evaluating it, rejecting numeric operators on strings, compiling it to stack +and frame VM bytecode, and running a source string through the pipeline. + +- [x] **Step 2: Extend core syntax and values** + +Add `Expr.str` and `Value.str`, with equality support matching the Rust +interpreter surface. + +- [x] **Step 3: Extend parser and static checker** + +Parse `stringLit` tokens into `Expr.str`, treat string literals as `Ty.str`, +and preserve existing operand validation so only equality/inequality works for +known string operands. + +- [x] **Step 4: Extend VM and pipeline rendering** + +Lower string expressions to `push (Value.str ...)` in stack and frame compilers +and render `Ty.str` diagnostics. + +- [x] **Step 5: Document and verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 62: Lean Expression-Start Parse Diagnostic Spans + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: parser statement failure classification and located token streams. +- Produces: parse diagnostics that point at the offending expression-start + token for malformed `let`, assignment, return, `if`, `while`, and + conditional `seal` expressions when the first expression token is invalid. + +- [x] **Step 1: Add failing parser examples** + +Update checked parser diagnostics so invalid expression starts report the +offending token instead of the statement keyword. + +- [x] **Step 2: Add failing source diagnostic examples** + +Update rendered source-pipeline examples so malformed expression starts use the +offending token's source range. + +- [x] **Step 3: Implement expression-start failure offsets** + +Add token-level helpers that identify invalid expression starts and return the +diagnostic token offset while preserving existing statement-start diagnostics +for incomplete expressions whose first token is valid. + +- [x] **Step 4: Document diagnostic precision** + +Record that the located source pipeline can now point at invalid expression +starts without requiring a fully spanned AST. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 28: Lean Frame Compiler For/Seal Loops + +**Files:** +- Modify: `Aether/VM.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 27 frame compiler control flow and proof-core loop syntax +- Produces: frame bytecode support for `forRange` and `seal` inside + frame-compiled functions + +- [x] **Step 1: Add frame forRange lowering** + +Lower integer-range `for` loops to iterator initialization, `iterator < end`, +body bytecode, iterator increment, and a backward frame jump. + +- [x] **Step 2: Add frame seal lowering** + +Lower `seal until condition` to a pre-body exit check using `not` plus +`FrameOp.jmpIfFalse`, and lower bare `seal` to body bytecode followed by a +backward `FrameOp.jmp`. + +- [x] **Step 3: Add checked execution examples** + +Verify a function using `forRange` accumulation and a function using +conditional `seal` through `runCompiledFrameProgram`. + +- [x] **Step 4: Document remaining loop boundaries** + +Record that loop-control patching for `break`/`continue`, named/method calls, +and full correspondence proofs remain future work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 27: Lean Frame Compiler Control Flow + +**Files:** +- Modify: `Aether/VM.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 26 source-to-frame compiler and structured statement syntax +- Produces: frame bytecode support for `if`/`else` and `while` inside + frame-compiled functions + +- [x] **Step 1: Add frame jump opcodes** + +Extend `FrameOp` with `jmp` and `jmpIfFalse`, and execute them in `frameStep`. + +- [x] **Step 2: Add frame if/while lowering** + +Lower `Stmt.ifThenElse` and `Stmt.while` in `compileFrameStmt` using relative +frame jumps and recursive frame block compilation. + +- [x] **Step 3: Add checked execution examples** + +Verify a function with `if`/`else` and a function with a bounded `while` loop +through `runCompiledFrameProgram`. + +- [x] **Step 4: Document remaining control-flow boundaries** + +Record that frame-compiled `for`/`seal`, loop-control patching for +`break`/`continue`, named/method calls, and full correspondence proofs remain +future work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 26: Lean Source-to-Frame Function Compiler + +**Files:** +- Modify: `Aether/VM.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 25 frame VM, `Stmt.fnDecl`, `Stmt.ret`, and `Expr.call` +- Produces: source-to-frame bytecode compilation for top-level functions and + positional calls + +- [x] **Step 1: Add frame compiler environments** + +Define frame function metadata, function lookup tables, function collection, +parameter-to-slot mapping, and target computation for hoisted function bodies. + +- [x] **Step 2: Add frame expression and statement compilation** + +Compile literals, variables, unary/binary expressions, positional calls, `let`, +assignment, expression statements, and `return` into `FrameOp` bytecode. + +- [x] **Step 3: Add program compilation and execution helpers** + +Compile main statements with top-level `fn` declarations skipped, append +`HALT`, append hoisted function bodies, and expose `runCompiledFrameProgram`. + +- [x] **Step 4: Add checked compiler examples and document boundaries** + +Verify emitted bytecode and execution for explicit-return functions and +parameter shadowing. Record that branches/loops inside frame-compiled functions, +named/method calls, and full correspondence proofs remain future work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 25: Lean VM Call Frames + +**Files:** +- Modify: `Aether/VM.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Rust VM call-frame behavior and Lean proof-core VM values/operators +- Produces: direct Lean bytecode VM model for function calls and returns + +- [x] **Step 1: Add frame bytecode and state** + +Define `FrameOp`, `CallFrame`, `FrameState`, and initial frame-state helpers. + +- [x] **Step 2: Add call/return execution** + +Implement bounded `frameStep`, `runFrameFuel`, and `runFrame` with argument +passing, fresh callee locals, return IP tracking, caller-local restoration, and +unit return for empty return stacks. + +- [x] **Step 3: Add checked bytecode examples** + +Verify direct bytecode examples for explicit return values, implicit unit +return, and caller-local restoration after a call. + +- [x] **Step 4: Document remaining compiler boundary** + +Record that source-to-call-frame compilation remains future work before full +compiler/VM correspondence proofs. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 24: Lean Function Runtime Semantics + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 23 function parser output, `Expr.call`, and `Stmt.fnDecl` +- Produces: bounded executable Lean semantics for user-defined function calls + +- [x] **Step 1: Add function environment model** + +Define `Function`, `FnEnv`, lookup, binding, and parameter binding helpers. + +- [x] **Step 2: Add executable function evaluator** + +Add bounded `evalExprWithFns`, `execStmtWithFns`, and `execBlockWithFns` for +function declarations, positional calls, explicit returns, and implicit +last-value returns. + +- [x] **Step 3: Add checked semantic examples** + +Verify explicit return, implicit final-expression return, arity mismatch, and +parameter shadowing with `native_decide`. + +- [x] **Step 4: Document remaining function boundaries** + +Record that VM call-frame bytecode, named/method calls, source spans, and +correctness theorems remain future formalization work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 23: Lean Function Parser + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `Aether.Core.Expr.call`, `Aether.Core.Stmt.fnDecl`, and the Lean + token stream +- Produces: executable Lean parsing for positional function calls and function + declarations + +- [x] **Step 1: Add positional call argument parsing** + +Parse `name(expr, ...)` into `Expr.call name args`, including empty and +comma-separated argument lists. + +- [x] **Step 2: Add function parameter parsing** + +Parse `fn name(param, ...) { ... }` parameter lists into `List Ident`. + +- [x] **Step 3: Add function declaration parsing** + +Parse function declarations into `Stmt.fnDecl` with a brace-delimited body. + +- [x] **Step 4: Add checked examples and document boundaries** + +Verify positional call parsing and a function declaration plus call site with +`native_decide`; record that named arguments, method calls, source spans, +function runtime semantics, and correctness theorems remain future work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 22: Lean VM For/Seal Compilation + +**Files:** +- Modify: `Aether/VM.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 21 parsed loop syntax, `Aether.Core` loop statements, and + Lean VM branch/jump instructions +- Produces: checked compiler lowering for integer-range `for` loops and `seal` + loops + +- [x] **Step 1: Add forRange lowering** + +Resolve or allocate the iterator slot, initialize it to the start value, compile +the body with the iterator in scope, check `iterator < end`, increment by one, +and jump back to the condition. + +- [x] **Step 2: Add seal lowering** + +Lower `seal until condition` to a pre-body exit check using `not` plus +`JMP_IF_FALSE`, and lower bare `seal` to body bytecode followed by a backward +`JMP`. + +- [x] **Step 3: Add checked bytecode and execution examples** + +Verify emitted bytecode and final VM state for `for i in 0..3`, verify emitted +bytecode and final VM state for `seal until x == 3`, and verify emitted bytecode +for bare `seal`. + +- [x] **Step 4: Document remaining VM boundaries** + +Record that loop-control patching for `break`/`continue`, functions, and call +frames remain future Lean VM proof targets. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 21: Lean For/Seal Parser + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 20 structured parser and `Aether.Core` loop statement syntax +- Produces: executable Lean parsing for integer-range `for` loops and `seal` + loops + +- [x] **Step 1: Add integer range parser** + +Parse numeric `start..end` ranges for the Lean proof-core `Stmt.forRange` +surface. + +- [x] **Step 2: Add for-loop parsing** + +Parse `for name in start..end { ... }` into `Stmt.forRange`. + +- [x] **Step 3: Add seal-loop parsing** + +Parse `seal until condition { ... }` into `Stmt.seal (some condition)` and +`seal { ... }` into `Stmt.seal none`. + +- [x] **Step 4: Add checked parser examples and document boundaries** + +Verify `for`, conditional `seal`, and unconditional `seal` parsing with +`native_decide`; record that functions, calls, source spans, and correctness +theorems remain future parser work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 20: Lean Structured Statement Parser + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 19 proof-core parser and `Aether.Core` structured statement syntax +- Produces: executable Lean parsing for brace blocks, `if`/`else`, and `while` + +- [x] **Step 1: Add brace block parser** + +Parse `{ ... }` blocks with newline and tilde separators while letting `}` serve +as a statement boundary inside blocks. + +- [x] **Step 2: Add structured statement parsing** + +Parse `if condition { ... }`, optional `else { ... }`, and +`while condition { ... }` into `Stmt.ifThenElse` and `Stmt.while`. + +- [x] **Step 3: Add checked parser examples** + +Verify block parsing, if/else parsing, and while-body parsing with +`native_decide`. + +- [x] **Step 4: Document remaining parser boundaries** + +Record that structured `for`/`seal`, functions, calls, source spans, and parser +correctness theorems remain future Lean parser work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 19: Lean Proof-Core Parser + +**Files:** +- Add: `Aether/Parser.lean` +- Modify: `Aether.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 18 Lean lexer and `Aether.Core` expression/statement syntax +- Produces: executable Lean parser for proof-core expressions and simple statements + +- [x] **Step 1: Add precedence-aware expression parser** + +Parse literals, variables, parenthesized expressions, unary negation/not, +multiplicative/additive operators, comparisons, equality, logical `&&`, and +logical `||` into `Aether.Core.Expr`. + +- [x] **Step 2: Add simple statement parser** + +Parse `let`, assignment, `return`, `break`, `continue`, expression statements, +newline separators, tilde separators, and EOF termination into +`Aether.Core.Stmt`. + +- [x] **Step 3: Add checked parser examples** + +Verify arithmetic precedence, parenthesized boolean precedence, tilde-separated +programs, and newline-separated control-flow statements with `native_decide`. + +- [x] **Step 4: Wire module and document boundaries** + +Import `Aether.Parser` from the top-level Lean module and document remaining +block parsing, structured control flow, functions, calls, spans, and parser +correctness theorem work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 18: Lean Lexical Scanner + +**Files:** +- Add: `Aether/Lexer.lean` +- Modify: `Aether.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Rust lexer token surface and Lean proof DSL module scaffold +- Produces: executable Lean token-kind scanner with checked lexical examples + +- [x] **Step 1: Model token kinds in Lean** + +Define keyword, identifier, literal, operator, punctuation, newline, EOF, and +error token kinds corresponding to the Rust lexer surface. + +- [x] **Step 2: Implement executable scanning** + +Scan identifiers/keywords, integers, fixed micro-precision floats, strings, +comments, statement separators, ranges, operators, delimiters, lexical errors, +and EOF. + +- [x] **Step 3: Add checked examples** + +Verify `1.5` versus `1..10`, keyword/operator scanning, comment handling, +tilde separators, and string termination errors with `native_decide`. + +- [x] **Step 4: Wire module and document boundaries** + +Import `Aether.Lexer` from the top-level Lean module and record that source-span +tracking plus parser integration remain future work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 17: Lean While Loop Compilation + +**Files:** +- Modify: `Aether/VM.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 16 branch-aware compiler and VM relative jumps +- Produces: checked compiler lowering for bounded `while` statements + +- [x] **Step 1: Add while lowering to branch-aware compiler** + +Compile the loop condition, emit `JMP_IF_FALSE` over the body and back jump, +compile the body with threaded slots, and emit a negative `JMP` back to the +condition. + +- [x] **Step 2: Add checked bytecode example** + +Verify emitted bytecode for `while x < 3 { x = x + 1 }`, including the forward +exit offset and backward continuation offset. + +- [x] **Step 3: Add checked execution examples** + +Verify bounded VM execution for both multi-iteration and zero-iteration loop +paths. + +- [x] **Step 4: Document boundaries** + +Record that bounded `while` lowering is modeled in Lean, while `for`/`seal` +loops, functions, and call frames remain future Lean compiler formalization +work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 16: Lean If/Branch Compilation + +**Files:** +- Modify: `Aether/VM.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 15 block compiler and VM branch opcodes +- Produces: checked branch-aware compiler for `if` statements + +- [x] **Step 1: Add branch-aware compiler layer** + +Define mutually recursive statement/block compilation that supports previous straight-line forms plus `if`. + +- [x] **Step 2: Lower if to VM jumps** + +Emit condition bytecode, `JMP_IF_FALSE`, then-branch bytecode, optional `JMP`, and else-branch bytecode with checked offsets. + +- [x] **Step 3: Add checked true/false branch examples** + +Verify emitted bytecode for an if/else assignment and final VM locals for true and false conditions. + +- [x] **Step 4: Document boundaries** + +Record that loops, functions, and call frames remain future Lean compiler formalization work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 15: Lean Straight-Line Block Compilation + +**Files:** +- Modify: `Aether/VM.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 14 straight-line statement compiler +- Produces: checked block compiler for sequences of supported straight-line statements + +- [x] **Step 1: Add block compiler** + +Thread `SlotEnv` through a list of statements and concatenate emitted bytecode. + +- [x] **Step 2: Add block execution helper** + +Run compiled block bytecode with a trailing `halt` against explicit initial locals. + +- [x] **Step 3: Add checked block examples** + +Verify emitted bytecode and final VM state for `let x = 1; x = x + 2; x`, and verify unsupported block statements fail compilation. + +- [x] **Step 4: Document boundaries** + +Record that branching, loops, functions, and call frames remain future block compiler formalization work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 14: Lean Straight-Line Statement Compilation + +**Files:** +- Modify: `Aether/VM.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 13 slot-aware expression compilation and VM local-slot model +- Produces: checked statement compilation for `let`, assignment, and expression statements + +- [x] **Step 1: Add slot resolution** + +Define `SlotEnv.resolve` so declarations allocate a new local slot or reuse an existing one. + +- [x] **Step 2: Add straight-line statement compiler** + +Lower `let` to expression bytecode plus `STORE`, lower assignment to existing-slot `STORE`, and lower expression statements to expression bytecode. + +- [x] **Step 3: Add checked statement execution examples** + +Verify emitted bytecode and final VM state for declaration and assignment, plus missing-slot assignment failure. + +- [x] **Step 4: Document boundaries** + +Record that branching, loops, functions, and call frames remain future statement compiler formalization work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 13: Lean Variable Slot Expression Compilation + +**Files:** +- Modify: `Aether/VM.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 12 closed-expression compiler and VM locals model +- Produces: checked slot-aware expression compiler for variables + +- [x] **Step 1: Add slot environment** + +Define `SlotEnv` as an explicit identifier-to-local-slot table with lookup. + +- [x] **Step 2: Add slot-aware expression compiler** + +Lower variables to `Op.load slot` while preserving literal, unary, and binary expression lowering. + +- [x] **Step 3: Add checked variable correspondence examples** + +Verify emitted bytecode for `x + 2`, compare compiled execution against direct `evalExpr`, and check missing variables fail compilation. + +- [x] **Step 4: Document scope** + +Record that slot-aware variable expression compilation is modeled, while function calls and call frames remain future Lean work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 12: Lean Expression Compiler Correspondence Slice + +**Files:** +- Modify: `Aether/VM.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `Aether.Core.evalExpr` and `Aether.VM` stack-machine execution +- Produces: checked expression-to-bytecode compiler for closed literal/unary/binary expressions and evaluator/compiler agreement examples + +- [x] **Step 1: Add `compileExpr`** + +Lower numeric literals, boolean literals, unary expressions, and binary expressions into stack bytecode. + +- [x] **Step 2: Add compiled-expression execution helpers** + +Run emitted bytecode with a trailing `halt` and expose the top stack value for checked examples. + +- [x] **Step 3: Add checked correspondence examples** + +Compare compiled execution with direct expression evaluation for multiplication, nested arithmetic with modulo and unary negation, and boolean negation. + +- [x] **Step 4: Document scope and remaining boundaries** + +Call out that variable-slot compilation, calls, and call frames remain future formalization work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 11: Lean Core VM Semantics + +**Files:** +- Create: `Aether/VM.lean` +- Modify: `Aether.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `Aether.Core` values, operators, and truthiness +- Produces: checked Lean stack-machine model for the core Titan VM subset + +- [x] **Step 1: Define Lean bytecode and VM state** + +Add opcodes for constants, locals, binary/unary operations, unconditional jumps, conditional false jumps, and halt. + +- [x] **Step 2: Define VM stepping** + +Implement one-step execution plus bounded fuel-based execution over instruction pointer, stack, locals, code, and halted state. + +- [x] **Step 3: Add checked VM examples** + +Cover arithmetic stack execution, local store/load with unary negation, and false conditional jump behavior. + +- [x] **Step 4: Document VM formalization status** + +Record the current bytecode subset and call out function call-frame formalization as remaining work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 9: Lean 4 Formalization Scaffold + +**Files:** +- Create: `lakefile.lean` +- Create: `lean-toolchain` +- Create: `Aether.lean` +- Create: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-facing core language from `docs/FORMAL_CORE.md` +- Produces: checked Lean 4 definitions for core syntax, values, expression evaluation, environments, and statement flow + +- [x] **Step 1: Check local Lean tooling** + +Verify `lean --version` and `lake --version` are available. + +- [x] **Step 2: Add Lake package scaffold** + +Create a root Lake package with `Aether` as the default target and pin the Lean toolchain. + +- [x] **Step 3: Encode the initial core** + +Define `Expr`, `Stmt`, `Value`, `Flow`, `Env`, expression evaluation, and a small statement-step relation. + +- [x] **Step 4: Add initial checked facts** + +Prove lookup after bind and variable evaluation after bind; add checked examples for modulo and truthiness-backed boolean evaluation. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 61: Lean Float Literal Parser Rejection + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: lexer float tokens and the integer-valued proof-core parser. +- Produces: explicit rejection of decimal float literals by the current + proof-core parser, avoiding silent truncation into integer expressions. + +- [x] **Step 1: Add failing parser examples** + +Add checked examples requiring `parseExpr` and `parseProgramDetailed` to reject +float literals in proof-core expressions. + +- [x] **Step 2: Add failing source diagnostic example** + +Add a rendered source-pipeline diagnostic for a statement containing a float +literal. + +- [x] **Step 3: Remove float-to-int parser conversion** + +Remove the parser branch that turns lexer float tokens into integer `Expr.num` +values. + +- [x] **Step 4: Document lexer/parser boundary** + +Record that decimal float tokens remain lexed for host/runtime compatibility +but are outside the current integer proof-core expression parser. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 8: VM Loop Flow Parity + +**Files:** +- Modify: `crates/aether-lang/src/vm.rs` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: parser/interpreter `Break` and `Continue` statements from Task 3 +- Produces: compiler-lowered VM jumps for loop exit and loop continuation + +- [x] **Step 1: Write failing VM tests for loop flow** + +Cover `break` exiting a `while` loop and `continue` skipping the rest of a `while` loop body through source-level VM execution. + +- [x] **Step 2: Verify red** + +Run: `cargo test -p aether-lang vm::tests::test_vm_ -- --nocapture` +Expected: FAIL only for the new loop-flow VM tests. + +- [x] **Step 3: Add compiler loop context patching** + +Record pending `break` and `continue` jump sites while compiling loop bodies, then patch them to the loop exit or continuation target after lowering the loop. + +- [x] **Step 4: Document VM loop-flow correspondence** + +State that VM `break` and `continue` are compiler-lowered `JMP` instructions rather than dedicated runtime opcodes. + +- [x] **Step 5: Verify** + +Run: `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 6: Compiler/VM Lowering Parity + +**Files:** +- Modify: `crates/aether-lang/src/vm.rs` +- Modify: `crates/aether-lang/src/ast.rs` + +**Interfaces:** +- Consumes: AST expression and statement variants supported by the interpreter +- Produces: VM bytecode coverage for arithmetic, comparisons, branches, loops, and variables + +- [x] **Step 1: Write failing VM tests matching interpreter behavior** + +For each operator and control-flow construct supported by the interpreter, add a VM/compiler test. + +- [x] **Step 2: Add missing opcodes** + +Add explicit comparison, modulo, boolean, branch, and local assignment opcodes instead of encoding them as arithmetic hacks. + +- [x] **Step 3: Verify interpreter/VM parity** + +Run the same small programs through interpreter and VM where possible and assert matching numeric/boolean results. + +- [x] **Step 4: Verify** + +Run: `cargo test -p aether-lang` +Expected: PASS. + +### Task 7: Remaining VM and Proof-DSL Work + +**Files:** +- Modify: `crates/aether-lang/src/vm.rs` +- Modify: `crates/aether-lang/src/ast.rs` +- Create or modify: `docs/` proof-facing language specification files + +**Interfaces:** +- Consumes: user-defined functions from Task 4 and VM expression/control-flow support from Task 6 +- Produces: bytecode call frames for user functions, explicit return lowering, and a clearer proof-DSL formalization surface + +- [x] **Step 1: Add VM tests for function calls and returns** + +Cover `fn add(a, b) { return a + b~ }`, implicit last-expression return, and local parameter isolation through VM execution. + +- [x] **Step 2: Add VM call-frame opcodes** + +Introduce function labels, call/return opcodes, frame setup, parameter binding, and return value propagation. + +- [x] **Step 3: Document the proof-facing core language** + +Define the stable grammar and small-step or big-step semantics subset intended for Lean 4 formalization. + +- [x] **Step 4: Verify** + +Run: `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 29: Lean Frame Compiler Loop-Control Patching + +**Files:** +- Modify: `Aether/VM.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Lean `Stmt.break` and `Stmt.continue` parsed into the proof core. +- Produces: frame-compiler jump patching for loop exits and loop continuation + targets inside frame-compiled functions. + +- [x] **Step 1: Add pending loop-control compile result** + +Introduce a frame compile result that carries generated bytecode, threaded slot +state, pending `break` jump sites, and pending `continue` jump sites. + +- [x] **Step 2: Patch loop boundaries** + +Patch pending sites at `while`, integer-range `for`, and `seal` boundaries. +`break` targets the loop exit; `continue` targets the condition check for +`while`/`seal` and the increment block for `for`. + +- [x] **Step 3: Preserve public compiler API** + +Keep `compileFrameStmt` and `compileFrameBlock` returning `(SlotEnv × List +FrameOp)` and reject unconsumed `break`/`continue` outside loops. + +- [x] **Step 4: Add checked examples** + +Verify a frame-compiled function where `break` exits a `while` loop and another +where `continue` skips the rest of the current loop body. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 30: Lean Static Well-Formedness Gate + +**Files:** +- Create: `Aether/Static.lean` +- Modify: `Aether.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Lean `Aether.Core` syntax for expressions, statements, function + declarations, calls, and loop-control statements. +- Produces: executable well-formedness checks for the unannotated proof-core + language before compiler and VM correspondence proofs. + +- [x] **Step 1: Add static type and signature model** + +Define proof-core static types `num`, `bool`, `unit`, and `unknown`, plus +variable and function-signature environments. + +- [x] **Step 2: Check expressions** + +Validate known arithmetic and comparison operand shapes, resolve variables, +check function call arity, and use `unknown` for unannotated parameters and +call results. + +- [x] **Step 3: Check statements and programs** + +Validate declaration-before-use, assignment compatibility, valid +`return` placement, valid `break`/`continue` placement, loops, branches, and +function bodies after collecting top-level signatures. + +- [x] **Step 4: Add checked examples** + +Cover valid and invalid expressions, undeclared assignment rejection, +top-level loop-control/return rejection, valid loop control, valid function +calls, and arity mismatch rejection. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 31: Lean Checked Frame Compilation Bridge + +**Files:** +- Modify: `Aether/VM.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `Aether.Static.checkProgram` and the existing frame compiler. +- Produces: a checked source-to-bytecode entrypoint that refuses statically + invalid proof-core programs before frame bytecode lowering. + +- [x] **Step 1: Import the static checker into the VM module** + +Keep raw frame compilation available while making static checking usable by +compiler entrypoints. + +- [x] **Step 2: Add checked compiler and runner APIs** + +Add `compileCheckedFrameProgram`, `runCheckedFrameProgram`, and +`checkedFrameLocal?`. + +- [x] **Step 3: Record a static-gate theorem** + +Prove that successful checked frame compilation implies +`Static.checkProgram` did not reject the source program. + +- [x] **Step 4: Add checked examples** + +Verify valid function code still runs through the checked entrypoint, while +numeric/boolean arithmetic misuse and function arity mismatch are rejected. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 32: Lean Source-To-Checked-VM Pipeline + +**Files:** +- Modify: `Aether/VM.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `Aether.Parser.parseProgram`, `compileCheckedFrameProgram`, and + the frame VM runner. +- Produces: source-string entrypoints that tokenize, parse, statically check, + lower, and run proof-core programs in Lean. + +- [x] **Step 1: Import parser access into VM pipeline helpers** + +Use the existing parser module without changing the parser API. + +- [x] **Step 2: Add source entrypoints** + +Add `compileCheckedFrameSource`, `runCheckedFrameSource`, and +`checkedFrameSourceLocal?`. + +- [x] **Step 3: Add checked source examples** + +Verify a valid function source program executes through the full pipeline. +Verify malformed source, invalid numeric/boolean arithmetic, and arity mismatch +are rejected before bytecode execution. + +- [x] **Step 4: Document the end-to-end path** + +Record the tokenize/parse/static-check/lower/run path and its current +boundaries in the formal core documentation. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 33: Lean Stage-Aware Source Diagnostics + +**Files:** +- Create: `Aether/Pipeline.lean` +- Modify: `Aether.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Lean lexer tokens, parser output, static checker results, checked + frame compiler output, and frame VM execution. +- Produces: source pipeline entrypoints with explicit failure phases instead + of undifferentiated `Option.none`. + +- [x] **Step 1: Define pipeline error stages** + +Add lexical, parse, static, compile, and runtime error constructors. + +- [x] **Step 2: Preserve lexer failures before parsing** + +Scan token output for the first `TokenKind.error` and return a lexical +diagnostic rather than collapsing it into parse failure. + +- [x] **Step 3: Add staged source entrypoints** + +Add `parseSource`, `checkSource`, `compileSource`, `runSource`, and +`sourceLocal?`. + +- [x] **Step 4: Add checked examples** + +Verify successful execution and distinct lexical, parse, static arithmetic, +static arity, and runtime/fuel behavior. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 34: Lean Detailed Static Diagnostics + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core expressions/statements and the stage-aware source + pipeline. +- Produces: concrete static error reasons propagated through source + diagnostics. + +- [x] **Step 1: Add static diagnostic type** + +Define errors for undeclared variables/functions, unary and binary operand +mismatches, assignment mismatches, arity mismatches, function signature +mismatches, and invalid `return`/`break`/`continue` placement. + +- [x] **Step 2: Add detailed static checker** + +Add `checkExprDetailed`, `checkStmtDetailed`, `checkBlockDetailed`, and +`checkProgramDetailed` alongside the existing `Option` checker. + +- [x] **Step 3: Propagate detailed static errors through the pipeline** + +Change `Pipeline.Error.static` to carry `Static.CheckError` and make +`checkSource`/`compileSource` use the detailed checker. + +- [x] **Step 4: Add checked examples** + +Verify detailed diagnostics for operand mismatch, undeclared assignment, +top-level loop control, top-level return, and arity mismatch. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 35: Lean Parser Diagnostic Wrapper + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: existing `Option` parser and stage-aware source pipeline. +- Produces: parse diagnostics carrying a failure context and the first token + at the failed statement start. + +- [x] **Step 1: Add parser diagnostic types** + +Define `ParseContext` and `ParseError` for broad expression, statement, block, +range, parameter, terminator, and program-end failures. + +- [x] **Step 2: Add detailed parser wrapper** + +Add `parseProgramFromTokensDetailed` and `parseProgramDetailed` without +rewriting the existing parser. + +- [x] **Step 3: Add checked parser examples** + +Verify diagnostic classification for expression, range, and parameter-list +parse failures. + +- [x] **Step 4: Propagate parser diagnostics through the pipeline** + +Change `Pipeline.Error.parse` to carry `Parser.ParseError`. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 36: Lean Positioned Lexer Diagnostics + +**Files:** +- Modify: `Aether/Lexer.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Lean source strings and existing token-kind lexer. +- Produces: location-aware tokenization and lexical diagnostics with line and + column while preserving the parser's token-kind API. + +- [x] **Step 1: Add source-position model** + +Define `SourcePos`, `LocatedToken`, and position advancement helpers. + +- [x] **Step 2: Add located tokenization** + +Add `tokenizeLocated` as a parallel lexer API that emits token start +positions without changing `tokenize`. + +- [x] **Step 3: Add checked lexer examples** + +Verify ordinary token positions and lexical error positions. + +- [x] **Step 4: Propagate lexical positions through the pipeline** + +Change `Pipeline.Error.lex` to carry the first lexer error's `SourcePos` and +make `parseSource` use located tokenization. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 37: Lean Positioned Parse Diagnostics + +**Files:** +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: parser diagnostics and located lexer tokens. +- Produces: parse diagnostics that include source line and column without + changing the parser's token-kind API. + +- [x] **Step 1: Add located terminator skipping** + +Mirror parser terminator skipping on `LocatedToken` lists so leading newlines +and tildes do not hide the failed statement start. + +- [x] **Step 2: Attach parse positions in the pipeline** + +Change `Pipeline.Error.parse` to carry `Parser.ParseError` and +`Lexer.SourcePos`. + +- [x] **Step 3: Add checked examples** + +Verify parse position for an expression failure and for a range failure after +leading terminators. + +- [x] **Step 4: Document positioned parse diagnostics** + +Record that parser diagnostics in the pipeline now carry context plus source +position. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 38: Lean Duplicate Declaration Static Checks + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: top-level function declarations and function parameter lists. +- Produces: detailed static diagnostics for duplicate names that would + otherwise be hidden by environment shadowing. + +- [x] **Step 1: Add duplicate diagnostic constructors** + +Add `duplicateFunction` and `duplicateParameter` to `Static.CheckError`. + +- [x] **Step 2: Check duplicate parameters** + +Add `bindUnknownParamsDetailed` so function body checking rejects duplicate +parameter names. + +- [x] **Step 3: Check duplicate top-level functions** + +Add `collectFnSigsDetailed` and make `checkProgramDetailed` use it before +checking the program body. + +- [x] **Step 4: Add checked examples** + +Verify duplicate function names and duplicate parameter names are rejected +directly and through `Pipeline.compileSource`. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 39: Lean Checked Compiler Uses Detailed Static Gate + +**Files:** +- Modify: `Aether/VM.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `Static.checkProgramDetailed` and the frame compiler. +- Produces: strict checked compilation aligned with the diagnostic source + pipeline. + +- [x] **Step 1: Swap checked compiler gate** + +Make `compileCheckedFrameProgram` use `checkProgramDetailed` rather than the +older `Option` checker. + +- [x] **Step 2: Update static-gate theorem** + +Record that successful checked frame compilation implies an accepted detailed +static check witness. + +- [x] **Step 3: Add checked compiler examples** + +Verify duplicate functions and duplicate parameters are rejected through the +checked compiler/source APIs. + +- [x] **Step 4: Document strict checked compilation** + +Record that checked AST/source compilation uses the detailed static checker. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 40: Lean Pipeline Diagnostic Rendering + +**Files:** +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: pipeline lexical, parser, static, compile, and runtime diagnostic + variants. +- Produces: deterministic string rendering for source-pipeline diagnostics. + +- [x] **Step 1: Add rendering helpers** + +Add renderers for source positions, operators, static types, token summaries, +parse contexts, parser errors, and static errors. + +- [x] **Step 2: Add full pipeline error rendering** + +Add `errorString` and `compileSourceErrorString`. + +- [x] **Step 3: Add checked string examples** + +Verify stable rendered strings for lexical, parse, static operand, and +duplicate-function diagnostics. + +- [x] **Step 4: Document rendered diagnostics** + +Record diagnostic rendering in the formal core documentation. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 41: Lean Source Diagnostic Helper Coverage + +**Files:** +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `Pipeline.errorString` and staged source entrypoints. +- Produces: deterministic rendered-error helpers for parse, check, compile, + run, and source-local lookup APIs. + +- [x] **Step 1: Add shared result renderer** + +Add a generic `resultErrorString` helper for `Except Pipeline.Error α`. + +- [x] **Step 2: Add source-stage wrappers** + +Add `parseSourceErrorString`, `checkSourceErrorString`, +`runSourceErrorString`, and `sourceLocalErrorString` alongside the existing +`compileSourceErrorString`. + +- [x] **Step 3: Add checked examples** + +Verify stable strings for lexical, parse, static, and runtime/local-access +failures, plus a no-error run case. + +- [x] **Step 4: Document helper coverage** + +Record that deterministic rendered diagnostics are available across the public +source pipeline entrypoints. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 42: Lean Later-Statement Parse Positions + +**Files:** +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: located lexer tokens and the existing token-kind parser API. +- Produces: parse diagnostics whose `SourcePos` follows the failed statement + boundary even after earlier statements parsed successfully. + +- [x] **Step 1: Add located token-kind projection** + +Add a helper that projects `LocatedToken` streams into parser token-kind +streams without changing the parser API. + +- [x] **Step 2: Replay statement parsing over located tokens** + +Add `parseLocatedProgramDetailed` so the pipeline advances through successful +statements and reports parse failures at the current located statement start. + +- [x] **Step 3: Use located parsing in `parseSource`** + +Replace the old whole-program fallback position with the located program +parser. + +- [x] **Step 4: Add checked examples** + +Verify parse failures after a valid first statement report the second +statement's line and column. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 43: Lean Token Source Spans + +**Files:** +- Modify: `Aether/Lexer.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: located lexer tokenization and source-position advancement. +- Produces: half-open token source ranges for future parser/AST diagnostics + while preserving the parser's `TokenKind` stream. + +- [x] **Step 1: Add span model** + +Define `SourceSpan`, extend `LocatedToken` with a `stop` position, and expose +`LocatedToken.span`. + +- [x] **Step 2: Emit token end positions** + +Update `scanLocatedFuel` to compute each token's end position for single-char, +multi-char, literal, identifier, newline, EOF, and error tokens. + +- [x] **Step 3: Preserve pipeline behavior** + +Update located-token pattern matches in `Aether.Pipeline` while continuing to +use token starts for current diagnostics. + +- [x] **Step 4: Add checked examples** + +Verify located-token ranges, newline ranges, unterminated string ranges, and +`1..10` source spans. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 44: Lean Pipeline Span Diagnostics + +**Files:** +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `Lexer.SourceSpan`, span-aware `LocatedToken` values, and staged + source diagnostics. +- Produces: lexical and parse pipeline diagnostics carrying token ranges + instead of start positions only. + +- [x] **Step 1: Change pipeline error payloads** + +Update `Pipeline.Error.lex` and `Pipeline.Error.parse` to carry +`Lexer.SourceSpan`. + +- [x] **Step 2: Preserve spans while scanning diagnostics** + +Make `firstLexError` return the lexer error token's full span and make +`parseLocatedProgramDetailed` return the failed statement token span. + +- [x] **Step 3: Render diagnostic ranges** + +Add `spanString` and update `errorString` to render lexical and parse errors +with half-open ranges. + +- [x] **Step 4: Add checked examples** + +Update checked pipeline examples so lexical and parse diagnostics prove the +exact reported source ranges, including later-statement parse failures. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 45: Lean Static Diagnostic Source Spans + +**Files:** +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: detailed static checker errors and located lexer tokens. +- Produces: best-effort source ranges for static diagnostics without changing + the current unspanned parser AST. + +- [x] **Step 1: Add token matching helpers** + +Add helpers that match located tokens by identifier, binary operator, unary +operator, and control-flow keyword. + +- [x] **Step 2: Add span search helpers** + +Add first, last, and second matching token span lookup helpers so static errors +can point at the most useful occurrence for ordinary, call-site, and duplicate +name failures. + +- [x] **Step 3: Attach spans to static errors** + +Change `Pipeline.Error.static` to carry `Option Lexer.SourceSpan` and have +`checkSource`/`compileSource` attach a best-effort span. + +- [x] **Step 4: Render and test static spans** + +Update `errorString` and checked examples for operator mismatch, arity +mismatch, duplicate names, and invalid top-level `break`. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 46: Lean Seal Emoji Lexer Alias + +**Files:** +- Modify: `Aether/Lexer.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Rust lexer behavior where `seal` and `🦭` map to the same token. +- Produces: Lean lexer parity for the proof DSL seal control-flow alias. + +- [x] **Step 1: Add plain lexer emoji handling** + +Update `scanFuel` so `🦭` emits `TokenKind.seal`. + +- [x] **Step 2: Add located lexer emoji handling** + +Update `scanLocatedFuel` so the emoji alias emits `TokenKind.seal` with a +checked source range. + +- [x] **Step 3: Add checked examples** + +Verify ordinary tokenization for `🦭 until ...` and located-token ranges for +the emoji alias. + +- [x] **Step 4: Document alias parity** + +Record that the Lean lexer recognizes both `seal` and `🦭`, matching the Rust +lexer surface. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 47: Lean Structural Duplicate Diagnostic Spans + +**Files:** +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: located token streams and detailed duplicate-name static errors. +- Produces: more precise duplicate function and duplicate parameter spans + without requiring a spanned AST. + +- [x] **Step 1: Add function-name span scanning** + +Add helpers that find identifier spans in `fn name` token patterns and return +the duplicate declaration span for repeated functions. + +- [x] **Step 2: Add parameter-list span scanning** + +Add helpers that search function parameter lists up to `)` and return the +repeated parameter span. + +- [x] **Step 3: Use structural duplicate spans** + +Route `duplicateFunction` and `duplicateParameter` through the structural +helpers instead of loose second-identifier matching. + +- [x] **Step 4: Add regression examples** + +Verify duplicate function diagnostics ignore same-name calls in function +bodies and duplicate parameter diagnostics work when the function name matches +the repeated parameter. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 48: Lean Seal Emoji Source Pipeline + +**Files:** +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Lean lexer support for `🦭` as `TokenKind.seal`, parser seal + statements, static checking, and frame VM lowering. +- Produces: checked end-to-end proof-DSL source pipeline coverage for the seal + emoji alias. + +- [x] **Step 1: Add parse pipeline example** + +Verify `parseSource` maps `🦭 until x == 3 { ... }` to the same `Stmt.seal` +syntax as the word-form keyword. + +- [x] **Step 2: Add execution pipeline example** + +Verify a source program using `🦭 until` statically checks, compiles, runs, and +stores the expected local value. + +- [x] **Step 3: Document end-to-end alias coverage** + +Record that the seal emoji alias is checked beyond tokenization through the +source pipeline. + +- [x] **Step 4: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 49: Lean Conditional Seal Parse Diagnostics + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: existing parser diagnostic wrapper and `seal until` grammar. +- Produces: more accurate parse context for malformed conditional seal loops. + +- [x] **Step 1: Refine statement-start classification** + +Classify `seal until ...` parse failures as expression-context failures rather +than generic block failures. + +- [x] **Step 2: Add parser diagnostic example** + +Verify `parseProgramDetailed "seal until { break }"` reports an expected +expression context. + +- [x] **Step 3: Add pipeline rendered example** + +Verify the source pipeline renders the conditional seal parse error with the +expected expression context and source range. + +- [x] **Step 4: Document diagnostic coverage** + +Record conditional `seal until` expression failures in the formal-core parser +diagnostic coverage. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 50: Lean If/While Condition Parse Diagnostics + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: existing parser diagnostic wrapper and structured `if`/`while` + grammar. +- Produces: more accurate parse context for malformed condition-bearing + structured statements. + +- [x] **Step 1: Refine `if` classification** + +Classify `if { ... }` as an expression-context failure while preserving +block-context failures for `if condition` without a block. + +- [x] **Step 2: Refine `while` classification** + +Classify `while { ... }` as an expression-context failure while preserving +block-context failures for `while condition` without a block. + +- [x] **Step 3: Add checked diagnostics** + +Add parser and rendered pipeline examples for missing `if` and `while` +conditions. + +- [x] **Step 4: Document condition diagnostics** + +Record missing `if`/`while` condition-expression coverage in the formal-core +parser diagnostic notes. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 51: Lean Function Body Parse Diagnostics + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: parser diagnostic wrapper and function declaration grammar. +- Produces: more accurate parse context for function declarations whose + parameter list is complete but whose body block is missing. + +- [x] **Step 1: Recognize parameter-list prefixes** + +Add token-level recognition for complete function parameter-list prefixes after +`fn name(`. + +- [x] **Step 2: Refine function classification** + +Classify `fn name(params)` failures as block-context failures while preserving +params-context failures for malformed parameter lists. + +- [x] **Step 3: Add checked examples** + +Add parser and rendered pipeline examples for a missing function body after a +valid parameter list. + +- [x] **Step 4: Document diagnostic distinction** + +Record that function parser diagnostics distinguish malformed parameters from +missing body blocks. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 52: Lean For-Loop Body Parse Diagnostics + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: parser diagnostic wrapper and integer-range `for` grammar. +- Produces: more accurate parse context for `for` loops whose range is + complete but whose body block is missing. + +- [x] **Step 1: Recognize integer-range prefixes** + +Add token-level recognition for `number .. number` range prefixes after +`for name in`. + +- [x] **Step 2: Refine `for` classification** + +Classify `for name in start..end` failures as block-context failures while +preserving range-context failures for malformed ranges. + +- [x] **Step 3: Add checked examples** + +Add parser and rendered pipeline examples for a missing loop body after a valid +integer range. + +- [x] **Step 4: Document diagnostic distinction** + +Record that `for` parser diagnostics distinguish malformed ranges from missing +body blocks. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 53: Lean Stray Else Parse Diagnostics + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: parser diagnostic wrapper and `if`/`else` grammar. +- Produces: explicit parse context for an `else` token that appears without a + preceding parsed `if` statement. + +- [x] **Step 1: Add `if`-statement parse context** + +Represent malformed standalone `else` input as an `if`-statement context rather +than a generic statement or expression failure. + +- [x] **Step 2: Classify stray `else`** + +Teach statement-start classification to recognize `else` as a dependent token +that requires a preceding parsed `if`. + +- [x] **Step 3: Add checked examples** + +Add parser and rendered pipeline examples for a stray `else` token. + +- [x] **Step 4: Document diagnostic coverage** + +Record that parser diagnostics explicitly distinguish stray `else` from other +statement-start failures. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 54: Located Lexer Token Stream Consistency + +**Files:** +- Modify: `Aether/Lexer.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: plain `tokenize` scanner and located `tokenizeLocated` scanner. +- Produces: checked projection helper showing the located scanner preserves the + same token-kind stream as the parser-facing scanner for representative core + inputs. + +- [x] **Step 1: Add failing projection examples** + +Add Lean examples that express the desired equality between `tokenize source` +and the token-kind projection of `tokenizeLocated source`. + +- [x] **Step 2: Implement token-kind projection** + +Expose a small helper that maps located tokens back to their `TokenKind` +stream. + +- [x] **Step 3: Cover representative lexical surfaces** + +Check equality for ordinary programs, comments/newlines, ranges, strings, +lexical errors, and the `🦭` alias. + +- [x] **Step 4: Document the invariant** + +Record the located/plain scanner consistency invariant in the formal-core +lexer notes. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 55: Lean Signed Integer For-Ranges + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `Aether/VM.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `Stmt.forRange` signed `Int` bounds, lexer minus/number tokens, and + existing VM lowering for integer range bounds. +- Produces: parser and source pipeline support for negative integer endpoints + in `for` ranges. + +- [x] **Step 1: Add failing parser and pipeline examples** + +Add checked examples for `for i in -2..2 { ... }` parsing and source execution. + +- [x] **Step 2: Parse signed integer literals in range positions** + +Extend range parsing to accept either `number` or `- number` endpoints while +preserving malformed-range diagnostics. + +- [x] **Step 3: Update diagnostic range-prefix classification** + +Recognize signed complete range prefixes so missing-body errors still report +`expected block` instead of `expected range`. + +- [x] **Step 4: Document signed ranges** + +Update the formal-core grammar and parser notes to include signed integer +range endpoints. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 56: Lean Boolean Control-Flow Conditions + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: detailed static checker, source diagnostics, and control-flow + statements with condition expressions. +- Produces: explicit static rejection for concrete non-boolean `if`, `while`, + and `seal until` conditions. + +- [x] **Step 1: Add failing static examples** + +Add checked examples requiring numeric control-flow conditions to produce a +condition-specific static error. + +- [x] **Step 2: Add failing source diagnostic examples** + +Add rendered pipeline examples for numeric `if`, `while`, and `seal until` +conditions. + +- [x] **Step 3: Implement condition compatibility** + +Accept `bool` and `unknown` condition types while rejecting concrete `num` and +`unit` conditions with a `conditionMismatch` error. + +- [x] **Step 4: Document condition checking** + +Update the formal-core static checker notes to record boolean condition +requirements. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 57: Lean Logical Operator Operand Checking + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: static expression typing and existing binary operand mismatch + diagnostics. +- Produces: explicit rejection for concrete non-boolean operands to `&&` and + `||`, while preserving `unknown` compatibility for unannotated calls. + +- [x] **Step 1: Add failing static examples** + +Add checked examples requiring `num && bool` and `bool || num` to fail with +`operandMismatch`. + +- [x] **Step 2: Add failing source diagnostic examples** + +Add rendered source diagnostics for invalid logical operands. + +- [x] **Step 3: Implement logical operand compatibility** + +Introduce boolean-like operand checking for `&&` and `||`, accepting only +`bool` and `unknown`. + +- [x] **Step 4: Document logical operator checking** + +Update the formal-core static checker notes to distinguish arithmetic, +comparison, and logical operand validation. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 58: Lean Equality Operand Compatibility + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: static expression typing and existing binary operand mismatch + diagnostics. +- Produces: explicit rejection for concrete incompatible operands to `==` and + `!=`, while preserving `unknown` compatibility for unannotated calls. + +- [x] **Step 1: Add failing static examples** + +Add checked examples requiring `num == bool` and `bool != num` to fail with +`operandMismatch`. + +- [x] **Step 2: Add failing source diagnostic examples** + +Add rendered source diagnostics for invalid equality operands. + +- [x] **Step 3: Implement equality compatibility** + +Accept equality when operand types match or either side is `unknown`; reject +known mixed-type equality. + +- [x] **Step 4: Document equality checking** + +Update the formal-core static checker notes to mention equality compatibility. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 59: Lean Unary Not Operand Checking + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: static expression typing and existing unary mismatch diagnostics. +- Produces: explicit rejection for concrete non-boolean operands to unary `!`, + while preserving `unknown` compatibility for unannotated calls. + +- [x] **Step 1: Add failing static examples** + +Add a checked example requiring `!1` to fail with `unaryMismatch`. + +- [x] **Step 2: Add failing source diagnostic examples** + +Add a rendered source diagnostic for an invalid unary `!` operand. + +- [x] **Step 3: Implement unary not compatibility** + +Accept unary `!` only for `bool` and `unknown` operand types. + +- [x] **Step 4: Document unary checking** + +Update the formal-core static checker notes to include unary logical operand +validation. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 60: Lean Nested Function Declaration Diagnostics + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: static statement checking, function signature collection, and + source diagnostic rendering. +- Produces: explicit static rejection for `fn` declarations nested inside + blocks, loops, or other functions. + +- [x] **Step 1: Add failing static examples** + +Add checked examples requiring nested `fn` declarations to produce a +function-specific static error. + +- [x] **Step 2: Add failing source diagnostic examples** + +Add a rendered source diagnostic for a nested function declaration. + +- [x] **Step 3: Track top-level scope** + +Extend static checking scope so only direct top-level statements may declare +functions. + +- [x] **Step 4: Document top-level function rule** + +Update the formal-core static checker notes to record that proof-core function +declarations are top-level only. + +- [x] **Step 5: Verify** + +Run: `lake build` +Expected: PASS. + +### Task 78: Lean If Statement Big-Step Semantics + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core `Stmt.ifThenElse`, `truthy`, and existing block-flow + relation. +- Produces: checked big-step statement semantics for `if`/`else` branches, + including no-`else` falsey conditions. + +- [x] **Step 1: Add failing checked examples** + +Add examples proving a truthy condition steps through the then branch, a falsey +condition with `else` steps through the else branch, and a falsey condition +without `else` produces `unit` with the original environment. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `StepStmt.ifTrue`, `StepStmt.ifFalseSome`, and +`StepStmt.ifFalseNone` do not exist. + +- [x] **Step 3: Add mutual statement/block semantics** + +Refactor `StepStmt` and `StepBlock` into a mutual inductive relation so an +`if` statement can delegate the selected branch to `StepBlock` while preserving +the existing block sequencing and early-flow rules. + +- [x] **Step 4: Document structured statement semantics** + +Record that proof-core statement semantics now evaluate selected `if` branches +as blocks and treat missing `else` as `unit`. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 79: Lean Executable If/Else Core Semantics + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: bounded `evalExprWithFns`, `execStmtWithFns`, `execBlockWithFns`, + and `Stmt.ifThenElse`. +- Produces: executable bounded core support for running selected `if`/`else` + branches, including no-`else` falsey conditions. + +- [x] **Step 1: Add failing executable examples** + +Add checked `native_decide` examples proving `execBlockWithFns` runs the then +branch for true conditions, runs the else branch for false conditions, and +returns `unit` with the original environment when false without `else`. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `execStmtWithFns` falls through to `none` for +`Stmt.ifThenElse`. + +- [x] **Step 3: Implement bounded if execution** + +Evaluate the condition with the remaining fuel, execute the selected branch via +`execBlockWithFns`, and return `unit` without environment changes when no else +branch is present. + +- [x] **Step 4: Document executable structured semantics** + +Record the bounded executor's `if`/`else` behavior separately from the Prop +big-step relation. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 80: Lean Executable While Core Semantics + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: bounded `evalExprWithFns`, `execStmtWithFns`, `execBlockWithFns`, + `truthy`, assignments, `break`, and `continue`. +- Produces: executable bounded core support for `Stmt.while` with normal exit, + repeated state updates, break exit, continue iteration, and preserved return + flow. + +- [x] **Step 1: Add failing executable examples** + +Add checked `native_decide` examples proving false conditions leave the +environment unchanged and return `unit`, repeated assignment reaches the loop +bound, `break` exits while skipping later body statements, and `continue` +starts the next iteration. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `execStmtWithFns` falls through to `none` for +`Stmt.while`. + +- [x] **Step 3: Implement bounded while execution** + +Evaluate the condition with remaining fuel, execute the body as a block when +truthy, recurse with decreased fuel after ordinary body values or `continue`, +return `unit` on false conditions and `break`, and preserve `return` flow. + +- [x] **Step 4: Document executable loop semantics** + +Record the bounded executor's `while` behavior, including `break`, `continue`, +and `return` handling. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 81: Lean Executable For-Range Core Semantics + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: bounded `execStmtWithFns`, `execBlockWithFns`, integer + `Stmt.forRange` bounds, assignment, `break`, and `continue`. +- Produces: executable bounded core support for ascending `forRange` + iteration, iterator rebinding, normal completion, break exit, continue + iteration, and preserved return flow. + +- [x] **Step 1: Add failing executable examples** + +Add checked `native_decide` examples proving `0..3` accumulation, empty range +iterator binding, `break` exit with the current iterator value, and +`continue` advancing to the next integer. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `execStmtWithFns` falls through to `none` for +`Stmt.forRange`. + +- [x] **Step 3: Implement bounded for-range execution** + +Bind the iterator to `start` while `start < stop`, execute the body as a block, +recurse with `start + 1` after ordinary body values or `continue`, return +`unit` on `break`, preserve `return` flow, and bind the iterator to `stop` on +normal completion. + +- [x] **Step 4: Document executable for-range semantics** + +Record the bounded executor's ascending integer-range behavior, including +iterator rebinding and control-flow handling. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 82: Lean Executable Seal Core Semantics + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: bounded `execStmtWithFns`, `execBlockWithFns`, optional + `Stmt.seal` conditions, `truthy`, assignment, `break`, and `continue`. +- Produces: executable bounded core support for conditional `seal until` and + bare `seal`, including pre-check termination, bounded repetition, + break/continue handling, and preserved return flow. + +- [x] **Step 1: Add failing executable examples** + +Add checked `native_decide` examples proving a truthy initial `seal until` +condition skips the body, a conditional seal increments until its condition is +truthy, bare seal exits on `break`, and `continue` starts the next iteration. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `execStmtWithFns` falls through to `none` for +`Stmt.seal`. + +- [x] **Step 3: Implement bounded seal execution** + +For `seal until`, evaluate the condition before each iteration and stop with +`unit` when truthy. For bare `seal`, execute the body until fuel exhaustion or +control flow exits. In both forms, recurse after ordinary body values or +`continue`, return `unit` on `break`, and preserve `return` flow. + +- [x] **Step 4: Document executable seal semantics** + +Record the bounded executor's conditional and bare seal behavior, including +pre-checking, fuel bounding, and control-flow handling. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 83: Lean While Statement Big-Step Semantics + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core `Stmt.while`, `truthy`, expression evaluation, and the + existing mutually defined `StepStmt`/`StepBlock` relation. +- Produces: checked big-step statement semantics for false loop exit, ordinary + value-producing loop bodies, return propagation, break exit, and continue + recursion. + +- [x] **Step 1: Add failing checked examples** + +Add examples proving a falsey `while` condition exits with `unit`, a single +ordinary iteration can recurse to a false exit, and a loop body `break` exits +the loop after preserving body state changes. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `StepStmt.whileFalse`, `StepStmt.whileValue`, and +`StepStmt.whileBreak` do not exist. + +- [x] **Step 3: Add while constructors** + +Extend the mutual `StepStmt`/`StepBlock` relation with constructors for false +exit, ordinary body recursion, return propagation, break exit, and continue +recursion. + +- [x] **Step 4: Document big-step while semantics** + +Record the Prop-level `while` rules separately from the bounded executable +core. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 84: Lean For-Range Statement Big-Step Semantics + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core `Stmt.forRange`, integer bounds, iterator binding, and + the existing mutually defined `StepStmt`/`StepBlock` relation. +- Produces: checked big-step statement semantics for completed ranges, + ordinary iteration recursion, return propagation, break exit, and continue + recursion. + +- [x] **Step 1: Add failing checked examples** + +Add examples proving an empty range binds the iterator to the stop value and +returns `unit`, a one-iteration range can recurse to completion, and `break` +exits the range after preserving the current iterator environment. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `StepStmt.forDone`, `StepStmt.forValue`, and +`StepStmt.forBreak` do not exist. + +- [x] **Step 3: Add for-range constructors** + +Extend the mutual `StepStmt`/`StepBlock` relation with constructors for range +completion, ordinary body recursion, return propagation, break exit, and +continue recursion. + +- [x] **Step 4: Document big-step for-range semantics** + +Record the Prop-level `forRange` rules separately from the bounded executable +core. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 85: Lean Seal Statement Big-Step Semantics + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core `Stmt.seal`, optional conditions, `truthy`, expression + evaluation, and the existing mutually defined `StepStmt`/`StepBlock` + relation. +- Produces: checked big-step statement semantics for conditional stop, + conditional iteration, bare seal iteration, return propagation, break exit, + and continue recursion. + +- [x] **Step 1: Add failing checked examples** + +Add examples proving a truthy initial `seal until` condition skips the body, a +conditional seal can perform one ordinary iteration and recurse to stop, and a +bare seal body `break` exits the loop after preserving body state changes. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `StepStmt.sealUntilDone`, +`StepStmt.sealUntilValue`, and `StepStmt.sealBreak` do not exist. + +- [x] **Step 3: Add seal constructors** + +Extend the mutual `StepStmt`/`StepBlock` relation with constructors for +conditional stop, conditional value/return/break/continue behavior, and bare +seal value/return/break/continue behavior. + +- [x] **Step 4: Document big-step seal semantics** + +Record the Prop-level `seal` rules separately from the bounded executable +core. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 86: Lean Function Declaration Env-Only Big-Step Semantics + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core `Stmt.fnDecl` and the existing env-only + `StepStmt`/`StepBlock` relation. +- Produces: checked big-step statement semantics for function declarations as + unit-producing statements with no variable-environment effect. + +- [x] **Step 1: Add failing checked example** + +Add an example proving a single `fnDecl` statement steps as a block to +`Flow.value Value.unit` without changing the variable environment. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `StepStmt.fnDecl` does not exist. + +- [x] **Step 3: Add function declaration constructor** + +Extend `StepStmt` with a `fnDecl` constructor that preserves the variable +environment and produces `unit`. + +- [x] **Step 4: Document the relation boundary** + +Record that this is an env-only statement relation rule, while full +function-environment behavior remains modeled by bounded executable `FnEnv` +semantics until a future Prop relation carries function bindings explicitly. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 87: Prop-Level Function Environment Semantics Slice + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `FnEnv`, `Function`, `bindParams`, proof-core expressions and + statements, and the executable function semantics as the behavioral guide. +- Produces: initial Prop-level relations for function-aware expression + evaluation, argument evaluation, statement stepping, and block stepping. + +- [x] **Step 1: Add failing checked example** + +Add an example proving a block that declares `id(x)`, calls it with `3`, and +binds the result to `y` while threading the function environment. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `StepBlockWithFns` and the related Prop relation names +do not exist. + +- [x] **Step 3: Add minimal function-aware Prop relations** + +Define `EvalExprWithFnsRel`, `EvalArgsWithFnsRel`, `StepStmtWithFns`, and +`StepBlockWithFns` for literals, variables, function calls, `let`, expression +statements, returns, function declarations, and declaration sequencing. + +- [x] **Step 4: Document relation coverage** + +Record that the new Prop-level `FnEnv` slice covers declaration binding, call +argument evaluation, parameter frame binding, return/value call results, and +basic block sequencing, while structured control flow remains future work for +this relation. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 88: Prop-Level Function Environment If Semantics + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `EvalExprWithFnsRel`, `StepStmtWithFns`, + `StepBlockWithFns`, `truthy`, and proof-core `Stmt.ifThenElse`. +- Produces: checked function-aware big-step semantics for true branches, + false branches with `else`, and false branches without `else`. + +- [x] **Step 1: Add failing checked examples** + +Add examples proving `StepBlockWithFns` behavior for truthy `if`, falsey +`if` with `else`, and falsey `if` without `else`. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `StepStmtWithFns.ifTrue`, +`StepStmtWithFns.ifFalseSome`, and `StepStmtWithFns.ifFalseNone` do not exist. + +- [x] **Step 3: Add function-aware if constructors** + +Extend `StepStmtWithFns` with constructors that evaluate the condition through +`EvalExprWithFnsRel`, select the correct branch, and thread both variable and +function environments through `StepBlockWithFns`. + +- [x] **Step 4: Document coverage** + +Record that the Prop-level `FnEnv` relation now covers structured +`if`/`else`, while loops and other structured control forms remain future work +for this relation. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 89: Prop-Level Function Environment While Semantics + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `EvalExprWithFnsRel`, `StepStmtWithFns`, + `StepBlockWithFns`, `truthy`, `Flow.break`, `Flow.continue`, and proof-core + `Stmt.while`. +- Produces: checked function-aware big-step semantics for falsey loop + completion, ordinary loop recursion, return propagation, break exit, + continue recursion, and block propagation of break/continue. + +- [x] **Step 1: Add failing checked examples** + +Add examples proving `StepBlockWithFns` behavior for a falsey `while`, a +one-iteration value-producing `while`, and a `while` body that exits through +`break`. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `StepStmtWithFns.whileFalse`, +`StepStmtWithFns.whileValue`, and `StepStmtWithFns.whileBreak` do not exist. + +- [x] **Step 3: Add function-aware while and control-flow constructors** + +Extend `StepStmtWithFns` with `whileFalse`, `whileValue`, `whileReturn`, +`whileBreak`, `whileContinue`, `break`, and `continue`. Extend +`StepBlockWithFns` with `consBreak` and `consContinue`. + +- [x] **Step 4: Document coverage** + +Record that the Prop-level `FnEnv` relation now covers `while` and +break/continue block propagation, while `forRange`, `seal`, and assignment +remain future work for this relation. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 90: Prop-Level Function Environment Assignment Semantics + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `EvalExprWithFnsRel`, `StepStmtWithFns`, `Env.assign`, and + proof-core `Stmt.assign`. +- Produces: checked function-aware big-step semantics for assignment to an + existing variable binding while preserving the function environment. + +- [x] **Step 1: Add failing checked examples** + +Add examples proving direct assignment followed by variable lookup, and a +`while` body that mutates an existing condition variable through assignment. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `StepStmtWithFns.assign` does not exist. + +- [x] **Step 3: Add function-aware assignment constructor** + +Extend `StepStmtWithFns` with an `assign` constructor that evaluates the RHS +through `EvalExprWithFnsRel`, applies `Env.assign`, preserves `FnEnv`, and +returns the assigned value. + +- [x] **Step 4: Document coverage** + +Record that the Prop-level `FnEnv` relation now covers assignment, while +`forRange` and `seal` remain future work for this relation. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 91: Prop-Level Function Environment For-Range Semantics + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `StepStmtWithFns`, `StepBlockWithFns`, `Env.bind`, + `Flow.break`, `Flow.continue`, and proof-core `Stmt.forRange`. +- Produces: checked function-aware big-step semantics for completed ranges, + ordinary iteration recursion, return propagation, break exit, and continue + recursion. + +- [x] **Step 1: Add failing checked examples** + +Add examples proving `StepBlockWithFns` behavior for an empty range, a +one-iteration range with assignment from the iterator, and a range body that +exits through `break`. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `StepStmtWithFns.forDone`, +`StepStmtWithFns.forValue`, and `StepStmtWithFns.forBreak` do not exist. + +- [x] **Step 3: Add function-aware for-range constructors** + +Extend `StepStmtWithFns` with `forDone`, `forValue`, `forReturn`, +`forBreak`, and `forContinue`, threading both variable and function +environments through the body and recursive step. + +- [x] **Step 4: Document coverage** + +Record that the Prop-level `FnEnv` relation now covers `forRange`, leaving +`seal` as the remaining structured statement gap for this relation. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 92: Prop-Level Function Environment Seal Semantics + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `EvalExprWithFnsRel`, `StepStmtWithFns`, + `StepBlockWithFns`, `truthy`, `Flow.break`, `Flow.continue`, and + proof-core `Stmt.seal`. +- Produces: checked function-aware big-step semantics for conditional seal + stop, conditional iteration, bare seal iteration, return propagation, break + exit, and continue recursion. + +- [x] **Step 1: Add failing checked examples** + +Add examples proving `StepBlockWithFns` behavior for a truthy initial +`seal until` condition, a conditional seal that performs one ordinary +iteration and recurses to stop, and a bare seal body that exits through +`break`. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `StepStmtWithFns.sealUntilDone`, +`StepStmtWithFns.sealUntilValue`, and `StepStmtWithFns.sealBreak` do not +exist. + +- [x] **Step 3: Add function-aware seal constructors** + +Extend `StepStmtWithFns` with conditional seal done/value/return/break/continue +constructors and bare seal value/return/break/continue constructors, threading +both variable and function environments through body and recursive steps. + +- [x] **Step 4: Document coverage** + +Record that the Prop-level `FnEnv` relation now covers both conditional and +bare `seal` control flow. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 93: Prop-Level Function Environment Unary/Binary Expressions + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `EvalExprWithFnsRel`, `evalUnOp`, `evalBinOp`, + proof-core `Expr.unary`, and proof-core `Expr.binary`. +- Produces: checked function-aware expression semantics for unary operators + and binary operators, including arithmetic and comparisons. + +- [x] **Step 1: Add failing checked examples** + +Add examples proving `StepBlockWithFns` expression statements for numeric +addition, numeric comparison, and boolean negation. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `EvalExprWithFnsRel.binary` and +`EvalExprWithFnsRel.unary` do not exist. + +- [x] **Step 3: Add function-aware expression constructors** + +Extend `EvalExprWithFnsRel` with `unary` and `binary` constructors that +evaluate subexpressions and use the existing operator evaluator helpers. + +- [x] **Step 4: Document coverage** + +Record that the Prop-level `FnEnv` expression relation now covers unary and +binary operators through the shared operator evaluators. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 94: Prop-Level Function Environment List/Index Expressions + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `EvalExprWithFnsRel`, `EvalArgsWithFnsRel`, `evalIndex`, + proof-core `Expr.list`, and proof-core `Expr.index`. +- Produces: checked function-aware expression semantics for list construction + and successful list/string indexing. + +- [x] **Step 1: Add failing checked examples** + +Add examples proving `StepBlockWithFns` expression statements for a mixed list +literal and a successful list index. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `EvalExprWithFnsRel.list` and +`EvalExprWithFnsRel.index` do not exist. + +- [x] **Step 3: Add function-aware list/index constructors** + +Extend `EvalExprWithFnsRel` with `list`, backed by `EvalArgsWithFnsRel`, and +`index`, backed by the existing `evalIndex` helper. + +- [x] **Step 4: Document coverage** + +Record that the Prop-level `FnEnv` expression relation now covers list +construction and indexing. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 95: Prop-Level Function Environment Field/Method Expressions + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `EvalExprWithFnsRel`, `EvalArgsWithFnsRel`, `evalField`, + `evalMethod`, proof-core `Expr.field`, and proof-core `Expr.method`. +- Produces: checked function-aware expression semantics for supported field + access and pure method calls. + +- [x] **Step 1: Add failing checked examples** + +Add examples proving `StepBlockWithFns` expression statements for list +`.length` field access and string `.len()` method call. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `EvalExprWithFnsRel.field` and +`EvalExprWithFnsRel.method` do not exist. + +- [x] **Step 3: Add function-aware field/method constructors** + +Extend `EvalExprWithFnsRel` with `field`, backed by the existing `evalField` +helper, and `method`, backed by `EvalArgsWithFnsRel` plus `evalMethod`. + +- [x] **Step 4: Document coverage** + +Record that the Prop-level `FnEnv` expression relation now covers field access +and pure method calls through the shared evaluator helpers. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 96: Prop-Level Function Environment Implicit Call Result Example + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `EvalExprWithFnsRel.callValue`, `StepBlockWithFns`, function + declarations, positional calls, and final expression statement semantics. +- Produces: checked proof coverage for function calls whose body returns an + ordinary final expression value rather than an explicit `return`. + +- [x] **Step 1: Add checked example** + +Add an example proving a block that declares `one() { 1 }`, calls it, and binds +the implicit final-expression result to `y`. + +- [x] **Step 2: Verify** + +Run: `lake build` +Expected: PASS, proving the existing `callValue` constructor covers implicit +final-expression results. + +- [x] **Step 3: Document coverage** + +Record that checked Prop-level `FnEnv` call witnesses cover both explicit +`return` and implicit final-expression call results. + +- [x] **Step 4: Full verification** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 97: Base Prop-to-Executable Expression Correspondence Witnesses + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `EvalExprWithFnsRel` base expression constructors and the bounded + executable `evalExprWithFns`. +- Produces: checked concrete witnesses that selected Prop expression facts for + numeric literals, booleans, and variables agree with executable evaluation. + +- [x] **Step 1: Add failing checked examples** + +Add examples using intended correspondence witness names before defining them. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the base correspondence witness names do not exist. + +- [x] **Step 3: Add concrete correspondence witnesses** + +Define witnesses for numeric literals, booleans, and variables. Keep them +concrete because `evalExprWithFns` is a bounded partial executable evaluator +that reduces through computation rather than ordinary theorem unfolding. + +- [x] **Step 4: Document scope** + +Record that these are initial concrete correspondence witnesses, while full +inductive correspondence remains future work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 98: Compound Prop-to-Executable Expression Correspondence Witnesses + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `EvalExprWithFnsRel` compound expression constructors and the + bounded executable `evalExprWithFns`. +- Produces: checked concrete witnesses that selected Prop expression facts for + binary arithmetic, unary boolean negation, and list construction agree with + executable evaluation. + +- [x] **Step 1: Add failing checked examples** + +Add examples using intended correspondence witness names before defining them. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the compound correspondence witness names do not exist. + +- [x] **Step 3: Add concrete correspondence witnesses** + +Define witnesses for binary addition, unary boolean negation, and list +construction. Keep them concrete for the same reason as the base witnesses: +`evalExprWithFns` is a bounded partial executable evaluator. + +- [x] **Step 4: Document scope** + +Record that correspondence witness coverage now includes selected compound +expression constructors while full inductive correspondence remains future work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 99: Accessor Prop-to-Executable Expression Correspondence Witnesses + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `EvalExprWithFnsRel.index`, `EvalExprWithFnsRel.field`, + `EvalExprWithFnsRel.method`, and the bounded executable `evalExprWithFns`. +- Produces: checked concrete witnesses that selected Prop expression facts for + list indexing, list `length` fields, and string `len()` methods agree with + executable evaluation. + +- [x] **Step 1: Inspect accessor helper semantics** + +Confirm that `evalIndex` supports list indexing, `evalField` supports +`length`, and `evalMethod` supports zero-argument `len`. + +- [x] **Step 2: Add failing checked examples** + +Add examples using intended correspondence witness names before defining them. + +- [x] **Step 3: Verify red** + +Run: `lake build` +Expected: FAIL because the accessor correspondence witness names do not exist. + +- [x] **Step 4: Add concrete correspondence witnesses** + +Define witnesses for list indexing, list `length`, and string `len()`. +Accessor examples over list literals use fuel `3` because the accessor +evaluation consumes one step for the accessor and one for the list target +before evaluating the list elements. + +- [x] **Step 5: Document scope** + +Record that correspondence witness coverage now includes selected accessor and +method-call expression constructors while full inductive correspondence remains +future work. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 100: Function Call Prop-to-Executable Expression Correspondence Witnesses + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `EvalExprWithFnsRel.callReturn`, `EvalExprWithFnsRel.callValue`, + `StepBlockWithFns`, function environments, and the bounded executable + `evalExprWithFns`. +- Produces: checked concrete witnesses that selected Prop expression facts for + explicit-return and implicit-final-expression function calls agree with + executable evaluation. + +- [x] **Step 1: Inspect call semantics** + +Confirm the relation and executable both handle explicit `return` results and +ordinary function-body values as call expression results. + +- [x] **Step 2: Add failing checked examples** + +Add examples using intended call correspondence witness names before defining +them. + +- [x] **Step 3: Verify red** + +Run: `lake build` +Expected: FAIL because the function-call correspondence witness names do not +exist. + +- [x] **Step 4: Add concrete correspondence witnesses** + +Define witnesses for `id(x) { return x }` and `one() { 1 }`. Use fuel `3` so +the call, function-body statement, and returned/body expression all have fuel. + +- [x] **Step 5: Document scope** + +Record that correspondence witness coverage now includes both explicit-return +and implicit-final-expression function calls while full inductive +correspondence remains future work. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 101: Base Statement Prop-to-Executable Correspondence Witnesses + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `StepStmtWithFns`, `execStmtWithFns`, function-aware expression + witnesses, and function environments. +- Produces: checked concrete witnesses that selected Prop statement facts for + `let`, `fn` declaration, and `return` statements agree with executable + statement evaluation through projected observable results. + +- [x] **Step 1: Inspect statement executor semantics** + +Confirm `execStmtWithFns` evaluates `let` and `return expr` with one lower +expression fuel, while `fnDecl` immediately adds a function binding and returns +`unit`. + +- [x] **Step 2: Add failing checked examples** + +Add examples using intended statement correspondence witness names before +defining them. + +- [x] **Step 3: Verify red** + +Run: `lake build` +Expected: FAIL because the statement correspondence witness names do not exist. + +- [x] **Step 4: Add concrete statement witnesses** + +Define witnesses for `let x = 7`, `fn id(x)`, and `return x`. Compare projected +executable results so the checks cover observable environments and flow without +requiring decidable equality over function body payloads. + +- [x] **Step 5: Document scope** + +Record that initial statement correspondence witnesses cover selected +function-aware statements and projected executable outputs. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 102: Assignment and Control Statement Prop-to-Executable Witnesses + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `StepStmtWithFns.assign`, `StepStmtWithFns.expr`, + `StepStmtWithFns.retNone`, `StepStmtWithFns.break`, + `StepStmtWithFns.continue`, and the bounded executable `execStmtWithFns`. +- Produces: checked concrete witnesses that selected assignment, expression, + return-none, break, and continue statement facts agree with executable + statement evaluation through projected observable results. + +- [x] **Step 1: Inspect existing projection pattern** + +Confirm the Task 101 `(env, flow)` projection shape works for assignment, +expression statements, and direct control-flow statements. + +- [x] **Step 2: Add failing checked examples** + +Add examples using intended witness names before defining them. + +- [x] **Step 3: Verify red** + +Run: `lake build` +Expected: FAIL because the assignment/control statement witness names do not +exist. + +- [x] **Step 4: Add concrete witnesses** + +Define witnesses for assigning `x = 9`, evaluating `true` as an expression +statement, `return none`, `break`, and `continue`. + +- [x] **Step 5: Document scope** + +Record that statement correspondence witness coverage now includes assignment, +expression statements, and direct control-flow statements. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 103: Base Block Prop-to-Executable Correspondence Witnesses + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `StepBlockWithFns`, `execBlockWithFns`, and the projected + statement executable witness pattern. +- Produces: checked concrete witnesses that selected Prop block facts for empty + blocks, single-statement blocks, value sequencing, and early + `return`/`break`/`continue` propagation agree with executable block + evaluation through projected observable results. + +- [x] **Step 1: Inspect block executor semantics** + +Confirm `execBlockWithFns` returns `unit` for empty blocks, delegates singleton +blocks to `execStmtWithFns`, sequences after `Flow.value`, and stops early on +`return`, `break`, or `continue`. + +- [x] **Step 2: Add failing checked examples** + +Add examples using intended block correspondence witness names before defining +them. + +- [x] **Step 3: Verify red** + +Run: `lake build` +Expected: FAIL because the block correspondence witness names do not exist. + +- [x] **Step 4: Add concrete block witnesses** + +Define witnesses for an empty block, a single boolean expression block, `let` +then `var` value sequencing, and early `return`, `break`, and `continue`. + +- [x] **Step 5: Document scope** + +Record that block correspondence witness coverage now includes selected block +forms and projected executable outputs. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 104: If Statement Prop-to-Executable Correspondence Witnesses + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `StepStmtWithFns.ifTrue`, `StepStmtWithFns.ifFalseSome`, + `StepStmtWithFns.ifFalseNone`, `StepBlockWithFns`, and the bounded + executable `execStmtWithFns`. +- Produces: checked concrete witnesses that selected Prop `if` statement facts + agree with executable statement evaluation through projected observable + results. + +- [x] **Step 1: Inspect if executor semantics** + +Confirm `execStmtWithFns` evaluates the condition, executes the selected branch +as a block when present, and returns `unit` for a falsey condition without an +`else`. + +- [x] **Step 2: Add failing checked examples** + +Add examples using intended `if` witness names before defining them. + +- [x] **Step 3: Verify red** + +Run: `lake build` +Expected: FAIL because the `if` witness names do not exist. + +- [x] **Step 4: Add concrete witnesses** + +Define witnesses for true-branch selection, false-branch `else` selection, and +false-without-else behavior. + +- [x] **Step 5: Document scope** + +Record that structured statement witness coverage now includes selected `if` +branching cases. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 105: Non-Recursive While Prop-to-Executable Correspondence Witnesses + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `StepStmtWithFns.whileFalse`, `StepStmtWithFns.whileReturn`, + `StepStmtWithFns.whileBreak`, `StepBlockWithFns`, and the bounded executable + `execStmtWithFns`. +- Produces: checked concrete witnesses that selected non-recursive Prop `while` + statement facts agree with executable statement evaluation through projected + observable results. + +- [x] **Step 1: Inspect while semantics** + +Confirm false conditions exit immediately, body `return` propagates, and body +`break` exits as `unit`; leave recursive value and continue cases for later. + +- [x] **Step 2: Add failing checked examples** + +Add examples using intended while witness names before defining them. + +- [x] **Step 3: Verify red** + +Run: `lake build` +Expected: FAIL because the while witness names do not exist. + +- [x] **Step 4: Add concrete witnesses** + +Define witnesses for while-false, while-return, and while-break behavior. + +- [x] **Step 5: Document scope** + +Record that loop correspondence witness coverage now includes selected +non-recursive while exits. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 106: Non-Recursive ForRange Prop-to-Executable Correspondence Witnesses + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `StepStmtWithFns.forDone`, `StepStmtWithFns.forReturn`, + `StepStmtWithFns.forBreak`, `StepBlockWithFns`, and the bounded executable + `execStmtWithFns`. +- Produces: checked concrete witnesses that selected non-recursive Prop + `forRange` statement facts agree with executable statement evaluation through + projected observable results. + +- [x] **Step 1: Inspect forRange semantics** + +Confirm completed ranges bind the iterator to the stop value, body `return` +propagates, and body `break` exits with `unit`; leave recursive value and +continue cases for later. + +- [x] **Step 2: Add failing checked examples** + +Add examples using intended forRange witness names before defining them. + +- [x] **Step 3: Verify red** + +Run: `lake build` +Expected: FAIL because the forRange witness names do not exist. + +- [x] **Step 4: Add concrete witnesses** + +Define witnesses for for-done, for-return, and for-break behavior. + +- [x] **Step 5: Document scope** + +Record that loop correspondence witness coverage now includes selected +non-recursive forRange exits. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 107: Recursive ForRange Prop-to-Executable Correspondence Witnesses + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `StepStmtWithFns.forValue`, `StepStmtWithFns.forContinue`, + `StepStmtWithFns.forDone`, `StepBlockWithFns`, and the bounded executable + `execStmtWithFns`. +- Produces: checked concrete witnesses that selected recursive Prop `forRange` + statement facts agree with executable statement evaluation through projected + observable results. + +- [x] **Step 1: Inspect recursive forRange semantics** + +Confirm ordinary body values and body `continue` both advance to the next range +value and then recurse through the executable evaluator. + +- [x] **Step 2: Add failing checked examples** + +Add examples using intended recursive forRange witness names before defining +them. + +- [x] **Step 3: Verify red** + +Run: `lake build` +Expected: FAIL because the recursive forRange witness names do not exist. + +- [x] **Step 4: Add concrete witnesses** + +Define witnesses for value-body recursion into completion and continue-body +recursion into completion. + +- [x] **Step 5: Document scope** + +Record that loop correspondence witness coverage now includes selected +recursive forRange value and continue behavior. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 108: Seal-Until Prop-to-Executable Correspondence Witnesses + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `StepStmtWithFns.sealUntilDone`, + `StepStmtWithFns.sealUntilValue`, `StepStmtWithFns.sealUntilBreak`, + `StepBlockWithFns`, and the bounded executable `execStmtWithFns`. +- Produces: checked concrete witnesses that selected Prop `seal until` + statement facts agree with executable statement evaluation through projected + observable results. + +- [x] **Step 1: Inspect seal semantics** + +Confirm satisfied conditions exit immediately, ordinary body values recurse to +the next condition check, and body `break` exits with `unit`. + +- [x] **Step 2: Add failing checked examples** + +Add examples using intended `seal until` witness names before defining them. + +- [x] **Step 3: Verify red** + +Run: `lake build` +Expected: FAIL because the `seal until` witness names do not exist. + +- [x] **Step 4: Add concrete witnesses** + +Define witnesses for already-done, value-body recursion into completion, and +break-body exit. + +- [x] **Step 5: Document scope** + +Record that loop correspondence witness coverage now includes selected +`seal until` exits and value recursion. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 109: Seal-Until Return and Continue Correspondence Witnesses + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `StepStmtWithFns.sealUntilReturn`, + `StepStmtWithFns.sealUntilContinue`, `StepBlockWithFns`, and the bounded + executable `execStmtWithFns`. +- Produces: checked concrete witnesses that selected Prop `seal until` + `return` and `continue` facts agree with executable statement evaluation + through projected observable results. + +- [x] **Step 1: Inspect remaining conditional seal semantics** + +Confirm body `return` propagates immediately and body `continue` rechecks the +condition using the body-updated environment. + +- [x] **Step 2: Add failing checked examples** + +Add examples using intended `seal until` return and continue witness names +before defining them. + +- [x] **Step 3: Verify red** + +Run: `lake build` +Expected: FAIL because the `seal until` return and continue witness names do +not exist. + +- [x] **Step 4: Add concrete witnesses** + +Define witnesses for return propagation and continue-driven condition recheck. + +- [x] **Step 5: Document scope** + +Record that conditional seal correspondence witness coverage includes selected +return and continue behavior. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 110: Bare Seal Prop-to-Executable Correspondence Witnesses + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `StepStmtWithFns.sealValue`, `StepStmtWithFns.sealReturn`, + `StepStmtWithFns.sealBreak`, `StepBlockWithFns`, and the bounded executable + `execStmtWithFns`. +- Produces: checked concrete witnesses that selected Prop bare `seal` statement + facts agree with executable statement evaluation through projected observable + results. + +- [x] **Step 1: Inspect bare seal semantics** + +Confirm bare `seal` repeats after ordinary body values, propagates body +`return`, and exits with `unit` on body `break`. + +- [x] **Step 2: Add failing checked examples** + +Add examples using intended bare `seal` witness names before defining them. + +- [x] **Step 3: Verify red** + +Run: `lake build` +Expected: FAIL because the bare `seal` witness names do not exist. + +- [x] **Step 4: Add concrete witnesses** + +Define witnesses for value-body recursion into a later break, direct return +propagation, and direct break exit. + +- [x] **Step 5: Document scope** + +Record that bare seal correspondence witness coverage includes selected value, +return, and break behavior. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 111: Bare Seal Continue Correspondence Witness + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `StepStmtWithFns.sealContinue`, `StepStmtWithFns.sealBreak`, + `StepBlockWithFns`, and the bounded executable `execStmtWithFns`. +- Produces: a checked concrete witness that a selected Prop bare `seal` + `continue` fact agrees with executable statement evaluation through projected + observable results. + +- [x] **Step 1: Inspect bare seal continue semantics** + +Confirm body `continue` recurses into the next bare-seal iteration with the +body-updated environment. + +- [x] **Step 2: Add a failing checked example** + +Add an example using the intended bare `seal` continue witness name before +defining it. + +- [x] **Step 3: Verify red** + +Run: `lake build` +Expected: FAIL because the bare `seal` continue witness name does not exist. + +- [x] **Step 4: Add concrete witness** + +Define the witness for continue-driven recursion into a later break. + +- [x] **Step 5: Document scope** + +Record that bare seal correspondence witness coverage includes selected +continue behavior. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 112: Lean Parser Self Expression Support + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: lexer `TokenKind.self` and core variable expressions. +- Produces: parser support for `self` as `Expr.var "self"`, including postfix + field access such as `self.length`. + +- [x] **Step 1: Inspect Rust and Lean parser behavior** + +Confirm the Rust parser treats `self` as an expression identifier while the Lean +parser tokenizes `self` but does not accept it as a primary expression. + +- [x] **Step 2: Add failing checked examples** + +Add checked parser examples for `self` and `self.length` before implementing +the parser support. + +- [x] **Step 3: Verify red** + +Run: `lake build` +Expected: FAIL because `self` is not yet parsed as an expression primary. + +- [x] **Step 4: Implement parser support** + +Treat `TokenKind.self` as `Expr.var "self"` in `parsePrimary` so existing +postfix parsing handles field and method forms. + +- [x] **Step 5: Document scope** + +Record that parser coverage includes `self` expression parsing. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 113: Self Static Diagnostic Span Support + +**Files:** +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: parser support for `Expr.var "self"`, located lexer + `TokenKind.self`, and static undeclared-variable diagnostics. +- Produces: positioned pipeline diagnostics for undeclared `self` references. + +- [x] **Step 1: Inspect diagnostic span lookup** + +Confirm `self` parses as a variable expression but static diagnostic span +lookup only matches identifier tokens. + +- [x] **Step 2: Add a failing checked example** + +Add a checked `checkSourceErrorString` example requiring undeclared `self` to +include the `self` source span. + +- [x] **Step 3: Verify red** + +Run: `lake build` +Expected: FAIL because the undeclared `self` diagnostic lacks a source span. + +- [x] **Step 4: Implement matcher support** + +Treat `TokenKind.self` as matching the variable name `self` in +`tokenMatchesIdent`. + +- [x] **Step 5: Document scope** + +Record that positioned static diagnostics handle reserved `self` variable +tokens. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 114: Flexible Postfix Field and Method Names + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: lexer keyword tokens for domain names such as `dim` and `cluster`. +- Produces: Lean parser support for reserved domain keyword tokens as postfix + field and method names, matching the Rust parser's flexible identifier + behavior. + +- [x] **Step 1: Inspect Rust and Lean postfix parsing** + +Confirm Rust accepts flexible identifiers after `.`, while Lean only accepts +ordinary identifier tokens for postfix field and method names. + +- [x] **Step 2: Add failing checked examples** + +Add parser examples for `self.dim` and `self.cluster()` before implementing the +helper. + +- [x] **Step 3: Verify red** + +Run: `lake build` +Expected: FAIL because reserved domain keyword tokens are not accepted after +`.`. + +- [x] **Step 4: Implement flexible postfix helper** + +Add `flexibleIdent?` and use it for postfix field and method names. + +- [x] **Step 5: Document scope** + +Record that parser coverage includes reserved domain keyword field/method names. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 115: Render Keyword Tokens in Parse Diagnostics + +**Files:** +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Lean lexer keyword tokens and source-pipeline parse-error + rendering. +- Produces: deterministic rendered names for domain, object/module, and other + proof-core keyword tokens in parser diagnostics. + +- [x] **Step 1: Add failing rendered-diagnostic examples** + +Add `parseSourceErrorString` examples requiring `manifold` and `class` parse +errors to render those keyword names instead of the generic `token` fallback. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because keyword tokens without explicit `tokenString` cases +render as `token`. + +- [x] **Step 3: Extend token rendering** + +Add explicit `tokenString` cases for the remaining named proof-core lexer +keyword tokens. + +- [x] **Step 4: Document diagnostic coverage** + +Record that parser diagnostics render proof-core lexer keywords by name. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 116: Self Expression-Start Diagnostics + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: parser support for `TokenKind.self` as `Expr.var "self"` and + parse diagnostic classification. +- Produces: expression-start classification that treats `self` as a valid + expression start, matching the executable expression parser. + +- [x] **Step 1: Add failing parser diagnostic example** + +Add a checked `parseProgramDetailed` example for a malformed expression that +starts with `self`. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `startsExpr` does not classify `TokenKind.self` as a +valid expression start. + +- [x] **Step 3: Update expression-start classifier** + +Add `TokenKind.self` to `startsExpr`. + +- [x] **Step 4: Document diagnostic boundary** + +Record that parse diagnostic classification recognizes `self` expression +starts, while recursive failure locations remain future parser work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 117: Float Expression-Start Diagnostics + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: parser support for `TokenKind.float` as `Expr.float` and parse + diagnostic classification. +- Produces: expression-start classification that treats decimal float literals + as valid expression starts, matching the executable expression parser. + +- [x] **Step 1: Add failing parser diagnostic example** + +Add a checked `parseProgramDetailed` example for a malformed expression that +starts with a decimal float literal. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `startsExpr` does not classify `TokenKind.float` as a +valid expression start. + +- [x] **Step 3: Update expression-start classifier** + +Add `TokenKind.float` to `startsExpr`. + +- [x] **Step 4: Document diagnostic boundary** + +Record that parse diagnostic classification recognizes float literal +expression starts, while recursive failure locations remain future parser work. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 118: Keyword Function Call Parsing + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Lean lexer keyword tokens `embed` and `convergence`, Rust parser + behavior for keyword calls, and pipeline static diagnostic span lookup. +- Produces: Lean parser support for `embed(...)` and `convergence(...)` as + ordinary `Expr.call` nodes, expression-start classification for those + keyword calls, and source spans for undeclared keyword-call functions. + +- [x] **Step 1: Add failing parser and pipeline examples** + +Add checked parser examples for `embed(1)` and `convergence(0.1)`, plus a +rendered static diagnostic for undeclared `embed`. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the Lean parser does not yet accept `embed` or +`convergence` keyword tokens as call expressions. + +- [x] **Step 3: Implement keyword-call parser support** + +Add a keyword-call helper, parse `embed(...)`/`convergence(...)` into +`Expr.call`, and classify those keyword tokens as expression starts. + +- [x] **Step 4: Implement keyword-call diagnostic spans** + +Match `embed` and `convergence` keyword tokens as function names in pipeline +static diagnostic span lookup. + +- [x] **Step 5: Document scope** + +Record keyword-call parser support and undeclared keyword-call diagnostic span +coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 119: Context-Sensitive Keyword Call Diagnostics + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 118 keyword-call parsing for `embed(...)` and + `convergence(...)`, plus parse diagnostic expression-start classification. +- Produces: diagnostics that treat `embed`/`convergence` as valid expression + starts only when followed by `(`, so bare keyword-call names are reported at + the offending keyword token. + +- [x] **Step 1: Add failing parser diagnostic examples** + +Add checked `parseProgramDetailed` examples for `let x = embed` and +`let x = convergence`. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the keyword-call tokens are classified as expression +starts even when they are not followed by `(`. + +- [x] **Step 3: Implement context-sensitive classification** + +Add `startsExprPrefix` so `embed` and `convergence` only start expressions when +the next token is `(`, while existing token-level expression starts keep their +behavior. + +- [x] **Step 4: Document diagnostic boundary** + +Record that bare keyword-call names are malformed expression starts, while +keyword calls with `(` remain valid expression starts. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 120: Named Call and Method Arguments + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Rust parser named-argument behavior, Lean call/method expression + syntax, static argument checking, evaluator argument evaluation, and VM + argument lowering. +- Produces: proof-core `Arg` nodes that preserve positional and named call or + method arguments, parser support for `name=value` arguments, and conservative + source-order evaluation/checking/lowering of argument payloads. + +- [x] **Step 1: Add failing parser examples** + +Add checked parser examples for `embed(data, dim=3)` and +`self.cluster(axis=2)` that require named argument nodes. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `Arg` and named-argument parsing do not exist yet. + +- [x] **Step 3: Extend core AST** + +Add `Arg.positional` and `Arg.named`, change `Expr.call` and `Expr.method` to +store `List Arg`, and keep a coercion from `Expr` to positional `Arg` so +existing positional examples remain concise. + +- [x] **Step 4: Parse named arguments** + +Extend `parseArgList` to preserve `flexibleIdent = expr` as `Arg.named` and +ordinary expressions as `Arg.positional`. + +- [x] **Step 5: Update consumers** + +Update static checking, executable evaluation, relational witnesses, and stack +and frame VM compilation to traverse named argument payload expressions in +source order while preserving names in the AST. + +- [x] **Step 6: Document semantics** + +Record that named argument names are preserved, while current runtime binding +uses source-order argument values and arity. + +- [x] **Step 7: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 121: Named Function Argument Binding + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 120 `Arg.named` syntax, function parameter lists, executable + function evaluation, and frame VM call lowering. +- Produces: executable named function-call semantics where named arguments bind + to matching parameters, plus frame compiler normalization that lowers named + calls into the VM's positional call ABI. + +- [x] **Step 1: Add failing named-binding examples** + +Add checked examples for `pick(b=2, a=7)` in both direct function evaluation +and the checked source pipeline. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because named arguments are still bound by source order. + +- [x] **Step 3: Implement evaluator binding** + +Add `bindCallArgs` so positional calls delegate to the existing positional +binding path, while named calls bind argument values to parameter names and +reject unknown, duplicate, or incomplete bindings. + +- [x] **Step 4: Normalize frame compiler calls** + +Normalize named function-call arguments to parameter order before frame +bytecode generation so existing `CALL target arity` instructions keep their +positional ABI. + +- [x] **Step 5: Document semantics** + +Record that named function calls bind by parameter name and that the frame +compiler lowers them by normalizing call arguments to parameter order. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 122: Static Named Argument Diagnostics + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 121 named function-call runtime semantics, `FnSig` + collection, detailed static checking, and source diagnostic rendering. +- Produces: static rejection for unknown or duplicate named function + arguments, including deterministic rendered source ranges. + +- [x] **Step 1: Add failing static and pipeline examples** + +Add checked examples for `pick(c=1, a=2)` and `pick(a=1, a=2)` that expect +static named-argument diagnostics. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the named-argument diagnostic constructors and +validation do not exist. + +- [x] **Step 3: Track function parameter names in signatures** + +Extend `FnSig` with `params`, preserve parameter names during signature +collection and result inference, and keep existing arity/result behavior. + +- [x] **Step 4: Validate named arguments in static calls** + +Reject unknown named arguments and duplicate named arguments before checking +argument payload expressions. + +- [x] **Step 5: Render source diagnostics** + +Add pipeline error strings and best-effort token spans for the offending named +argument. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 123: Basic Typed Function Parameters + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: lexer `:` tokens, proof-core function declarations, static + function signatures, and checked source diagnostics. +- Produces: typed function declarations with basic `num`, `bool`, `str`, and + `unit` parameter annotations, plus static argument type validation for calls. + +- [x] **Step 1: Add failing parser/static/pipeline examples** + +Add checked examples for parsing `fn id(x: num) { return x }` and rejecting +`id(true)` against a `num` parameter. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because typed function declarations and argument mismatch +diagnostics do not exist. + +- [x] **Step 3: Add typed declaration AST and parser support** + +Add `AnnTy`, `Stmt.fnDeclTyped`, typed parameter parsing, and parser examples. + +- [x] **Step 4: Enforce typed parameter arguments statically** + +Extend `FnSig` with parameter type vectors, bind typed function bodies with +annotated parameter types, and reject incompatible call argument types. + +- [x] **Step 5: Preserve typed declarations through VM lowering** + +Collect and normalize typed function declarations as ordinary frame functions +using their parameter names. + +- [x] **Step 6: Document scope** + +Record basic typed parameters while keeping richer type syntax and declared +return types as future work. + +- [x] **Step 7: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 124: Declared Function Return Types + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 123 `AnnTy` and typed function parameters, parser colon + tokens, static return inference, and source diagnostic rendering. +- Produces: typed function declarations with declared return annotations and + detailed static rejection when the inferred body return type disagrees. + +- [x] **Step 1: Add failing parser/static/pipeline examples** + +Add checked examples for parsing `fn id(x: num): num { return x }` and +rejecting `fn bad(x: num): num { return true }`. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because return-typed function declarations and return mismatch +diagnostics do not exist. + +- [x] **Step 3: Extend core and parser** + +Add `Stmt.fnDeclTypedReturn` and parse `): type` after typed parameter lists. + +- [x] **Step 4: Check declared return types** + +Use the declared return annotation as the function result type and compare it +against inferred body return type in the detailed checker. + +- [x] **Step 5: Preserve through runtime and VM surfaces** + +Treat return-typed declarations as ordinary functions at runtime and during +frame VM function collection/normalization. + +- [x] **Step 6: Render source diagnostics** + +Add `returnMismatch` error strings and source spans pointing at the mismatched +return expression token. + +- [x] **Step 7: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 125: List Type Annotations + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 123/124 annotation grammar, existing `Ty.list`, list literal + inference, typed parameter checking, and source diagnostic rendering. +- Produces: parsed source annotations such as `xs: list[num]`, including nested + list annotations, lowered into `Ty.list` for static argument validation. + +- [x] **Step 1: Add failing parser/static/pipeline examples** + +Add checked examples for parsing `fn first(xs: list[num]): num`, rejecting a +`list[bool]` call argument where `list[num]` is declared, and rendering the +source diagnostic on the bad list literal. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `AnnTy.list` does not exist. + +- [x] **Step 3: Extend annotation AST and parser** + +Add `AnnTy.list` and parse recursive `list[...]` annotation syntax. + +- [x] **Step 4: Lower annotations into static types** + +Map `AnnTy.list elem` to `Ty.list (annTyToTy elem)` so existing call argument +compatibility checks handle list annotations. + +- [x] **Step 5: Render source diagnostics** + +Treat `[` as the source token for concrete list type mismatches. + +- [x] **Step 6: Document scope** + +Record `list[...]` annotation syntax in the formal core grammar and parser +coverage notes. + +- [x] **Step 7: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 126: Typed Local Declarations + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: existing `AnnTy`, `annTyToTy`, assignment compatibility, source + static diagnostics, executable statement semantics, and frame VM local-slot + compilation. +- Produces: source syntax `let name: type = expr`, static initializer + validation against the declared type, and runtime/VM behavior equivalent to + ordinary local binding after static acceptance. + +- [x] **Step 1: Add failing parser/static/pipeline examples** + +Add checked examples for parsing `let count: num = 1`, rejecting +`let count: num = true`, and rendering the source diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `Stmt.letDeclTyped` does not exist. + +- [x] **Step 3: Extend core and parser** + +Add `Stmt.letDeclTyped` and parse optional local declaration annotations before +the `=` token. + +- [x] **Step 4: Enforce typed local initializers statically** + +Lower the annotation with `annTyToTy`, check initializer compatibility, bind the +declared/refined type on success, and report `assignmentMismatch` on failure. + +- [x] **Step 5: Preserve through runtime and VM surfaces** + +Execute typed locals like ordinary `let` statements and compile them to the +same local-slot store sequence in the frame VM. + +- [x] **Step 6: Document scope** + +Record typed local syntax and static compatibility behavior in the formal core. + +- [x] **Step 7: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 127: Type Annotation Parse Diagnostics + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: typed local declarations, typed function return syntax, existing + `ParseContext`, `ParseError`, `parseAnnTy`, `parseTypedParamList`, and + located source pipeline diagnostics. +- Produces: dedicated `expected type` parser diagnostics for malformed type + annotation positions instead of generic statement/block/parameter failures. + +- [x] **Step 1: Add failing parser/pipeline examples** + +Add checked examples for `let count: = 1` and +`fn id(x: num): { return x }` that expect `ParseContext.typeAnnotation` and +user-facing `expected type` source errors. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `ParseContext.typeAnnotation` does not exist. + +- [x] **Step 3: Add parser type context** + +Extend `ParseContext` with `typeAnnotation` and render it as `type` in the +pipeline. + +- [x] **Step 4: Classify malformed annotation sites** + +Use `parseAnnTy` for `let name: ...` and `parseTypedParamList` plus +`parseAnnTy` for `fn name(params): ...` to classify malformed annotation starts +and place the diagnostic token after the colon. + +- [x] **Step 5: Document diagnostics** + +Record that malformed type annotations now report a dedicated parser context +and source span. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 128: Typed Parameter Parse Diagnostics + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Task 127 `ParseContext.typeAnnotation`, existing typed parameter + syntax, `parseAnnTy`, and located parser diagnostics. +- Produces: dedicated `expected type` parser diagnostics when a function + parameter colon is not followed by a valid type annotation. + +- [x] **Step 1: Add failing parser/pipeline examples** + +Add checked examples for `fn id(x:) { return x }` expecting +`ParseContext.typeAnnotation` and a source error at the `)` token. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because malformed typed parameter annotations still report a +generic parameter-list parse failure. + +- [x] **Step 3: Detect malformed parameter annotation starts** + +Add a diagnostic helper that scans function parameter tokens for +`identifier :` followed by a non-type token and returns the offset after the +colon. + +- [x] **Step 4: Wire classification and source spans** + +Classify those failures as `typeAnnotation` and reuse the returned offset for +both parser and pipeline diagnostics. + +- [x] **Step 5: Document coverage** + +Record malformed function parameter annotations in the parser diagnostic +coverage notes. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 129: Unit Literal Expression + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: existing `Value.unit`, `Ty.unit`, annotation syntax for `unit`, + expression parser literals, static expression checking, runtime expression + evaluation, and stack/frame VM expression compilation. +- Produces: source-level `unit` expression literal that parses, evaluates, + type-checks as `unit`, and compiles to a pushed unit value. + +- [x] **Step 1: Add failing parser/static/runtime/VM/pipeline examples** + +Add checked examples for parsing `unit`, checking it as `Ty.unit`, evaluating +it to `Value.unit`, compiling it in stack and frame VM expression compilers, +and parsing `let done: unit = unit` through the source pipeline. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `Expr.unit` does not exist. + +- [x] **Step 3: Add core unit expression** + +Add `Expr.unit`, evaluate it to `Value.unit`, and add the function-aware +expression relation constructor. + +- [x] **Step 4: Parse and check unit** + +Parse identifier `unit` as the unit literal before generic identifier fallback +and type it as `Ty.unit` in both static checker paths. + +- [x] **Step 5: Compile unit** + +Compile unit literals to `Op.push Value.unit` and `FrameOp.push Value.unit` in +all expression compiler variants. + +- [x] **Step 6: Document scope** + +Record `unit` as a source literal, static type, and bytecode constant in the +formal core. + +- [x] **Step 7: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 130: Implicit Unit Return Mismatch Spans + +**Files:** +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: declared return type checking, implicit `Ty.unit` for functions + with no return-producing body, `returnMismatch` diagnostics, and function-name + span helpers. +- Produces: source-positioned diagnostics for non-unit functions that + implicitly return unit because no return expression is present. + +- [x] **Step 1: Add failing pipeline example** + +Add a checked source example for `fn bad(): num { }` expecting a +`returnMismatch` rendered at the function name. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `returnMismatch` currently tries to locate a concrete +actual-value token, and implicit unit has no source token. + +- [x] **Step 3: Add function-name fallback** + +For `returnMismatch`, keep the concrete actual-token span when available and +fall back to `fnNameSpanWhere` when no token matches the actual type. + +- [x] **Step 4: Document diagnostic behavior** + +Record that implicit-unit declared return mismatches point at the function +name. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 131: Explicit Unit Return Mismatch Spans + +**Files:** +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: source-level `unit` literal, declared return mismatch diagnostics, + `tokenMatchesTy`, and the implicit-unit fallback added in Task 130. +- Produces: precise source spans for explicit `return unit` mismatches before + falling back to the function-name span for implicit unit returns. + +- [x] **Step 1: Add failing pipeline example** + +Add a checked source example for `fn bad(): num { return unit }` expecting the +diagnostic range to cover the `unit` literal. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `Ty.unit` does not currently match any source token. + +- [x] **Step 3: Match unit source tokens** + +Teach `tokenMatchesTy` to recognize `Static.Ty.unit` as +`Lexer.TokenKind.identifier "unit"`. + +- [x] **Step 4: Document diagnostic behavior** + +Record that explicit unit return mismatches point at the `unit` token while +implicit unit mismatches still fall back to the function name. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 132: Option Checker Declared Return Contract + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `checkProgramDetailed`, `CheckError.returnMismatch`, typed return + declarations, and the option-returning `checkProgram` API. +- Produces: consistent declared-return enforcement for both checker APIs while + preserving `checkProgram : List Stmt -> Option CheckState`. + +- [x] **Step 1: Add failing static example** + +Add a checked example showing that plain `checkProgram` rejects a typed +function declared as `num` when the body returns `bool`. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `checkProgram` currently checks the body but ignores the +declared return type. + +- [x] **Step 3: Delegate option checker to detailed checker** + +Move the `checkProgram` wrapper after `checkProgramDetailed` and implement it +by converting `Except.ok state` to `some state` and any detailed error to +`none`. + +- [x] **Step 4: Document checker consistency** + +Record that `checkProgram` erases detailed errors rather than maintaining a +separate weaker checker path. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 133: Branch Declared Return Path Checking + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: declared return annotations, `inferBlockReturnTy?`, branch return + inference, `CheckError.returnMismatch`, and typed function body checking. +- Produces: concrete declared-return mismatch detection for explicit `return` + paths and final-expression returns inside branches and loops, without + removing the existing gradual `unknown` compatibility model. + +- [x] **Step 1: Add failing static example** + +Add a checked example for a `num` function whose `if` branches return `num` and +`bool`, expecting a `returnMismatch` for the `bool` branch. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the old checker merges the branch return types to +`unknown`, then treats `unknown` as compatible with the declared return type. + +- [x] **Step 3: Check concrete return paths** + +Add a detailed declared-return path walk that checks explicit `return` +statements and final-expression returns against the declared type while +threading local variable bindings through the body. + +- [x] **Step 4: Document the invariant** + +Record that declared return checking now catches concrete branch mismatches +before relying on the merged return summary. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 134: Partial Branch Implicit Unit Return Checking + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: declared return path checking, `Stmt.ifThenElse`, implicit + `Ty.unit`, and `CheckError.returnMismatch`. +- Produces: rejection for non-unit declared functions where an `if` branch can + fall through because no `else` is present. + +- [x] **Step 1: Add failing static example** + +Add a checked example for `fn maybe(b: bool): num { if b { return 1 } }`, +expecting a `returnMismatch` against implicit `unit`. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the previous return-path checker only inspected the +present `then` branch. + +- [x] **Step 3: Treat missing else as implicit unit** + +When declared-return checking sees an `if` without an `else`, require the +declared return type to accept `unit`; otherwise report `returnMismatch`. + +- [x] **Step 4: Document the invariant** + +Record that non-unit functions must cover both branches explicitly. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 135: While Fallthrough Declared Return Checking + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: declared return path checking, `Stmt.while`, implicit `Ty.unit`, + and `CheckError.returnMismatch`. +- Produces: rejection for non-unit declared functions whose only return path is + inside a `while` body that may execute zero times. + +- [x] **Step 1: Add failing static example** + +Add a checked example for `fn loopReturn(b: bool): num { while b { return 1 } }`, +expecting a `returnMismatch` against implicit `unit`. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the previous return-path checker inspected the loop body +but did not account for zero-iteration fallthrough. + +- [x] **Step 3: Treat while as fallthrough-capable** + +After checking the loop body return paths, require the declared return type to +accept `unit`; otherwise report `returnMismatch`. + +- [x] **Step 4: Document the invariant** + +Record that non-unit functions cannot rely on a `while` body as their only +return source. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 136: For-Range Fallthrough Declared Return Checking + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: declared return path checking, `Stmt.forRange`, implicit + `Ty.unit`, and `CheckError.returnMismatch`. +- Produces: rejection for non-unit declared functions whose only return path is + inside an integer range loop that may execute zero times. + +- [x] **Step 1: Add failing static example** + +Add a checked example for `fn rangeReturn(): num { for i in 0..0 { return 1 } }`, +expecting a `returnMismatch` against implicit `unit`. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the previous return-path checker inspected the range +loop body but did not account for empty-range fallthrough. + +- [x] **Step 3: Treat for-range as fallthrough-capable** + +After checking body return paths with the iterator bound as `num`, require the +declared return type to accept `unit`; otherwise report `returnMismatch`. + +- [x] **Step 4: Document the invariant** + +Record that non-unit functions cannot rely on a `for` range body as their only +return source. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 137: Conditional Seal Fallthrough Declared Return Checking + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: declared return path checking, `Stmt.seal`, implicit `Ty.unit`, + and `CheckError.returnMismatch`. +- Produces: rejection for non-unit declared functions whose only return path is + inside a conditional `seal until` body that may be skipped. + +- [x] **Step 1: Add failing static example** + +Add a checked example for `fn sealReturn(b: bool): num { seal until b { return 1 } }`, +expecting a `returnMismatch` against implicit `unit`. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the previous return-path checker inspected the +conditional seal body but did not account for the already-satisfied exit +condition. + +- [x] **Step 3: Treat conditional seal as fallthrough-capable** + +After checking body return paths for `Stmt.seal (some _)`, require the declared +return type to accept `unit`; keep bare `seal` body-only. + +- [x] **Step 4: Document the invariant** + +Record that non-unit functions cannot rely on a conditional `seal until` body +as their only return source. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 138: Compositional Declared Return Fallthrough + +**Files:** +- Modify: `Aether/Static.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: declared return path checking, fallthrough-aware `if`/loop/seal + handling, `inferStmtVars?`, and `CheckError.returnMismatch`. +- Produces: block-level declared-return checking that lets fallthrough continue + to later statements while still rejecting fallthrough at function-exit points. + +- [x] **Step 1: Add failing static example** + +Add a checked example for `fn guarded(b: bool): num { if b { return 1 } return 2 }`, +expecting the function to satisfy the declared return contract. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the previous checker rejected the missing `else` before +considering the following unconditional return. + +- [x] **Step 3: Split path checks from block-exit checks** + +Refactor declared-return checking into path-only statement/block checks and a +complete-block check that reports implicit `unit` only at block-exit points. + +- [x] **Step 4: Preserve final return termination** + +Handle final explicit `return` statements as terminating the checked block +rather than returning a value and then falling through to `unit`. + +- [x] **Step 5: Document the invariant** + +Record that missing branches fall through to later statements when present, +but still count as implicit `unit` at function-exit points. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 139: Portable Lean Lexer Line Endings + +**Files:** +- Modify: `Aether/Lexer.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: plain `tokenize`, located `tokenizeLocated`, `SourcePos`, line + comments, block comments, and string scanning. +- Produces: LF, CRLF, and CR line endings as one logical `TokenKind.newline` + separator with stable located spans. + +- [x] **Step 1: Add failing lexer examples** + +Add Lean examples proving standalone CR produces a newline token, CRLF remains +a single newline token, and located tokenization advances following diagnostics +to line 2 for both line-ending forms. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because standalone CR was treated as whitespace and CRLF located +spans advanced through the skipped CR before the newline token. + +- [x] **Step 3: Add logical newline handling** + +Introduce `advanceNewline`, stop skipping CR as whitespace, scan CR and CRLF as +newline tokens, terminate line comments on CR as well as LF, and make raw CR +unterminate strings like LF. + +- [x] **Step 4: Preserve block-comment positions** + +Advance located block comments across CR and CRLF as one logical line break so +tokens after comments receive stable source positions. + +- [x] **Step 5: Document the invariant** + +Record that source newlines are platform-portable and that LF, CRLF, and CR all +represent one logical statement separator. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 140: Untyped-Parameter Function Return Annotations + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: proof-core function declarations, untyped parameter lists, + `AnnTy`, declared-return checking, function signatures, and VM function + collection. +- Produces: support for `fn name(params): type { ... }`, where parameters stay + statically unknown but the function result is the declared type. + +- [x] **Step 1: Add failing parser and static examples** + +Add Lean examples for parsing `fn id(x): num { return x }`, rejecting +`fn bad(x): num { return true }`, and using a declared result type at call +sites. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the AST lacks an untyped-parameter typed-return +function constructor. + +- [x] **Step 3: Add the AST and parser form** + +Introduce `Stmt.fnDeclReturn` and parse `): type` after ordinary untyped +parameter lists. + +- [x] **Step 4: Thread through static checking** + +Collect function signatures with unknown parameter types and the declared +result type, validate duplicate untyped parameters, and run declared-return +checking against the body. + +- [x] **Step 5: Preserve runtime and VM behavior** + +Treat the new declaration as an ordinary function declaration for executable +semantics, function environments, frame-call normalization, and main-frame +lowering. + +- [x] **Step 6: Document the implemented grammar** + +Record that both untyped and typed parameter lists can carry declared return +annotations in the Lean proof-core parser. + +- [x] **Step 7: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 141: Untyped-Parameter Return Annotation Diagnostics + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: `parseProgramDetailed`, parser diagnostic context + classification, located source diagnostics, typed and untyped function + parameter-list parsing, and `parseAnnTy`. +- Produces: type-context diagnostics for malformed return annotations after + ordinary untyped parameter lists, matching typed-parameter return annotation + diagnostics. + +- [x] **Step 1: Add failing parser and source examples** + +Add checked examples for `fn id(x): { return x }`, expecting +`ParseContext.typeAnnotation` and a source error at the `{` token after the +colon. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because return-annotation diagnostics only inspect typed +parameter-list remainders. + +- [x] **Step 3: Generalize function parameter-list remainders** + +Add a helper that returns the remainder after either a typed or untyped +parameter list and use it for malformed return annotation classification and +diagnostic offsets. + +- [x] **Step 4: Document coverage** + +Record that malformed function return annotations are diagnosed after both +untyped and typed parameter lists. + +- [x] **Step 5: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 142: Nested Lean Block Comments + +**Files:** +- Modify: `Aether/Lexer.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Lean plain and located block-comment lexing, `SourcePos`, and + unterminated-comment diagnostics. +- Produces: depth-aware `/* ... */` block comments so nested block comments + are skipped as one comment region and unterminated nested comments remain + deterministic lexer errors. + +- [x] **Step 1: Add failing lexer examples** + +Add Lean examples proving `/* outer /* inner */ still */` skips the whole +comment and `/* outer /* inner */` reports an unterminated block comment. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the previous scanner closed at the first `*/`, leaking +the outer comment tail back into tokenization. + +- [x] **Step 3: Implement depth-aware scanning** + +Track block-comment depth in both plain and located skippers, incrementing on +nested `/*` and decrementing on `*/`. + +- [x] **Step 4: Preserve located behavior** + +Keep CRLF and newline position advancement inside comments and add a located +token-kind projection example for nested block comments. + +- [x] **Step 5: Document the lexical rule** + +Record that Lean proof-core block comments may nest and that unterminated +nested comments are lexer errors. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 143: Implicit Unit Return Mismatch Span + +**Files:** +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: located token streams, static `returnMismatch` errors, function + name matching, and brace-delimited function bodies. +- Produces: source diagnostics for implicit `unit` return mismatches that point + at the function body's closing brace instead of falling back to the function + name. + +- [x] **Step 1: Add failing source diagnostic example** + +Update the `fn bad(): num { }` source diagnostic example to expect the closing +brace range as the fallthrough source. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because implicit `unit` return mismatches had no concrete value +token and fell back to `fnNameSpanWhere`. + +- [x] **Step 3: Add function-body close lookup** + +Add a located-token helper that finds `fn name`, scans to its body opening +brace, and returns the matching closing brace span with nested brace depth. + +- [x] **Step 4: Use only for implicit unit mismatches** + +Keep explicit `return unit` diagnostics on the `unit` token, but use the +closing brace fallback when the actual return type is implicit `unit`. + +- [x] **Step 5: Document the diagnostic rule** + +Record that implicit fallthrough return mismatches point at the matched +function closing brace when available. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 144: Control-Flow Condition Mismatch Spans + +**Files:** +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: static `conditionMismatch` errors, located token streams, + `tokenMatchesTy`, and source diagnostic rendering. +- Produces: source diagnostics for non-boolean `if`, `while`, and + `seal until` conditions that point at the offending condition token instead + of the control-flow keyword when the concrete condition type can be matched. + +- [x] **Step 1: Add failing source diagnostic examples** + +Update the `if 1`, `while 1`, and `seal until 1` source diagnostic examples to +expect the numeric condition token spans. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because condition mismatches previously used the first +control-flow keyword span. + +- [x] **Step 3: Add condition-token span lookup** + +Scan located tokens for `if `, `while `, and +`seal until ` starts, and return the condition token span when it +matches the mismatched static type. + +- [x] **Step 4: Keep keyword fallback** + +Fall back to the previous keyword span when the condition token cannot be +matched from the static type. + +- [x] **Step 5: Document the diagnostic rule** + +Record that non-boolean control-flow diagnostics prefer the offending condition +token. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 145: Trailing Binary Operator Parse Spans + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: detailed parser diagnostic offsets, token streams for + `let`/assignment/`return` expressions, binary operator tokens, and source + diagnostic rendering. +- Produces: parse diagnostics for expressions ending in a binary operator that + point at the trailing operator instead of the statement start. + +- [x] **Step 1: Add failing parser and source examples** + +Update examples for `let x = 1 +`, `let x = self +`, `let x = 1.5 +`, and the +same failure after an earlier valid statement to expect the `+` token. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because incomplete expressions with valid starts previously +fell back to the broad statement-start diagnostic. + +- [x] **Step 3: Add trailing binary-operator detection** + +Add token helpers that identify binary operators followed by a statement +terminator or EOF and return their offset inside expression-bearing statements. + +- [x] **Step 4: Preserve invalid-start behavior** + +Keep the existing invalid-expression-start offsets for malformed starts when no +trailing binary operator is present. + +- [x] **Step 5: Document the diagnostic rule** + +Record that missing right operands at statement end point at the trailing +operator. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 146: Control-Flow Condition Trailing Operator Spans + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: detailed parser diagnostics for `if`, `while`, and `seal until`, + located source spans, condition expression token streams, and the existing + binary-operator diagnostic helpers. +- Produces: parse diagnostics for condition expressions ending in a binary + operator before a body block that point at the trailing operator instead of + the control-flow keyword or broad block context. + +- [x] **Step 1: Add failing parser and source examples** + +Add checked examples for `if 1 + { break }`, `while 1 + { break }`, and +`seal until 1 + { break }`, expecting expression diagnostics at the `+` token +and matching source spans. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because control-flow condition failures previously fell back to +the broad keyword/block diagnostic when the expression start was valid. + +- [x] **Step 3: Add condition-specific trailing-operator detection** + +Add a scanner that treats `{` as a condition-expression terminator and use it +for `if`, `while`, and `seal until` classification and diagnostic offsets. + +- [x] **Step 4: Preserve malformed-start behavior** + +Keep missing-condition diagnostics such as `if { break }`, `while { break }`, +and `seal until { break }` pointing at the offending `{` token. + +- [x] **Step 5: Document the diagnostic rule** + +Record that trailing binary operators before control-flow body blocks point at +the operator as the missing-right-operand site. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 147: Nested List Type Annotation Diagnostic Spans + +**Files:** +- Modify: `Aether/Parser.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: recursive `parseAnnTy`, local declaration annotations, typed + function parameter annotations, function return annotations, parser + diagnostic offsets, and located source rendering. +- Produces: malformed `list[...]` type diagnostics that point at the inner + token where the element type or closing bracket is missing instead of the + outer `list` token. + +- [x] **Step 1: Add failing parser and source examples** + +Add checked examples for `let xs: list[ = [1]`, +`fn id(x: list[) { return x }`, and `fn id(x): list[ { return x }`, +expecting type diagnostics at `=`, `)`, and `{` respectively. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because malformed nested annotation offsets previously reported +the beginning of the annotation rather than the inner failing token. + +- [x] **Step 3: Add recursive annotation failure offsets** + +Add a helper that follows `list[` annotations into their element type and +returns the token where the element type or closing bracket fails. + +- [x] **Step 4: Wire all annotation sites** + +Use the helper for local declaration annotations, typed function parameter +annotations, and function return annotations after both typed and untyped +parameter lists. + +- [x] **Step 5: Document the diagnostic rule** + +Record that nested `list[...]` type annotation failures point at the inner +missing element or bracket location. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 148: Proof-Core `is_empty` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, static method compatibility, + stack/frame method opcodes, parser postfix method calls, checked source frame + compilation, and pipeline source execution. +- Produces: zero-argument `.is_empty()` support for proof-core strings and + lists, returning `bool`, through the evaluator, checker, VM, checked compiler, + and source pipeline. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `[].is_empty()` and `"open".is_empty()` through +`evalExpr`, `checkExpr`, closed expression bytecode execution, frame expression +compilation, `checkedFrameSourceLocal?`, and `sourceLocal?`. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the shared method evaluator and static compatibility +table only support `.len()` for strings and lists. + +- [x] **Step 3: Implement shared runtime support** + +Extend `evalMethod` so zero-argument `is_empty` returns `Value.bool` for lists +and strings. + +- [x] **Step 4: Implement static method typing** + +Extend `compatibleMethod` so zero-argument `is_empty` on strings and lists +checks as `Ty.bool`. + +- [x] **Step 5: Document the proof-core method** + +Record `.is_empty()` in the proof-core method surface and checked compiler +coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 149: Field and Method Mismatch Member Spans + +**Files:** +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: static `fieldMismatch` and `methodMismatch` errors, located token + streams, flexible member identifiers, and source diagnostic rendering. +- Produces: source diagnostics for unsupported fields and methods that point + at the member identifier after `.` instead of the dot token when the member + can be matched. + +- [x] **Step 1: Add failing source diagnostic examples** + +Update checked examples for `let bad = 1.length` and `let bad = 1.len()` to +expect spans over `length` and `len` rather than the dot. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because static member mismatch spans previously used the last +dot token. + +- [x] **Step 3: Add member-name span lookup** + +Scan located tokens for `.` followed by the matching field or method name and +return that identifier span. + +- [x] **Step 4: Preserve fallback behavior** + +Fall back to the previous dot-token span if no matching member identifier can +be recovered. + +- [x] **Step 5: Document the diagnostic rule** + +Record that field and method mismatch diagnostics prefer the member identifier. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 150: Proof-Core List `first` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, static method compatibility, + stack/frame method opcodes, parser postfix method calls, checked source frame + compilation, and pipeline source execution. +- Produces: zero-argument `.first()` support for proof-core lists, returning + the first runtime value when present and the static list element type. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `[7, 9].first()` through `evalExpr`, `checkExpr`, +closed expression bytecode execution, frame expression compilation, +`checkedFrameSourceLocal?`, and `sourceLocal?`. Include an empty-list runtime +example returning `none`. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the shared method evaluator and static compatibility +table do not yet support `.first()`. + +- [x] **Step 3: Implement shared runtime support** + +Extend `evalMethod` so zero-argument `first` on a non-empty list returns the +first element and empty lists remain runtime failures. + +- [x] **Step 4: Implement static method typing** + +Extend `compatibleMethod` so zero-argument `first` on `list[T]` checks as `T`. + +- [x] **Step 5: Document the proof-core method** + +Record `.first()` in the proof-core method surface and checked compiler +coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 151: Proof-Core List `last` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, static method compatibility, + stack/frame method opcodes, parser postfix method calls, checked source frame + compilation, and pipeline source execution. +- Produces: zero-argument `.last()` support for proof-core lists, returning + the final runtime value when present and the static list element type. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `[7, 9].last()` through `evalExpr`, `checkExpr`, +closed expression bytecode execution, frame expression compilation, +`checkedFrameSourceLocal?`, and `sourceLocal?`. Include an empty-list runtime +example returning `none`. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the shared method evaluator and static compatibility +table do not yet support `.last()`. + +- [x] **Step 3: Implement shared runtime support** + +Extend `evalMethod` so zero-argument `last` on a non-empty list returns the +last element and empty lists remain runtime failures. + +- [x] **Step 4: Implement static method typing** + +Extend `compatibleMethod` so zero-argument `last` on `list[T]` checks as `T`. + +- [x] **Step 5: Document the proof-core method** + +Record `.last()` in the proof-core method surface and checked compiler +coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 152: Proof-Core List `at(index)` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, static method argument type inference, + stack/frame method opcodes, parser postfix method arguments, checked source + frame compilation, and pipeline source execution. +- Produces: `.at(index)` support for proof-core lists, returning the selected + runtime value when the numeric index is in range and the static list element + type when the index argument checks as numeric. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `[7, 9].at(1)` through `evalExpr`, `checkExpr`, closed +expression bytecode execution, frame expression compilation, +`checkedFrameSourceLocal?`, and `sourceLocal?`. Include out-of-range runtime +failure and a boolean-index static diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the shared method evaluator and static compatibility +table do not yet support `.at(index)`. + +- [x] **Step 3: Implement shared runtime support** + +Extend `evalMethod` so list `at` delegates to indexed list lookup with numeric +indices. + +- [x] **Step 4: Implement argument-aware static method typing** + +Extend static method compatibility to consume inferred argument types, and use +that path from both detailed and option-returning expression checkers. + +- [x] **Step 5: Document the proof-core method** + +Record `.at(index)` in the proof-core method surface and checked compiler +coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 153: Proof-Core List `contains(value)` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, static method argument type inference, + stack/frame method opcodes, parser postfix method arguments, checked source + frame compilation, and pipeline source execution. +- Produces: `.contains(value)` support for proof-core lists, returning `bool` + at runtime and statically requiring the searched value to be compatible with + the list element type. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `[7, 9].contains(9)` through `evalExpr`, +`checkExpr`, closed expression bytecode execution, frame expression +compilation, `checkedFrameSourceLocal?`, and `sourceLocal?`. Include a false +runtime membership case and a mismatched argument static diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the shared method evaluator and static compatibility +table do not yet support `.contains(value)`. + +- [x] **Step 3: Implement shared runtime support** + +Add list membership evaluation over `Value` equality and wire list +`.contains(value)` into `evalMethod`. + +- [x] **Step 4: Implement argument-aware static method typing** + +Extend static method compatibility so list `.contains(value)` returns `bool` +only when the argument type is compatible with the list element type. + +- [x] **Step 5: Document the proof-core method** + +Record `.contains(value)` in the proof-core method surface and checked compiler +coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 154: Proof-Core List `tail()` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, static method compatibility, stack/frame + method opcodes, parser postfix method calls, checked source frame + compilation, and pipeline source execution. +- Produces: `.tail()` support for proof-core lists, returning the remaining + runtime list for non-empty lists and statically preserving the list element + type as `list[T]`. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `[7, 9].tail()` through `evalExpr`, `checkExpr`, +closed expression bytecode execution, frame expression compilation, +`checkedFrameSourceLocal?`, and `sourceLocal?`. Include an empty-list runtime +failure and an arity mismatch static diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the shared method evaluator and static compatibility +table do not yet support `.tail()`. + +- [x] **Step 3: Implement shared runtime support** + +Extend `evalMethod` so zero-argument `tail` on a non-empty list returns the +remaining values as `Value.list`; empty lists remain runtime failures. + +- [x] **Step 4: Implement static method typing** + +Extend static method compatibility so list `.tail()` returns `list[T]`. + +- [x] **Step 5: Document the proof-core method** + +Record `.tail()` in the proof-core method surface and checked compiler +coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 155: Proof-Core List `take(count)` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, static method argument type inference, + stack/frame method opcodes, parser postfix method arguments, checked source + frame compilation, and pipeline source execution. +- Produces: `.take(count)` support for proof-core lists, returning the prefix + list at runtime for non-negative numeric counts and statically preserving + the list element type as `list[T]`. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `[7, 9].take(1)` through `evalExpr`, `checkExpr`, +closed expression bytecode execution, frame expression compilation, +`checkedFrameSourceLocal?`, and `sourceLocal?`. Include oversized-count +runtime behavior, negative-count runtime failure, and boolean-count static +diagnostics. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the shared method evaluator and static compatibility +table do not yet support `.take(count)`. + +- [x] **Step 3: Implement shared runtime support** + +Add an explicit recursive list-prefix helper and wire list `.take(count)` into +`evalMethod`, rejecting negative counts at runtime. + +- [x] **Step 4: Implement argument-aware static method typing** + +Extend static method compatibility so list `.take(count)` returns `list[T]` +only when the count argument checks as numeric. + +- [x] **Step 5: Document the proof-core method** + +Record `.take(count)` in the proof-core method surface and checked compiler +coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 156: Proof-Core List `drop(count)` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, static method argument type inference, + stack/frame method opcodes, parser postfix method arguments, checked source + frame compilation, and pipeline source execution. +- Produces: `.drop(count)` support for proof-core lists, returning the suffix + list at runtime for non-negative numeric counts and statically preserving + the list element type as `list[T]`. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `[7, 9].drop(1)` through `evalExpr`, `checkExpr`, +closed expression bytecode execution, frame expression compilation, +`checkedFrameSourceLocal?`, and `sourceLocal?`. Include oversized-count +runtime behavior, negative-count runtime failure, and boolean-count static +diagnostics. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the shared method evaluator and static compatibility +table do not yet support `.drop(count)`. + +- [x] **Step 3: Implement shared runtime support** + +Add an explicit recursive list-suffix helper and wire list `.drop(count)` into +`evalMethod`, rejecting negative counts at runtime. + +- [x] **Step 4: Implement argument-aware static method typing** + +Extend static method compatibility so list `.drop(count)` returns `list[T]` +only when the count argument checks as numeric. + +- [x] **Step 5: Document the proof-core method** + +Record `.drop(count)` in the proof-core method surface and checked compiler +coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 157: Lean String Carriage-Return Escape + +**Files:** +- Modify: `Aether/Lexer.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: Lean plain and located string literal scanners, token-kind + projection checks, source parsing, and source execution. +- Produces: `\r` string escape support in the Lean proof-core lexer, preserving + located-token parity and runtime string values through the pipeline. + +- [x] **Step 1: Add failing lexer/source examples** + +Add checked examples proving plain tokenization accepts `"row\\rnext"`, +located token-kind projection matches plain tokenization, and `sourceLocal?` +executes a source string containing the escaped carriage return. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because `\r` is still reported as an invalid string escape. + +- [x] **Step 3: Implement plain string escape support** + +Extend `readString` so escaped `r` appends the carriage-return character. + +- [x] **Step 4: Implement located string escape support** + +Extend `readStringLocated` with the same escaped `r` handling while preserving +the existing consumed source span. + +- [x] **Step 5: Document the lexical rule** + +Record `\r` in the proof-core string escape list. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 158: Proof-Core String Indexing + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: parser postfix indexing, shared `evalIndex`, static index + compatibility, stack/frame index opcodes, checked source frame compilation, + and pipeline diagnostic rendering. +- Produces: string indexing in the proof-core executable semantics, returning + a one-character string at runtime for in-range numeric indexes and statically + typing `str[num]` as `str`. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `"open"[1]` through `evalExpr`, `checkExpr`, closed +expression bytecode execution, and `sourceLocal?`. Include out-of-range +runtime failure and a boolean-index static diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because the shared index evaluator and static compatibility +table do not yet support string targets. + +- [x] **Step 3: Implement shared runtime support** + +Extend `evalIndex` so string targets with non-negative numeric indexes return +the selected character as a one-character `Value.str`. + +- [x] **Step 4: Implement static index typing** + +Extend static index compatibility so `str` indexed by a numeric value returns +`str`. + +- [x] **Step 5: Document string indexing** + +Record that proof-core indexing supports both lists and strings. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 159: Proof-Core String `at(index)` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared string indexing, shared method evaluation, argument-aware + static method compatibility, stack/frame method opcodes, checked source + frame compilation, and source diagnostic rendering. +- Produces: `.at(index)` support for proof-core strings, returning a + one-character string at runtime for in-range numeric indexes and statically + typing `str.at(num)` as `str`. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `"open".at(1)` through `evalExpr`, `checkExpr`, +closed expression bytecode execution, frame expression compilation, +`checkedFrameSourceLocal?`, and `sourceLocal?`. Include out-of-range runtime +failure and a boolean-index static diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because string method evaluation and static method +compatibility do not yet support `.at(index)`. + +- [x] **Step 3: Implement shared runtime support** + +Wire string `.at(index)` through `evalIndex` so method calls and indexing share +the same runtime behavior. + +- [x] **Step 4: Implement argument-aware static method typing** + +Extend static method compatibility so `str.at(index)` returns `str` only when +the index argument checks as numeric. + +- [x] **Step 5: Document the proof-core method** + +Record string `.at(index)` in the proof-core method surface and checked +compiler coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. + +### Task 160: Proof-Core String `contains(value)` Method + +**Files:** +- Modify: `Aether/Core.lean` +- Modify: `Aether/Static.lean` +- Modify: `Aether/VM.lean` +- Modify: `Aether/Pipeline.lean` +- Modify: `docs/FORMAL_CORE.md` + +**Interfaces:** +- Consumes: shared method evaluation, argument-aware static method + compatibility, stack/frame method opcodes, checked source frame compilation, + and source diagnostic rendering. +- Produces: `.contains(value)` support for proof-core strings, returning + `bool` at runtime for substring membership and statically requiring a string + argument. + +- [x] **Step 1: Add failing evaluator/static/VM/source examples** + +Add checked examples for `"open".contains("pe")` through `evalExpr`, +`checkExpr`, closed expression bytecode execution, frame expression +compilation, `checkedFrameSourceLocal?`, and `sourceLocal?`. Include a false +runtime membership case and a non-string argument static diagnostic. + +- [x] **Step 2: Verify red** + +Run: `lake build` +Expected: FAIL because string method evaluation and static method +compatibility do not yet support `.contains(value)`. + +- [x] **Step 3: Implement shared runtime support** + +Add explicit character-list prefix and substring helpers, then wire string +`.contains(value)` into `evalMethod`. + +- [x] **Step 4: Implement argument-aware static method typing** + +Extend static method compatibility so `str.contains(value)` returns `bool` +only when the value argument checks as `str`. + +- [x] **Step 5: Document the proof-core method** + +Record string `.contains(value)` in the proof-core method surface and checked +compiler coverage. + +- [x] **Step 6: Verify** + +Run: `lake build` and `cargo test -p aether-lang -p aether-cli` +Expected: PASS. diff --git a/docs/topology/derivations.md b/docs/topology/derivations.md new file mode 100644 index 0000000..98414a8 --- /dev/null +++ b/docs/topology/derivations.md @@ -0,0 +1,148 @@ +# Derivations + +This page records the formulas used across the Aether docs. It is intentionally +mechanical: each formula names the object, the implementation surface, and the +claim boundary. + +## Time-Delay Embedding + +Implementation: `TimeDelayEmbedder`. + +For scalar samples \(x(t)\), delay \(\tau\), and dimension \(D\): + +\[ +\Phi(t) = [x(t), x(t-\tau), x(t-2\tau), \ldots, x(t-(D-1)\tau)] +\] + +Current interpreter boundary: + +- the DSL workspace uses \(D=3\); +- `tau=0` is normalized to `1`; +- an embedded point is emitted only after enough samples exist. + +## Euclidean Distance + +Implementation: `ManifoldPoint::distance`. + +\[ +d(p,q) = \sqrt{\sum_{i=1}^{D}(p_i-q_i)^2} +\] + +This distance is used by manifold neighborhoods, Vietoris-Rips construction, +lazy witness construction, and block metadata. + +## Block Centroid + +Implementation: `BlockMetadata::from_points`. + +For a block \(B = \{x_1,\ldots,x_n\}\): + +\[ +\mu_B = \frac{1}{n}\sum_{i=1}^{n} x_i +\] + +## Block Radius + +\[ +r_B = \max_i d(x_i,\mu_B) +\] + +## Distance Variance + +Let: + +\[ +\bar{d} = \frac{1}{n}\sum_{i=1}^{n} d(x_i,\mu_B) +\] + +Then: + +\[ +\sigma_B^2 = + \frac{1}{n}\sum_{i=1}^{n} d(x_i,\mu_B)^2 - \bar{d}^2 +\] + +## Concentration + +\[ +c_B = +\frac{1}{n}\sum_{i=1}^{n} +\frac{x_i \cdot \mu_B}{\|x_i\|\|\mu_B\|} +\] + +Zero-norm terms are skipped by implementation guards. + +## Cauchy-Schwarz Upper Bound + +Implementation: `BlockMetadata::upper_bound_score`. + +For query \(q\): + +\[ +score(q,B) \le \|q\|(\|\mu_B\| + r_B) +\] + +If this bound is below a threshold, the block can be pruned without inspecting +every point in the block. + +## Sparse Event Trigger + +Implementation: `SparseScheduler::should_wake`. + +For system state \(\mu(t)\), last handled state \(\mu(t_{last})\), and adaptive +threshold \(\epsilon(t)\): + +\[ +\Delta(t) = \|\mu(t)-\mu(t_{last})\|_2 +\] + +\[ +\text{wake} \iff \Delta(t) \ge \epsilon(t) +\] + +## Governor Update + +Implementation: `GeometricGovernor::adapt`. + +The observed rate is: + +\[ +R_{actual} = \frac{\Delta(t)}{\epsilon(t)} +\] + +The error is: + +\[ +e(t) = R_{target} - R_{actual} +\] + +The derivative term is: + +\[ +\frac{de}{dt} = \frac{e(t)-e(t-1)}{dt} +\] + +The implementation applies a proportional-derivative adjustment and clamps +\(\epsilon\) into a fixed interval: + +\[ +\epsilon(t+1) = clamp(\epsilon(t) - \alpha e(t) - \beta \frac{de}{dt}) +\] + +The sign follows the current code path: high observed rate raises epsilon after +the update dynamics settle. + +## Binary Shape Heuristic + +Implementation: `crates/aether-core/src/topology.rs`. + +The binary shape gate computes: + +\[ +density = \frac{\beta_0}{|B|} +\] + +and compares density and approximate loop count against fixed thresholds. + +Claim boundary: this is a heuristic gate with tests. It is not documented as a +production malware detector or a formally complete authentication system. diff --git a/docs/topology/persistent-homology.md b/docs/topology/persistent-homology.md new file mode 100644 index 0000000..fc4ec21 --- /dev/null +++ b/docs/topology/persistent-homology.md @@ -0,0 +1,75 @@ +# Persistent Homology + +Aether's active persistent-homology engine lives in +`crates/aether-core/src/persistence.rs`. + +## Input + +The engine consumes a point cloud: + +\[ +X = \{x_1, x_2, \ldots, x_n\} +\] + +where each point is a `ManifoldPoint`. + +## Vietoris-Rips Complex + +For radius \(r\), the Vietoris-Rips complex is: + +\[ +VR_r(X) = \{\sigma \subseteq X : \max_{u,v \in \sigma} d(u,v) \le r\} +\] + +Plain meaning: + +- vertices enter at radius `0`; +- an edge enters when its endpoints are within the radius; +- a triangle enters when all three edges are within the radius; +- a tetrahedron enters when all six edges are within the radius. + +The implementation supports homology dimensions 0 through 2, so it builds +simplexes through tetrahedra. + +## Lazy Witness Mode + +For lower-load DSL runs, Aether can select landmarks and use all points as +witnesses. A simplex filtration value is: + +\[ +f(\sigma) = \min_{w \in X} + \left(\max_{\ell \in \sigma} d(w,\ell) - d(w,L)\right) +\] + +where \(L\) is the landmark set and \(d(w,L)\) is the distance from witness +\(w\) to its nearest landmark. + +This reduces the selected complex size. It is not the same claim as exact +Vietoris-Rips homology over the full point cloud. + +## Reduction + +The engine sorts simplexes by filtration value and dimension, constructs +boundary columns, and reduces them over \(\mathbb{Z}_2\). A reduced empty column +births a feature. A later column with a low pivot kills the feature born by that +pivot. + +The output is a `PersistenceDiagram` containing pairs: + +```rust +pub struct PersistencePair { + pub dimension: usize, + pub birth: f64, + pub death: Option, +} +``` + +## Betti Query + +For radius \(r\): + +\[ +\beta_k(r) = |\{(b_i,d_i) : b_i \le r < d_i,\ dimension=i=k\}| +\] + +Essential intervals have no death value and remain live after birth. diff --git a/docs/topology/shape-gates.md b/docs/topology/shape-gates.md new file mode 100644 index 0000000..59def8b --- /dev/null +++ b/docs/topology/shape-gates.md @@ -0,0 +1,41 @@ +# Shape Gates + +Aether has two topology surfaces. + +## Persistent-Homology Surface + +Files: + +- `crates/aether-core/src/persistence.rs`; +- `crates/aether-lang/src/interpreter.rs`. + +This is the active topological ML surface. The DSL can construct a persistence +diagram from a manifold and query Betti numbers or intervals. + +```aether +import topology~ +let data = [1.0, 1.0, 1.0, 1.0, 1.0]~ +manifold M = embed(data, tau=1)~ +let diagram = topology.ph(M, max_dim=2, mode="vr", max_points=16)~ +let b = topology.betti(diagram, radius=0.0)~ +``` + +## Binary-Shape Surface + +Files: + +- `crates/aether-core/src/topology.rs`; +- `crates/aether-kernel/src/loader.rs`. + +This surface computes approximate `beta_0`, approximate `beta_1`, density, and +verification results for byte slices. + +Current rejection reasons: + +- invalid density; +- excessive loops; +- mismatch from a reference shape. + +This is a gatekeeping heuristic. It should not be described as proof of binary +safety without external validation, corpora, baselines, and false-positive / +false-negative artifacts. diff --git a/examples/ml_test.ag b/examples/ml_test.ag new file mode 100644 index 0000000..c74fe0c --- /dev/null +++ b/examples/ml_test.ag @@ -0,0 +1,49 @@ +import Ml + +fn main() { + print("Running ML Test...") + + // 1. Tensor Creation + print("1. Creating Tensors...") + let t1 = Ml.load_weights([[1.0, 2.0], [3.0, 4.0]]) + let t2 = Ml.load_weights([[0.5, 0.5], [0.5, 0.5]]) + + // 2. Math Ops + print("2. Testing Math...") + let sum = Ml.add(t1, t2) + // Expected: [[1.5, 2.5], [3.5, 4.5]] + // print(sum) -- Tensor printing not fully implemented in verify script? + // But interpreter print uses Debug, which should work for Tensor(Tensor) because aether-core Tensor has Debug. + + let prod = Ml.matmul(t1, t2) + // [[1*0.5+2*0.5, 1*0.5+2*0.5], [3*0.5+4*0.5, ...]] + // [[1.5, 1.5], [3.5, 3.5]] + + let relu_test = Ml.load_weights([[0.0-1.0, 1.0], [0.0, 0.0-5.0]]) + let relu_res = Ml.relu(relu_test) + // [[0.0, 1.0], [0.0, 0.0]] + + print("Math Ops Done.") + + // 3. MLP Test + print("3. Testing MLP...") + let mlp = Ml.MLP(0.1) // lr=0.1 + mlp.add_layer(2, 4, "relu") + mlp.add_layer(4, 1, "sigmoid") + + let x = Ml.load_weights([[0.0, 0.0], [1.0, 1.0]]) // 2 samples, 2 features + let y = Ml.load_weights([[0.0], [1.0]]) // 2 targets + + print("Training...") + let loss = mlp.train(x, y, 10.0) + print("Final Loss:") + print(loss) + + print("Prediction...") + let pred = mlp.forward(x) + print(pred) + + print("Success!") +} + +main() diff --git a/lake-manifest.json b/lake-manifest.json new file mode 100644 index 0000000..c823abd --- /dev/null +++ b/lake-manifest.json @@ -0,0 +1,6 @@ +{"version": "1.2.0", + "packagesDir": ".lake/packages", + "packages": [], + "name": "«aether-formal»", + "lakeDir": ".lake", + "fixedToolchain": false} diff --git a/lakefile.lean b/lakefile.lean new file mode 100644 index 0000000..51c57a4 --- /dev/null +++ b/lakefile.lean @@ -0,0 +1,9 @@ +import Lake +open Lake DSL + +package «aether-formal» where + version := v!"0.1.0" + +@[default_target] +lean_lib Aether where + roots := #[`Aether] diff --git a/lean-toolchain b/lean-toolchain new file mode 100644 index 0000000..310af3b --- /dev/null +++ b/lean-toolchain @@ -0,0 +1 @@ +leanprover/lean4:4.31.0 diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..7677e9c --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,72 @@ +site_name: Aether Lang +site_description: Mechanical systems documentation for the Aether language runtime, topology core, ML primitives, and sparse-event kernel. +site_url: https://teerthsharma.github.io/Aether-Lang/ +repo_url: https://github.com/teerthsharma/Aether-Lang +theme: + name: material + features: + - navigation.sections + - navigation.tabs + - content.code.copy +markdown_extensions: + - admonition + - attr_list + - md_in_html + - pymdownx.arithmatex: + generic: true + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format +extra_javascript: + - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js +exclude_docs: | + superpowers/** +nav: + - Home: index.md + - Concepts: + - Benefit Emergence: concepts/benefit-emergence.md + - Language Pipeline: concepts/language-pipeline.md + - Runtime Surface: concepts/runtime-surface.md + - Language: + - Syntax: language/syntax.md + - Execution Model: language/execution-model.md + - Module Contracts: language/modules.md + - Topology: + - Persistent Homology: topology/persistent-homology.md + - Derivations: topology/derivations.md + - Shape Gates: topology/shape-gates.md + - ML: + - Primitives: ml/primitives.md + - Topological Convergence: ml/topological-convergence.md + - Kernel: + - Sparse Events: kernel/sparse-events.md + - Hardware Boundary: kernel/hardware-boundary.md + - Benchmarks: + - Benchmark Policy: benchmarks/index.md + - Evidence Gates: benchmarks/evidence-gates.md + - Reference: + - API: reference/api.md + - Status Matrix: reference/status.md + - Contribution Standard: reference/contributing.md + - Compatibility: + - Getting Started: GETTING_STARTED.md + - Language: LANGUAGE.md + - Syntax: SYNTAX.md + - API: API.md + - Architecture: ARCHITECTURE.md + - Mathematics: MATHEMATICS.md + - Benchmarks: BENCHMARKS.md + - Examples: EXAMPLES.md + - FAQ: FAQ.md + - LLM Runtime Status: AEGIS_LLM_SPECS.md + - Bio Clock Status: BIO_CLOCK_PRD.md + - Formal Core: FORMAL_CORE.md + - Hardware Status: HARDWARE_SPEC.md + - Aether ML: ML_AETHER.md + - ML From Scratch: ML_FROM_SCRATCH.md + - ML Library: ML_LIBRARY.md + - ML Tasks: ML_TASKS.md + - OS Development: OS_DEVELOPMENT.md + - Tutorial: TUTORIAL.md diff --git a/patch.diff b/patch.diff new file mode 100644 index 0000000..f2363cd --- /dev/null +++ b/patch.diff @@ -0,0 +1,24 @@ +--- crates/aether-core/src/ml/neural.rs ++++ crates/aether-core/src/ml/neural.rs +@@ -415,11 +415,16 @@ + + /// Forward pass through all layers + pub fn forward(&mut self, input: &Tensor) -> Tensor { +- let mut current = input.clone(); +- for layer in &mut self.layers { ++ let mut iter = self.layers.iter_mut(); ++ if let Some(first_layer) = iter.next() { ++ let mut current = first_layer.forward(input); ++ for layer in iter { ++ current = layer.forward(¤t); ++ } ++ current ++ } else { +- current = layer.forward(¤t); +- } +- current ++ input.clone() ++ } + } + + /// Predict (Forward without mutating state if possible? No, dense layer caches input) diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..4fbea14 --- /dev/null +++ b/plan.md @@ -0,0 +1,4 @@ +1. Read `.jules/bolt.md` (creating it if missing) and append a journal entry about avoiding tensor metadata clones during reverse-mode autograd passes by using `Option::take()` and value-based accumulation. Verify the creation/modification using `cat`. +2. Modify `crates/aether-core/src/ml/autograd.rs` to change `accumulate_grad` signature to take `grad: Tensor` by value and update its implementation. Also, refactor `Context::backward` to use `grads[out.index].take()` for `grad_out` instead of `.clone()`, pass computed gradients by value to `accumulate_grad`, and re-insert `grad_out` into `grads[out.index]`. Verify the modification using `git diff`. +3. Complete pre-commit steps to ensure proper testing, verification, review, and reflection are done. +4. Run `cargo test -p aether-core` to verify everything works correctly. Submit a PR with title `⚡ Bolt: Optimize backward pass gradient accumulation` and include What, Why, Impact, and Measurement in the description. diff --git a/pyproject.toml b/pyproject.toml index e319fa9..4dd20d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,10 +23,10 @@ classifiers = [ [tool.maturin] # Use the executable name 'aether' -python-source = "python" +python-source = "bindings/python" module-name = "aether_lang" -manifest-path = "aether-lang/Cargo.toml" +manifest-path = "crates/aether-lang/Cargo.toml" package = "aether-lang" features = ["python"] diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 0000000..e6872c6 --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1,3 @@ +mkdocs>=1.6,<2 +mkdocs-material>=9.5,<10 +pymdown-extensions>=10,<11 diff --git a/test_opt.sh b/test_opt.sh new file mode 100755 index 0000000..99e2904 --- /dev/null +++ b/test_opt.sh @@ -0,0 +1,2 @@ +cargo check -p aether-core --offline +cargo test -p aether-core --offline