use std::collections::BTreeMap;
use std::rc::Rc;
use harn_lexer::StringSegment;
use harn_parser::{BindingPattern, Node, ParallelMode, SNode, TypedParam};
use crate::chunk::{Chunk, CompiledFunction, Constant, Op};
use crate::schema;
use crate::value::VmValue;
#[derive(Debug)]
pub struct CompileError {
pub message: String,
pub line: u32,
}
impl std::fmt::Display for CompileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Compile error at line {}: {}", self.line, self.message)
}
}
impl std::error::Error for CompileError {}
#[derive(Clone, Debug)]
enum FinallyEntry {
Finally(Vec<SNode>),
CatchBarrier,
}
struct LoopContext {
start_offset: usize,
break_patches: Vec<usize>,
has_iterator: bool,
handler_depth: usize,
finally_depth: usize,
scope_depth: usize,
}
pub struct Compiler {
chunk: Chunk,
line: u32,
column: u32,
enum_names: std::collections::HashSet<String>,
interface_methods: std::collections::HashMap<String, Vec<String>>,
loop_stack: Vec<LoopContext>,
handler_depth: usize,
finally_bodies: Vec<FinallyEntry>,
temp_counter: usize,
scope_depth: usize,
}
impl Compiler {
pub fn new() -> Self {
Self {
chunk: Chunk::new(),
line: 1,
column: 1,
enum_names: std::collections::HashSet::new(),
interface_methods: std::collections::HashMap::new(),
loop_stack: Vec::new(),
handler_depth: 0,
finally_bodies: Vec::new(),
temp_counter: 0,
scope_depth: 0,
}
}
pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
Self::collect_enum_names(program, &mut self.enum_names);
self.enum_names.insert("Result".to_string());
Self::collect_interface_methods(program, &mut self.interface_methods);
for sn in program {
match &sn.node {
Node::ImportDecl { .. } | Node::SelectiveImport { .. } => {
self.compile_node(sn)?;
}
_ => {}
}
}
let main = program
.iter()
.find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == "default"))
.or_else(|| {
program
.iter()
.find(|sn| matches!(&sn.node, Node::Pipeline { .. }))
});
if let Some(sn) = main {
self.compile_top_level_declarations(program)?;
if let Node::Pipeline { body, extends, .. } = &sn.node {
if let Some(parent_name) = extends {
self.compile_parent_pipeline(program, parent_name)?;
}
self.compile_block(body)?;
}
} else {
let top_level: Vec<&SNode> = program
.iter()
.filter(|sn| {
!matches!(
&sn.node,
Node::ImportDecl { .. } | Node::SelectiveImport { .. }
)
})
.collect();
for sn in &top_level {
self.compile_node(sn)?;
if Self::produces_value(&sn.node) {
self.chunk.emit(Op::Pop, self.line);
}
}
}
for fb in self.all_pending_finallys() {
self.compile_finally_inline(&fb)?;
}
self.chunk.emit(Op::Nil, self.line);
self.chunk.emit(Op::Return, self.line);
Ok(self.chunk)
}
pub fn compile_named(
mut self,
program: &[SNode],
pipeline_name: &str,
) -> Result<Chunk, CompileError> {
Self::collect_enum_names(program, &mut self.enum_names);
Self::collect_interface_methods(program, &mut self.interface_methods);
for sn in program {
if matches!(
&sn.node,
Node::ImportDecl { .. } | Node::SelectiveImport { .. }
) {
self.compile_node(sn)?;
}
}
let target = program
.iter()
.find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == pipeline_name));
if let Some(sn) = target {
self.compile_top_level_declarations(program)?;
if let Node::Pipeline { body, extends, .. } = &sn.node {
if let Some(parent_name) = extends {
self.compile_parent_pipeline(program, parent_name)?;
}
self.compile_block(body)?;
}
}
for fb in self.all_pending_finallys() {
self.compile_finally_inline(&fb)?;
}
self.chunk.emit(Op::Nil, self.line);
self.chunk.emit(Op::Return, self.line);
Ok(self.chunk)
}
fn compile_parent_pipeline(
&mut self,
program: &[SNode],
parent_name: &str,
) -> Result<(), CompileError> {
let parent = program
.iter()
.find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == parent_name));
if let Some(sn) = parent {
if let Node::Pipeline { body, extends, .. } = &sn.node {
if let Some(grandparent) = extends {
self.compile_parent_pipeline(program, grandparent)?;
}
for stmt in body {
self.compile_node(stmt)?;
if Self::produces_value(&stmt.node) {
self.chunk.emit(Op::Pop, self.line);
}
}
}
}
Ok(())
}
fn emit_default_preamble(&mut self, params: &[TypedParam]) -> Result<(), CompileError> {
for (i, param) in params.iter().enumerate() {
if let Some(default_expr) = ¶m.default_value {
self.chunk.emit(Op::GetArgc, self.line);
let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
self.chunk.emit(Op::GreaterEqual, self.line);
let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
self.chunk.emit(Op::Pop, self.line);
self.compile_node(default_expr)?;
let name_idx = self
.chunk
.add_constant(Constant::String(param.name.clone()));
self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
self.chunk.patch_jump(skip_jump);
self.chunk.emit(Op::Pop, self.line);
self.chunk.patch_jump(end_jump);
}
}
Ok(())
}
fn emit_type_checks(&mut self, params: &[TypedParam]) {
for param in params {
if let Some(type_expr) = ¶m.type_expr {
if let harn_parser::TypeExpr::Named(name) = type_expr {
if let Some(methods) = self.interface_methods.get(name) {
let fn_idx = self
.chunk
.add_constant(Constant::String("__assert_interface".into()));
self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
let var_idx = self
.chunk
.add_constant(Constant::String(param.name.clone()));
self.chunk.emit_u16(Op::GetVar, var_idx, self.line);
let name_idx = self
.chunk
.add_constant(Constant::String(param.name.clone()));
self.chunk.emit_u16(Op::Constant, name_idx, self.line);
let iface_idx = self.chunk.add_constant(Constant::String(name.clone()));
self.chunk.emit_u16(Op::Constant, iface_idx, self.line);
let methods_str = methods.join(",");
let methods_idx = self.chunk.add_constant(Constant::String(methods_str));
self.chunk.emit_u16(Op::Constant, methods_idx, self.line);
self.chunk.emit_u8(Op::Call, 4, self.line);
self.chunk.emit(Op::Pop, self.line);
continue;
}
}
if let Some(schema) = Self::type_expr_to_schema_value(type_expr) {
let fn_idx = self
.chunk
.add_constant(Constant::String("__assert_schema".into()));
self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
let var_idx = self
.chunk
.add_constant(Constant::String(param.name.clone()));
self.chunk.emit_u16(Op::GetVar, var_idx, self.line);
let name_idx = self
.chunk
.add_constant(Constant::String(param.name.clone()));
self.chunk.emit_u16(Op::Constant, name_idx, self.line);
self.emit_vm_value_literal(&schema);
self.chunk.emit_u8(Op::Call, 3, self.line);
self.chunk.emit(Op::Pop, self.line);
}
}
}
}
fn type_expr_to_schema_value(type_expr: &harn_parser::TypeExpr) -> Option<VmValue> {
match type_expr {
harn_parser::TypeExpr::Named(name) => match name.as_str() {
"int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
| "closure" => Some(VmValue::Dict(Rc::new(BTreeMap::from([(
"type".to_string(),
VmValue::String(Rc::from(name.as_str())),
)])))),
_ => None,
},
harn_parser::TypeExpr::Shape(fields) => {
let mut properties = BTreeMap::new();
let mut required = Vec::new();
for field in fields {
let field_schema = Self::type_expr_to_schema_value(&field.type_expr)?;
properties.insert(field.name.clone(), field_schema);
if !field.optional {
required.push(VmValue::String(Rc::from(field.name.as_str())));
}
}
let mut out = BTreeMap::new();
out.insert("type".to_string(), VmValue::String(Rc::from("dict")));
out.insert("properties".to_string(), VmValue::Dict(Rc::new(properties)));
if !required.is_empty() {
out.insert("required".to_string(), VmValue::List(Rc::new(required)));
}
Some(VmValue::Dict(Rc::new(out)))
}
harn_parser::TypeExpr::List(inner) => {
let mut out = BTreeMap::new();
out.insert("type".to_string(), VmValue::String(Rc::from("list")));
if let Some(item_schema) = Self::type_expr_to_schema_value(inner) {
out.insert("items".to_string(), item_schema);
}
Some(VmValue::Dict(Rc::new(out)))
}
harn_parser::TypeExpr::DictType(key, value) => {
let mut out = BTreeMap::new();
out.insert("type".to_string(), VmValue::String(Rc::from("dict")));
if matches!(key.as_ref(), harn_parser::TypeExpr::Named(name) if name == "string") {
if let Some(value_schema) = Self::type_expr_to_schema_value(value) {
out.insert("additional_properties".to_string(), value_schema);
}
}
Some(VmValue::Dict(Rc::new(out)))
}
harn_parser::TypeExpr::Union(members) => {
let branches = members
.iter()
.filter_map(Self::type_expr_to_schema_value)
.collect::<Vec<_>>();
if branches.is_empty() {
None
} else {
Some(VmValue::Dict(Rc::new(BTreeMap::from([(
"union".to_string(),
VmValue::List(Rc::new(branches)),
)]))))
}
}
harn_parser::TypeExpr::FnType { .. } => {
Some(VmValue::Dict(Rc::new(BTreeMap::from([(
"type".to_string(),
VmValue::String(Rc::from("closure")),
)]))))
}
harn_parser::TypeExpr::Applied { .. } => None,
harn_parser::TypeExpr::Iter(_) => None,
harn_parser::TypeExpr::Never => None,
}
}
fn emit_vm_value_literal(&mut self, value: &VmValue) {
match value {
VmValue::String(text) => {
let idx = self.chunk.add_constant(Constant::String(text.to_string()));
self.chunk.emit_u16(Op::Constant, idx, self.line);
}
VmValue::Int(number) => {
let idx = self.chunk.add_constant(Constant::Int(*number));
self.chunk.emit_u16(Op::Constant, idx, self.line);
}
VmValue::Float(number) => {
let idx = self.chunk.add_constant(Constant::Float(*number));
self.chunk.emit_u16(Op::Constant, idx, self.line);
}
VmValue::Bool(value) => {
let idx = self.chunk.add_constant(Constant::Bool(*value));
self.chunk.emit_u16(Op::Constant, idx, self.line);
}
VmValue::Nil => self.chunk.emit(Op::Nil, self.line),
VmValue::List(items) => {
for item in items.iter() {
self.emit_vm_value_literal(item);
}
self.chunk
.emit_u16(Op::BuildList, items.len() as u16, self.line);
}
VmValue::Dict(entries) => {
for (key, item) in entries.iter() {
let key_idx = self.chunk.add_constant(Constant::String(key.clone()));
self.chunk.emit_u16(Op::Constant, key_idx, self.line);
self.emit_vm_value_literal(item);
}
self.chunk
.emit_u16(Op::BuildDict, entries.len() as u16, self.line);
}
_ => {}
}
}
fn emit_type_name_extra(&mut self, type_name_idx: u16) {
let hi = (type_name_idx >> 8) as u8;
let lo = type_name_idx as u8;
self.chunk.code.push(hi);
self.chunk.code.push(lo);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
}
fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
if body.is_empty() {
self.chunk.emit(Op::Nil, self.line);
} else {
self.compile_scoped_block(body)?;
}
Ok(())
}
fn compile_catch_binding(&mut self, error_var: &Option<String>) -> Result<(), CompileError> {
if let Some(var_name) = error_var {
let idx = self.chunk.add_constant(Constant::String(var_name.clone()));
self.chunk.emit_u16(Op::DefLet, idx, self.line);
} else {
self.chunk.emit(Op::Pop, self.line);
}
Ok(())
}
fn compile_finally_inline(&mut self, finally_body: &[SNode]) -> Result<(), CompileError> {
if !finally_body.is_empty() {
self.compile_scoped_block(finally_body)?;
if Self::produces_value(&finally_body.last().unwrap().node) {
self.chunk.emit(Op::Pop, self.line);
}
}
Ok(())
}
fn pending_finallys_until_barrier(&self) -> Vec<Vec<SNode>> {
let mut out = Vec::new();
for entry in self.finally_bodies.iter().rev() {
match entry {
FinallyEntry::CatchBarrier => break,
FinallyEntry::Finally(body) => out.push(body.clone()),
}
}
out
}
fn pending_finallys_down_to(&self, floor: usize) -> Vec<Vec<SNode>> {
let mut out = Vec::new();
for entry in self.finally_bodies[floor..].iter().rev() {
if let FinallyEntry::Finally(body) = entry {
out.push(body.clone());
}
}
out
}
fn all_pending_finallys(&self) -> Vec<Vec<SNode>> {
self.pending_finallys_down_to(0)
}
fn has_pending_finally(&self) -> bool {
self.finally_bodies
.iter()
.any(|e| matches!(e, FinallyEntry::Finally(_)))
}
fn compile_plain_rethrow(&mut self) -> Result<(), CompileError> {
self.temp_counter += 1;
let temp_name = format!("__finally_err_{}__", self.temp_counter);
let err_idx = self.chunk.add_constant(Constant::String(temp_name.clone()));
self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
let get_idx = self.chunk.add_constant(Constant::String(temp_name));
self.chunk.emit_u16(Op::GetVar, get_idx, self.line);
self.chunk.emit(Op::Throw, self.line);
Ok(())
}
fn begin_scope(&mut self) {
self.chunk.emit(Op::PushScope, self.line);
self.scope_depth += 1;
}
fn end_scope(&mut self) {
if self.scope_depth > 0 {
self.chunk.emit(Op::PopScope, self.line);
self.scope_depth -= 1;
}
}
fn unwind_scopes_to(&mut self, target_depth: usize) {
while self.scope_depth > target_depth {
self.chunk.emit(Op::PopScope, self.line);
self.scope_depth -= 1;
}
}
fn compile_scoped_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
self.begin_scope();
if stmts.is_empty() {
self.chunk.emit(Op::Nil, self.line);
} else {
self.compile_block(stmts)?;
}
self.end_scope();
Ok(())
}
fn compile_scoped_statements(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
self.begin_scope();
for sn in stmts {
self.compile_node(sn)?;
if Self::produces_value(&sn.node) {
self.chunk.emit(Op::Pop, self.line);
}
}
self.end_scope();
Ok(())
}
fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
for (i, snode) in stmts.iter().enumerate() {
self.compile_node(snode)?;
let is_last = i == stmts.len() - 1;
if is_last {
if !Self::produces_value(&snode.node) {
self.chunk.emit(Op::Nil, self.line);
}
} else {
if Self::produces_value(&snode.node) {
self.chunk.emit(Op::Pop, self.line);
}
}
}
Ok(())
}
fn compile_node(&mut self, snode: &SNode) -> Result<(), CompileError> {
self.line = snode.span.line as u32;
self.column = snode.span.column as u32;
self.chunk.set_column(self.column);
match &snode.node {
Node::IntLiteral(n) => {
let idx = self.chunk.add_constant(Constant::Int(*n));
self.chunk.emit_u16(Op::Constant, idx, self.line);
}
Node::FloatLiteral(n) => {
let idx = self.chunk.add_constant(Constant::Float(*n));
self.chunk.emit_u16(Op::Constant, idx, self.line);
}
Node::StringLiteral(s) | Node::RawStringLiteral(s) => {
let idx = self.chunk.add_constant(Constant::String(s.clone()));
self.chunk.emit_u16(Op::Constant, idx, self.line);
}
Node::BoolLiteral(true) => self.chunk.emit(Op::True, self.line),
Node::BoolLiteral(false) => self.chunk.emit(Op::False, self.line),
Node::NilLiteral => self.chunk.emit(Op::Nil, self.line),
Node::DurationLiteral(ms) => {
let idx = self.chunk.add_constant(Constant::Duration(*ms));
self.chunk.emit_u16(Op::Constant, idx, self.line);
}
Node::Identifier(name) => {
let idx = self.chunk.add_constant(Constant::String(name.clone()));
self.chunk.emit_u16(Op::GetVar, idx, self.line);
}
Node::LetBinding { pattern, value, .. } => {
self.compile_node(value)?;
self.compile_destructuring(pattern, false)?;
}
Node::VarBinding { pattern, value, .. } => {
self.compile_node(value)?;
self.compile_destructuring(pattern, true)?;
}
Node::Assignment {
target, value, op, ..
} => {
if let Node::Identifier(name) = &target.node {
let idx = self.chunk.add_constant(Constant::String(name.clone()));
if let Some(op) = op {
self.chunk.emit_u16(Op::GetVar, idx, self.line);
self.compile_node(value)?;
self.emit_compound_op(op)?;
self.chunk.emit_u16(Op::SetVar, idx, self.line);
} else {
self.compile_node(value)?;
self.chunk.emit_u16(Op::SetVar, idx, self.line);
}
} else if let Node::PropertyAccess { object, property } = &target.node {
if let Some(var_name) = self.root_var_name(object) {
let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
let prop_idx = self.chunk.add_constant(Constant::String(property.clone()));
if let Some(op) = op {
self.compile_node(target)?;
self.compile_node(value)?;
self.emit_compound_op(op)?;
} else {
self.compile_node(value)?;
}
self.chunk.emit_u16(Op::SetProperty, prop_idx, self.line);
let hi = (var_idx >> 8) as u8;
let lo = var_idx as u8;
self.chunk.code.push(hi);
self.chunk.code.push(lo);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
}
} else if let Node::SubscriptAccess { object, index } = &target.node {
if let Some(var_name) = self.root_var_name(object) {
let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
if let Some(op) = op {
self.compile_node(target)?;
self.compile_node(value)?;
self.emit_compound_op(op)?;
} else {
self.compile_node(value)?;
}
self.compile_node(index)?;
self.chunk.emit_u16(Op::SetSubscript, var_idx, self.line);
}
}
}
Node::BinaryOp { op, left, right } => {
match op.as_str() {
"&&" => {
self.compile_node(left)?;
let jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
self.compile_node(right)?;
self.chunk.patch_jump(jump);
self.chunk.emit(Op::Not, self.line);
self.chunk.emit(Op::Not, self.line);
return Ok(());
}
"||" => {
self.compile_node(left)?;
let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
self.chunk.emit(Op::Pop, self.line);
self.compile_node(right)?;
self.chunk.patch_jump(jump);
self.chunk.emit(Op::Not, self.line);
self.chunk.emit(Op::Not, self.line);
return Ok(());
}
"??" => {
self.compile_node(left)?;
self.chunk.emit(Op::Dup, self.line);
self.chunk.emit(Op::Nil, self.line);
self.chunk.emit(Op::NotEqual, self.line);
let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
self.chunk.emit(Op::Pop, self.line);
self.chunk.emit(Op::Pop, self.line);
self.compile_node(right)?;
let end = self.chunk.emit_jump(Op::Jump, self.line);
self.chunk.patch_jump(jump);
self.chunk.emit(Op::Pop, self.line);
self.chunk.patch_jump(end);
return Ok(());
}
"|>" => {
self.compile_node(left)?;
if contains_pipe_placeholder(right) {
let replaced = replace_pipe_placeholder(right);
let closure_node = SNode::dummy(Node::Closure {
params: vec![TypedParam {
name: "__pipe".into(),
type_expr: None,
default_value: None,
rest: false,
}],
body: vec![replaced],
fn_syntax: false,
});
self.compile_node(&closure_node)?;
} else {
self.compile_node(right)?;
}
self.chunk.emit(Op::Pipe, self.line);
return Ok(());
}
_ => {}
}
self.compile_node(left)?;
self.compile_node(right)?;
match op.as_str() {
"+" => self.chunk.emit(Op::Add, self.line),
"-" => self.chunk.emit(Op::Sub, self.line),
"*" => self.chunk.emit(Op::Mul, self.line),
"/" => self.chunk.emit(Op::Div, self.line),
"%" => self.chunk.emit(Op::Mod, self.line),
"**" => self.chunk.emit(Op::Pow, self.line),
"==" => self.chunk.emit(Op::Equal, self.line),
"!=" => self.chunk.emit(Op::NotEqual, self.line),
"<" => self.chunk.emit(Op::Less, self.line),
">" => self.chunk.emit(Op::Greater, self.line),
"<=" => self.chunk.emit(Op::LessEqual, self.line),
">=" => self.chunk.emit(Op::GreaterEqual, self.line),
"in" => self.chunk.emit(Op::Contains, self.line),
"not_in" => {
self.chunk.emit(Op::Contains, self.line);
self.chunk.emit(Op::Not, self.line);
}
_ => {
return Err(CompileError {
message: format!("Unknown operator: {op}"),
line: self.line,
})
}
}
}
Node::UnaryOp { op, operand } => {
self.compile_node(operand)?;
match op.as_str() {
"-" => self.chunk.emit(Op::Negate, self.line),
"!" => self.chunk.emit(Op::Not, self.line),
_ => {}
}
}
Node::Ternary {
condition,
true_expr,
false_expr,
} => {
self.compile_node(condition)?;
let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
self.compile_node(true_expr)?;
let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
self.chunk.patch_jump(else_jump);
self.chunk.emit(Op::Pop, self.line);
self.compile_node(false_expr)?;
self.chunk.patch_jump(end_jump);
}
Node::FunctionCall { name, args } => {
let has_spread = args.iter().any(|a| matches!(&a.node, Node::Spread(_)));
let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
self.chunk.emit_u16(Op::Constant, name_idx, self.line);
if has_spread {
self.chunk.emit_u16(Op::BuildList, 0, self.line);
let mut pending = 0u16;
for arg in args {
if let Node::Spread(inner) = &arg.node {
if pending > 0 {
self.chunk.emit_u16(Op::BuildList, pending, self.line);
self.chunk.emit(Op::Add, self.line);
pending = 0;
}
self.compile_node(inner)?;
self.chunk.emit(Op::Dup, self.line);
let assert_idx = self
.chunk
.add_constant(Constant::String("__assert_list".into()));
self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
self.chunk.emit(Op::Swap, self.line);
self.chunk.emit_u8(Op::Call, 1, self.line);
self.chunk.emit(Op::Pop, self.line);
self.chunk.emit(Op::Add, self.line);
} else {
self.compile_node(arg)?;
pending += 1;
}
}
if pending > 0 {
self.chunk.emit_u16(Op::BuildList, pending, self.line);
self.chunk.emit(Op::Add, self.line);
}
self.chunk.emit(Op::CallSpread, self.line);
} else {
for arg in args {
self.compile_node(arg)?;
}
self.chunk.emit_u8(Op::Call, args.len() as u8, self.line);
}
}
Node::MethodCall {
object,
method,
args,
} => {
if let Node::Identifier(name) = &object.node {
if self.enum_names.contains(name) {
for arg in args {
self.compile_node(arg)?;
}
let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
let var_idx = self.chunk.add_constant(Constant::String(method.clone()));
self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
let hi = (var_idx >> 8) as u8;
let lo = var_idx as u8;
self.chunk.code.push(hi);
self.chunk.code.push(lo);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
let fc = args.len() as u16;
let fhi = (fc >> 8) as u8;
let flo = fc as u8;
self.chunk.code.push(fhi);
self.chunk.code.push(flo);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
return Ok(());
}
}
let has_spread = args.iter().any(|a| matches!(&a.node, Node::Spread(_)));
self.compile_node(object)?;
let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
if has_spread {
self.chunk.emit_u16(Op::BuildList, 0, self.line);
let mut pending = 0u16;
for arg in args {
if let Node::Spread(inner) = &arg.node {
if pending > 0 {
self.chunk.emit_u16(Op::BuildList, pending, self.line);
self.chunk.emit(Op::Add, self.line);
pending = 0;
}
self.compile_node(inner)?;
self.chunk.emit(Op::Dup, self.line);
let assert_idx = self
.chunk
.add_constant(Constant::String("__assert_list".into()));
self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
self.chunk.emit(Op::Swap, self.line);
self.chunk.emit_u8(Op::Call, 1, self.line);
self.chunk.emit(Op::Pop, self.line);
self.chunk.emit(Op::Add, self.line);
} else {
self.compile_node(arg)?;
pending += 1;
}
}
if pending > 0 {
self.chunk.emit_u16(Op::BuildList, pending, self.line);
self.chunk.emit(Op::Add, self.line);
}
self.chunk
.emit_u16(Op::MethodCallSpread, name_idx, self.line);
} else {
for arg in args {
self.compile_node(arg)?;
}
self.chunk
.emit_method_call(name_idx, args.len() as u8, self.line);
}
}
Node::OptionalMethodCall {
object,
method,
args,
} => {
self.compile_node(object)?;
for arg in args {
self.compile_node(arg)?;
}
let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
self.chunk
.emit_method_call_opt(name_idx, args.len() as u8, self.line);
}
Node::PropertyAccess { object, property } => {
if let Node::Identifier(name) = &object.node {
if self.enum_names.contains(name) {
let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
let var_idx = self.chunk.add_constant(Constant::String(property.clone()));
self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
let hi = (var_idx >> 8) as u8;
let lo = var_idx as u8;
self.chunk.code.push(hi);
self.chunk.code.push(lo);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
self.chunk.code.push(0);
self.chunk.code.push(0);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
return Ok(());
}
}
self.compile_node(object)?;
let idx = self.chunk.add_constant(Constant::String(property.clone()));
self.chunk.emit_u16(Op::GetProperty, idx, self.line);
}
Node::OptionalPropertyAccess { object, property } => {
self.compile_node(object)?;
let idx = self.chunk.add_constant(Constant::String(property.clone()));
self.chunk.emit_u16(Op::GetPropertyOpt, idx, self.line);
}
Node::SubscriptAccess { object, index } => {
self.compile_node(object)?;
self.compile_node(index)?;
self.chunk.emit(Op::Subscript, self.line);
}
Node::SliceAccess { object, start, end } => {
self.compile_node(object)?;
if let Some(s) = start {
self.compile_node(s)?;
} else {
self.chunk.emit(Op::Nil, self.line);
}
if let Some(e) = end {
self.compile_node(e)?;
} else {
self.chunk.emit(Op::Nil, self.line);
}
self.chunk.emit(Op::Slice, self.line);
}
Node::IfElse {
condition,
then_body,
else_body,
} => {
self.compile_node(condition)?;
let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
self.compile_scoped_block(then_body)?;
if let Some(else_body) = else_body {
let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
self.chunk.patch_jump(else_jump);
self.chunk.emit(Op::Pop, self.line);
self.compile_scoped_block(else_body)?;
self.chunk.patch_jump(end_jump);
} else {
self.chunk.patch_jump(else_jump);
self.chunk.emit(Op::Pop, self.line);
self.chunk.emit(Op::Nil, self.line);
}
}
Node::WhileLoop { condition, body } => {
let loop_start = self.chunk.current_offset();
self.loop_stack.push(LoopContext {
start_offset: loop_start,
break_patches: Vec::new(),
has_iterator: false,
handler_depth: self.handler_depth,
finally_depth: self.finally_bodies.len(),
scope_depth: self.scope_depth,
});
self.compile_node(condition)?;
let exit_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
self.compile_scoped_statements(body)?;
self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
self.chunk.patch_jump(exit_jump);
self.chunk.emit(Op::Pop, self.line);
let ctx = self.loop_stack.pop().unwrap();
for patch_pos in ctx.break_patches {
self.chunk.patch_jump(patch_pos);
}
self.chunk.emit(Op::Nil, self.line);
}
Node::ForIn {
pattern,
iterable,
body,
} => {
self.compile_node(iterable)?;
self.chunk.emit(Op::IterInit, self.line);
let loop_start = self.chunk.current_offset();
self.loop_stack.push(LoopContext {
start_offset: loop_start,
break_patches: Vec::new(),
has_iterator: true,
handler_depth: self.handler_depth,
finally_depth: self.finally_bodies.len(),
scope_depth: self.scope_depth,
});
let exit_jump_pos = self.chunk.emit_jump(Op::IterNext, self.line);
self.begin_scope();
self.compile_destructuring(pattern, true)?;
for sn in body {
self.compile_node(sn)?;
if Self::produces_value(&sn.node) {
self.chunk.emit(Op::Pop, self.line);
}
}
self.end_scope();
self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
self.chunk.patch_jump(exit_jump_pos);
let ctx = self.loop_stack.pop().unwrap();
for patch_pos in ctx.break_patches {
self.chunk.patch_jump(patch_pos);
}
self.chunk.emit(Op::Nil, self.line);
}
Node::ReturnStmt { value } => {
if self.has_pending_finally() {
if let Some(val) = value {
self.compile_node(val)?;
} else {
self.chunk.emit(Op::Nil, self.line);
}
self.temp_counter += 1;
let temp_name = format!("__return_val_{}__", self.temp_counter);
let save_idx = self.chunk.add_constant(Constant::String(temp_name.clone()));
self.chunk.emit_u16(Op::DefVar, save_idx, self.line);
for fb in self.all_pending_finallys() {
self.compile_finally_inline(&fb)?;
}
let restore_idx = self.chunk.add_constant(Constant::String(temp_name));
self.chunk.emit_u16(Op::GetVar, restore_idx, self.line);
self.chunk.emit(Op::Return, self.line);
} else {
if let Some(val) = value {
if let Node::FunctionCall { name, args } = &val.node {
let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
self.chunk.emit_u16(Op::Constant, name_idx, self.line);
for arg in args {
self.compile_node(arg)?;
}
self.chunk
.emit_u8(Op::TailCall, args.len() as u8, self.line);
} else if let Node::BinaryOp { op, left, right } = &val.node {
if op == "|>" {
self.compile_node(left)?;
self.compile_node(right)?;
self.chunk.emit(Op::Swap, self.line);
self.chunk.emit_u8(Op::TailCall, 1, self.line);
} else {
self.compile_node(val)?;
}
} else {
self.compile_node(val)?;
}
} else {
self.chunk.emit(Op::Nil, self.line);
}
self.chunk.emit(Op::Return, self.line);
}
}
Node::BreakStmt => {
if self.loop_stack.is_empty() {
return Err(CompileError {
message: "break outside of loop".to_string(),
line: self.line,
});
}
let ctx = self.loop_stack.last().unwrap();
let finally_depth = ctx.finally_depth;
let handler_depth = ctx.handler_depth;
let has_iterator = ctx.has_iterator;
let scope_depth = ctx.scope_depth;
for _ in handler_depth..self.handler_depth {
self.chunk.emit(Op::PopHandler, self.line);
}
for fb in self.pending_finallys_down_to(finally_depth) {
self.compile_finally_inline(&fb)?;
}
self.unwind_scopes_to(scope_depth);
if has_iterator {
self.chunk.emit(Op::PopIterator, self.line);
}
let patch = self.chunk.emit_jump(Op::Jump, self.line);
self.loop_stack
.last_mut()
.unwrap()
.break_patches
.push(patch);
}
Node::ContinueStmt => {
if self.loop_stack.is_empty() {
return Err(CompileError {
message: "continue outside of loop".to_string(),
line: self.line,
});
}
let ctx = self.loop_stack.last().unwrap();
let finally_depth = ctx.finally_depth;
let handler_depth = ctx.handler_depth;
let loop_start = ctx.start_offset;
let scope_depth = ctx.scope_depth;
for _ in handler_depth..self.handler_depth {
self.chunk.emit(Op::PopHandler, self.line);
}
for fb in self.pending_finallys_down_to(finally_depth) {
self.compile_finally_inline(&fb)?;
}
self.unwind_scopes_to(scope_depth);
self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
}
Node::ListLiteral(elements) => {
let has_spread = elements.iter().any(|e| matches!(&e.node, Node::Spread(_)));
if !has_spread {
for el in elements {
self.compile_node(el)?;
}
self.chunk
.emit_u16(Op::BuildList, elements.len() as u16, self.line);
} else {
self.chunk.emit_u16(Op::BuildList, 0, self.line);
let mut pending = 0u16;
for el in elements {
if let Node::Spread(inner) = &el.node {
if pending > 0 {
self.chunk.emit_u16(Op::BuildList, pending, self.line);
self.chunk.emit(Op::Add, self.line);
pending = 0;
}
self.compile_node(inner)?;
self.chunk.emit(Op::Dup, self.line);
let assert_idx = self
.chunk
.add_constant(Constant::String("__assert_list".into()));
self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
self.chunk.emit(Op::Swap, self.line);
self.chunk.emit_u8(Op::Call, 1, self.line);
self.chunk.emit(Op::Pop, self.line);
self.chunk.emit(Op::Add, self.line);
} else {
self.compile_node(el)?;
pending += 1;
}
}
if pending > 0 {
self.chunk.emit_u16(Op::BuildList, pending, self.line);
self.chunk.emit(Op::Add, self.line);
}
}
}
Node::DictLiteral(entries) => {
let has_spread = entries
.iter()
.any(|e| matches!(&e.value.node, Node::Spread(_)));
if !has_spread {
for entry in entries {
self.compile_node(&entry.key)?;
self.compile_node(&entry.value)?;
}
self.chunk
.emit_u16(Op::BuildDict, entries.len() as u16, self.line);
} else {
self.chunk.emit_u16(Op::BuildDict, 0, self.line);
let mut pending = 0u16;
for entry in entries {
if let Node::Spread(inner) = &entry.value.node {
if pending > 0 {
self.chunk.emit_u16(Op::BuildDict, pending, self.line);
self.chunk.emit(Op::Add, self.line);
pending = 0;
}
self.compile_node(inner)?;
self.chunk.emit(Op::Dup, self.line);
let assert_idx = self
.chunk
.add_constant(Constant::String("__assert_dict".into()));
self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
self.chunk.emit(Op::Swap, self.line);
self.chunk.emit_u8(Op::Call, 1, self.line);
self.chunk.emit(Op::Pop, self.line);
self.chunk.emit(Op::Add, self.line);
} else {
self.compile_node(&entry.key)?;
self.compile_node(&entry.value)?;
pending += 1;
}
}
if pending > 0 {
self.chunk.emit_u16(Op::BuildDict, pending, self.line);
self.chunk.emit(Op::Add, self.line);
}
}
}
Node::InterpolatedString(segments) => {
let mut part_count = 0u16;
for seg in segments {
match seg {
StringSegment::Literal(s) => {
let idx = self.chunk.add_constant(Constant::String(s.clone()));
self.chunk.emit_u16(Op::Constant, idx, self.line);
part_count += 1;
}
StringSegment::Expression(expr_str, expr_line, expr_col) => {
let mut lexer =
harn_lexer::Lexer::with_position(expr_str, *expr_line, *expr_col);
if let Ok(tokens) = lexer.tokenize() {
let mut parser = harn_parser::Parser::new(tokens);
if let Ok(snode) = parser.parse_single_expression() {
self.compile_node(&snode)?;
let to_str = self
.chunk
.add_constant(Constant::String("to_string".into()));
self.chunk.emit_u16(Op::Constant, to_str, self.line);
self.chunk.emit(Op::Swap, self.line);
self.chunk.emit_u8(Op::Call, 1, self.line);
part_count += 1;
} else {
let idx =
self.chunk.add_constant(Constant::String(expr_str.clone()));
self.chunk.emit_u16(Op::Constant, idx, self.line);
part_count += 1;
}
}
}
}
}
if part_count > 1 {
self.chunk.emit_u16(Op::Concat, part_count, self.line);
}
}
Node::FnDecl {
name, params, body, ..
} => {
let mut fn_compiler = Compiler::new();
fn_compiler.enum_names = self.enum_names.clone();
fn_compiler.emit_default_preamble(params)?;
fn_compiler.emit_type_checks(params);
let is_gen = body_contains_yield(body);
fn_compiler.compile_block(body)?;
for fb in fn_compiler.all_pending_finallys() {
fn_compiler.compile_finally_inline(&fb)?;
}
fn_compiler.chunk.emit(Op::Nil, self.line);
fn_compiler.chunk.emit(Op::Return, self.line);
let func = CompiledFunction {
name: name.clone(),
params: TypedParam::names(params),
default_start: TypedParam::default_start(params),
chunk: fn_compiler.chunk,
is_generator: is_gen,
has_rest_param: params.last().is_some_and(|p| p.rest),
};
let fn_idx = self.chunk.functions.len();
self.chunk.functions.push(func);
self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
}
Node::ToolDecl {
name,
description,
params,
return_type,
body,
..
} => {
let mut fn_compiler = Compiler::new();
fn_compiler.enum_names = self.enum_names.clone();
fn_compiler.emit_default_preamble(params)?;
fn_compiler.emit_type_checks(params);
fn_compiler.compile_block(body)?;
for fb in fn_compiler.all_pending_finallys() {
fn_compiler.compile_finally_inline(&fb)?;
}
fn_compiler.chunk.emit(Op::Return, self.line);
let func = CompiledFunction {
name: name.clone(),
params: TypedParam::names(params),
default_start: TypedParam::default_start(params),
chunk: fn_compiler.chunk,
is_generator: false,
has_rest_param: params.last().is_some_and(|p| p.rest),
};
let fn_idx = self.chunk.functions.len();
self.chunk.functions.push(func);
let define_name = self
.chunk
.add_constant(Constant::String("tool_define".into()));
self.chunk.emit_u16(Op::Constant, define_name, self.line);
let reg_name = self
.chunk
.add_constant(Constant::String("tool_registry".into()));
self.chunk.emit_u16(Op::Constant, reg_name, self.line);
self.chunk.emit_u8(Op::Call, 0, self.line);
let tool_name_idx = self.chunk.add_constant(Constant::String(name.clone()));
self.chunk.emit_u16(Op::Constant, tool_name_idx, self.line);
let desc = description.as_deref().unwrap_or("");
let desc_idx = self.chunk.add_constant(Constant::String(desc.to_string()));
self.chunk.emit_u16(Op::Constant, desc_idx, self.line);
let mut param_count: u16 = 0;
for p in params {
let pn_idx = self.chunk.add_constant(Constant::String(p.name.clone()));
self.chunk.emit_u16(Op::Constant, pn_idx, self.line);
let base_schema = p
.type_expr
.as_ref()
.and_then(Self::type_expr_to_schema_value)
.unwrap_or_else(|| {
VmValue::Dict(Rc::new(BTreeMap::from([(
"type".to_string(),
VmValue::String(Rc::from("any")),
)])))
});
let public_schema =
schema::schema_to_json_schema_value(&base_schema).map_err(|error| {
CompileError {
message: format!(
"failed to lower tool parameter schema for '{}': {}",
p.name, error
),
line: self.line,
}
})?;
let mut param_schema = match public_schema {
VmValue::Dict(map) => (*map).clone(),
_ => BTreeMap::new(),
};
if p.default_value.is_some() {
param_schema.insert("required".to_string(), VmValue::Bool(false));
}
self.emit_vm_value_literal(&VmValue::Dict(Rc::new(param_schema)));
if let Some(default_value) = p.default_value.as_ref() {
let default_key =
self.chunk.add_constant(Constant::String("default".into()));
self.chunk.emit_u16(Op::Constant, default_key, self.line);
self.compile_node(default_value)?;
self.chunk.emit_u16(Op::BuildDict, 1, self.line);
self.chunk.emit(Op::Add, self.line);
}
param_count += 1;
}
self.chunk.emit_u16(Op::BuildDict, param_count, self.line);
let params_key = self
.chunk
.add_constant(Constant::String("parameters".into()));
self.chunk.emit_u16(Op::Constant, params_key, self.line);
self.chunk.emit(Op::Swap, self.line);
let handler_key = self.chunk.add_constant(Constant::String("handler".into()));
self.chunk.emit_u16(Op::Constant, handler_key, self.line);
self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
let mut config_entries = 2u16;
if let Some(return_type) = return_type
.as_ref()
.and_then(Self::type_expr_to_schema_value)
{
let return_type =
schema::schema_to_json_schema_value(&return_type).map_err(|error| {
CompileError {
message: format!(
"failed to lower tool return schema for '{}': {}",
name, error
),
line: self.line,
}
})?;
let returns_key = self.chunk.add_constant(Constant::String("returns".into()));
self.chunk.emit_u16(Op::Constant, returns_key, self.line);
self.emit_vm_value_literal(&return_type);
config_entries += 1;
}
self.chunk
.emit_u16(Op::BuildDict, config_entries, self.line);
self.chunk.emit_u8(Op::Call, 4, self.line);
let bind_idx = self.chunk.add_constant(Constant::String(name.clone()));
self.chunk.emit_u16(Op::DefLet, bind_idx, self.line);
}
Node::Closure { params, body, .. } => {
let mut fn_compiler = Compiler::new();
fn_compiler.enum_names = self.enum_names.clone();
fn_compiler.emit_default_preamble(params)?;
fn_compiler.emit_type_checks(params);
let is_gen = body_contains_yield(body);
fn_compiler.compile_block(body)?;
for fb in fn_compiler.all_pending_finallys() {
fn_compiler.compile_finally_inline(&fb)?;
}
fn_compiler.chunk.emit(Op::Return, self.line);
let func = CompiledFunction {
name: "<closure>".to_string(),
params: TypedParam::names(params),
default_start: TypedParam::default_start(params),
chunk: fn_compiler.chunk,
is_generator: is_gen,
has_rest_param: false,
};
let fn_idx = self.chunk.functions.len();
self.chunk.functions.push(func);
self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
}
Node::ThrowStmt { value } => {
let pending = self.pending_finallys_until_barrier();
if !pending.is_empty() {
self.compile_node(value)?;
self.temp_counter += 1;
let temp_name = format!("__throw_val_{}__", self.temp_counter);
let save_idx = self.chunk.add_constant(Constant::String(temp_name.clone()));
self.chunk.emit_u16(Op::DefVar, save_idx, self.line);
for fb in &pending {
self.compile_finally_inline(fb)?;
}
let restore_idx = self.chunk.add_constant(Constant::String(temp_name));
self.chunk.emit_u16(Op::GetVar, restore_idx, self.line);
self.chunk.emit(Op::Throw, self.line);
} else {
self.compile_node(value)?;
self.chunk.emit(Op::Throw, self.line);
}
}
Node::MatchExpr { value, arms } => {
self.compile_node(value)?;
let mut end_jumps = Vec::new();
for arm in arms {
match &arm.pattern.node {
Node::Identifier(name) if name == "_" => {
if let Some(ref guard) = arm.guard {
self.compile_node(guard)?;
let guard_skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
self.begin_scope();
self.chunk.emit(Op::Pop, self.line);
self.compile_match_body(&arm.body)?;
self.end_scope();
end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
self.chunk.patch_jump(guard_skip);
self.chunk.emit(Op::Pop, self.line);
} else {
self.begin_scope();
self.chunk.emit(Op::Pop, self.line);
self.compile_match_body(&arm.body)?;
self.end_scope();
end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
}
}
Node::EnumConstruct {
enum_name,
variant,
args: pat_args,
} => {
self.chunk.emit(Op::Dup, self.line);
let en_idx =
self.chunk.add_constant(Constant::String(enum_name.clone()));
let vn_idx = self.chunk.add_constant(Constant::String(variant.clone()));
self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
let hi = (vn_idx >> 8) as u8;
let lo = vn_idx as u8;
self.chunk.code.push(hi);
self.chunk.code.push(lo);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
self.begin_scope();
for (i, pat_arg) in pat_args.iter().enumerate() {
if let Node::Identifier(binding_name) = &pat_arg.node {
self.chunk.emit(Op::Dup, self.line);
let fields_idx = self
.chunk
.add_constant(Constant::String("fields".to_string()));
self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
let idx_const =
self.chunk.add_constant(Constant::Int(i as i64));
self.chunk.emit_u16(Op::Constant, idx_const, self.line);
self.chunk.emit(Op::Subscript, self.line);
let name_idx = self
.chunk
.add_constant(Constant::String(binding_name.clone()));
self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
}
}
if let Some(ref guard) = arm.guard {
self.compile_node(guard)?;
let guard_skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
self.chunk.emit(Op::Pop, self.line);
self.compile_match_body(&arm.body)?;
self.end_scope();
end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
self.chunk.patch_jump(guard_skip);
self.chunk.emit(Op::Pop, self.line);
self.end_scope();
} else {
self.chunk.emit(Op::Pop, self.line);
self.compile_match_body(&arm.body)?;
self.end_scope();
end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
}
self.chunk.patch_jump(skip);
self.chunk.emit(Op::Pop, self.line);
}
Node::PropertyAccess { object, property } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
{
let enum_name = if let Node::Identifier(n) = &object.node {
n.clone()
} else {
unreachable!()
};
self.chunk.emit(Op::Dup, self.line);
let en_idx = self.chunk.add_constant(Constant::String(enum_name));
let vn_idx =
self.chunk.add_constant(Constant::String(property.clone()));
self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
let hi = (vn_idx >> 8) as u8;
let lo = vn_idx as u8;
self.chunk.code.push(hi);
self.chunk.code.push(lo);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
if let Some(ref guard) = arm.guard {
self.compile_node(guard)?;
let guard_skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
self.begin_scope();
self.chunk.emit(Op::Pop, self.line);
self.compile_match_body(&arm.body)?;
self.end_scope();
end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
self.chunk.patch_jump(guard_skip);
self.chunk.emit(Op::Pop, self.line);
} else {
self.begin_scope();
self.chunk.emit(Op::Pop, self.line);
self.compile_match_body(&arm.body)?;
self.end_scope();
end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
}
self.chunk.patch_jump(skip);
self.chunk.emit(Op::Pop, self.line);
}
Node::MethodCall {
object,
method,
args: pat_args,
} if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
{
let enum_name = if let Node::Identifier(n) = &object.node {
n.clone()
} else {
unreachable!()
};
self.chunk.emit(Op::Dup, self.line);
let en_idx = self.chunk.add_constant(Constant::String(enum_name));
let vn_idx = self.chunk.add_constant(Constant::String(method.clone()));
self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
let hi = (vn_idx >> 8) as u8;
let lo = vn_idx as u8;
self.chunk.code.push(hi);
self.chunk.code.push(lo);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
self.begin_scope();
for (i, pat_arg) in pat_args.iter().enumerate() {
if let Node::Identifier(binding_name) = &pat_arg.node {
self.chunk.emit(Op::Dup, self.line);
let fields_idx = self
.chunk
.add_constant(Constant::String("fields".to_string()));
self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
let idx_const =
self.chunk.add_constant(Constant::Int(i as i64));
self.chunk.emit_u16(Op::Constant, idx_const, self.line);
self.chunk.emit(Op::Subscript, self.line);
let name_idx = self
.chunk
.add_constant(Constant::String(binding_name.clone()));
self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
}
}
if let Some(ref guard) = arm.guard {
self.compile_node(guard)?;
let guard_skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
self.chunk.emit(Op::Pop, self.line);
self.compile_match_body(&arm.body)?;
self.end_scope();
end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
self.chunk.patch_jump(guard_skip);
self.chunk.emit(Op::Pop, self.line);
self.end_scope();
} else {
self.chunk.emit(Op::Pop, self.line);
self.compile_match_body(&arm.body)?;
self.end_scope();
end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
}
self.chunk.patch_jump(skip);
self.chunk.emit(Op::Pop, self.line);
}
Node::Identifier(name) => {
self.begin_scope();
self.chunk.emit(Op::Dup, self.line);
let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
if let Some(ref guard) = arm.guard {
self.compile_node(guard)?;
let guard_skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
self.chunk.emit(Op::Pop, self.line);
self.compile_match_body(&arm.body)?;
self.end_scope();
end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
self.chunk.patch_jump(guard_skip);
self.chunk.emit(Op::Pop, self.line);
self.end_scope();
} else {
self.chunk.emit(Op::Pop, self.line);
self.compile_match_body(&arm.body)?;
self.end_scope();
end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
}
}
Node::DictLiteral(entries)
if entries
.iter()
.all(|e| matches!(&e.key.node, Node::StringLiteral(_))) =>
{
self.chunk.emit(Op::Dup, self.line);
let typeof_idx =
self.chunk.add_constant(Constant::String("type_of".into()));
self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
self.chunk.emit(Op::Swap, self.line);
self.chunk.emit_u8(Op::Call, 1, self.line);
let dict_str = self.chunk.add_constant(Constant::String("dict".into()));
self.chunk.emit_u16(Op::Constant, dict_str, self.line);
self.chunk.emit(Op::Equal, self.line);
let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
let mut constraint_skips = Vec::new();
let mut bindings = Vec::new();
self.begin_scope();
for entry in entries {
if let Node::StringLiteral(key) = &entry.key.node {
match &entry.value.node {
Node::StringLiteral(_)
| Node::IntLiteral(_)
| Node::FloatLiteral(_)
| Node::BoolLiteral(_)
| Node::NilLiteral => {
self.chunk.emit(Op::Dup, self.line);
let key_idx = self
.chunk
.add_constant(Constant::String(key.clone()));
self.chunk.emit_u16(Op::Constant, key_idx, self.line);
self.chunk.emit(Op::Subscript, self.line);
self.compile_node(&entry.value)?;
self.chunk.emit(Op::Equal, self.line);
let skip =
self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
constraint_skips.push(skip);
}
Node::Identifier(binding) => {
bindings.push((key.clone(), binding.clone()));
}
_ => {
self.chunk.emit(Op::Dup, self.line);
let key_idx = self
.chunk
.add_constant(Constant::String(key.clone()));
self.chunk.emit_u16(Op::Constant, key_idx, self.line);
self.chunk.emit(Op::Subscript, self.line);
self.compile_node(&entry.value)?;
self.chunk.emit(Op::Equal, self.line);
let skip =
self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
constraint_skips.push(skip);
}
}
}
}
for (key, binding) in &bindings {
self.chunk.emit(Op::Dup, self.line);
let key_idx =
self.chunk.add_constant(Constant::String(key.clone()));
self.chunk.emit_u16(Op::Constant, key_idx, self.line);
self.chunk.emit(Op::Subscript, self.line);
let name_idx =
self.chunk.add_constant(Constant::String(binding.clone()));
self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
}
if let Some(ref guard) = arm.guard {
self.compile_node(guard)?;
let guard_skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
self.chunk.emit(Op::Pop, self.line);
self.compile_match_body(&arm.body)?;
self.end_scope();
end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
self.chunk.patch_jump(guard_skip);
self.chunk.emit(Op::Pop, self.line);
} else {
self.chunk.emit(Op::Pop, self.line);
self.compile_match_body(&arm.body)?;
self.end_scope();
end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
}
let type_fail_target = self.chunk.code.len();
self.chunk.emit(Op::Pop, self.line);
let next_arm_jump = self.chunk.emit_jump(Op::Jump, self.line);
let scoped_fail_target = self.chunk.code.len();
self.chunk.emit(Op::PopScope, self.line);
self.chunk.emit(Op::Pop, self.line);
let next_arm_target = self.chunk.code.len();
for skip in constraint_skips {
self.chunk.patch_jump_to(skip, scoped_fail_target);
}
self.chunk.patch_jump_to(skip_type, type_fail_target);
self.chunk.patch_jump_to(next_arm_jump, next_arm_target);
}
Node::ListLiteral(elements) => {
self.chunk.emit(Op::Dup, self.line);
let typeof_idx =
self.chunk.add_constant(Constant::String("type_of".into()));
self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
self.chunk.emit(Op::Swap, self.line);
self.chunk.emit_u8(Op::Call, 1, self.line);
let list_str = self.chunk.add_constant(Constant::String("list".into()));
self.chunk.emit_u16(Op::Constant, list_str, self.line);
self.chunk.emit(Op::Equal, self.line);
let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
self.chunk.emit(Op::Dup, self.line);
let len_idx = self.chunk.add_constant(Constant::String("len".into()));
self.chunk.emit_u16(Op::Constant, len_idx, self.line);
self.chunk.emit(Op::Swap, self.line);
self.chunk.emit_u8(Op::Call, 1, self.line);
let count = self
.chunk
.add_constant(Constant::Int(elements.len() as i64));
self.chunk.emit_u16(Op::Constant, count, self.line);
self.chunk.emit(Op::GreaterEqual, self.line);
let skip_len = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
let mut constraint_skips = Vec::new();
let mut bindings = Vec::new();
self.begin_scope();
for (i, elem) in elements.iter().enumerate() {
match &elem.node {
Node::Identifier(name) if name != "_" => {
bindings.push((i, name.clone()));
}
Node::Identifier(_) => {} _ => {
self.chunk.emit(Op::Dup, self.line);
let idx_const =
self.chunk.add_constant(Constant::Int(i as i64));
self.chunk.emit_u16(Op::Constant, idx_const, self.line);
self.chunk.emit(Op::Subscript, self.line);
self.compile_node(elem)?;
self.chunk.emit(Op::Equal, self.line);
let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
constraint_skips.push(skip);
}
}
}
for (i, name) in &bindings {
self.chunk.emit(Op::Dup, self.line);
let idx_const = self.chunk.add_constant(Constant::Int(*i as i64));
self.chunk.emit_u16(Op::Constant, idx_const, self.line);
self.chunk.emit(Op::Subscript, self.line);
let name_idx =
self.chunk.add_constant(Constant::String(name.clone()));
self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
}
if let Some(ref guard) = arm.guard {
self.compile_node(guard)?;
let guard_skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
self.chunk.emit(Op::Pop, self.line);
self.compile_match_body(&arm.body)?;
self.end_scope();
end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
self.chunk.patch_jump(guard_skip);
self.chunk.emit(Op::Pop, self.line);
} else {
self.chunk.emit(Op::Pop, self.line);
self.compile_match_body(&arm.body)?;
self.end_scope();
end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
}
let pre_scope_fail_target = self.chunk.code.len();
self.chunk.emit(Op::Pop, self.line);
let next_arm_jump = self.chunk.emit_jump(Op::Jump, self.line);
let scoped_fail_target = self.chunk.code.len();
self.chunk.emit(Op::PopScope, self.line);
self.chunk.emit(Op::Pop, self.line);
let next_arm_target = self.chunk.code.len();
for skip in constraint_skips {
self.chunk.patch_jump_to(skip, scoped_fail_target);
}
self.chunk.patch_jump_to(skip_len, pre_scope_fail_target);
self.chunk.patch_jump_to(skip_type, pre_scope_fail_target);
self.chunk.patch_jump_to(next_arm_jump, next_arm_target);
}
_ => {
self.chunk.emit(Op::Dup, self.line);
self.compile_node(&arm.pattern)?;
self.chunk.emit(Op::Equal, self.line);
let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
if let Some(ref guard) = arm.guard {
self.compile_node(guard)?;
let guard_skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
self.begin_scope();
self.chunk.emit(Op::Pop, self.line);
self.compile_match_body(&arm.body)?;
self.end_scope();
end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
self.chunk.patch_jump(guard_skip);
self.chunk.emit(Op::Pop, self.line);
} else {
self.begin_scope();
self.chunk.emit(Op::Pop, self.line);
self.compile_match_body(&arm.body)?;
self.end_scope();
end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
}
self.chunk.patch_jump(skip);
self.chunk.emit(Op::Pop, self.line);
}
}
}
let msg_idx = self.chunk.add_constant(Constant::String(
"No match arm matched the value".to_string(),
));
self.chunk.emit(Op::Pop, self.line);
self.chunk.emit_u16(Op::Constant, msg_idx, self.line);
self.chunk.emit(Op::Throw, self.line);
for j in end_jumps {
self.chunk.patch_jump(j);
}
}
Node::RangeExpr {
start,
end,
inclusive,
} => {
let name_idx = self
.chunk
.add_constant(Constant::String("__range__".to_string()));
self.chunk.emit_u16(Op::Constant, name_idx, self.line);
self.compile_node(start)?;
self.compile_node(end)?;
if *inclusive {
self.chunk.emit(Op::True, self.line);
} else {
self.chunk.emit(Op::False, self.line);
}
self.chunk.emit_u8(Op::Call, 3, self.line);
}
Node::GuardStmt {
condition,
else_body,
} => {
self.compile_node(condition)?;
let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
self.chunk.emit(Op::Pop, self.line);
self.compile_scoped_block(else_body)?;
if !else_body.is_empty() && Self::produces_value(&else_body.last().unwrap().node) {
self.chunk.emit(Op::Pop, self.line);
}
let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
self.chunk.patch_jump(skip_jump);
self.chunk.emit(Op::Pop, self.line);
self.chunk.patch_jump(end_jump);
self.chunk.emit(Op::Nil, self.line);
}
Node::RequireStmt { condition, message } => {
self.compile_node(condition)?;
let ok_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
self.chunk.emit(Op::Pop, self.line);
if let Some(message) = message {
self.compile_node(message)?;
} else {
let idx = self
.chunk
.add_constant(Constant::String("require condition failed".to_string()));
self.chunk.emit_u16(Op::Constant, idx, self.line);
}
self.chunk.emit(Op::Throw, self.line);
self.chunk.patch_jump(ok_jump);
self.chunk.emit(Op::Pop, self.line);
}
Node::Block(stmts) => {
self.compile_scoped_block(stmts)?;
}
Node::DeadlineBlock { duration, body } => {
self.compile_node(duration)?;
self.chunk.emit(Op::DeadlineSetup, self.line);
self.compile_scoped_block(body)?;
self.chunk.emit(Op::DeadlineEnd, self.line);
}
Node::MutexBlock { body } => {
self.begin_scope();
for sn in body {
self.compile_node(sn)?;
if Self::produces_value(&sn.node) {
self.chunk.emit(Op::Pop, self.line);
}
}
self.chunk.emit(Op::Nil, self.line);
self.end_scope();
}
Node::DeferStmt { body } => {
self.finally_bodies
.push(FinallyEntry::Finally(body.clone()));
self.chunk.emit(Op::Nil, self.line);
}
Node::YieldExpr { value } => {
if let Some(val) = value {
self.compile_node(val)?;
} else {
self.chunk.emit(Op::Nil, self.line);
}
self.chunk.emit(Op::Yield, self.line);
}
Node::EnumConstruct {
enum_name,
variant,
args,
} => {
for arg in args {
self.compile_node(arg)?;
}
let enum_idx = self.chunk.add_constant(Constant::String(enum_name.clone()));
let var_idx = self.chunk.add_constant(Constant::String(variant.clone()));
self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
let hi = (var_idx >> 8) as u8;
let lo = var_idx as u8;
self.chunk.code.push(hi);
self.chunk.code.push(lo);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
let fc = args.len() as u16;
let fhi = (fc >> 8) as u8;
let flo = fc as u8;
self.chunk.code.push(fhi);
self.chunk.code.push(flo);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
}
Node::StructConstruct {
struct_name,
fields,
} => {
let make_idx = self
.chunk
.add_constant(Constant::String("__make_struct".to_string()));
let struct_name_idx = self
.chunk
.add_constant(Constant::String(struct_name.clone()));
self.chunk.emit_u16(Op::Constant, make_idx, self.line);
self.chunk
.emit_u16(Op::Constant, struct_name_idx, self.line);
for entry in fields {
self.compile_node(&entry.key)?;
self.compile_node(&entry.value)?;
}
self.chunk
.emit_u16(Op::BuildDict, fields.len() as u16, self.line);
self.chunk.emit_u8(Op::Call, 2, self.line);
}
Node::ImportDecl { path } => {
let idx = self.chunk.add_constant(Constant::String(path.clone()));
self.chunk.emit_u16(Op::Import, idx, self.line);
}
Node::SelectiveImport { names, path } => {
let path_idx = self.chunk.add_constant(Constant::String(path.clone()));
let names_str = names.join(",");
let names_idx = self.chunk.add_constant(Constant::String(names_str));
self.chunk
.emit_u16(Op::SelectiveImport, path_idx, self.line);
let hi = (names_idx >> 8) as u8;
let lo = names_idx as u8;
self.chunk.code.push(hi);
self.chunk.code.push(lo);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
}
Node::TryOperator { operand } => {
self.compile_node(operand)?;
self.chunk.emit(Op::TryUnwrap, self.line);
}
Node::ImplBlock { type_name, methods } => {
for method_sn in methods {
if let Node::FnDecl {
name, params, body, ..
} = &method_sn.node
{
let key_idx = self.chunk.add_constant(Constant::String(name.clone()));
self.chunk.emit_u16(Op::Constant, key_idx, self.line);
let mut fn_compiler = Compiler::new();
fn_compiler.enum_names = self.enum_names.clone();
fn_compiler.emit_default_preamble(params)?;
fn_compiler.emit_type_checks(params);
fn_compiler.compile_block(body)?;
fn_compiler.chunk.emit(Op::Nil, self.line);
fn_compiler.chunk.emit(Op::Return, self.line);
let func = CompiledFunction {
name: format!("{}.{}", type_name, name),
params: TypedParam::names(params),
default_start: TypedParam::default_start(params),
chunk: fn_compiler.chunk,
is_generator: false,
has_rest_param: false,
};
let fn_idx = self.chunk.functions.len();
self.chunk.functions.push(func);
self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
}
}
let method_count = methods
.iter()
.filter(|m| matches!(m.node, Node::FnDecl { .. }))
.count();
self.chunk
.emit_u16(Op::BuildDict, method_count as u16, self.line);
let impl_name = format!("__impl_{}", type_name);
let name_idx = self.chunk.add_constant(Constant::String(impl_name));
self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
}
Node::StructDecl { name, .. } => {
let mut fn_compiler = Compiler::new();
fn_compiler.enum_names = self.enum_names.clone();
let params = vec![TypedParam::untyped("__fields")];
fn_compiler.emit_default_preamble(¶ms)?;
let make_idx = fn_compiler
.chunk
.add_constant(Constant::String("__make_struct".into()));
fn_compiler
.chunk
.emit_u16(Op::Constant, make_idx, self.line);
let sname_idx = fn_compiler
.chunk
.add_constant(Constant::String(name.clone()));
fn_compiler
.chunk
.emit_u16(Op::Constant, sname_idx, self.line);
let fields_idx = fn_compiler
.chunk
.add_constant(Constant::String("__fields".into()));
fn_compiler
.chunk
.emit_u16(Op::GetVar, fields_idx, self.line);
fn_compiler.chunk.emit_u8(Op::Call, 2, self.line);
fn_compiler.chunk.emit(Op::Return, self.line);
let func = CompiledFunction {
name: name.clone(),
params: TypedParam::names(¶ms),
default_start: None,
chunk: fn_compiler.chunk,
is_generator: false,
has_rest_param: false,
};
let fn_idx = self.chunk.functions.len();
self.chunk.functions.push(func);
self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
}
Node::Pipeline { .. }
| Node::OverrideDecl { .. }
| Node::TypeDecl { .. }
| Node::EnumDecl { .. }
| Node::InterfaceDecl { .. } => {
self.chunk.emit(Op::Nil, self.line);
}
Node::TryCatch {
body,
error_var,
error_type,
catch_body,
finally_body,
} => {
let type_name = error_type.as_ref().and_then(|te| {
if let harn_parser::TypeExpr::Named(name) = te {
Some(name.clone())
} else {
None
}
});
let type_name_idx = if let Some(ref tn) = type_name {
self.chunk.add_constant(Constant::String(tn.clone()))
} else {
self.chunk.add_constant(Constant::String(String::new()))
};
let has_catch = !catch_body.is_empty() || error_var.is_some();
let has_finally = finally_body.is_some();
if has_catch && has_finally {
let finally_body = finally_body.as_ref().unwrap();
self.finally_bodies.push(FinallyEntry::CatchBarrier);
self.finally_bodies
.push(FinallyEntry::Finally(finally_body.clone()));
self.handler_depth += 1;
let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
self.emit_type_name_extra(type_name_idx);
self.compile_try_body(body)?;
self.handler_depth -= 1;
self.chunk.emit(Op::PopHandler, self.line);
self.compile_finally_inline(finally_body)?;
self.finally_bodies.pop(); self.finally_bodies.pop(); let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
self.chunk.patch_jump(catch_jump);
self.begin_scope();
self.compile_catch_binding(error_var)?;
self.handler_depth += 1;
let rethrow_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
let empty_type = self.chunk.add_constant(Constant::String(String::new()));
self.emit_type_name_extra(empty_type);
self.compile_try_body(catch_body)?;
self.handler_depth -= 1;
self.chunk.emit(Op::PopHandler, self.line);
self.end_scope();
let end_jump2 = self.chunk.emit_jump(Op::Jump, self.line);
self.chunk.patch_jump(rethrow_jump);
self.compile_plain_rethrow()?;
self.end_scope();
self.chunk.patch_jump(end_jump);
self.chunk.patch_jump(end_jump2);
} else if has_finally {
let finally_body = finally_body.as_ref().unwrap();
self.finally_bodies
.push(FinallyEntry::Finally(finally_body.clone()));
self.handler_depth += 1;
let error_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
let empty_type = self.chunk.add_constant(Constant::String(String::new()));
self.emit_type_name_extra(empty_type);
self.compile_try_body(body)?;
self.handler_depth -= 1;
self.chunk.emit(Op::PopHandler, self.line);
self.compile_finally_inline(finally_body)?;
let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
self.chunk.patch_jump(error_jump);
self.compile_plain_rethrow()?;
self.chunk.patch_jump(end_jump);
self.finally_bodies.pop(); } else {
self.finally_bodies.push(FinallyEntry::CatchBarrier);
self.handler_depth += 1;
let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
self.emit_type_name_extra(type_name_idx);
self.compile_try_body(body)?;
self.handler_depth -= 1;
self.chunk.emit(Op::PopHandler, self.line);
self.finally_bodies.pop(); let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
self.chunk.patch_jump(catch_jump);
self.begin_scope();
self.compile_catch_binding(error_var)?;
self.compile_try_body(catch_body)?;
self.end_scope();
self.chunk.patch_jump(end_jump);
}
}
Node::TryExpr { body } => {
self.handler_depth += 1;
let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
let empty_type = self.chunk.add_constant(Constant::String(String::new()));
self.emit_type_name_extra(empty_type);
self.compile_try_body(body)?;
self.handler_depth -= 1;
self.chunk.emit(Op::PopHandler, self.line);
let ok_idx = self.chunk.add_constant(Constant::String("Ok".to_string()));
self.chunk.emit_u16(Op::Constant, ok_idx, self.line);
self.chunk.emit(Op::Swap, self.line);
self.chunk.emit_u8(Op::Call, 1, self.line);
let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
self.chunk.patch_jump(catch_jump);
let err_idx = self.chunk.add_constant(Constant::String("Err".to_string()));
self.chunk.emit_u16(Op::Constant, err_idx, self.line);
self.chunk.emit(Op::Swap, self.line);
self.chunk.emit_u8(Op::Call, 1, self.line);
self.chunk.patch_jump(end_jump);
}
Node::Retry { count, body } => {
self.compile_node(count)?;
let counter_name = "__retry_counter__";
let counter_idx = self
.chunk
.add_constant(Constant::String(counter_name.to_string()));
self.chunk.emit_u16(Op::DefVar, counter_idx, self.line);
self.chunk.emit(Op::Nil, self.line);
let err_name = "__retry_last_error__";
let err_idx = self
.chunk
.add_constant(Constant::String(err_name.to_string()));
self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
let loop_start = self.chunk.current_offset();
let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
let empty_type = self.chunk.add_constant(Constant::String(String::new()));
let hi = (empty_type >> 8) as u8;
let lo = empty_type as u8;
self.chunk.code.push(hi);
self.chunk.code.push(lo);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
self.chunk.lines.push(self.line);
self.chunk.columns.push(self.column);
self.compile_block(body)?;
self.chunk.emit(Op::PopHandler, self.line);
let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
self.chunk.patch_jump(catch_jump);
self.chunk.emit(Op::Dup, self.line);
self.chunk.emit_u16(Op::SetVar, err_idx, self.line);
self.chunk.emit(Op::Pop, self.line);
self.chunk.emit_u16(Op::GetVar, counter_idx, self.line);
let one_idx = self.chunk.add_constant(Constant::Int(1));
self.chunk.emit_u16(Op::Constant, one_idx, self.line);
self.chunk.emit(Op::Sub, self.line);
self.chunk.emit(Op::Dup, self.line);
self.chunk.emit_u16(Op::SetVar, counter_idx, self.line);
let zero_idx = self.chunk.add_constant(Constant::Int(0));
self.chunk.emit_u16(Op::Constant, zero_idx, self.line);
self.chunk.emit(Op::Greater, self.line);
let retry_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
self.chunk.patch_jump(retry_jump);
self.chunk.emit(Op::Pop, self.line);
self.chunk.emit_u16(Op::GetVar, err_idx, self.line);
self.chunk.emit(Op::Throw, self.line);
self.chunk.patch_jump(end_jump);
self.chunk.emit(Op::Nil, self.line);
}
Node::Parallel {
mode,
expr,
variable,
body,
options,
} => {
let cap_expr = options
.iter()
.find(|(key, _)| key == "max_concurrent")
.map(|(_, value)| value);
if let Some(cap_expr) = cap_expr {
self.compile_node(cap_expr)?;
} else {
let zero_idx = self.chunk.add_constant(Constant::Int(0));
self.chunk.emit_u16(Op::Constant, zero_idx, self.line);
}
self.compile_node(expr)?;
let mut fn_compiler = Compiler::new();
fn_compiler.enum_names = self.enum_names.clone();
fn_compiler.compile_block(body)?;
fn_compiler.chunk.emit(Op::Return, self.line);
let (fn_name, params) = match mode {
ParallelMode::Count => (
"<parallel>",
vec![variable.clone().unwrap_or_else(|| "__i__".to_string())],
),
ParallelMode::Each => (
"<parallel_each>",
vec![variable.clone().unwrap_or_else(|| "__item__".to_string())],
),
ParallelMode::Settle => (
"<parallel_settle>",
vec![variable.clone().unwrap_or_else(|| "__item__".to_string())],
),
};
let func = CompiledFunction {
name: fn_name.to_string(),
params,
default_start: None,
chunk: fn_compiler.chunk,
is_generator: false,
has_rest_param: false,
};
let fn_idx = self.chunk.functions.len();
self.chunk.functions.push(func);
self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
let op = match mode {
ParallelMode::Count => Op::Parallel,
ParallelMode::Each => Op::ParallelMap,
ParallelMode::Settle => Op::ParallelSettle,
};
self.chunk.emit(op, self.line);
}
Node::SpawnExpr { body } => {
let mut fn_compiler = Compiler::new();
fn_compiler.enum_names = self.enum_names.clone();
fn_compiler.compile_block(body)?;
fn_compiler.chunk.emit(Op::Return, self.line);
let func = CompiledFunction {
name: "<spawn>".to_string(),
params: vec![],
default_start: None,
chunk: fn_compiler.chunk,
is_generator: false,
has_rest_param: false,
};
let fn_idx = self.chunk.functions.len();
self.chunk.functions.push(func);
self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
self.chunk.emit(Op::Spawn, self.line);
}
Node::SelectExpr {
cases,
timeout,
default_body,
} => {
let builtin_name = if timeout.is_some() {
"__select_timeout"
} else if default_body.is_some() {
"__select_try"
} else {
"__select_list"
};
let name_idx = self
.chunk
.add_constant(Constant::String(builtin_name.into()));
self.chunk.emit_u16(Op::Constant, name_idx, self.line);
for case in cases {
self.compile_node(&case.channel)?;
}
self.chunk
.emit_u16(Op::BuildList, cases.len() as u16, self.line);
if let Some((duration_expr, _)) = timeout {
self.compile_node(duration_expr)?;
self.chunk.emit_u8(Op::Call, 2, self.line);
} else {
self.chunk.emit_u8(Op::Call, 1, self.line);
}
self.temp_counter += 1;
let result_name = format!("__sel_result_{}__", self.temp_counter);
let result_idx = self
.chunk
.add_constant(Constant::String(result_name.clone()));
self.chunk.emit_u16(Op::DefVar, result_idx, self.line);
let mut end_jumps = Vec::new();
for (i, case) in cases.iter().enumerate() {
let get_r = self
.chunk
.add_constant(Constant::String(result_name.clone()));
self.chunk.emit_u16(Op::GetVar, get_r, self.line);
let idx_prop = self.chunk.add_constant(Constant::String("index".into()));
self.chunk.emit_u16(Op::GetProperty, idx_prop, self.line);
let case_i = self.chunk.add_constant(Constant::Int(i as i64));
self.chunk.emit_u16(Op::Constant, case_i, self.line);
self.chunk.emit(Op::Equal, self.line);
let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
self.chunk.emit(Op::Pop, self.line);
self.begin_scope();
let get_r2 = self
.chunk
.add_constant(Constant::String(result_name.clone()));
self.chunk.emit_u16(Op::GetVar, get_r2, self.line);
let val_prop = self.chunk.add_constant(Constant::String("value".into()));
self.chunk.emit_u16(Op::GetProperty, val_prop, self.line);
let var_idx = self
.chunk
.add_constant(Constant::String(case.variable.clone()));
self.chunk.emit_u16(Op::DefLet, var_idx, self.line);
self.compile_try_body(&case.body)?;
self.end_scope();
end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
self.chunk.patch_jump(skip);
self.chunk.emit(Op::Pop, self.line);
}
if let Some((_, ref timeout_body)) = timeout {
self.compile_try_body(timeout_body)?;
} else if let Some(ref def_body) = default_body {
self.compile_try_body(def_body)?;
} else {
self.chunk.emit(Op::Nil, self.line);
}
for ej in end_jumps {
self.chunk.patch_jump(ej);
}
}
Node::Spread(_) => {
return Err(CompileError {
message: "spread (...) can only be used inside list literals, dict literals, or function call arguments".into(),
line: self.line,
});
}
}
Ok(())
}
fn compile_destructuring(
&mut self,
pattern: &BindingPattern,
is_mutable: bool,
) -> Result<(), CompileError> {
let def_op = if is_mutable { Op::DefVar } else { Op::DefLet };
match pattern {
BindingPattern::Identifier(name) => {
let idx = self.chunk.add_constant(Constant::String(name.clone()));
self.chunk.emit_u16(def_op, idx, self.line);
}
BindingPattern::Dict(fields) => {
self.chunk.emit(Op::Dup, self.line);
let assert_idx = self
.chunk
.add_constant(Constant::String("__assert_dict".into()));
self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
self.chunk.emit(Op::Swap, self.line);
self.chunk.emit_u8(Op::Call, 1, self.line);
self.chunk.emit(Op::Pop, self.line);
let non_rest: Vec<_> = fields.iter().filter(|f| !f.is_rest).collect();
let rest_field = fields.iter().find(|f| f.is_rest);
for field in &non_rest {
self.chunk.emit(Op::Dup, self.line);
let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
self.chunk.emit_u16(Op::Constant, key_idx, self.line);
self.chunk.emit(Op::Subscript, self.line);
if let Some(default_expr) = &field.default_value {
self.chunk.emit(Op::Dup, self.line);
self.chunk.emit(Op::Nil, self.line);
self.chunk.emit(Op::NotEqual, self.line);
let skip_default = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
self.chunk.emit(Op::Pop, self.line);
self.chunk.emit(Op::Pop, self.line);
self.compile_node(default_expr)?;
let end = self.chunk.emit_jump(Op::Jump, self.line);
self.chunk.patch_jump(skip_default);
self.chunk.emit(Op::Pop, self.line);
self.chunk.patch_jump(end);
}
let binding_name = field.alias.as_deref().unwrap_or(&field.key);
let name_idx = self
.chunk
.add_constant(Constant::String(binding_name.to_string()));
self.chunk.emit_u16(def_op, name_idx, self.line);
}
if let Some(rest) = rest_field {
let fn_idx = self
.chunk
.add_constant(Constant::String("__dict_rest".into()));
self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
self.chunk.emit(Op::Swap, self.line);
for field in &non_rest {
let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
self.chunk.emit_u16(Op::Constant, key_idx, self.line);
}
self.chunk
.emit_u16(Op::BuildList, non_rest.len() as u16, self.line);
self.chunk.emit_u8(Op::Call, 2, self.line);
let rest_name = &rest.key;
let rest_idx = self.chunk.add_constant(Constant::String(rest_name.clone()));
self.chunk.emit_u16(def_op, rest_idx, self.line);
} else {
self.chunk.emit(Op::Pop, self.line);
}
}
BindingPattern::Pair(first_name, second_name) => {
self.chunk.emit(Op::Dup, self.line);
let first_key_idx = self
.chunk
.add_constant(Constant::String("first".to_string()));
self.chunk
.emit_u16(Op::GetProperty, first_key_idx, self.line);
let first_name_idx = self
.chunk
.add_constant(Constant::String(first_name.clone()));
self.chunk.emit_u16(def_op, first_name_idx, self.line);
let second_key_idx = self
.chunk
.add_constant(Constant::String("second".to_string()));
self.chunk
.emit_u16(Op::GetProperty, second_key_idx, self.line);
let second_name_idx = self
.chunk
.add_constant(Constant::String(second_name.clone()));
self.chunk.emit_u16(def_op, second_name_idx, self.line);
}
BindingPattern::List(elements) => {
self.chunk.emit(Op::Dup, self.line);
let assert_idx = self
.chunk
.add_constant(Constant::String("__assert_list".into()));
self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
self.chunk.emit(Op::Swap, self.line);
self.chunk.emit_u8(Op::Call, 1, self.line);
self.chunk.emit(Op::Pop, self.line);
let non_rest: Vec<_> = elements.iter().filter(|e| !e.is_rest).collect();
let rest_elem = elements.iter().find(|e| e.is_rest);
for (i, elem) in non_rest.iter().enumerate() {
self.chunk.emit(Op::Dup, self.line);
let idx_const = self.chunk.add_constant(Constant::Int(i as i64));
self.chunk.emit_u16(Op::Constant, idx_const, self.line);
self.chunk.emit(Op::Subscript, self.line);
if let Some(default_expr) = &elem.default_value {
self.chunk.emit(Op::Dup, self.line);
self.chunk.emit(Op::Nil, self.line);
self.chunk.emit(Op::NotEqual, self.line);
let skip_default = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
self.chunk.emit(Op::Pop, self.line);
self.chunk.emit(Op::Pop, self.line);
self.compile_node(default_expr)?;
let end = self.chunk.emit_jump(Op::Jump, self.line);
self.chunk.patch_jump(skip_default);
self.chunk.emit(Op::Pop, self.line);
self.chunk.patch_jump(end);
}
let name_idx = self.chunk.add_constant(Constant::String(elem.name.clone()));
self.chunk.emit_u16(def_op, name_idx, self.line);
}
if let Some(rest) = rest_elem {
let start_idx = self
.chunk
.add_constant(Constant::Int(non_rest.len() as i64));
self.chunk.emit_u16(Op::Constant, start_idx, self.line);
self.chunk.emit(Op::Nil, self.line);
self.chunk.emit(Op::Slice, self.line);
let rest_name_idx =
self.chunk.add_constant(Constant::String(rest.name.clone()));
self.chunk.emit_u16(def_op, rest_name_idx, self.line);
} else {
self.chunk.emit(Op::Pop, self.line);
}
}
}
Ok(())
}
fn produces_value(node: &Node) -> bool {
match node {
Node::LetBinding { .. }
| Node::VarBinding { .. }
| Node::Assignment { .. }
| Node::ReturnStmt { .. }
| Node::FnDecl { .. }
| Node::ToolDecl { .. }
| Node::ImplBlock { .. }
| Node::StructDecl { .. }
| Node::EnumDecl { .. }
| Node::InterfaceDecl { .. }
| Node::TypeDecl { .. }
| Node::ThrowStmt { .. }
| Node::BreakStmt
| Node::ContinueStmt
| Node::RequireStmt { .. }
| Node::DeferStmt { .. } => false,
Node::TryCatch { .. }
| Node::TryExpr { .. }
| Node::Retry { .. }
| Node::GuardStmt { .. }
| Node::DeadlineBlock { .. }
| Node::MutexBlock { .. }
| Node::Spread(_) => true,
_ => true,
}
}
}
impl Compiler {
pub fn compile_fn_body(
&mut self,
params: &[TypedParam],
body: &[SNode],
source_file: Option<String>,
) -> Result<CompiledFunction, CompileError> {
let mut fn_compiler = Compiler::new();
fn_compiler.enum_names = self.enum_names.clone();
fn_compiler.emit_default_preamble(params)?;
fn_compiler.emit_type_checks(params);
let is_gen = body_contains_yield(body);
fn_compiler.compile_block(body)?;
fn_compiler.chunk.emit(Op::Nil, 0);
fn_compiler.chunk.emit(Op::Return, 0);
fn_compiler.chunk.source_file = source_file;
Ok(CompiledFunction {
name: String::new(),
params: TypedParam::names(params),
default_start: TypedParam::default_start(params),
chunk: fn_compiler.chunk,
is_generator: is_gen,
has_rest_param: false,
})
}
fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
self.begin_scope();
if body.is_empty() {
self.chunk.emit(Op::Nil, self.line);
} else {
self.compile_block(body)?;
if !Self::produces_value(&body.last().unwrap().node) {
self.chunk.emit(Op::Nil, self.line);
}
}
self.end_scope();
Ok(())
}
fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
match op {
"+" => self.chunk.emit(Op::Add, self.line),
"-" => self.chunk.emit(Op::Sub, self.line),
"*" => self.chunk.emit(Op::Mul, self.line),
"/" => self.chunk.emit(Op::Div, self.line),
"%" => self.chunk.emit(Op::Mod, self.line),
_ => {
return Err(CompileError {
message: format!("Unknown compound operator: {op}"),
line: self.line,
})
}
}
Ok(())
}
fn root_var_name(&self, node: &SNode) -> Option<String> {
match &node.node {
Node::Identifier(name) => Some(name.clone()),
Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
self.root_var_name(object)
}
Node::SubscriptAccess { object, .. } => self.root_var_name(object),
_ => None,
}
}
fn compile_top_level_declarations(&mut self, program: &[SNode]) -> Result<(), CompileError> {
for sn in program {
if matches!(&sn.node, Node::LetBinding { .. } | Node::VarBinding { .. }) {
self.compile_node(sn)?;
}
}
for sn in program {
if matches!(
&sn.node,
Node::FnDecl { .. }
| Node::ToolDecl { .. }
| Node::ImplBlock { .. }
| Node::StructDecl { .. }
| Node::EnumDecl { .. }
| Node::InterfaceDecl { .. }
| Node::TypeDecl { .. }
) {
self.compile_node(sn)?;
}
}
Ok(())
}
}
impl Compiler {
fn collect_enum_names(nodes: &[SNode], names: &mut std::collections::HashSet<String>) {
for sn in nodes {
match &sn.node {
Node::EnumDecl { name, .. } => {
names.insert(name.clone());
}
Node::Pipeline { body, .. } => {
Self::collect_enum_names(body, names);
}
Node::FnDecl { body, .. } | Node::ToolDecl { body, .. } => {
Self::collect_enum_names(body, names);
}
Node::Block(stmts) => {
Self::collect_enum_names(stmts, names);
}
_ => {}
}
}
}
fn collect_interface_methods(
nodes: &[SNode],
interfaces: &mut std::collections::HashMap<String, Vec<String>>,
) {
for sn in nodes {
match &sn.node {
Node::InterfaceDecl { name, methods, .. } => {
let method_names: Vec<String> =
methods.iter().map(|m| m.name.clone()).collect();
interfaces.insert(name.clone(), method_names);
}
Node::Pipeline { body, .. }
| Node::FnDecl { body, .. }
| Node::ToolDecl { body, .. } => {
Self::collect_interface_methods(body, interfaces);
}
Node::Block(stmts) => {
Self::collect_interface_methods(stmts, interfaces);
}
_ => {}
}
}
}
}
impl Default for Compiler {
fn default() -> Self {
Self::new()
}
}
fn body_contains_yield(nodes: &[SNode]) -> bool {
nodes.iter().any(|sn| node_contains_yield(&sn.node))
}
fn node_contains_yield(node: &Node) -> bool {
match node {
Node::YieldExpr { .. } => true,
Node::FnDecl { .. } | Node::Closure { .. } => false,
Node::Block(stmts) => body_contains_yield(stmts),
Node::IfElse {
condition,
then_body,
else_body,
} => {
node_contains_yield(&condition.node)
|| body_contains_yield(then_body)
|| else_body.as_ref().is_some_and(|b| body_contains_yield(b))
}
Node::WhileLoop { condition, body } => {
node_contains_yield(&condition.node) || body_contains_yield(body)
}
Node::ForIn { iterable, body, .. } => {
node_contains_yield(&iterable.node) || body_contains_yield(body)
}
Node::TryCatch {
body, catch_body, ..
} => body_contains_yield(body) || body_contains_yield(catch_body),
Node::TryExpr { body } => body_contains_yield(body),
_ => false,
}
}
fn contains_pipe_placeholder(node: &SNode) -> bool {
match &node.node {
Node::Identifier(name) if name == "_" => true,
Node::FunctionCall { args, .. } => args.iter().any(contains_pipe_placeholder),
Node::MethodCall { object, args, .. } => {
contains_pipe_placeholder(object) || args.iter().any(contains_pipe_placeholder)
}
Node::BinaryOp { left, right, .. } => {
contains_pipe_placeholder(left) || contains_pipe_placeholder(right)
}
Node::UnaryOp { operand, .. } => contains_pipe_placeholder(operand),
Node::ListLiteral(items) => items.iter().any(contains_pipe_placeholder),
Node::PropertyAccess { object, .. } => contains_pipe_placeholder(object),
Node::SubscriptAccess { object, index } => {
contains_pipe_placeholder(object) || contains_pipe_placeholder(index)
}
_ => false,
}
}
fn replace_pipe_placeholder(node: &SNode) -> SNode {
let new_node = match &node.node {
Node::Identifier(name) if name == "_" => Node::Identifier("__pipe".into()),
Node::FunctionCall { name, args } => Node::FunctionCall {
name: name.clone(),
args: args.iter().map(replace_pipe_placeholder).collect(),
},
Node::MethodCall {
object,
method,
args,
} => Node::MethodCall {
object: Box::new(replace_pipe_placeholder(object)),
method: method.clone(),
args: args.iter().map(replace_pipe_placeholder).collect(),
},
Node::BinaryOp { op, left, right } => Node::BinaryOp {
op: op.clone(),
left: Box::new(replace_pipe_placeholder(left)),
right: Box::new(replace_pipe_placeholder(right)),
},
Node::UnaryOp { op, operand } => Node::UnaryOp {
op: op.clone(),
operand: Box::new(replace_pipe_placeholder(operand)),
},
Node::ListLiteral(items) => {
Node::ListLiteral(items.iter().map(replace_pipe_placeholder).collect())
}
Node::PropertyAccess { object, property } => Node::PropertyAccess {
object: Box::new(replace_pipe_placeholder(object)),
property: property.clone(),
},
Node::SubscriptAccess { object, index } => Node::SubscriptAccess {
object: Box::new(replace_pipe_placeholder(object)),
index: Box::new(replace_pipe_placeholder(index)),
},
_ => return node.clone(),
};
SNode::new(new_node, node.span)
}
#[cfg(test)]
mod tests {
use super::*;
use harn_lexer::Lexer;
use harn_parser::Parser;
fn compile_source(source: &str) -> Chunk {
let mut lexer = Lexer::new(source);
let tokens = lexer.tokenize().unwrap();
let mut parser = Parser::new(tokens);
let program = parser.parse().unwrap();
Compiler::new().compile(&program).unwrap()
}
#[test]
fn test_compile_arithmetic() {
let chunk = compile_source("pipeline test(task) { let x = 2 + 3 }");
assert!(!chunk.code.is_empty());
assert!(chunk.constants.contains(&Constant::Int(2)));
assert!(chunk.constants.contains(&Constant::Int(3)));
}
#[test]
fn test_compile_function_call() {
let chunk = compile_source("pipeline test(task) { log(42) }");
let disasm = chunk.disassemble("test");
assert!(disasm.contains("CALL"));
}
#[test]
fn test_compile_if_else() {
let chunk =
compile_source(r#"pipeline test(task) { if true { log("yes") } else { log("no") } }"#);
let disasm = chunk.disassemble("test");
assert!(disasm.contains("JUMP_IF_FALSE"));
assert!(disasm.contains("JUMP"));
}
#[test]
fn test_compile_while() {
let chunk = compile_source("pipeline test(task) { var i = 0\n while i < 5 { i = i + 1 } }");
let disasm = chunk.disassemble("test");
assert!(disasm.contains("JUMP_IF_FALSE"));
assert!(disasm.contains("JUMP"));
}
#[test]
fn test_compile_closure() {
let chunk = compile_source("pipeline test(task) { let f = { x -> x * 2 } }");
assert!(!chunk.functions.is_empty());
assert_eq!(chunk.functions[0].params, vec!["x"]);
}
#[test]
fn test_compile_list() {
let chunk = compile_source("pipeline test(task) { let a = [1, 2, 3] }");
let disasm = chunk.disassemble("test");
assert!(disasm.contains("BUILD_LIST"));
}
#[test]
fn test_compile_dict() {
let chunk = compile_source(r#"pipeline test(task) { let d = {name: "test"} }"#);
let disasm = chunk.disassemble("test");
assert!(disasm.contains("BUILD_DICT"));
}
#[test]
fn test_disassemble() {
let chunk = compile_source("pipeline test(task) { log(2 + 3) }");
let disasm = chunk.disassemble("test");
assert!(disasm.contains("CONSTANT"));
assert!(disasm.contains("ADD"));
assert!(disasm.contains("CALL"));
}
}