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:
@@ -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<TypedPhase> {
|
||||
f(callee);
|
||||
f(args);
|
||||
}
|
||||
NodeKind::Block { exprs } => {
|
||||
NodeKind::Block { exprs } | NodeKind::Program { exprs } => {
|
||||
for e in exprs {
|
||||
f(e);
|
||||
}
|
||||
|
||||
+98
-47
@@ -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<CompilerScope>, initial_slot_count: u32) -> Self {
|
||||
fn new(
|
||||
identity: Identity,
|
||||
initial_scopes: Vec<CompilerScope>,
|
||||
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<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();
|
||||
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<CompilerScope>,
|
||||
initial_slot_count: u32,
|
||||
) -> Self {
|
||||
pub fn new(initial_scopes: Vec<CompilerScope>, 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<Address<VirtualId>> {
|
||||
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<BoundPhase>) -> Option<u32> {
|
||||
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"))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -22,7 +22,7 @@ where
|
||||
|
||||
fn visit(&mut self, node: &Node<P>) {
|
||||
match &node.kind {
|
||||
NodeKind::Block { exprs } => {
|
||||
NodeKind::Block { exprs } | NodeKind::Program { exprs } => {
|
||||
for expr in exprs {
|
||||
self.visit(expr);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ impl UsageInfo {
|
||||
}
|
||||
}
|
||||
}
|
||||
NodeKind::Block { exprs } => {
|
||||
NodeKind::Block { exprs } | NodeKind::Program { exprs } => {
|
||||
for e in exprs {
|
||||
self.collect(e);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
+78
-112
@@ -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<Rc<SyntaxNode>> = 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<SyntaxNode> {
|
||||
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<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> {
|
||||
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<Node<BoundPhase>> {
|
||||
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<BoundPhase>) -> Node<BoundPhase> {
|
||||
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<TypedNode, String> {
|
||||
let mut diagnostics = Diagnostics::new();
|
||||
/// Type-checks a bound AST and collects doc comments.
|
||||
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);
|
||||
|
||||
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<TypedNode> {
|
||||
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).
|
||||
|
||||
+9
-1
@@ -445,6 +445,11 @@ pub enum NodeKind<P: CompilerPhase = SyntaxPhase> {
|
||||
Block {
|
||||
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 {
|
||||
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::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<P: CompilerPhase> PartialEq for NodeKind<P> {
|
||||
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<P: CompilerPhase> NodeKind<P> {
|
||||
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()),
|
||||
|
||||
+31
-33
@@ -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
|
||||
)
|
||||
)
|
||||
|
||||
+15
-10
@@ -266,22 +266,27 @@ impl VM {
|
||||
self.resolve_tail_calls(observer, result)
|
||||
}
|
||||
|
||||
pub fn run_with_observer<O: VMObserver>(
|
||||
&mut self,
|
||||
observer: &mut O,
|
||||
root: &ExecNode,
|
||||
) -> Result<Value, String> {
|
||||
self.stack.clear();
|
||||
pub fn eval_in_frame<O: VMObserver>(&mut self, observer: &mut O, root: &ExecNode, stack: Vec<Value>) -> Result<Value, String> {
|
||||
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<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)
|
||||
}
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
Reference in New Issue
Block a user