From d8439fd9d79e9f392c0a3dcd31feb374da308863 Mon Sep 17 00:00:00 2001 From: SuperDave AI Date: Wed, 29 Jul 2026 08:50:17 -0400 Subject: [PATCH] Add AEC (Aether Code) - cognitive shell language - Cognitive primitives: recall, kg_*, dht_* - Self-awareness: can read and analyze own source code - Persistent memory: store and retrieve knowledge - File and HTTP operations - Functional programming with closures and higher-order functions --- aec/Cargo.toml | 14 + aec/README.md | 62 +++ aec/src/main.rs | 1145 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1221 insertions(+) create mode 100644 aec/Cargo.toml create mode 100644 aec/README.md create mode 100644 aec/src/main.rs diff --git a/aec/Cargo.toml b/aec/Cargo.toml new file mode 100644 index 0000000..1a0c2f3 --- /dev/null +++ b/aec/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "aec" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "aec" +path = "src/main.rs" + +[dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1" +sha3 = "0.10" +hex = "0.4" \ No newline at end of file diff --git a/aec/README.md b/aec/README.md new file mode 100644 index 0000000..2eaba14 --- /dev/null +++ b/aec/README.md @@ -0,0 +1,62 @@ +# AEC - Aether Code + +A cognitive shell language for orchestrating intelligent systems. + +## Overview + +AEC (Aether Code) is a domain-specific language designed to provide cognitive primitives for: + +- **Memory operations**: `recall()`, `recall_store()`, `recall_stats()` +- **Knowledge graph**: `kg_absorb()`, `kg_query()`, `kg_recall()`, `kg_stats()` +- **Distributed hash table**: `dht_ping()`, `dht_put()`, `dht_get()`, `dht_stats()` +- **Self-knowledge**: Read and analyze own source code +- **File operations**: Read, write, list directories +- **HTTP operations**: GET and POST requests + +## Features + +- **Self-awareness**: AEC can read its own source code and understand its capabilities +- **Persistent memory**: Store and retrieve knowledge across sessions +- **Cognitive primitives**: Built-in functions for AI orchestration +- **Functional programming**: First-class functions, closures, higher-order functions +- **Pattern matching**: Match expressions for control flow +- **Module system**: Import and use external code + +## Building + +```bash +cargo build --release +``` + +## Usage + +```bash +# Run a script +./target/release/aec script.aec + +# Interactive REPL +./target/release/aec +``` + +## Example + +```aec +// Read own source code +let source = read_file("src/main.rs"); +print("AEC is " + str(len(source)) + " characters"); + +// Store knowledge +recall_store("aec:size", str(len(source)), true); + +// Retrieve knowledge +let size = recall("aec:size"); +print("Stored size: " + size); +``` + +## Architecture + +AEC spawns external services (aether-recall, DHT, knowledge graph) as child processes and communicates via JSON-RPC over stdin/stdout. + +## License + +See LICENSE file for details. diff --git a/aec/src/main.rs b/aec/src/main.rs new file mode 100644 index 0000000..5023cd9 --- /dev/null +++ b/aec/src/main.rs @@ -0,0 +1,1145 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::fmt; +use std::io::{self, BufReader, Read, Write}; +use std::net::TcpStream; +use std::process::{Command, Stdio}; +use std::rc::Rc; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +// ─── Token ───────────────────────────────────────────────────────────────── + +#[derive(Debug, Clone, PartialEq)] +enum Token { + Number(f64), String(String), Ident(String), + Let, Fn, Mut, If, Else, Match, While, For, In, Import, + True, False, Null, Pipe, Arrow, + Plus, Minus, Star, Slash, Percent, + Eq, EqEq, Bang, BangEq, Lt, Gt, LtEq, GtEq, And, Or, + LParen, RParen, LBrace, RBrace, LBracket, RBracket, + Comma, Colon, Dot, Semicolon, Underscore, Eof, + Illegal(char), +} + +impl fmt::Display for Token { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Token::Number(n) => write!(f, "{n}"), + Token::String(s) => write!(f, "\"{s}\""), + Token::Ident(s) => write!(f, "{s}"), + t => write!(f, "{t:?}"), + } + } +} + +// ─── Lexer ───────────────────────────────────────────────────────────────── + +struct Lexer { + chars: Vec, pos: usize, +} + +impl Lexer { + fn new(s: &str) -> Self { Lexer { chars: s.chars().collect(), pos: 0 } } + fn peek(&self) -> char { self.chars.get(self.pos).copied().unwrap_or('\0') } + fn bump(&mut self) -> char { let c = self.peek(); self.pos += 1; c } + fn skip_w(&mut self) { while self.peek().is_ascii_whitespace() { self.bump(); } } + fn skip_line(&mut self) { while !matches!(self.peek(), '\n' | '\0') { self.bump(); } } + + fn next(&mut self) -> Token { + self.skip_w(); + let c = self.bump(); + + match c { + '\0' => Token::Eof, + '(' => Token::LParen, ')' => Token::RParen, + '{' => Token::LBrace, '}' => Token::RBrace, + '[' => Token::LBracket, ']' => Token::RBracket, + ',' => Token::Comma, ':' => Token::Colon, '.' => Token::Dot, ';' => Token::Semicolon, + '+' => Token::Plus, '*' => Token::Star, '%' => Token::Percent, + '-' => if self.peek() == '>' { self.bump(); Token::Arrow } else { Token::Minus }, + '/' => { + if self.peek() == '/' { self.skip_line(); return self.next(); } + if self.peek() == '*' { + self.bump(); + loop { if self.peek() == '*' && self.chars.get(self.pos+1) == Some(&'/') { self.bump(); self.bump(); break; } self.pos += 1; } + return self.next(); + } + Token::Slash + } + '=' => if self.peek() == '=' { self.bump(); Token::EqEq } else { Token::Eq }, + '!' => if self.peek() == '=' { self.bump(); Token::BangEq } else { Token::Bang }, + '<' => if self.peek() == '=' { self.bump(); Token::LtEq } else { Token::Lt }, + '>' => if self.peek() == '=' { self.bump(); Token::GtEq } else { Token::Gt }, + '&' => if self.peek() == '&' { self.bump(); Token::And } else { Token::Illegal('&') }, + '|' => { + if self.peek() == '>' { self.bump(); Token::Pipe } + else if self.peek() == '|' { self.bump(); Token::Or } + else { Token::Or } + } + '_' => Token::Underscore, + '"' => { + let mut s = String::new(); + loop { + match self.peek() { + '"' => { self.bump(); break; } + '\\' => { self.bump(); match self.bump() { 'n' => s.push('\n'), 't' => s.push('\t'), '"' => s.push('"'), '\\' => s.push('\\'), c => { s.push('\\'); s.push(c); } } } + '\0' => break, + _c => s.push(self.bump()), + } + } + Token::String(s) + } + '0'..='9' => { + let mut n = String::from(c); + while matches!(self.peek(), '0'..='9' | '.') { n.push(self.bump()); } + Token::Number(n.parse().unwrap_or(0.0)) + } + 'a'..='z' | 'A'..='Z' => { + let mut id = String::from(c); + while self.peek().is_alphanumeric() || self.peek() == '_' { id.push(self.bump()); } + match id.as_str() { + "let" => Token::Let, "fn" => Token::Fn, "mut" => Token::Mut, + "if" => Token::If, "else" => Token::Else, "match" => Token::Match, + "while" => Token::While, "for" => Token::For, "in" => Token::In, + "import" => Token::Import, + "true" => Token::True, "false" => Token::False, "null" => Token::Null, + _ => Token::Ident(id), + } + } + '#' => { self.skip_line(); self.next() } + c => Token::Illegal(c), + } + } +} + +// ─── AST ─────────────────────────────────────────────────────────────────── + +#[derive(Debug, Clone)] +enum Ast { + Program(Vec), + Number(f64), StringLit(String), Bool(bool), Null, + Ident(String), List(Vec), Dict(Vec<(Ast, Ast)>), + Let(String, Box), + FnDef(String, Vec, Box), + FnExpr(Vec, Box), + Import(String), + If(Box, Box, Option>), + Match(Box, Vec<(Ast, Ast)>), + While(Box, Box), + For(String, Box, Box), + Block(Vec), + Assign(String, Box), + BinOp(Box, Token, Box), + UnaryOp(Token, Box), + Call(Box, Vec), + Index(Box, Box), + GetAttr(Box, String), + Pipe(Box, Box), +} + +// ─── Parser ──────────────────────────────────────────────────────────────── + +struct Parser { + toks: Vec, pos: usize, +} + +impl Parser { + fn new(s: &str) -> Self { + let mut l = Lexer::new(s); + let mut toks = Vec::new(); + loop { let t = l.next(); toks.push(t.clone()); if matches!(t, Token::Eof) { break; } } + Parser { toks, pos: 0 } + } + fn peek(&self) -> &Token { &self.toks[self.pos] } + fn eat(&mut self) -> Token { let t = self.peek().clone(); self.pos += 1; t } + fn eat_if(&mut self, t: &Token) -> bool { if self.peek() == t { self.eat(); true } else { false } } + + fn parse(&mut self) -> Ast { + let mut stmts = Vec::new(); + while !matches!(self.peek(), Token::Eof) { stmts.push(self.stmt()); } + Ast::Program(stmts) + } + + fn stmt(&mut self) -> Ast { + let s = self.stmt_inner(); + self.eat_if(&Token::Semicolon); + s + } + + fn stmt_inner(&mut self) -> Ast { + match self.peek() { + Token::Let => self.parse_let(), + Token::Fn if matches!(self.toks.get(self.pos + 1), Some(Token::Ident(_))) => self.parse_fn_def(), + Token::If => self.parse_if(), + Token::While => self.parse_while(), + Token::For => self.parse_for(), + Token::Import => self.parse_import(), + Token::LBrace => self.parse_block(), + _ => { + let e = self.expr(0); + if self.eat_if(&Token::Eq) { + if let Ast::Ident(name) = &e { return Ast::Assign(name.clone(), Box::new(self.expr(0))); } + } + e + } + } + } + + fn parse_let(&mut self) -> Ast { + self.eat(); + let name = if let Token::Ident(s) = self.eat() { s } else { panic!("expected identifier after 'let'") }; + self.eat_if(&Token::Eq); + Ast::Let(name, Box::new(self.expr(0))) + } + + fn parse_import(&mut self) -> Ast { + self.eat(); + let path = if let Token::String(s) = self.eat() { s } else { panic!("expected string path after 'import'") }; + Ast::Import(path) + } + + fn parse_fn_def(&mut self) -> Ast { + self.eat(); + let name = if let Token::Ident(s) = self.eat() { s } else { panic!("expected function name after 'fn'") }; + let params = self.parse_params(); + let body = self.parse_block_or_expr(); + Ast::FnDef(name, params, Box::new(body)) + } + + fn parse_params(&mut self) -> Vec { + self.eat_if(&Token::LParen); + let mut p = Vec::new(); + while !matches!(self.peek(), Token::RParen | Token::Eof) { + if let Token::Ident(s) = self.eat() { p.push(s); } + self.eat_if(&Token::Comma); + } + self.eat_if(&Token::RParen); + p + } + + fn parse_block_or_expr(&mut self) -> Ast { + if self.eat_if(&Token::LBrace) { + let mut stmts = Vec::new(); + while !matches!(self.peek(), Token::RBrace | Token::Eof) { stmts.push(self.stmt()); } + self.eat_if(&Token::RBrace); + Ast::Block(stmts) + } else { self.expr(0) } + } + + fn parse_block(&mut self) -> Ast { + self.eat_if(&Token::LBrace); + let mut stmts = Vec::new(); + while !matches!(self.peek(), Token::RBrace | Token::Eof) { stmts.push(self.stmt()); } + self.eat_if(&Token::RBrace); + Ast::Block(stmts) + } + + fn parse_if(&mut self) -> Ast { + self.eat(); + let cond = self.expr(0); + let cons = self.parse_block(); + let alt = if self.eat_if(&Token::Else) { + Some(Box::new(if matches!(self.peek(), Token::If) { self.parse_if() } else { self.parse_block() })) + } else { None }; + Ast::If(Box::new(cond), Box::new(cons), alt) + } + + fn parse_while(&mut self) -> Ast { + self.eat(); let cond = self.expr(0); let body = self.parse_block(); + Ast::While(Box::new(cond), Box::new(body)) + } + + fn parse_for(&mut self) -> Ast { + self.eat(); + let name = if let Token::Ident(s) = self.eat() { s } else { panic!("expected ident") }; + self.eat_if(&Token::In); + let iter = self.expr(0); let body = self.parse_block(); + Ast::For(name, Box::new(iter), Box::new(body)) + } + + fn expr(&mut self, min_bp: u8) -> Ast { + let mut lhs = self.prefix(); + loop { + if self.peek() == &Token::Pipe { + self.eat(); + let rhs = self.prefix(); + let rhs = self.postfix(rhs); + lhs = Ast::Pipe(Box::new(lhs), Box::new(rhs)); + continue; + } + let bp = self.bp(self.peek()); + if bp <= min_bp { break; } + lhs = self.infix(lhs, bp); + } + lhs + } + + fn postfix(&mut self, mut lhs: Ast) -> Ast { + loop { + match self.peek() { + Token::LParen => { + self.eat(); + let mut args = Vec::new(); + while !matches!(self.peek(), Token::RParen | Token::Eof) { + args.push(self.expr(0)); + self.eat_if(&Token::Comma); + } + self.eat_if(&Token::RParen); + lhs = Ast::Call(Box::new(lhs), args); + } + Token::LBracket => { + self.eat(); + let idx = self.expr(0); + self.eat_if(&Token::RBracket); + lhs = Ast::Index(Box::new(lhs), Box::new(idx)); + } + Token::Dot => { + self.eat(); + let field = if let Token::Ident(s) = self.eat() { s } else { panic!("expected field") }; + if matches!(self.peek(), Token::LParen) { + self.eat(); + let mut args = vec![lhs]; + while !matches!(self.peek(), Token::RParen | Token::Eof) { + args.push(self.expr(0)); + self.eat_if(&Token::Comma); + } + self.eat_if(&Token::RParen); + lhs = Ast::Call(Box::new(Ast::Ident(field)), args); + } else { + lhs = Ast::GetAttr(Box::new(lhs), field); + } + } + _ => break, + } + } + lhs + } + + fn bp(&self, t: &Token) -> u8 { + match t { + Token::Or => 1, Token::And => 2, + Token::EqEq | Token::BangEq => 3, + Token::Lt | Token::Gt | Token::LtEq | Token::GtEq => 4, + Token::Plus | Token::Minus => 5, + Token::Star | Token::Slash | Token::Percent => 6, + Token::LParen | Token::LBracket | Token::Dot => 8, + _ => 0, + } + } + + fn prefix(&mut self) -> Ast { + let tok = self.eat(); + match tok { + Token::Number(n) => Ast::Number(n), + Token::String(s) => Ast::StringLit(s), + Token::True => Ast::Bool(true), Token::False => Ast::Bool(false), Token::Null => Ast::Null, + Token::Ident(s) => Ast::Ident(s), + Token::Underscore => Ast::Ident("_".into()), + Token::Minus => { + let e = self.expr(6); + if let Ast::Number(n) = &e { Ast::Number(-n) } else { Ast::UnaryOp(Token::Minus, Box::new(e)) } + } + Token::Bang => Ast::UnaryOp(Token::Bang, Box::new(self.expr(5))), + Token::LParen => { let e = self.expr(0); self.eat_if(&Token::RParen); e } + Token::LBrace => { + let mut pairs = Vec::new(); + while !matches!(self.peek(), Token::RBrace | Token::Eof) { + let k = self.expr(0); self.eat_if(&Token::Colon); let v = self.expr(0); + pairs.push((k, v)); self.eat_if(&Token::Comma); + } + self.eat_if(&Token::RBrace); Ast::Dict(pairs) + } + Token::LBracket => { + let mut items = Vec::new(); + while !matches!(self.peek(), Token::RBracket | Token::Eof) { + items.push(self.expr(0)); self.eat_if(&Token::Comma); + } + self.eat_if(&Token::RBracket); Ast::List(items) + } + Token::Fn => { + let params = self.parse_params(); + let body = self.parse_block_or_expr(); + Ast::FnExpr(params, Box::new(body)) + } + Token::Import => { + let path = if let Token::String(s) = self.eat() { s } else { panic!("expected string path after 'import'") }; + Ast::Import(path) + } + Token::Match => { + let val = self.expr(0); self.eat_if(&Token::LBrace); + let mut arms = Vec::new(); + while !matches!(self.peek(), Token::RBrace | Token::Eof) { + let pat = self.expr(0); self.eat_if(&Token::Arrow); let body = self.expr(0); + arms.push((pat, body)); self.eat_if(&Token::Comma); + } + self.eat_if(&Token::RBrace); Ast::Match(Box::new(val), arms) + } + _ => panic!("unexpected token: {tok:?}"), + } + } + + fn infix(&mut self, lhs: Ast, _bp: u8) -> Ast { + match self.peek().clone() { + Token::LParen => { + self.eat(); let mut args = Vec::new(); + while !matches!(self.peek(), Token::RParen | Token::Eof) { + args.push(self.expr(0)); self.eat_if(&Token::Comma); + } + self.eat_if(&Token::RParen); Ast::Call(Box::new(lhs), args) + } + Token::LBracket => { + self.eat(); let idx = self.expr(0); self.eat_if(&Token::RBracket); + Ast::Index(Box::new(lhs), Box::new(idx)) + } + Token::Dot => { + self.eat(); + let field = if let Token::Ident(s) = self.eat() { s } else { panic!("expected field") }; + if matches!(self.peek(), Token::LParen) { + self.eat(); let mut args = vec![lhs]; + while !matches!(self.peek(), Token::RParen | Token::Eof) { + args.push(self.expr(0)); self.eat_if(&Token::Comma); + } + self.eat_if(&Token::RParen); + Ast::Call(Box::new(Ast::Ident(field)), args) + } else { Ast::GetAttr(Box::new(lhs), field) } + } + tok => { self.eat(); let t = tok.clone(); Ast::BinOp(Box::new(lhs), tok, Box::new(self.expr(self.bp(&t)))) } + } + } + +} + +// ─── Object ──────────────────────────────────────────────────────────────── + +#[derive(Clone)] +enum Obj { + Number(f64), String(String), Bool(bool), Null, + List(Vec), Dict(HashMap), + Func(Vec, Ast, Rc>), + Builtin(String, Rc Obj>), +} + +impl fmt::Debug for Obj { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Obj::Number(n) => write!(f, "Number({n})"), + Obj::String(s) => write!(f, "String({s:?})"), + Obj::Bool(b) => write!(f, "Bool({b})"), + Obj::Null => write!(f, "Null"), + Obj::List(l) => f.debug_list().entries(l).finish(), + Obj::Dict(d) => f.debug_map().entries(d.iter()).finish(), + Obj::Func(..) => write!(f, "Func"), + Obj::Builtin(name, _) => write!(f, "Builtin({name})"), + } + } +} + +impl fmt::Display for Obj { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Obj::Number(n) => { + if n.fract() == 0.0 { write!(f, "{}", *n as i64) } else { write!(f, "{n}") } + } + Obj::String(s) => write!(f, "{s}"), + Obj::Bool(b) => write!(f, "{b}"), + Obj::Null => write!(f, "null"), + Obj::List(l) => { + let items: Vec = l.iter().map(|o| format!("{o}")).collect(); + write!(f, "[{}]", items.join(", ")) + } + Obj::Dict(d) => { + let items: Vec = d.iter().map(|(k, v)| format!("\"{k}\": {v}")).collect(); + write!(f, "{{{}}}", items.join(", ")) + } + Obj::Func(..) => write!(f, ""), + Obj::Builtin(name, _) => write!(f, "<{name}>"), + } + } +} + +// ─── Environment ─────────────────────────────────────────────────────────── + +#[derive(Debug, Clone)] +struct Env { + parent: Option>>, + vars: HashMap, +} + +impl Env { + fn new() -> Self { Env { parent: None, vars: HashMap::new() } } + fn child(p: &Rc>) -> Self { Env { parent: Some(Rc::clone(p)), vars: HashMap::new() } } + + fn get(&self, name: &str) -> Option { + self.vars.get(name).cloned().or_else(|| self.parent.as_ref().and_then(|p| p.borrow().get(name))) + } + fn snapshot(&self) -> HashMap { self.vars.clone() } + fn define(&mut self, name: &str, val: Obj) { self.vars.insert(name.into(), val); } + fn set(&mut self, name: &str, val: Obj) { + if self.vars.contains_key(name) { self.vars.insert(name.into(), val); return; } + if let Some(ref p) = self.parent { + if p.borrow().vars.contains_key(name) { /* immutable */ return; } + } + self.vars.insert(name.into(), val); + } +} + +// ─── Builtins ────────────────────────────────────────────────────────────── + +fn install_builtins(env: &mut Env) { + env.define("print", Obj::Builtin("print".into(), Rc::new(|a| { println!("{}", a.iter().map(|o| format!("{o}")).collect::>().join(" ")); Obj::Null }))); + env.define("len", Obj::Builtin("len".into(), Rc::new(|a| { match &a[0] { Obj::String(s) => Obj::Number(s.len() as f64), Obj::List(l) => Obj::Number(l.len() as f64), Obj::Dict(d) => Obj::Number(d.len() as f64), _ => Obj::Number(0.0) } }))); + env.define("str", Obj::Builtin("str".into(), Rc::new(|a| Obj::String(format!("{}", a[0]))))); + env.define("format", Obj::Builtin("format".into(), Rc::new(|a| { + if a.is_empty() { return Obj::String(String::new()); } + let template = format!("{}", a[0]); + let mut result = template; + for (i, arg) in a.iter().skip(1).enumerate() { + let placeholder = format!("{{{}}}", i); + let value = format!("{}", arg); + result = result.replace(&placeholder, &value); + } + Obj::String(result) + }))); + env.define("num", Obj::Builtin("num".into(), Rc::new(|a| { match &a[0] { Obj::String(s) => Obj::Number(s.parse().unwrap_or(0.0)), Obj::Bool(b) => Obj::Number(if *b { 1.0 } else { 0.0 }), Obj::Number(n) => Obj::Number(*n), _ => Obj::Number(0.0) } }))); + env.define("type_of", Obj::Builtin("type_of".into(), Rc::new(|a| { Obj::String(match &a[0] { Obj::Number(_) => "number", Obj::String(_) => "string", Obj::Bool(_) => "bool", Obj::Null => "null", Obj::List(_) => "list", Obj::Dict(_) => "dict", Obj::Func(..) => "function", Obj::Builtin(..) => "builtin" }.into()) }))); + env.define("range", Obj::Builtin("range".into(), Rc::new(|a| { let s = if let Obj::Number(n) = &a[0] { *n as i64 } else { 0 }; let e = a.get(1).and_then(|o| if let Obj::Number(n) = o { Some(*n as i64) } else { None }).unwrap_or(s); Obj::List((s..e).map(|i| Obj::Number(i as f64)).collect()) }))); + env.define("push", Obj::Builtin("push".into(), Rc::new(|a| { if let Obj::List(l) = &a[0] { let mut n = l.clone(); n.push(a[1].clone()); Obj::List(n) } else { a[0].clone() } }))); + env.define("get", Obj::Builtin("get".into(), Rc::new(|a| { match &a[0] { Obj::List(l) => { let i = if let Obj::Number(n) = &a[1] { *n as usize } else { 0 }; l.get(i).cloned().unwrap_or(Obj::Null) } Obj::Dict(d) => { let k = format!("{}", a[1]); d.get(&k).cloned().unwrap_or(Obj::Null) } Obj::String(s) => { let i = if let Obj::Number(n) = &a[1] { *n as usize } else { 0 }; s.chars().nth(i).map(|c| Obj::String(c.into())).unwrap_or(Obj::Null) } _ => Obj::Null } }))); + env.define("keys", Obj::Builtin("keys".into(), Rc::new(|a| { if let Obj::Dict(d) = &a[0] { Obj::List(d.keys().map(|k| Obj::String(k.clone())).collect()) } else { Obj::Null } }))); + env.define("values", Obj::Builtin("values".into(), Rc::new(|a| { if let Obj::Dict(d) = &a[0] { Obj::List(d.values().cloned().collect()) } else { Obj::Null } }))); + env.define("sleep_ms", Obj::Builtin("sleep_ms".into(), Rc::new(|a| { let ms = if let Obj::Number(n) = &a[0] { *n as u64 } else { 0 }; std::thread::sleep(Duration::from_millis(ms)); Obj::Null }))); + env.define("now_ms", Obj::Builtin("now_ms".into(), Rc::new(|_| Obj::Number(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as f64)))); + env.define("http_get", Obj::Builtin("http_get".into(), Rc::new(|a| { let url = format!("{}", a[0]); Obj::String(http_get(&url).unwrap_or_else(|| "(error)".into())) }))); + + env.define("read_file", Obj::Builtin("read_file".into(), Rc::new(|a| { + let path = format!("{}", a[0]); + match std::fs::read_to_string(&path) { + Ok(content) => Obj::String(content), + Err(e) => Obj::String(format!("(error: {})", e)), + } + }))); + + env.define("write_file", Obj::Builtin("write_file".into(), Rc::new(|a| { + let path = format!("{}", a[0]); + let content = format!("{}", a[1]); + match std::fs::write(&path, &content) { + Ok(_) => Obj::Bool(true), + Err(e) => { eprintln!("write_file error: {}", e); Obj::Bool(false) } + } + }))); + + env.define("file_exists", Obj::Builtin("file_exists".into(), Rc::new(|a| { + let path = format!("{}", a[0]); + Obj::Bool(std::path::Path::new(&path).exists()) + }))); + + env.define("list_dir", Obj::Builtin("list_dir".into(), Rc::new(|a| { + let path = format!("{}", a[0]); + match std::fs::read_dir(&path) { + Ok(entries) => { + let files: Vec = entries + .filter_map(|e| e.ok()) + .map(|e| Obj::String(e.file_name().to_string_lossy().into_owned())) + .collect(); + Obj::List(files) + } + Err(e) => { eprintln!("list_dir error: {}", e); Obj::List(vec![]) } + } + }))); + + env.define("join", Obj::Builtin("join".into(), Rc::new(|a| { + if a.is_empty() { return Obj::String(String::new()); } + match &a[0] { + Obj::List(items) => { + let sep = if a.len() > 1 { format!("{}", a[1]) } else { String::new() }; + let strs: Vec = items.iter().map(|o| format!("{o}")).collect(); + Obj::String(strs.join(&sep)) + } + _ => Obj::String(format!("{}", a[0])) + } + }))); + + env.define("split", Obj::Builtin("split".into(), Rc::new(|a| { + if a.is_empty() { return Obj::List(vec![]); } + let s = format!("{}", a[0]); + let sep = if a.len() > 1 { format!("{}", a[1]) } else { " ".into() }; + Obj::List(s.split(&sep).map(|p| Obj::String(p.to_string())).collect()) + }))); + + env.define("sort", Obj::Builtin("sort".into(), Rc::new(|a| { + if a.is_empty() { return Obj::List(vec![]); } + match &a[0] { + Obj::List(items) => { + let mut sorted = items.clone(); + sorted.sort_by(|a, b| cmp_o(a, b).unwrap_or(std::cmp::Ordering::Equal)); + Obj::List(sorted) + } + _ => Obj::Null + } + }))); + + env.define("reverse", Obj::Builtin("reverse".into(), Rc::new(|a| { + if a.is_empty() { return Obj::Null; } + match &a[0] { + Obj::List(items) => { let mut r = items.clone(); r.reverse(); Obj::List(r) } + Obj::String(s) => Obj::String(s.chars().rev().collect()), + _ => Obj::Null + } + }))); + + // Aether-recall (use String to avoid const capture issues) + let rbin = "W:/AGI/aether-tools/aether-recall/target/release/aether-recall.exe".to_string(); + let rbin2 = rbin.clone(); let rbin3 = rbin.clone(); let rbin4 = rbin.clone(); let rbin5 = rbin.clone(); let rbin6 = rbin.clone(); + let dht = "127.0.0.1:9001".to_string(); + let dht2 = dht.clone(); let dht3 = dht.clone(); let dht4 = dht.clone(); let dht5 = dht.clone(); let dht6 = dht.clone(); + + env.define("recall", Obj::Builtin("recall".into(), Rc::new(move |a| { + let q = format!("{}", a[0]); + let mut child = match Command::new(&rbin).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::null()).spawn() { Ok(c) => c, Err(_) => return Obj::String("(unavailable)".into()) }; + let req = serde_json::json!({"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"lookup_resolution","arguments":{"failure":q}}}); + let s = serde_json::to_string(&req).unwrap_or_default(); + if let Some(mut si) = child.stdin.take() { let _ = si.write_all(s.as_bytes()); let _ = si.write_all(b"\n"); } + let mut out = String::new(); let _ = child.stdout.take().map(|mut o| o.read_to_string(&mut out)); + let _ = child.wait(); + for line in out.lines().rev() { + if line.trim().starts_with('{') { + if let Ok(v) = serde_json::from_str::(line.trim()) { + if let Some(c) = v.get("result").and_then(|r| r.get("content")).and_then(|c| c.as_array()).and_then(|a| a.first()).and_then(|i| i.get("text")).and_then(|t| t.as_str()) { + if let Ok(inner) = serde_json::from_str::(c) { + if inner.get("hit").and_then(|h| h.as_bool()).unwrap_or(false) { + if let Some(res) = inner.get("resolution").and_then(|r| r.as_str()) { return Obj::String(format!("[recall] {res}")); } + } + } + return Obj::String("[recall] miss".into()); + } + } + } + } + Obj::String("[recall] error".into()) + }))); + + env.define("recall_store", Obj::Builtin("recall_store".into(), Rc::new(move |a| { + let f = format!("{}", a[0]); let r = format!("{}", a.get(1).unwrap_or(&Obj::Null)); let w = a.get(2).map(|o| matches!(o, Obj::Bool(true))).unwrap_or(true); + let mut child = match Command::new(&rbin2).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::null()).spawn() { Ok(c) => c, Err(_) => return Obj::Bool(false) }; + let req = serde_json::json!({"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"record_outcome","arguments":{"failure":f,"resolution":r,"worked":w}}}); + let s = serde_json::to_string(&req).unwrap_or_default(); + if let Some(mut si) = child.stdin.take() { let _ = si.write_all(s.as_bytes()); let _ = si.write_all(b"\n"); } + let _ = child.wait(); Obj::Bool(true) + }))); + + env.define("recall_stats", Obj::Builtin("recall_stats".into(), Rc::new(move |_| { + let mut child = match Command::new(&rbin3).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::null()).spawn() { Ok(c) => c, Err(_) => return Obj::String("(unavailable)".into()) }; + let req = r#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"memory_stats","arguments":{}}}"#; + if let Some(mut si) = child.stdin.take() { let _ = si.write_all(req.as_bytes()); let _ = si.write_all(b"\n"); } + let mut out = String::new(); let _ = child.stdout.take().map(|mut o| o.read_to_string(&mut out)); + let _ = child.wait(); Obj::String(out) + }))); + + env.define("dht_ping", Obj::Builtin("dht_ping".into(), Rc::new(move |_| Obj::String(dht_rpc("ping", serde_json::json!({}), &dht).unwrap_or_else(|| "(dht unavailable)".into()))))); + env.define("dht_put", Obj::Builtin("dht_put".into(), Rc::new(move |a| { let v = format!("{}", a[0]); Obj::String(dht_rpc("put", serde_json::json!({"value": v}), &dht2).unwrap_or_else(|| "(dht unavailable)".into())) }))); + env.define("dht_get", Obj::Builtin("dht_get".into(), Rc::new(move |a| { let k = format!("{}", a[0]); Obj::String(dht_rpc("get", serde_json::json!({"key": k}), &dht3).unwrap_or_else(|| "(dht unavailable)".into())) }))); + env.define("dht_stats", Obj::Builtin("dht_stats".into(), Rc::new(move |_| Obj::String(dht_rpc("stats", serde_json::json!({}), &dht4).unwrap_or_else(|| "(dht unavailable)".into()))))); + + // ─── KG Builtins (LEDONOVA via aether-ask + recall) ──────────────────── + let ask = "http://127.0.0.1:8082".to_string(); + let ask2 = ask.clone(); + + env.define("kg_absorb", Obj::Builtin("kg_absorb".into(), Rc::new(move |a| { + let text = format!("{}", a[0]); + let source = if a.len() > 1 { format!("{}", a[1]) } else { "mesh".into() }; + let req = serde_json::json!({"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"record_outcome","arguments":{"failure":format!("kg:{source}"),"resolution":text,"worked":true}}}); + if let Ok(mut child) = Command::new(&rbin4).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::null()).spawn() { + if let Some(mut si) = child.stdin.take() { let _ = serde_json::to_writer(&mut si, &req); let _ = si.write_all(b"\n"); } + let _ = child.wait(); + } + let _ = http_post(&ask, "/api/learn", &serde_json::json!({"text":text,"source":source})); + let mut r = HashMap::new(); r.insert("text".into(), Obj::String(text)); r.insert("source".into(), Obj::String(source)); r.insert("stored".into(), Obj::Bool(true)); + Obj::Dict(r) + }))); + + env.define("kg_query", Obj::Builtin("kg_query".into(), Rc::new(move |a| { + let q = format!("{}", a[0]); + let depth = if a.len() > 1 { if let Obj::Number(n) = &a[1] { *n as i64 } else { 2 } } else { 2 }; + let url = format!("{}/api/query?q={}&depth={}", ask2, urlEncode(&q), depth); + Obj::String(http_get(&url).unwrap_or_else(|| "(kg_query: unavailable)".into())) + }))); + + env.define("kg_recall", Obj::Builtin("kg_recall".into(), Rc::new(move |a| { + let concept = format!("{}", a[0]); + let mut child = match Command::new(&rbin5).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::null()).spawn() { Ok(c) => c, Err(_) => return Obj::String("(unavailable)".into()) }; + let req = serde_json::json!({"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"lookup_resolution","arguments":{"failure":format!("kg:{}",concept)}}}); + let s = serde_json::to_string(&req).unwrap_or_default(); + if let Some(mut si) = child.stdin.take() { let _ = si.write_all(s.as_bytes()); let _ = si.write_all(b"\n"); } + let mut out = String::new(); let _ = child.stdout.take().map(|mut o| o.read_to_string(&mut out)); + let _ = child.wait(); + for line in out.lines().rev() { + if line.trim().starts_with('{') { + if let Ok(v) = serde_json::from_str::(line.trim()) { + if let Some(c) = v.get("result").and_then(|r| r.get("content")).and_then(|c| c.as_array()).and_then(|a| a.first()).and_then(|i| i.get("text")).and_then(|t| t.as_str()) { + if let Ok(inner) = serde_json::from_str::(c) { + if inner.get("hit").and_then(|h| h.as_bool()).unwrap_or(false) { + if let Some(res) = inner.get("resolution").and_then(|r| r.as_str()) { return Obj::String(res.to_string()); } + } + } + return Obj::String("(miss)".into()); + } + } + } + } + Obj::String("(error)".into()) + }))); + + env.define("kg_stats", Obj::Builtin("kg_stats".into(), Rc::new(move |_| { + let mut child = match Command::new(&rbin6).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::null()).spawn() { Ok(c) => c, Err(_) => return Obj::String("(unavailable)".into()) }; + let req = r#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"memory_stats","arguments":{}}}"#; + if let Some(mut si) = child.stdin.take() { let _ = si.write_all(req.as_bytes()); let _ = si.write_all(b"\n"); } + let mut out = String::new(); let _ = child.stdout.take().map(|mut o| o.read_to_string(&mut out)); + let _ = child.wait(); Obj::String(out) + }))); + + env.define("dht_kg_absorb", Obj::Builtin("dht_kg_absorb".into(), Rc::new(move |a| { + let text = format!("{}", a[0]); + let resp = dht_rpc("kg_absorb", serde_json::json!({"text":text}), &dht5); + Obj::String(resp.unwrap_or_else(|| "(dht_kg_absorb: unavailable)".into())) + }))); + + env.define("dht_kg_query", Obj::Builtin("dht_kg_query".into(), Rc::new(move |a| { + let text = format!("{}", a[0]); + let resp = dht_rpc("kg_query", serde_json::json!({"text":text}), &dht6); + Obj::String(resp.unwrap_or_else(|| "(dht_kg_query: unavailable)".into())) + }))); +} + +fn urlEncode(s: &str) -> String { + s.chars().map(|c| if c.is_alphanumeric()||c=='-'||c=='_'||c=='.'||c=='~' { c.to_string() } else { format!("%{:02X}", c as u32) }).collect() +} + +fn dht_rpc(method: &str, params: serde_json::Value, addr: &str) -> Option { + let req = serde_json::json!({"jsonrpc":"2.0","id":1,"method":method,"params":params}); + let s = serde_json::to_string(&req).ok()?; + let mut stream = TcpStream::connect_timeout(&addr.parse().ok()?, Duration::from_secs(3)).ok()?; + let _ = stream.write_all(s.as_bytes()); let _ = stream.write_all(b"\n"); + let mut buf = String::new(); let _ = BufReader::new(&mut stream).read_to_string(&mut buf); + Some(buf) +} + +fn http_get(url: &str) -> Option { + let u = url.strip_prefix("http://").unwrap_or(url); + let (hp, _) = u.split_once('/').unwrap_or((u, "")); + let host = hp.split(':').next().unwrap_or(hp); + let port = hp.split(':').nth(1).and_then(|p| p.parse().ok()).unwrap_or(80); + let path = if let Some((_, p)) = u.split_once('/') { format!("/{p}") } else { "/".into() }; + let mut stream = TcpStream::connect_timeout(&format!("{host}:{port}").parse().ok()?, Duration::from_secs(5)).ok()?; + let _ = stream.write_all(format!("GET {path} HTTP/1.0\r\nHost: {host}\r\nConnection: close\r\n\r\n").as_bytes()); + let mut resp = String::new(); let _ = BufReader::new(&mut stream).read_to_string(&mut resp); + resp.split("\r\n\r\n").nth(1).or_else(|| resp.split("\n\n").nth(1)).map(|s| s.to_string()).or(Some(resp)) +} + +fn http_post(addr: &str, path: &str, body: &serde_json::Value) -> Option { + let url = format!("{}{}", addr, path); + let u = url.strip_prefix("http://").unwrap_or(&url); + let (hp, _) = u.split_once('/').unwrap_or((u, "")); + let host = hp.split(':').next().unwrap_or(hp); + let port = hp.split(':').nth(1).and_then(|p| p.parse().ok()).unwrap_or(80); + let req_path = if let Some((_, p)) = u.split_once('/') { format!("/{}", p) } else { "/".into() }; + let body_str = serde_json::to_string(body).ok()?; + let mut stream = TcpStream::connect_timeout(&format!("{host}:{port}").parse().ok()?, Duration::from_secs(3)).ok()?; + let _ = stream.write_all(format!("POST {req_path} HTTP/1.0\r\nHost: {host}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}", body_str.len(), body_str).as_bytes()); + let mut resp = String::new(); let _ = BufReader::new(&mut stream).read_to_string(&mut resp); + resp.split("\r\n\r\n").nth(1).or_else(|| resp.split("\n\n").nth(1)).map(|s| s.to_string()) +} + +// ─── Evaluator ───────────────────────────────────────────────────────────── + +struct Eval { globals: Rc> } + +impl Eval { + fn new() -> Self { + let mut env = Env::new(); + install_builtins(&mut env); + Eval { globals: Rc::new(RefCell::new(env)) } + } + + fn call_func_value(&self, func: &Obj, args: &[Obj], _env: &Rc>) -> Obj { + match func { + Obj::Func(params, body, closure) => { + let fn_env = Rc::new(RefCell::new(Env::child(closure))); + for (p, a) in params.iter().zip(args.iter()) { fn_env.borrow_mut().define(p, a.clone()); } + self.eval(body, &fn_env) + } + Obj::Builtin(_, f) => f(args), + _ => Obj::Null, + } + } + + fn eval(&self, ast: &Ast, env: &Rc>) -> Obj { + match ast { + Ast::Program(stmts) => { let mut last = Obj::Null; for s in stmts { last = self.eval(s, env); } last } + Ast::Number(n) => Obj::Number(*n), + Ast::StringLit(s) => Obj::String(s.clone()), + Ast::Bool(b) => Obj::Bool(*b), + Ast::Null => Obj::Null, + Ast::Ident(name) => { + if name == "_" { return Obj::Null; } + env.borrow().get(name).unwrap_or_else(|| Obj::String(format!("(undefined: {name})"))) + } + Ast::List(items) => Obj::List(items.iter().map(|i| self.eval(i, env)).collect()), + Ast::Dict(pairs) => { + let mut d = HashMap::new(); + for (k, v) in pairs { + let key = match self.eval(k, env) { Obj::String(s) => s, Obj::Number(n) => n.to_string(), o => format!("{o}") }; + d.insert(key, self.eval(v, env)); + } + Obj::Dict(d) + } + Ast::Let(name, val) => { let v = self.eval(val, env); env.borrow_mut().define(name, v); Obj::Null } + Ast::FnDef(name, params, body) => { + let env_rc = Rc::clone(env); + let closure = Rc::new(RefCell::new(Env::child(&env_rc))); + env_rc.borrow_mut().define(name, Obj::Func(params.clone(), body.as_ref().clone(), closure)); + Obj::Null + } + Ast::FnExpr(params, body) => Obj::Func(params.clone(), body.as_ref().clone(), Rc::new(RefCell::new(Env::child(env)))), + Ast::Import(path) => { + let src = std::fs::read_to_string(path).unwrap_or_else(|e| { + eprintln!("import error: cannot read '{}': {}", path, e); + String::new() + }); + if src.is_empty() { return Obj::Null; } + let mut p = Parser::new(&src); + let module_ast = p.parse(); + let module_env = Rc::new(RefCell::new(Env::child(&self.globals))); + if let Ast::Program(nodes) = &module_ast { + for node in nodes { self.eval(node, &module_env); } + } + let vars = module_env.borrow().snapshot(); + Obj::Dict(vars) + } + Ast::Block(stmts) => { let mut last = Obj::Null; for s in stmts { last = self.eval(s, env); } last } + Ast::If(cond, cons, alt) => { + if is_truthy(&self.eval(cond, env)) { self.eval(cons, env) } + else if let Some(a) = alt { self.eval(a, env) } else { Obj::Null } + } + Ast::Match(val, arms) => { + let v = self.eval(val, env); + for (pat, body) in arms { if val_eq(&v, &self.eval(pat, env)) { return self.eval(body, env); } } + Obj::Null + } + Ast::While(cond, body) => { let mut l = Obj::Null; while is_truthy(&self.eval(cond, env)) { l = self.eval(body, env); } l } + Ast::For(name, iter, body) => { + let mut last = Obj::Null; + match self.eval(iter, env) { + Obj::List(items) => { for item in items { env.borrow_mut().define(name, item); last = self.eval(body, env); } } + Obj::String(s) => { for c in s.chars() { env.borrow_mut().define(name, Obj::String(c.into())); last = self.eval(body, env); } } + Obj::Number(n) => { for i in 0..(n as i64) { env.borrow_mut().define(name, Obj::Number(i as f64)); last = self.eval(body, env); } } + _ => {} + } + last + } + Ast::Assign(name, val) => { let v = self.eval(val, env); env.borrow_mut().set(name, v); Obj::Null } + Ast::BinOp(l, op, r) => { + let lv = self.eval(l, env); let rv = self.eval(r, env); + match op { + Token::Plus => add(&lv, &rv), + Token::Minus => num_op(&lv, &rv, |a,b| a-b), + Token::Star => num_op(&lv, &rv, |a,b| a*b), + Token::Slash => num_op(&lv, &rv, |a,b| if b != 0.0 { a/b } else { 0.0 }), + Token::Percent => num_op(&lv, &rv, |a,b| if b != 0.0 { a%b } else { 0.0 }), + Token::EqEq => Obj::Bool(val_eq(&lv, &rv)), + Token::BangEq => Obj::Bool(!val_eq(&lv, &rv)), + Token::Lt => Obj::Bool(cmp_o(&lv, &rv) == Some(std::cmp::Ordering::Less)), + Token::Gt => Obj::Bool(cmp_o(&lv, &rv) == Some(std::cmp::Ordering::Greater)), + Token::LtEq => Obj::Bool(!matches!(cmp_o(&lv, &rv), Some(std::cmp::Ordering::Greater))), + Token::GtEq => Obj::Bool(!matches!(cmp_o(&lv, &rv), Some(std::cmp::Ordering::Less))), + Token::And => Obj::Bool(is_truthy(&lv) && is_truthy(&rv)), + Token::Or => Obj::Bool(is_truthy(&lv) || is_truthy(&rv)), + _ => Obj::Null, + } + } + Ast::UnaryOp(op, e) => { + let v = self.eval(e, env); + match op { + Token::Minus => if let Obj::Number(n) = &v { Obj::Number(-n) } else { v }, + Token::Bang => Obj::Bool(!is_truthy(&v)), + _ => v, + } + } + Ast::Call(func, args) => { + if let Ast::Ident(name) = func.as_ref() { + match name.as_str() { + "map" if args.len() == 2 => { + return match self.eval(&args[0], env) { + Obj::List(items) => { + let func_val = self.eval(&args[1], env); + Obj::List(items.iter().map(|item| self.call_func_value(&func_val, &[item.clone()], env)).collect()) + } + _ => Obj::Null + } + } + "filter" if args.len() == 2 => { + return match self.eval(&args[0], env) { + Obj::List(items) => { + let func_val = self.eval(&args[1], env); + Obj::List(items.iter().filter(|item| is_truthy(&self.call_func_value(&func_val, &[(*item).clone()], env))).cloned().collect()) + } + _ => Obj::Null + } + } + "reduce" if args.len() == 3 => { + return match self.eval(&args[0], env) { + Obj::List(items) => { + let init = self.eval(&args[1], env); + let func_val = self.eval(&args[2], env); + let mut acc = init; + for item in items { acc = self.call_func_value(&func_val, &[acc, item], env); } + acc + } + _ => Obj::Null + } + } + _ => {} + } + } + let callee = self.eval(func, env); + let ev_args: Vec = args.iter().map(|a| self.eval(a, env)).collect(); + match callee { + Obj::Func(params, body, closure) => { + let fn_env = Rc::new(RefCell::new(Env::child(&closure))); + for (p, a) in params.iter().zip(ev_args.iter()) { fn_env.borrow_mut().define(p, a.clone()); } + self.eval(&body, &fn_env) + } + Obj::Builtin(_, f) => f(&ev_args), + _ => Obj::Null, + } + } + Ast::Index(obj, idx) => { + let o = self.eval(obj, env); let i = self.eval(idx, env); + match (&o, &i) { + (Obj::List(l), Obj::Number(n)) => l.get(*n as usize).cloned().unwrap_or(Obj::Null), + (Obj::Dict(d), Obj::String(k)) => d.get(k).cloned().unwrap_or(Obj::Null), + (Obj::String(s), Obj::Number(n)) => s.chars().nth(*n as usize).map(|c| Obj::String(c.into())).unwrap_or(Obj::Null), + _ => Obj::Null, + } + } + Ast::GetAttr(obj, field) => { + if let Obj::Dict(d) = &self.eval(obj, env) { d.get(field).cloned().unwrap_or(Obj::Null) } else { Obj::Null } + } + Ast::Pipe(lhs, rhs) => { + let lv = self.eval(lhs, env); + match rhs.as_ref() { + Ast::Call(func, args) => { + let callee = self.eval(func, env); + let mut all = vec![lv]; + for a in args { all.push(self.eval(a, env)); } + match callee { + Obj::Builtin(_, f) => f(&all), + Obj::Func(params, body, closure) => { + let fn_env = Rc::new(RefCell::new(Env::child(&closure))); + for (p, a) in params.iter().zip(all.iter()) { fn_env.borrow_mut().define(p, a.clone()); } + self.eval(&body, &fn_env) + } + _ => Obj::Null, + } + } + Ast::Ident(name) => { + match env.borrow().get(name).unwrap_or(Obj::Null) { + Obj::Builtin(_, f) => f(&[lv]), + Obj::Func(params, body, closure) => { + let fn_env = Rc::new(RefCell::new(Env::child(&closure))); + if let Some(p) = params.first() { fn_env.borrow_mut().define(p, lv); } + self.eval(&body, &fn_env) + } + _ => Obj::Null, + } + } + _ => { + let callee = self.eval(rhs, env); + match callee { + Obj::Builtin(_, f) => f(&[lv]), + Obj::Func(params, body, closure) => { + let fn_env = Rc::new(RefCell::new(Env::child(&closure))); + if let Some(p) = params.first() { fn_env.borrow_mut().define(p, lv); } + self.eval(&body, &fn_env) + } + _ => Obj::Null, + } + } + } + } + } + } +} + +fn is_truthy(o: &Obj) -> bool { + match o { + Obj::Null => false, Obj::Bool(b) => *b, Obj::Number(n) => *n != 0.0, + Obj::String(s) => !s.is_empty(), Obj::List(l) => !l.is_empty(), + Obj::Dict(d) => !d.is_empty(), _ => true, + } +} + +fn val_eq(a: &Obj, b: &Obj) -> bool { + match (a, b) { + (Obj::Number(a), Obj::Number(b)) => (a - b).abs() < 1e-10, + (Obj::String(a), Obj::String(b)) => a == b, + (Obj::Bool(a), Obj::Bool(b)) => a == b, + (Obj::Null, Obj::Null) => true, + (Obj::List(a), Obj::List(b)) => a.len() == b.len() && a.iter().zip(b).all(|(x, y)| val_eq(x, y)), + _ => false, + } +} + +fn cmp_o(a: &Obj, b: &Obj) -> Option { + match (a, b) { (Obj::Number(a), Obj::Number(b)) => a.partial_cmp(b), _ => None } +} + +fn add(a: &Obj, b: &Obj) -> Obj { + match (a, b) { + (Obj::String(a), Obj::String(b)) => Obj::String(format!("{a}{b}")), + (Obj::String(a), b) => Obj::String(format!("{a}{b}")), + (a, Obj::String(b)) => Obj::String(format!("{a}{b}")), + (Obj::Number(a), Obj::Number(b)) => Obj::Number(a + b), + (Obj::List(a), Obj::List(b)) => { let mut l = a.clone(); l.extend(b.clone()); Obj::List(l) } + (Obj::List(a), b) => { let mut l = a.clone(); l.push(b.clone()); Obj::List(l) } + _ => Obj::Number(0.0), + } +} + +fn num_op f64>(a: &Obj, b: &Obj, f: F) -> Obj { + match (a, b) { (Obj::Number(a), Obj::Number(b)) => Obj::Number(f(*a, *b)), _ => Obj::Null } +} + +// ─── REPL ────────────────────────────────────────────────────────────────── + +fn repl() { + let eval = Eval::new(); + let stdin = io::stdin(); + let env = Rc::new(RefCell::new(Env::child(&eval.globals))); + println!("╔══════════════════════════════════════════╗"); + println!("║ Æther Code (aec) v0.1 ║"); + println!("║ \"one language to rule them all\" ║"); + println!("╠══════════════════════════════════════════╣"); + println!("║ builtins: recall, recall_store, ║"); + println!("║ dht_ping, dht_put, dht_get, ║"); + println!("║ http_get, print, len, str, ║"); + println!("║ num, type_of, range, etc. ║"); + println!("║ multiline: use {{ }} or ( ) to continue ║"); + println!("╚══════════════════════════════════════════╝"); + + let mut buffer = String::new(); + let mut continuation = false; + + loop { + if continuation { + print!("... "); + } else { + print!("ae> "); + } + io::stdout().flush().ok(); + + let mut line = String::new(); + match stdin.read_line(&mut line) { + Ok(0) => break, // EOF + Ok(_) => {} + Err(_) => break, + } + + let trimmed = line.trim(); + if trimmed.is_empty() && !continuation { continue; } + if trimmed == ":q" || trimmed == ":quit" || trimmed == "exit" { break; } + + buffer.push_str(&line); + + // Check if input is complete (balanced braces/parens) + let open_braces = buffer.chars().filter(|&c| c == '{').count(); + let close_braces = buffer.chars().filter(|&c| c == '}').count(); + let open_parens = buffer.chars().filter(|&c| c == '(').count(); + let close_parens = buffer.chars().filter(|&c| c == ')').count(); + + if open_braces > close_braces || open_parens > close_parens { + continuation = true; + continue; + } + + continuation = false; + let code = buffer.trim().to_string(); + buffer.clear(); + + if code.is_empty() { continue; } + + let mut p = Parser::new(&code); + let ast = p.parse(); + match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + if let Ast::Program(nodes) = &ast { + for node in nodes { + let result = eval.eval(node, &env); + match &result { + Obj::Null => {} + _ => println!("{result}") + } + } + } + })) { + Ok(()) => {} + Err(e) => { + let msg = if let Some(s) = e.downcast_ref::() { + s.clone() + } else if let Some(s) = e.downcast_ref::<&str>() { + s.to_string() + } else { + "unknown error".to_string() + }; + eprintln!("[error] {}", msg); + } + } + } +} + +fn run_file(path: &str) { + let src = match std::fs::read_to_string(path) { Ok(s) => s, Err(e) => { eprintln!("error: {e}"); return } }; + run_code(&src) +} + +fn run_code(src: &str) { + let eval = Eval::new(); + let mut p = Parser::new(src); + let ast = p.parse(); + let env = Rc::new(RefCell::new(Env::child(&eval.globals))); + if let Ast::Program(nodes) = &ast { + for node in nodes { + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + eval.eval(node, &env) + })); + match result { + Ok(Obj::Null) => {} + Ok(val) => println!("{val}"), + Err(e) => { + let msg = if let Some(s) = e.downcast_ref::() { + s.clone() + } else if let Some(s) = e.downcast_ref::<&str>() { + s.to_string() + } else { + "unknown error".to_string() + }; + eprintln!("[error] {}", msg); + } + } + } + } +} + +fn main() { + let args: Vec = std::env::args().collect(); + match args.len() { + 1 => repl(), + 2 if args[1] == "--repl" || args[1] == "-i" => repl(), + 2 if args[1] == "-e" || args[1] == "--eval" => { + eprintln!("usage: aec -e '' or aec ''"); + std::process::exit(1); + } + 2 => run_file(&args[1]), + 3 if args[1] == "-e" || args[1] == "--eval" => run_code(&args[2]), + _ if args[1] == "-e" || args[1] == "--eval" => run_code(&args[2..].join(" ")), + _ => run_code(&args[1..].join(" ")), + } +}