diff --git a/src/ast/compiler/analyzer.rs b/src/ast/compiler/analyzer.rs index 090e6b4..1f6f1ba 100644 --- a/src/ast/compiler/analyzer.rs +++ b/src/ast/compiler/analyzer.rs @@ -53,7 +53,7 @@ impl<'a> Analyzer<'a> { } self.collect_globals(value); } - NodeKind::Block { exprs } => { + NodeKind::Block { exprs } | NodeKind::Program { exprs } => { for e in exprs { self.collect_globals(e); } @@ -241,6 +241,16 @@ impl<'a> Analyzer<'a> { } (NodeKind::Block { exprs: new_exprs }, p) } + NodeKind::Program { exprs } => { + let mut new_exprs = Vec::with_capacity(exprs.len()); + let mut p = Purity::Pure; + for e in exprs { + let em = self.visit(e.clone()); + p = p.min(em.ty.purity); + new_exprs.push(Rc::new(em)); + } + (NodeKind::Program { exprs: new_exprs }, p) + } NodeKind::Tuple { elements } => { let mut new_elements = Vec::with_capacity(elements.len()); @@ -350,7 +360,7 @@ impl NodeExt for NodeKind { f(callee); f(args); } - NodeKind::Block { exprs } => { + NodeKind::Block { exprs } | NodeKind::Program { exprs } => { for e in exprs { f(e); } diff --git a/src/ast/compiler/binder.rs b/src/ast/compiler/binder.rs index 376291a..7368ea8 100644 --- a/src/ast/compiler/binder.rs +++ b/src/ast/compiler/binder.rs @@ -1,10 +1,11 @@ -use crate::ast::nodes::{ - Address, AssignBinding, BoundPhase, DeclarationKind, DefBinding, - IdentifierBinding, LambdaBinding, Node, NodeKind, SyntaxKind, SyntaxNode, Symbol, - UpvalueIdx, VirtualId, -}; use crate::ast::diagnostics::Diagnostics; -use crate::ast::types::{Identity, NodeIdentity, Purity, RecordLayout, SourceLocation, StaticType, Value}; +use crate::ast::nodes::{ + Address, AssignBinding, BoundPhase, DeclarationKind, DefBinding, GlobalIdx, IdentifierBinding, + LambdaBinding, Node, NodeKind, Symbol, SyntaxKind, SyntaxNode, UpvalueIdx, VirtualId, +}; +use crate::ast::types::{ + Identity, NodeIdentity, Purity, RecordLayout, SourceLocation, StaticType, Value, +}; use std::collections::HashMap; use std::rc::Rc; @@ -43,7 +44,11 @@ struct FunctionCompiler { } impl FunctionCompiler { - fn new(identity: Identity, initial_scopes: Vec, initial_slot_count: u32) -> Self { + fn new( + identity: Identity, + initial_scopes: Vec, + initial_slot_count: u32, + ) -> Self { Self { identity, scopes: if initial_scopes.is_empty() { @@ -66,7 +71,12 @@ impl FunctionCompiler { } } - fn define_variable(&mut self, name: &Symbol, identity: Identity) -> Result, String> { + fn define_variable( + &mut self, + name: &Symbol, + identity: Identity, + as_global: bool, + ) -> Result, String> { let current_scope = self.scopes.last_mut().unwrap(); if current_scope.locals.contains_key(name) { return Err(format!( @@ -76,17 +86,22 @@ impl FunctionCompiler { } let slot = VirtualId(self.slot_count); + let addr = if as_global { + Address::Global(GlobalIdx(slot.0)) + } else { + Address::Local(slot) + }; current_scope.locals.insert( name.clone(), LocalInfo { - addr: Address::Local(slot), + addr, identity, _ty: StaticType::Any, purity: Purity::Impure, }, ); self.slot_count += 1; - Ok(Address::Local(slot)) + Ok(addr) } fn resolve_local(&self, sym: &Symbol) -> Option<(LocalInfo, usize)> { @@ -127,19 +142,13 @@ pub struct Binder { } impl Binder { - pub fn new( - initial_scopes: Vec, - initial_slot_count: u32, - ) -> Self { + pub fn new(initial_scopes: Vec, initial_slot_count: u32) -> Self { let mut binder = Self { functions: Vec::new(), capture_map: HashMap::new(), }; binder.functions.push(FunctionCompiler::new( - NodeIdentity::new(SourceLocation { - line: 0, - col: 0, - }), + NodeIdentity::new(SourceLocation { line: 0, col: 0 }), initial_scopes, initial_slot_count, )); @@ -177,8 +186,9 @@ impl Binder { _kind: DeclarationKind, diag: &mut Diagnostics, ) -> Option> { + let as_global = self.functions.len() == 1; let current_fn = self.functions.last_mut().unwrap(); - match current_fn.define_variable(name, identity) { + match current_fn.define_variable(name, identity, as_global) { Ok(addr) => Some(addr), Err(e) => { diag.push_error(e, None); @@ -245,14 +255,15 @@ impl Binder { ) } - SyntaxKind::Def { pattern: target, value, .. } => { - if ctx == ExprContext::Expression { - diag.push_error( - "Statement 'def' cannot be used as an expression.", - Some(node.identity.clone()), - ); - } - if let SyntaxKind::Identifier { symbol: ref name, .. } = target.kind { + SyntaxKind::Def { + pattern: target, + value, + .. + } => { + if let SyntaxKind::Identifier { + symbol: ref name, .. + } = target.kind + { let addr_opt = self.declare_variable( name, node.identity.clone(), @@ -287,7 +298,8 @@ impl Binder { } } else { let val_node = self.bind(value, ExprContext::Expression, diag); - let target_node = self.bind_pattern(target.as_ref(), DeclarationKind::Variable, diag); + let target_node = + self.bind_pattern(target.as_ref(), DeclarationKind::Variable, diag); self.make_node( node.identity.clone(), @@ -343,17 +355,19 @@ impl Binder { self.functions .push(FunctionCompiler::new(identity.clone(), vec![], 0)); - let params_bound = self.bind_pattern(params.as_ref(), DeclarationKind::Parameter, diag); + let params_bound = + self.bind_pattern(params.as_ref(), DeclarationKind::Parameter, diag); let body_bound = self.bind(body.as_ref(), ExprContext::Expression, diag); let compiled_fn = self.functions.pop().unwrap(); fn count_params(node: &Node) -> Option { match &node.kind { NodeKind::Identifier { - binding: IdentifierBinding::Declaration { - kind: DeclarationKind::Parameter, - .. - }, + binding: + IdentifierBinding::Declaration { + kind: DeclarationKind::Parameter, + .. + }, .. } => Some(1), NodeKind::Tuple { elements } => { @@ -431,6 +445,22 @@ impl Binder { ) } + SyntaxKind::Program { exprs } => { + let mut bound_exprs = Vec::new(); + for (i, expr) in exprs.iter().enumerate() { + let expr_ctx = if i == exprs.len() - 1 { + ctx + } else { + ExprContext::Statement + }; + bound_exprs.push(Rc::new(self.bind(expr, expr_ctx, diag))); + } + self.make_node( + node.identity.clone(), + NodeKind::Program { exprs: bound_exprs }, + ) + } + SyntaxKind::Tuple { elements } => { let mut bound_elems = Vec::new(); for e in elements { @@ -452,9 +482,7 @@ impl Binder { let key_node = self.bind(k, ExprContext::Expression, diag); let val_node = self.bind(v, ExprContext::Expression, diag); - if let NodeKind::Constant(Value::Keyword(kw)) = - &key_node.kind - { + if let NodeKind::Constant(Value::Keyword(kw)) = &key_node.kind { layout_fields.push((*kw, StaticType::Any)); } else { diag.push_error( @@ -479,7 +507,10 @@ impl Binder { ) } - SyntaxKind::Expansion { original_call, expanded } => { + SyntaxKind::Expansion { + original_call, + expanded, + } => { let bound_expanded = self.bind(expanded.as_ref(), ctx, diag); self.make_node( node.identity.clone(), @@ -495,10 +526,16 @@ impl Binder { | SyntaxKind::Splice(_) | SyntaxKind::MacroDecl { .. } => { diag.push_error( - format!( - "Macro construct {:?} found in Binder. Macros must be expanded before binding.", - node.kind - ), + { + let label = match &node.kind { + SyntaxKind::MacroDecl { name, .. } => format!("macro '{}'", name.name), + SyntaxKind::Template(_) => "template".to_string(), + SyntaxKind::Placeholder(_) => "placeholder".to_string(), + SyntaxKind::Splice(_) => "splice".to_string(), + _ => "unknown".to_string(), + }; + format!("Unexpanded {label} found during binding. Macros must be expanded before binding.") + }, Some(node.identity.clone()), ); self.make_node(node.identity.clone(), NodeKind::Error) @@ -691,13 +728,18 @@ mod tests { let syntax = parser.parse_expression(); let mut diagnostics = Diagnostics::new(); - let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, &syntax, &mut diagnostics).unwrap(); + let (bound, captures, _scopes, _slots) = + Binder::bind_root(vec![], 0, &syntax, &mut diagnostics).unwrap(); if let NodeKind::Lambda { body, .. } = &bound.kind { if let NodeKind::Block { exprs } = &body.kind { let x_decl = &exprs[0]; if let NodeKind::Def { pattern, .. } = &x_decl.kind { - if let NodeKind::Identifier { binding: IdentifierBinding::Declaration { addr, .. }, .. } = &pattern.kind { + if let NodeKind::Identifier { + binding: IdentifierBinding::Declaration { addr, .. }, + .. + } = &pattern.kind + { assert!(matches!(addr, Address::Local(_))); assert!( captures.contains_key(&x_decl.identity), @@ -724,13 +766,18 @@ mod tests { let syntax = parser.parse_expression(); let mut diagnostics = Diagnostics::new(); - let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, &syntax, &mut diagnostics).unwrap(); + let (bound, captures, _scopes, _slots) = + Binder::bind_root(vec![], 0, &syntax, &mut diagnostics).unwrap(); if let NodeKind::Lambda { body, .. } = &bound.kind { if let NodeKind::Block { exprs } = &body.kind { let x_decl = &exprs[0]; if let NodeKind::Def { pattern, .. } = &x_decl.kind { - if let NodeKind::Identifier { binding: IdentifierBinding::Declaration { addr, .. }, .. } = &pattern.kind { + if let NodeKind::Identifier { + binding: IdentifierBinding::Declaration { addr, .. }, + .. + } = &pattern.kind + { assert!(matches!(addr, Address::Local(_))); assert!( !captures.contains_key(&x_decl.identity), @@ -760,7 +807,11 @@ mod tests { let _ = Binder::bind_root(vec![], 0, &syntax, &mut diagnostics); assert!(diagnostics.has_errors()); - assert!(diagnostics.items.iter().any(|i| i.message.contains("already defined"))); + assert!( + diagnostics + .items + .iter() + .any(|i| i.message.contains("already defined")) + ); } - } diff --git a/src/ast/compiler/dumper.rs b/src/ast/compiler/dumper.rs index 36afc57..816996d 100644 --- a/src/ast/compiler/dumper.rs +++ b/src/ast/compiler/dumper.rs @@ -190,6 +190,14 @@ impl Dumper { } self.indent -= 1; } + NodeKind::Program { exprs } => { + self.log("Program", node); + self.indent += 1; + for expr in exprs { + self.visit(expr); + } + self.indent -= 1; + } NodeKind::Tuple { elements } => { self.log("Tuple", node); diff --git a/src/ast/compiler/emitter.rs b/src/ast/compiler/emitter.rs index 8f43a74..7662328 100644 --- a/src/ast/compiler/emitter.rs +++ b/src/ast/compiler/emitter.rs @@ -148,6 +148,15 @@ fn emit_kind(node: &SyntaxNode, indent: usize, out: &mut String) { out.push(')'); } + NodeKind::Program { exprs } => { + out.push_str("(do"); + for expr in exprs { + out.push('\n'); + emit_block(expr, indent + 1, out); + } + out.push(')'); + } + NodeKind::Tuple { elements } => { out.push('['); for (i, el) in elements.iter().enumerate() { diff --git a/src/ast/compiler/lambda_collector.rs b/src/ast/compiler/lambda_collector.rs index 80222c3..7850ade 100644 --- a/src/ast/compiler/lambda_collector.rs +++ b/src/ast/compiler/lambda_collector.rs @@ -22,7 +22,7 @@ where fn visit(&mut self, node: &Node

) { match &node.kind { - NodeKind::Block { exprs } => { + NodeKind::Block { exprs } | NodeKind::Program { exprs } => { for expr in exprs { self.visit(expr); } diff --git a/src/ast/compiler/lowering.rs b/src/ast/compiler/lowering.rs index c20bc63..59b16fa 100644 --- a/src/ast/compiler/lowering.rs +++ b/src/ast/compiler/lowering.rs @@ -161,6 +161,32 @@ impl Lowering { } } + NodeKind::Program { exprs } => { + if exprs.is_empty() { + NodeKind::Program { exprs: vec![] } + } else { + let scope_locals = Self::collect_scope_locals(exprs); + + let last_idx = exprs.len() - 1; + let mut new_exprs = Vec::with_capacity(exprs.len()); + + for (i, expr) in exprs.iter().enumerate() { + let is_last = i == last_idx; + new_exprs.push(Rc::new(Self::transform( + expr.clone(), + is_tail_position && is_last, + allocator, + ))); + } + + for vid in scope_locals { + allocator.free_slot(vid); + } + + NodeKind::Program { exprs: new_exprs } + } + } + NodeKind::Lambda { params, body, diff --git a/src/ast/compiler/macros.rs b/src/ast/compiler/macros.rs index 038ce8d..97b5e02 100644 --- a/src/ast/compiler/macros.rs +++ b/src/ast/compiler/macros.rs @@ -288,7 +288,36 @@ impl MacroExpander { }) } - _ => Ok(node), + SyntaxKind::Program { exprs } => { + let mut expanded_exprs = Vec::new(); + for expr in exprs { + expanded_exprs.push(Rc::new(self.expand_recursive((*expr).clone())?)); + } + Ok(SyntaxNode { + comments: node.comments.clone(), + identity: node.identity, + kind: SyntaxKind::Program { + exprs: expanded_exprs, + }, + ty: (), + }) + } + + // Leaf nodes — no children to expand. + SyntaxKind::Identifier { .. } + | SyntaxKind::Constant(_) + | SyntaxKind::FieldAccessor(_) + | SyntaxKind::Nop + | SyntaxKind::Error => Ok(node), + + // Quoting forms — preserved as-is (expanded in expand_template). + SyntaxKind::Template(_) + | SyntaxKind::Placeholder(_) + | SyntaxKind::Splice(_) => Ok(node), + + // Post-binding constructs — should not appear in syntax phase. + SyntaxKind::GetField { .. } + | SyntaxKind::Extension(_) => Ok(node), } } diff --git a/src/ast/compiler/optimizer/utils.rs b/src/ast/compiler/optimizer/utils.rs index e26454e..dcd9980 100644 --- a/src/ast/compiler/optimizer/utils.rs +++ b/src/ast/compiler/optimizer/utils.rs @@ -122,7 +122,7 @@ impl UsageInfo { } } } - NodeKind::Block { exprs } => { + NodeKind::Block { exprs } | NodeKind::Program { exprs } => { for e in exprs { self.collect(e); } diff --git a/src/ast/compiler/type_checker/check.rs b/src/ast/compiler/type_checker/check.rs index e3043de..e373b37 100644 --- a/src/ast/compiler/type_checker/check.rs +++ b/src/ast/compiler/type_checker/check.rs @@ -497,6 +497,18 @@ impl TypeChecker { (NodeKind::Block { exprs: typed_exprs }, last_ty) } + NodeKind::Program { exprs } => { + let mut typed_exprs = Vec::new(); + let mut last_ty = StaticType::Void; + + for e in exprs { + let t = self.check_node(e, ctx, diag); + last_ty = t.ty.clone(); + typed_exprs.push(Rc::new(t)); + } + + (NodeKind::Program { exprs: typed_exprs }, last_ty) + } NodeKind::Lambda { params, diff --git a/src/ast/compiler/type_checker/finalize.rs b/src/ast/compiler/type_checker/finalize.rs index c400947..80907fa 100644 --- a/src/ast/compiler/type_checker/finalize.rs +++ b/src/ast/compiler/type_checker/finalize.rs @@ -90,6 +90,12 @@ impl TypeChecker { .map(|e| Rc::new(self.finalize_node((*e).clone(), subst))) .collect(), }, + NodeKind::Program { exprs } => NodeKind::Program { + exprs: exprs + .into_iter() + .map(|e| Rc::new(self.finalize_node((*e).clone(), subst))) + .collect(), + }, NodeKind::Tuple { elements } => NodeKind::Tuple { elements: elements .into_iter() diff --git a/src/ast/environment.rs b/src/ast/environment.rs index 8f92613..279bb75 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -2,10 +2,10 @@ use crate::ast::compiler::analyzer::Analyzer; use crate::ast::compiler::binder::{Binder, CompilerScope, LocalInfo}; use crate::ast::compiler::call_hooks::RtlCompilerHook; use crate::ast::compiler::{CapturePass, TypeChecker, TypedNode}; -use crate::ast::nodes::{AnalyzedPhase, Symbol, SyntaxKind, SyntaxNode}; +use crate::ast::nodes::{AnalyzedPhase, BoundPhase, Symbol, SyntaxKind, SyntaxNode}; use crate::ast::parser::Parser; use crate::ast::closure::Closure; -use crate::ast::vm::{GlobalStore, TracingObserver, VM}; +use crate::ast::vm::{self, GlobalStore, TracingObserver, VM}; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; @@ -334,21 +334,23 @@ impl Environment { /// Used to pre-load all dependencies of a script before compiling it. pub fn preload_dependencies(&self, source: &str, file_path: Option<&Path>) -> Result<(), String> { let base_path = file_path.and_then(|p| p.parent()).unwrap_or_else(|| Path::new(".")); - let mut files: Vec<(PathBuf, SyntaxNode)> = Vec::new(); + let mut all_forms: Vec> = Vec::new(); // 1. Always load the embedded system library first as a virtual module let system_path = PathBuf::from(SYSTEM_LIB_PATH); if !self.loaded_modules.borrow().contains(&system_path) { self.loaded_modules.borrow_mut().insert(system_path.clone()); let mut parser = Parser::new(SYSTEM_LIB_SOURCE); - let syntax_ast = parser.parse_expression(); + let forms = parser.parse_program(); if parser.diagnostics.has_errors() { return Err(format!( "Failed to parse embedded system library:\n{}", parser.diagnostics.items[0].message )); } - files.push((system_path, syntax_ast)); + for form in forms { + all_forms.push(Rc::new(form)); + } } // 2. Collect user dependencies (file I/O + parse, topological order) @@ -356,109 +358,68 @@ impl Environment { Rc::clone(&self.search_paths), Rc::clone(&self.loaded_modules), ); - files.extend(loader.collect_dependency_files(source, base_path)?); - - // Pass 1: Discovery (globals and macros) - for (_, syntax_ast) in &files { - self.discover_globals(syntax_ast); + for (_, syntax_ast) in loader.collect_dependency_files(source, base_path)? { + all_forms.push(Rc::new(syntax_ast)); } - // Pass 2: Compilation and initialization - for (path, syntax_ast) in files { - let typed_ast = self.compile_syntax(syntax_ast).map_err(|e: String| { - format!("Compilation error in {}:\n{}", path.display(), e) - })?; - self.run_script_compiled(typed_ast).map_err(|e: String| { - format!("Initialization error in {}:\n{}", path.display(), e) - })?; + // Compile and initialize all forms as a single program (no scope boundary) + if !all_forms.is_empty() { + let block = SyntaxNode { + identity: NodeIdentity::anonymous(), + kind: SyntaxKind::Program { exprs: all_forms }, + ty: (), + comments: Rc::from([]), + }; + let mut diagnostics = Diagnostics::new(); + + // 1. Expand macros + let expanded = self.expand(block, &mut diagnostics) + .ok_or_else(|| diagnostics.format_errors())?; + + // 2. Bind names and update root scopes + let bound = self.bind_and_update(&expanded, &mut diagnostics) + .ok_or_else(|| diagnostics.format_errors())?; + + // 3. Type-check (without lambda wrapping) + let typed = self.type_check(&bound, &mut diagnostics); + + if diagnostics.has_errors() { + return Err(diagnostics.format_errors()); + } + + // 4. Link and execute + let linked = self.link(typed); + let mut vm = self.create_vm(); + let stack = vec![Value::Void; linked.ty.stack_size as usize]; + let result = vm.eval_in_frame(&mut vm::NoOpObserver, &linked, stack)?; + vm.resolve_tail_calls(&mut vm::NoOpObserver, Ok(result))?; } Ok(()) } - fn discover_globals(&self, node: &SyntaxNode) { - match &node.kind { - SyntaxKind::Def { pattern, .. } => { - let mut root_scopes = self.root_scopes.borrow_mut(); - let last_idx = root_scopes.len() - 1; - let current_scope = &mut root_scopes[last_idx]; - - fn register_pattern( - pattern: &SyntaxNode, - scope: &mut CompilerScope, - slot_count: &mut u32, - identity: &crate::ast::types::NodeIdentity, - ) { - match &pattern.kind { - SyntaxKind::Identifier { symbol: sym, .. } => { - if !scope.locals.contains_key(sym) { - let slot = VirtualId(*slot_count); - scope.locals.insert( - sym.clone(), - LocalInfo { - addr: Address::Local(slot), - identity: Rc::new(identity.clone()), - _ty: StaticType::Any, - purity: Purity::Impure, - }, - ); - *slot_count += 1; - } - } - SyntaxKind::Tuple { elements } => { - for el in elements { - register_pattern(el, scope, slot_count, identity); - } - } - _ => {} - } - } - - let mut slot_count = self.root_slot_count.borrow_mut(); - register_pattern(pattern, current_scope, &mut slot_count, &node.identity); + /// Expands macros in a syntax AST. + fn expand(&self, syntax_ast: SyntaxNode, diagnostics: &mut Diagnostics) -> Option { + match self.get_expander().expand(syntax_ast) { + Ok(ast) => Some(ast), + Err(e) => { + diagnostics.push_error(e, None); + None } - SyntaxKind::MacroDecl { name, params, body } => { - let mut registry = self.macro_registry.borrow_mut(); - - fn extract_names(node: &SyntaxNode) -> Vec> { - match &node.kind { - SyntaxKind::Identifier { symbol: sym, .. } => vec![sym.name.clone()], - SyntaxKind::Tuple { elements } => { - elements.iter().flat_map(|e| extract_names(e)).collect() - } - _ => vec![], - } - } - - let p_names = extract_names(params); - registry.define(name.name.clone(), p_names, body.as_ref().clone()); - } - SyntaxKind::Block { exprs } => { - for expr in exprs { - self.discover_globals(expr); - } - } - SyntaxKind::Expansion { expanded, .. } => { - self.discover_globals(expanded); - } - _ => {} } } - fn compile_pipeline(&self, syntax_ast: SyntaxNode, diagnostics: &mut Diagnostics) -> Option { - let expanded_ast = match self.get_expander().expand(syntax_ast) { - Ok(ast) => ast, - Err(e) => { - diagnostics.push_error(e, None); - return None; - } - }; - + /// Binds names, applies captures, updates root scopes and pre-allocates global slots. + fn bind_and_update( + &self, + syntax_ast: &SyntaxNode, + diagnostics: &mut Diagnostics, + ) -> Option> { let initial_scopes = self.root_scopes.borrow().clone(); let initial_slot_count = *self.root_slot_count.borrow(); let (bound_ast, captures, final_scopes, final_slot_count) = - match Binder::bind_root(initial_scopes, initial_slot_count, &expanded_ast, diagnostics) { + match Binder::bind_root(initial_scopes, initial_slot_count, syntax_ast, diagnostics) { Ok(res) => res, Err(e) => { diagnostics.push_error(e, None); @@ -466,14 +427,13 @@ impl Environment { } }; - // Update environment state with new bindings from this script + // Update environment state with new bindings *self.root_scopes.borrow_mut() = final_scopes; *self.root_slot_count.borrow_mut() = final_slot_count; let bound_ast = CapturePass::apply(bound_ast, &captures); // Pre-allocate user global slots to prevent out-of-bounds during specialization/optimization. - // root_slot_count includes RTL slots; subtract to get the number of user-only slots. { let mut user = self.user_values.borrow_mut(); let count = *self.root_slot_count.borrow() as usize; @@ -485,20 +445,24 @@ impl Environment { LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut()); - let checker = TypeChecker::new(self.root_types.clone(), Rc::clone(&self.rtl.compiler_hooks)); - let wrapped_ast = if let NodeKind::Lambda { .. } = bound_ast.kind { + Some(bound_ast) + } + + /// Wraps a bound AST in a parameterless lambda (unless it already is one). + fn wrap_as_lambda(&self, bound_ast: Node) -> Node { + if let NodeKind::Lambda { .. } = bound_ast.kind { bound_ast } else { Node { identity: bound_ast.identity.clone(), kind: NodeKind::Lambda { - params: std::rc::Rc::new(Node { + params: Rc::new(Node { identity: bound_ast.identity.clone(), kind: NodeKind::Tuple { elements: vec![] }, ty: (), comments: Rc::from([]), }), - body: std::rc::Rc::new(bound_ast), + body: Rc::new(bound_ast), info: LambdaBinding { upvalues: vec![], positional_count: Some(0), @@ -507,22 +471,24 @@ impl Environment { ty: (), comments: Rc::from([]), } - }; - let typed = checker.check(&wrapped_ast, &[], diagnostics); - self.collect_doc_comments(&typed); - Some(typed) + } } - fn compile_syntax(&self, syntax_ast: SyntaxNode) -> Result { - let mut diagnostics = Diagnostics::new(); + /// Type-checks a bound AST and collects doc comments. + fn type_check(&self, bound_ast: &Node, diagnostics: &mut Diagnostics) -> TypedNode { + let checker = TypeChecker::new(self.root_types.clone(), Rc::clone(&self.rtl.compiler_hooks)); + let typed = checker.check(bound_ast, &[], diagnostics); + self.collect_doc_comments(&typed); + typed + } - let typed_ast_opt = self.compile_pipeline(syntax_ast, &mut diagnostics); - - if diagnostics.has_errors() || typed_ast_opt.is_none() { - return Err(diagnostics.format_errors()); - } - - Ok(typed_ast_opt.unwrap()) + /// Full compilation pipeline: expand → bind → wrap → type-check. + fn compile_pipeline(&self, syntax_ast: SyntaxNode, diagnostics: &mut Diagnostics) -> Option { + let expanded = self.expand(syntax_ast, diagnostics)?; + let bound = self.bind_and_update(&expanded, diagnostics)?; + let wrapped = self.wrap_as_lambda(bound); + let typed = self.type_check(&wrapped, diagnostics); + Some(typed) } /// Allocates a new global slot and registers a value in the fixed RTL scope (scope index 0). diff --git a/src/ast/nodes.rs b/src/ast/nodes.rs index 1a23bb1..f4db57e 100644 --- a/src/ast/nodes.rs +++ b/src/ast/nodes.rs @@ -445,6 +445,11 @@ pub enum NodeKind { Block { exprs: Vec>>, }, + /// Top-level program node. Like Block, but does not introduce a scope — + /// definitions propagate to the enclosing scope. + Program { + exprs: Vec>>, + }, Tuple { elements: Vec>>, }, @@ -514,6 +519,7 @@ impl Clone for NodeKind

{ }, NodeKind::Again { args } => NodeKind::Again { args: args.clone() }, NodeKind::Block { exprs } => NodeKind::Block { exprs: exprs.clone() }, + NodeKind::Program { exprs } => NodeKind::Program { exprs: exprs.clone() }, NodeKind::Tuple { elements } => NodeKind::Tuple { elements: elements.clone() }, NodeKind::Record { fields, layout } => NodeKind::Record { fields: fields.clone(), @@ -570,7 +576,8 @@ impl PartialEq for NodeKind

{ Rc::ptr_eq(ca, cb) && Rc::ptr_eq(aa, ab) } (NodeKind::Again { args: aa }, NodeKind::Again { args: ab }) => Rc::ptr_eq(aa, ab), - (NodeKind::Block { exprs: ea }, NodeKind::Block { exprs: eb }) => { + (NodeKind::Block { exprs: ea }, NodeKind::Block { exprs: eb }) + | (NodeKind::Program { exprs: ea }, NodeKind::Program { exprs: eb }) => { ea.len() == eb.len() && ea.iter().zip(eb.iter()).all(|(a, b)| Rc::ptr_eq(a, b)) } (NodeKind::Tuple { elements: ea }, NodeKind::Tuple { elements: eb }) => { @@ -613,6 +620,7 @@ impl NodeKind

{ NodeKind::Call { .. } => "CALL".to_string(), NodeKind::Again { .. } => "AGAIN".to_string(), NodeKind::Block { .. } => "BLOCK".to_string(), + NodeKind::Program { .. } => "PROGRAM".to_string(), NodeKind::Tuple { elements } => format!("TUPLE({})", elements.len()), NodeKind::Record { fields, .. } => format!("RECORD({})", fields.len()), NodeKind::GetField { field, .. } => format!("GET_FIELD(.{})", field.name()), diff --git a/src/ast/rtl/prelude.myc b/src/ast/rtl/prelude.myc index ce7e9cc..c7ba7f4 100644 --- a/src/ast/rtl/prelude.myc +++ b/src/ast/rtl/prelude.myc @@ -1,33 +1,31 @@ -;; Myc System Library -;; Standard macros and core utilities. - -(do - (def func1 (fn [] (print "Hello World"))) - - (macro while [cond body] - `((fn [] (if ~cond - (do ~body (again)) - ))) - ) - - (macro repeat [var limit body] - `((fn [~var __limit] - (if (< ~var __limit) - (do - ~body - (again (+ ~var 1) __limit) - ) - )) - 0 ~limit) - ) - - ;; Creates a stateful cache (Series) from a stateless stream. - ;; The element type is inferred from the stream's item type. - (macro cache [lookback src] - `(do - (def data (series ~lookback)) - (pipe [~src] (fn [s] (do (push data s)))) - data - ) - ) -) +;; Myc System Library +;; Standard macros and core utilities. + +(def func1 (fn [] (print "Hello World"))) + +(macro while [cond body] + `((fn [] (if ~cond + (do ~body (again)) + ))) +) + +(macro repeat [var limit body] + `((fn [~var __limit] + (if (< ~var __limit) + (do + ~body + (again (+ ~var 1) __limit) + ) + )) + 0 ~limit) +) + +;; Creates a stateful cache (Series) from a stateless stream. +;; The element type is inferred from the stream's item type. +(macro cache [lookback src] + `(do + (def data (series ~lookback)) + (pipe [~src] (fn [s] (do (push data s)))) + data + ) +) diff --git a/src/ast/vm.rs b/src/ast/vm.rs index 93251e1..46ae9da 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -266,22 +266,27 @@ impl VM { self.resolve_tail_calls(observer, result) } - pub fn run_with_observer( - &mut self, - observer: &mut O, - root: &ExecNode, - ) -> Result { - self.stack.clear(); + pub fn eval_in_frame(&mut self, observer: &mut O, root: &ExecNode, stack: Vec) -> Result { + self.stack = stack; self.frames.clear(); - - self.stack.resize(root.ty.stack_size as usize, Value::Void); - self.frames.push(CallFrame { stack_base: 0, closure: None, }); let result = self.eval_internal(observer, root); self.frames.pop(); + result + } + + pub fn run_with_observer( + &mut self, + observer: &mut O, + root: &ExecNode, + ) -> Result { + let mut stack = Vec::new(); + stack.resize(root.ty.stack_size as usize, Value::Void); + + let result = self.eval_in_frame(observer, root, stack); self.resolve_tail_calls(observer, result) } @@ -438,7 +443,7 @@ impl VM { Ok(Value::Void) } } - NodeKind::Block { exprs } => { + NodeKind::Block { exprs } | NodeKind::Program { exprs } => { let mut last = Value::Void; for e in exprs { last = self.eval_internal(obs, e)?; diff --git a/tests/error_recovery.rs b/tests/error_recovery.rs index fea1a84..69a31ac 100644 --- a/tests/error_recovery.rs +++ b/tests/error_recovery.rs @@ -21,7 +21,7 @@ fn test_destructuring_scalar_error() { assert!(result.is_err()); assert_eq!( result.unwrap_err(), - "Statement 'def' cannot be used as an expression.\nCannot destructure type int as a tuple/vector" + "Cannot destructure type int as a tuple/vector" ); }