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);
|
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
@@ -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"))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 {
|
for e in exprs {
|
||||||
self.collect(e);
|
self.collect(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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()),
|
||||||
|
|||||||
+26
-28
@@ -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
@@ -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)?;
|
||||||
|
|||||||
@@ -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"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user