Add Program node kind

The `Program` node kind is introduced to represent top-level code
blocks, distinguishing them from `Block` nodes which introduce new
scopes. This commit updates various compiler passes to handle the
`Program` node, ensuring correct AST traversal and processing.

The `Program` node is similar to `Block`, but its definitions propagate
to the enclosing scope, unlike `Block` which creates a new, isolated
scope. This distinction is important for how variables and functions are
resolved within the compiled code.
This commit is contained in:
2026-03-31 13:53:51 +02:00
parent 35f5ea0db3
commit 02ea2f0d80
15 changed files with 337 additions and 209 deletions
+12 -2
View File
@@ -53,7 +53,7 @@ impl<'a> Analyzer<'a> {
} }
self.collect_globals(value); self.collect_globals(value);
} }
NodeKind::Block { exprs } => { NodeKind::Block { exprs } | NodeKind::Program { exprs } => {
for e in exprs { for e in exprs {
self.collect_globals(e); self.collect_globals(e);
} }
@@ -241,6 +241,16 @@ impl<'a> Analyzer<'a> {
} }
(NodeKind::Block { exprs: new_exprs }, p) (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 } => { NodeKind::Tuple { elements } => {
let mut new_elements = Vec::with_capacity(elements.len()); let mut new_elements = Vec::with_capacity(elements.len());
@@ -350,7 +360,7 @@ impl NodeExt for NodeKind<TypedPhase> {
f(callee); f(callee);
f(args); f(args);
} }
NodeKind::Block { exprs } => { NodeKind::Block { exprs } | NodeKind::Program { exprs } => {
for e in exprs { for e in exprs {
f(e); f(e);
} }
+98 -47
View File
@@ -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::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::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
@@ -43,7 +44,11 @@ struct FunctionCompiler {
} }
impl FunctionCompiler { impl FunctionCompiler {
fn new(identity: Identity, initial_scopes: Vec<CompilerScope>, initial_slot_count: u32) -> Self { fn new(
identity: Identity,
initial_scopes: Vec<CompilerScope>,
initial_slot_count: u32,
) -> Self {
Self { Self {
identity, identity,
scopes: if initial_scopes.is_empty() { scopes: if initial_scopes.is_empty() {
@@ -66,7 +71,12 @@ impl FunctionCompiler {
} }
} }
fn define_variable(&mut self, name: &Symbol, identity: Identity) -> Result<Address<VirtualId>, String> { fn define_variable(
&mut self,
name: &Symbol,
identity: Identity,
as_global: bool,
) -> Result<Address<VirtualId>, String> {
let current_scope = self.scopes.last_mut().unwrap(); let current_scope = self.scopes.last_mut().unwrap();
if current_scope.locals.contains_key(name) { if current_scope.locals.contains_key(name) {
return Err(format!( return Err(format!(
@@ -76,17 +86,22 @@ impl FunctionCompiler {
} }
let slot = VirtualId(self.slot_count); let slot = VirtualId(self.slot_count);
let addr = if as_global {
Address::Global(GlobalIdx(slot.0))
} else {
Address::Local(slot)
};
current_scope.locals.insert( current_scope.locals.insert(
name.clone(), name.clone(),
LocalInfo { LocalInfo {
addr: Address::Local(slot), addr,
identity, identity,
_ty: StaticType::Any, _ty: StaticType::Any,
purity: Purity::Impure, purity: Purity::Impure,
}, },
); );
self.slot_count += 1; self.slot_count += 1;
Ok(Address::Local(slot)) Ok(addr)
} }
fn resolve_local(&self, sym: &Symbol) -> Option<(LocalInfo, usize)> { fn resolve_local(&self, sym: &Symbol) -> Option<(LocalInfo, usize)> {
@@ -127,19 +142,13 @@ pub struct Binder {
} }
impl Binder { impl Binder {
pub fn new( pub fn new(initial_scopes: Vec<CompilerScope>, initial_slot_count: u32) -> Self {
initial_scopes: Vec<CompilerScope>,
initial_slot_count: u32,
) -> Self {
let mut binder = Self { let mut binder = Self {
functions: Vec::new(), functions: Vec::new(),
capture_map: HashMap::new(), capture_map: HashMap::new(),
}; };
binder.functions.push(FunctionCompiler::new( binder.functions.push(FunctionCompiler::new(
NodeIdentity::new(SourceLocation { NodeIdentity::new(SourceLocation { line: 0, col: 0 }),
line: 0,
col: 0,
}),
initial_scopes, initial_scopes,
initial_slot_count, initial_slot_count,
)); ));
@@ -177,8 +186,9 @@ impl Binder {
_kind: DeclarationKind, _kind: DeclarationKind,
diag: &mut Diagnostics, diag: &mut Diagnostics,
) -> Option<Address<VirtualId>> { ) -> Option<Address<VirtualId>> {
let as_global = self.functions.len() == 1;
let current_fn = self.functions.last_mut().unwrap(); 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), Ok(addr) => Some(addr),
Err(e) => { Err(e) => {
diag.push_error(e, None); diag.push_error(e, None);
@@ -245,14 +255,15 @@ impl Binder {
) )
} }
SyntaxKind::Def { pattern: target, value, .. } => { SyntaxKind::Def {
if ctx == ExprContext::Expression { pattern: target,
diag.push_error( value,
"Statement 'def' cannot be used as an expression.", ..
Some(node.identity.clone()), } => {
); if let SyntaxKind::Identifier {
} symbol: ref name, ..
if let SyntaxKind::Identifier { symbol: ref name, .. } = target.kind { } = target.kind
{
let addr_opt = self.declare_variable( let addr_opt = self.declare_variable(
name, name,
node.identity.clone(), node.identity.clone(),
@@ -287,7 +298,8 @@ impl Binder {
} }
} else { } else {
let val_node = self.bind(value, ExprContext::Expression, diag); 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( self.make_node(
node.identity.clone(), node.identity.clone(),
@@ -343,17 +355,19 @@ impl Binder {
self.functions self.functions
.push(FunctionCompiler::new(identity.clone(), vec![], 0)); .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 body_bound = self.bind(body.as_ref(), ExprContext::Expression, diag);
let compiled_fn = self.functions.pop().unwrap(); let compiled_fn = self.functions.pop().unwrap();
fn count_params(node: &Node<BoundPhase>) -> Option<u32> { fn count_params(node: &Node<BoundPhase>) -> Option<u32> {
match &node.kind { match &node.kind {
NodeKind::Identifier { NodeKind::Identifier {
binding: IdentifierBinding::Declaration { binding:
kind: DeclarationKind::Parameter, IdentifierBinding::Declaration {
.. kind: DeclarationKind::Parameter,
}, ..
},
.. ..
} => Some(1), } => Some(1),
NodeKind::Tuple { elements } => { 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 } => { SyntaxKind::Tuple { elements } => {
let mut bound_elems = Vec::new(); let mut bound_elems = Vec::new();
for e in elements { for e in elements {
@@ -452,9 +482,7 @@ impl Binder {
let key_node = self.bind(k, ExprContext::Expression, diag); let key_node = self.bind(k, ExprContext::Expression, diag);
let val_node = self.bind(v, ExprContext::Expression, diag); let val_node = self.bind(v, ExprContext::Expression, diag);
if let NodeKind::Constant(Value::Keyword(kw)) = if let NodeKind::Constant(Value::Keyword(kw)) = &key_node.kind {
&key_node.kind
{
layout_fields.push((*kw, StaticType::Any)); layout_fields.push((*kw, StaticType::Any));
} else { } else {
diag.push_error( 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); let bound_expanded = self.bind(expanded.as_ref(), ctx, diag);
self.make_node( self.make_node(
node.identity.clone(), node.identity.clone(),
@@ -495,10 +526,16 @@ impl Binder {
| SyntaxKind::Splice(_) | SyntaxKind::Splice(_)
| SyntaxKind::MacroDecl { .. } => { | SyntaxKind::MacroDecl { .. } => {
diag.push_error( diag.push_error(
format!( {
"Macro construct {:?} found in Binder. Macros must be expanded before binding.", let label = match &node.kind {
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()), Some(node.identity.clone()),
); );
self.make_node(node.identity.clone(), NodeKind::Error) self.make_node(node.identity.clone(), NodeKind::Error)
@@ -691,13 +728,18 @@ mod tests {
let syntax = parser.parse_expression(); let syntax = parser.parse_expression();
let mut diagnostics = Diagnostics::new(); 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::Lambda { body, .. } = &bound.kind {
if let NodeKind::Block { exprs } = &body.kind { if let NodeKind::Block { exprs } = &body.kind {
let x_decl = &exprs[0]; let x_decl = &exprs[0];
if let NodeKind::Def { pattern, .. } = &x_decl.kind { 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!(matches!(addr, Address::Local(_)));
assert!( assert!(
captures.contains_key(&x_decl.identity), captures.contains_key(&x_decl.identity),
@@ -724,13 +766,18 @@ mod tests {
let syntax = parser.parse_expression(); let syntax = parser.parse_expression();
let mut diagnostics = Diagnostics::new(); 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::Lambda { body, .. } = &bound.kind {
if let NodeKind::Block { exprs } = &body.kind { if let NodeKind::Block { exprs } = &body.kind {
let x_decl = &exprs[0]; let x_decl = &exprs[0];
if let NodeKind::Def { pattern, .. } = &x_decl.kind { 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!(matches!(addr, Address::Local(_)));
assert!( assert!(
!captures.contains_key(&x_decl.identity), !captures.contains_key(&x_decl.identity),
@@ -760,7 +807,11 @@ mod tests {
let _ = Binder::bind_root(vec![], 0, &syntax, &mut diagnostics); let _ = Binder::bind_root(vec![], 0, &syntax, &mut diagnostics);
assert!(diagnostics.has_errors()); 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"))
);
} }
} }
+8
View File
@@ -190,6 +190,14 @@ impl Dumper {
} }
self.indent -= 1; 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 } => { NodeKind::Tuple { elements } => {
self.log("Tuple", node); self.log("Tuple", node);
+9
View File
@@ -148,6 +148,15 @@ fn emit_kind(node: &SyntaxNode, indent: usize, out: &mut String) {
out.push(')'); 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 } => { NodeKind::Tuple { elements } => {
out.push('['); out.push('[');
for (i, el) in elements.iter().enumerate() { for (i, el) in elements.iter().enumerate() {
+1 -1
View File
@@ -22,7 +22,7 @@ where
fn visit(&mut self, node: &Node<P>) { fn visit(&mut self, node: &Node<P>) {
match &node.kind { match &node.kind {
NodeKind::Block { exprs } => { NodeKind::Block { exprs } | NodeKind::Program { exprs } => {
for expr in exprs { for expr in exprs {
self.visit(expr); self.visit(expr);
} }
+26
View File
@@ -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 { NodeKind::Lambda {
params, params,
body, body,
+30 -1
View File
@@ -288,7 +288,36 @@ impl<E: MacroEvaluator> MacroExpander<E> {
}) })
} }
_ => 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),
} }
} }
+1 -1
View File
@@ -122,7 +122,7 @@ impl UsageInfo {
} }
} }
} }
NodeKind::Block { exprs } => { NodeKind::Block { exprs } | NodeKind::Program { exprs } => {
for e in exprs { for e in exprs {
self.collect(e); self.collect(e);
} }
+12
View File
@@ -497,6 +497,18 @@ impl TypeChecker {
(NodeKind::Block { exprs: typed_exprs }, last_ty) (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 { NodeKind::Lambda {
params, params,
@@ -90,6 +90,12 @@ impl TypeChecker {
.map(|e| Rc::new(self.finalize_node((*e).clone(), subst))) .map(|e| Rc::new(self.finalize_node((*e).clone(), subst)))
.collect(), .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 { NodeKind::Tuple { elements } => NodeKind::Tuple {
elements: elements elements: elements
.into_iter() .into_iter()
+78 -112
View File
@@ -2,10 +2,10 @@ use crate::ast::compiler::analyzer::Analyzer;
use crate::ast::compiler::binder::{Binder, CompilerScope, LocalInfo}; use crate::ast::compiler::binder::{Binder, CompilerScope, LocalInfo};
use crate::ast::compiler::call_hooks::RtlCompilerHook; use crate::ast::compiler::call_hooks::RtlCompilerHook;
use crate::ast::compiler::{CapturePass, TypeChecker, TypedNode}; 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::parser::Parser;
use crate::ast::closure::Closure; 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::cell::RefCell;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@@ -334,21 +334,23 @@ impl Environment {
/// Used to pre-load all dependencies of a script before compiling it. /// 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> { 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 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<Rc<SyntaxNode>> = Vec::new();
// 1. Always load the embedded system library first as a virtual module // 1. Always load the embedded system library first as a virtual module
let system_path = PathBuf::from(SYSTEM_LIB_PATH); let system_path = PathBuf::from(SYSTEM_LIB_PATH);
if !self.loaded_modules.borrow().contains(&system_path) { if !self.loaded_modules.borrow().contains(&system_path) {
self.loaded_modules.borrow_mut().insert(system_path.clone()); self.loaded_modules.borrow_mut().insert(system_path.clone());
let mut parser = Parser::new(SYSTEM_LIB_SOURCE); let mut parser = Parser::new(SYSTEM_LIB_SOURCE);
let syntax_ast = parser.parse_expression(); let forms = parser.parse_program();
if parser.diagnostics.has_errors() { if parser.diagnostics.has_errors() {
return Err(format!( return Err(format!(
"Failed to parse embedded system library:\n{}", "Failed to parse embedded system library:\n{}",
parser.diagnostics.items[0].message 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) // 2. Collect user dependencies (file I/O + parse, topological order)
@@ -356,109 +358,68 @@ impl Environment {
Rc::clone(&self.search_paths), Rc::clone(&self.search_paths),
Rc::clone(&self.loaded_modules), Rc::clone(&self.loaded_modules),
); );
files.extend(loader.collect_dependency_files(source, base_path)?); for (_, syntax_ast) in loader.collect_dependency_files(source, base_path)? {
all_forms.push(Rc::new(syntax_ast));
// Pass 1: Discovery (globals and macros)
for (_, syntax_ast) in &files {
self.discover_globals(syntax_ast);
} }
// Pass 2: Compilation and initialization // Compile and initialize all forms as a single program (no scope boundary)
for (path, syntax_ast) in files { if !all_forms.is_empty() {
let typed_ast = self.compile_syntax(syntax_ast).map_err(|e: String| { let block = SyntaxNode {
format!("Compilation error in {}:\n{}", path.display(), e) identity: NodeIdentity::anonymous(),
})?; kind: SyntaxKind::Program { exprs: all_forms },
self.run_script_compiled(typed_ast).map_err(|e: String| { ty: (),
format!("Initialization error in {}:\n{}", path.display(), e) 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(()) Ok(())
} }
fn discover_globals(&self, node: &SyntaxNode) { /// Expands macros in a syntax AST.
match &node.kind { fn expand(&self, syntax_ast: SyntaxNode, diagnostics: &mut Diagnostics) -> Option<SyntaxNode> {
SyntaxKind::Def { pattern, .. } => { match self.get_expander().expand(syntax_ast) {
let mut root_scopes = self.root_scopes.borrow_mut(); Ok(ast) => Some(ast),
let last_idx = root_scopes.len() - 1; Err(e) => {
let current_scope = &mut root_scopes[last_idx]; diagnostics.push_error(e, None);
None
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);
} }
SyntaxKind::MacroDecl { name, params, body } => {
let mut registry = self.macro_registry.borrow_mut();
fn extract_names(node: &SyntaxNode) -> Vec<Rc<str>> {
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<TypedNode> { /// Binds names, applies captures, updates root scopes and pre-allocates global slots.
let expanded_ast = match self.get_expander().expand(syntax_ast) { fn bind_and_update(
Ok(ast) => ast, &self,
Err(e) => { syntax_ast: &SyntaxNode,
diagnostics.push_error(e, None); diagnostics: &mut Diagnostics,
return None; ) -> Option<Node<BoundPhase>> {
}
};
let initial_scopes = self.root_scopes.borrow().clone(); let initial_scopes = self.root_scopes.borrow().clone();
let initial_slot_count = *self.root_slot_count.borrow(); let initial_slot_count = *self.root_slot_count.borrow();
let (bound_ast, captures, final_scopes, final_slot_count) = 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, Ok(res) => res,
Err(e) => { Err(e) => {
diagnostics.push_error(e, None); 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_scopes.borrow_mut() = final_scopes;
*self.root_slot_count.borrow_mut() = final_slot_count; *self.root_slot_count.borrow_mut() = final_slot_count;
let bound_ast = CapturePass::apply(bound_ast, &captures); let bound_ast = CapturePass::apply(bound_ast, &captures);
// Pre-allocate user global slots to prevent out-of-bounds during specialization/optimization. // 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 mut user = self.user_values.borrow_mut();
let count = *self.root_slot_count.borrow() as usize; 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()); LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut());
let checker = TypeChecker::new(self.root_types.clone(), Rc::clone(&self.rtl.compiler_hooks)); Some(bound_ast)
let wrapped_ast = if let NodeKind::Lambda { .. } = bound_ast.kind { }
/// Wraps a bound AST in a parameterless lambda (unless it already is one).
fn wrap_as_lambda(&self, bound_ast: Node<BoundPhase>) -> Node<BoundPhase> {
if let NodeKind::Lambda { .. } = bound_ast.kind {
bound_ast bound_ast
} else { } else {
Node { Node {
identity: bound_ast.identity.clone(), identity: bound_ast.identity.clone(),
kind: NodeKind::Lambda { kind: NodeKind::Lambda {
params: std::rc::Rc::new(Node { params: Rc::new(Node {
identity: bound_ast.identity.clone(), identity: bound_ast.identity.clone(),
kind: NodeKind::Tuple { elements: vec![] }, kind: NodeKind::Tuple { elements: vec![] },
ty: (), ty: (),
comments: Rc::from([]), comments: Rc::from([]),
}), }),
body: std::rc::Rc::new(bound_ast), body: Rc::new(bound_ast),
info: LambdaBinding { info: LambdaBinding {
upvalues: vec![], upvalues: vec![],
positional_count: Some(0), positional_count: Some(0),
@@ -507,22 +471,24 @@ impl Environment {
ty: (), ty: (),
comments: Rc::from([]), 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<TypedNode, String> { /// Type-checks a bound AST and collects doc comments.
let mut diagnostics = Diagnostics::new(); fn type_check(&self, bound_ast: &Node<BoundPhase>, 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); /// Full compilation pipeline: expand → bind → wrap → type-check.
fn compile_pipeline(&self, syntax_ast: SyntaxNode, diagnostics: &mut Diagnostics) -> Option<TypedNode> {
if diagnostics.has_errors() || typed_ast_opt.is_none() { let expanded = self.expand(syntax_ast, diagnostics)?;
return Err(diagnostics.format_errors()); let bound = self.bind_and_update(&expanded, diagnostics)?;
} let wrapped = self.wrap_as_lambda(bound);
let typed = self.type_check(&wrapped, diagnostics);
Ok(typed_ast_opt.unwrap()) Some(typed)
} }
/// Allocates a new global slot and registers a value in the fixed RTL scope (scope index 0). /// Allocates a new global slot and registers a value in the fixed RTL scope (scope index 0).
+9 -1
View File
@@ -445,6 +445,11 @@ pub enum NodeKind<P: CompilerPhase = SyntaxPhase> {
Block { Block {
exprs: Vec<Rc<Node<P>>>, exprs: Vec<Rc<Node<P>>>,
}, },
/// Top-level program node. Like Block, but does not introduce a scope —
/// definitions propagate to the enclosing scope.
Program {
exprs: Vec<Rc<Node<P>>>,
},
Tuple { Tuple {
elements: Vec<Rc<Node<P>>>, elements: Vec<Rc<Node<P>>>,
}, },
@@ -514,6 +519,7 @@ impl<P: CompilerPhase> Clone for NodeKind<P> {
}, },
NodeKind::Again { args } => NodeKind::Again { args: args.clone() }, NodeKind::Again { args } => NodeKind::Again { args: args.clone() },
NodeKind::Block { exprs } => NodeKind::Block { exprs: exprs.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::Tuple { elements } => NodeKind::Tuple { elements: elements.clone() },
NodeKind::Record { fields, layout } => NodeKind::Record { NodeKind::Record { fields, layout } => NodeKind::Record {
fields: fields.clone(), fields: fields.clone(),
@@ -570,7 +576,8 @@ impl<P: CompilerPhase> PartialEq for NodeKind<P> {
Rc::ptr_eq(ca, cb) && Rc::ptr_eq(aa, ab) Rc::ptr_eq(ca, cb) && Rc::ptr_eq(aa, ab)
} }
(NodeKind::Again { args: aa }, NodeKind::Again { args: ab }) => 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)) 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 }) => { (NodeKind::Tuple { elements: ea }, NodeKind::Tuple { elements: eb }) => {
@@ -613,6 +620,7 @@ impl<P: CompilerPhase> NodeKind<P> {
NodeKind::Call { .. } => "CALL".to_string(), NodeKind::Call { .. } => "CALL".to_string(),
NodeKind::Again { .. } => "AGAIN".to_string(), NodeKind::Again { .. } => "AGAIN".to_string(),
NodeKind::Block { .. } => "BLOCK".to_string(), NodeKind::Block { .. } => "BLOCK".to_string(),
NodeKind::Program { .. } => "PROGRAM".to_string(),
NodeKind::Tuple { elements } => format!("TUPLE({})", elements.len()), NodeKind::Tuple { elements } => format!("TUPLE({})", elements.len()),
NodeKind::Record { fields, .. } => format!("RECORD({})", fields.len()), NodeKind::Record { fields, .. } => format!("RECORD({})", fields.len()),
NodeKind::GetField { field, .. } => format!("GET_FIELD(.{})", field.name()), NodeKind::GetField { field, .. } => format!("GET_FIELD(.{})", field.name()),
+31 -33
View File
@@ -1,33 +1,31 @@
;; Myc System Library ;; Myc System Library
;; Standard macros and core utilities. ;; Standard macros and core utilities.
(do (def func1 (fn [] (print "Hello World")))
(def func1 (fn [] (print "Hello World")))
(macro while [cond body]
(macro while [cond body] `((fn [] (if ~cond
`((fn [] (if ~cond (do ~body (again))
(do ~body (again)) )))
))) )
)
(macro repeat [var limit body]
(macro repeat [var limit body] `((fn [~var __limit]
`((fn [~var __limit] (if (< ~var __limit)
(if (< ~var __limit) (do
(do ~body
~body (again (+ ~var 1) __limit)
(again (+ ~var 1) __limit) )
) ))
)) 0 ~limit)
0 ~limit) )
)
;; Creates a stateful cache (Series) from a stateless stream.
;; Creates a stateful cache (Series) from a stateless stream. ;; The element type is inferred from the stream's item type.
;; The element type is inferred from the stream's item type. (macro cache [lookback src]
(macro cache [lookback src] `(do
`(do (def data (series ~lookback))
(def data (series ~lookback)) (pipe [~src] (fn [s] (do (push data s))))
(pipe [~src] (fn [s] (do (push data s)))) data
data )
) )
)
)
+15 -10
View File
@@ -266,22 +266,27 @@ impl VM {
self.resolve_tail_calls(observer, result) self.resolve_tail_calls(observer, result)
} }
pub fn run_with_observer<O: VMObserver>( pub fn eval_in_frame<O: VMObserver>(&mut self, observer: &mut O, root: &ExecNode, stack: Vec<Value>) -> Result<Value, String> {
&mut self, self.stack = stack;
observer: &mut O,
root: &ExecNode,
) -> Result<Value, String> {
self.stack.clear();
self.frames.clear(); self.frames.clear();
self.stack.resize(root.ty.stack_size as usize, Value::Void);
self.frames.push(CallFrame { self.frames.push(CallFrame {
stack_base: 0, stack_base: 0,
closure: None, closure: None,
}); });
let result = self.eval_internal(observer, root); let result = self.eval_internal(observer, root);
self.frames.pop(); self.frames.pop();
result
}
pub fn run_with_observer<O: VMObserver>(
&mut self,
observer: &mut O,
root: &ExecNode,
) -> Result<Value, String> {
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) self.resolve_tail_calls(observer, result)
} }
@@ -438,7 +443,7 @@ impl VM {
Ok(Value::Void) Ok(Value::Void)
} }
} }
NodeKind::Block { exprs } => { NodeKind::Block { exprs } | NodeKind::Program { exprs } => {
let mut last = Value::Void; let mut last = Value::Void;
for e in exprs { for e in exprs {
last = self.eval_internal(obs, e)?; last = self.eval_internal(obs, e)?;
+1 -1
View File
@@ -21,7 +21,7 @@ fn test_destructuring_scalar_error() {
assert!(result.is_err()); assert!(result.is_err());
assert_eq!( assert_eq!(
result.unwrap_err(), 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"
); );
} }