#![forbid(unsafe_code)]
mod registry;
mod symbol;
use std::collections::HashMap;
use std::rc::Rc;
use maat_ast::{FmtSegment, parse_format_string, unescape_string, *};
use maat_bytecode::{
Bytecode, Instruction, Instructions, MAX_CONSTANT_POOL_SIZE, MAX_ENUM_VARIANTS, Opcode,
TypeTag, encode,
};
use maat_errors::{CompileError, CompileErrorKind, Error, Result};
use maat_runtime::{CompiledFn, Integer, TypeDef, Value, VariantInfo};
use maat_span::{SourceMap, Span};
use registry::{BUILTIN_METHOD_PREFIXES, VariantEntry};
pub use symbol::{Symbol, SymbolScope, SymbolsTable};
impl CompilationScope {
fn new() -> Self {
Self {
instructions: Instructions::new(),
last_instruction: None,
previous_instruction: None,
source_map: SourceMap::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct Compiler {
constants: Vec<Value>,
symbols_table: SymbolsTable,
scopes: Vec<CompilationScope>,
scope_index: usize,
loop_contexts: Vec<LoopContext>,
for_loop_counter: usize,
type_registry: Vec<TypeDef>,
variant_index: HashMap<String, VariantEntry>,
}
#[derive(Debug, Clone)]
struct CompilationScope {
instructions: Instructions,
last_instruction: Option<Instruction>,
previous_instruction: Option<Instruction>,
source_map: SourceMap,
}
#[derive(Debug, Clone)]
struct LoopContext {
label: Option<String>,
continue_target: Option<usize>,
break_jumps: Vec<usize>,
continue_jumps: Vec<usize>,
}
impl Default for Compiler {
fn default() -> Self {
Self::new()
}
}
impl Compiler {
const JUMP: usize = 9999;
pub fn new() -> Self {
let mut symbols_table = SymbolsTable::new();
registry::register_builtins(&mut symbols_table);
let type_registry = registry::builtin_type_registry();
let variant_index = registry::build_variant_index(&type_registry);
Self {
constants: Vec::new(),
symbols_table,
scopes: vec![CompilationScope::new()],
scope_index: 0,
loop_contexts: Vec::new(),
for_loop_counter: 0,
type_registry,
variant_index,
}
}
pub fn with_state(mut symbols_table: SymbolsTable, constants: Vec<Value>) -> Self {
registry::register_builtins(&mut symbols_table);
let type_registry = registry::builtin_type_registry();
let variant_index = registry::build_variant_index(&type_registry);
Self {
constants,
symbols_table,
scopes: vec![CompilationScope::new()],
scope_index: 0,
loop_contexts: Vec::new(),
for_loop_counter: 0,
type_registry,
variant_index,
}
}
pub fn symbols_table_mut(&mut self) -> &mut SymbolsTable {
&mut self.symbols_table
}
pub fn register_type(&mut self, typedef: TypeDef) {
let registry_index = self.type_registry.len();
if let TypeDef::Enum {
ref name,
ref variants,
} = typedef
{
registry::index_variants(
&mut self.variant_index,
registry_index,
variants,
name,
true,
);
}
self.type_registry.push(typedef);
}
pub fn bytecode(mut self) -> Result<Bytecode> {
let scope = self
.scopes
.pop()
.ok_or(CompileError::new(CompileErrorKind::ScopeUnderflow))?;
Ok(Bytecode {
instructions: scope.instructions,
constants: self.constants,
source_map: scope.source_map,
type_registry: self.type_registry,
})
}
pub fn symbols_table(&self) -> &SymbolsTable {
&self.symbols_table
}
pub fn compile(&mut self, node: &Node) -> Result<()> {
match node {
Node::Program(program) => self.compile_program(program),
Node::Stmt(stmt) => self.compile_statement(stmt),
Node::Expr(expr) => self.compile_expression(expr),
}
}
pub fn compile_program(&mut self, program: &Program) -> Result<()> {
for stmt in &program.statements {
if let Stmt::FuncDef(fn_item) = stmt {
let span = fn_item.span;
match self.symbols_table.define_symbol(&fn_item.name, false) {
Ok(_) => {}
Err(e) => return Err(self.attach_span(e, span)),
}
}
}
for stmt in &program.statements {
self.compile_statement(stmt)?;
}
Ok(())
}
fn compile_statement(&mut self, stmt: &Stmt) -> Result<()> {
match stmt {
Stmt::Expr(expr_stmt) => {
self.compile_expression(&expr_stmt.value)?;
self.emit(Opcode::Pop, &[], expr_stmt.span);
Ok(())
}
Stmt::Block(block) => self.compile_block_statement(block),
Stmt::Let(let_stmt) => {
let span = let_stmt.span;
if let Some(pattern) = &let_stmt.pattern {
self.compile_expression(&let_stmt.value)?;
self.compile_let_destructure(pattern, span)?;
Ok(())
} else {
if let Expr::Lambda(lambda) = &let_stmt.value {
self.compile_fn_body(
Some(&let_stmt.ident),
lambda.param_names(),
lambda.params.len(),
&lambda.body,
lambda.span,
)?;
} else {
self.compile_expression(&let_stmt.value)?;
}
self.define_and_set(&let_stmt.ident, let_stmt.mutable, span)?;
Ok(())
}
}
Stmt::ReAssign(assign_stmt) => self.compile_reassign(assign_stmt),
Stmt::Return(ret_stmt) => {
self.compile_expression(&ret_stmt.value)?;
self.emit(Opcode::ReturnValue, &[], ret_stmt.span);
Ok(())
}
Stmt::FuncDef(fn_item) => {
let span = fn_item.span;
self.compile_fn_body(
Some(&fn_item.name),
fn_item.param_names(),
fn_item.params.len(),
&fn_item.body,
span,
)?;
self.define_and_set(&fn_item.name, false, span)?;
Ok(())
}
Stmt::Loop(loop_stmt) => self.compile_loop(loop_stmt),
Stmt::While(while_stmt) => self.compile_while(while_stmt),
Stmt::StructDecl(decl) => {
self.register_type(TypeDef::Struct {
name: decl.name.clone(),
field_names: decl.fields.iter().map(|f| f.name.clone()).collect(),
});
Ok(())
}
Stmt::EnumDecl(decl) => {
let span = decl.span;
if decl.variants.len() > MAX_ENUM_VARIANTS {
return Err(CompileErrorKind::VariantTagOverflow {
name: decl.name.clone(),
count: decl.variants.len(),
max: MAX_ENUM_VARIANTS,
}
.at(span)
.into());
}
let variants = decl
.variants
.iter()
.map(|v| {
let count = match &v.kind {
EnumVariantKind::Unit => 0,
EnumVariantKind::Tuple(fields) => fields.len(),
EnumVariantKind::Struct(fields) => fields.len(),
};
let field_count = u8::try_from(count).map_err(|_| {
Error::from(
CompileErrorKind::UnsupportedExpr {
expr_type: format!(
"variant `{}` has {count} fields, exceeding the u8 maximum",
v.name
),
}
.at(span),
)
})?;
Ok(VariantInfo {
name: v.name.clone(),
field_count,
})
})
.collect::<Result<Vec<_>>>()?;
self.register_type(TypeDef::Enum {
name: decl.name.clone(),
variants,
});
Ok(())
}
Stmt::TraitDecl(_) => Ok(()),
Stmt::ImplBlock(impl_block) => self.compile_impl_block(impl_block),
Stmt::Use(_) | Stmt::Mod(_) => Ok(()),
Stmt::For(for_stmt) => {
if matches!(*for_stmt.iterable, Expr::Range(_)) {
self.compile_for_range(for_stmt)?;
} else if for_stmt.pattern.is_some() {
self.compile_for_map(for_stmt)?;
} else {
self.compile_for_array(for_stmt)?;
}
Ok(())
}
}
}
fn compile_reassign(&mut self, assign_stmt: &ReAssignStmt) -> Result<()> {
let span = assign_stmt.span;
let symbol = self.resolve_or_error(&assign_stmt.ident, span)?;
if !symbol.mutable {
return Err(CompileErrorKind::ImmutableAssignment {
name: assign_stmt.ident.clone(),
}
.at(span)
.into());
}
self.compile_expression(&assign_stmt.value)?;
match symbol.scope {
SymbolScope::Global => self.emit(Opcode::SetGlobal, &[symbol.index], span),
SymbolScope::Local | SymbolScope::Free => {
self.emit(Opcode::SetLocal, &[symbol.index], span)
}
SymbolScope::Builtin | SymbolScope::Function => {
return Err(CompileErrorKind::ImmutableAssignment {
name: assign_stmt.ident.clone(),
}
.at(span)
.into());
}
};
Ok(())
}
fn compile_loop(&mut self, loop_stmt: &LoopStmt) -> Result<()> {
let span = loop_stmt.span;
let bound = loop_stmt.bound;
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let counter_name = format!("__bound_{id}");
let bound_name = format!("__blimit_{id}");
let bound_idx = self.add_constant(Value::Integer(Integer::I64(bound as i64)))?;
self.emit(Opcode::Constant, &[bound_idx], span);
let bound_sym = self.define_and_set(&bound_name, false, span)?;
let zero_idx = self.add_constant(Value::Integer(Integer::I64(0)))?;
self.emit(Opcode::Constant, &[zero_idx], span);
let counter_sym = self.define_and_set(&counter_name, false, span)?;
let loop_start = self.current_instructions().len();
self.loop_contexts.push(LoopContext {
label: loop_stmt.label.clone(),
continue_target: None,
break_jumps: Vec::new(),
continue_jumps: Vec::new(),
});
self.load_symbol(&counter_sym, span);
self.load_symbol(&bound_sym, span);
self.emit(Opcode::LessThan, &[], span);
let exit_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.compile_block_statement(&loop_stmt.body)?;
let continue_target = self.current_instructions().len();
self.load_symbol(&counter_sym, span);
let one_idx = self.add_constant(Value::Integer(Integer::I64(1)))?;
self.emit(Opcode::Constant, &[one_idx], span);
self.emit(Opcode::Add, &[], span);
self.emit_set_symbol(&counter_sym, span);
self.emit(Opcode::Jump, &[loop_start], span);
let loop_exit = self.current_instructions().len();
self.replace_operand(exit_jump, loop_exit)?;
let ctx = self
.loop_contexts
.pop()
.expect("loop context was just pushed");
for jump_pos in ctx.break_jumps {
self.replace_operand(jump_pos, loop_exit)?;
}
for jump_pos in ctx.continue_jumps {
self.replace_operand(jump_pos, continue_target)?;
}
Ok(())
}
fn compile_while(&mut self, while_stmt: &WhileStmt) -> Result<()> {
let span = while_stmt.span;
let bound = while_stmt.bound;
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let counter_name = format!("__bound_{id}");
let bound_name = format!("__blimit_{id}");
let bound_idx = self.add_constant(Value::Integer(Integer::I64(bound as i64)))?;
self.emit(Opcode::Constant, &[bound_idx], span);
let bound_sym = self.define_and_set(&bound_name, false, span)?;
let zero_idx = self.add_constant(Value::Integer(Integer::I64(0)))?;
self.emit(Opcode::Constant, &[zero_idx], span);
let counter_sym = self.define_and_set(&counter_name, false, span)?;
let loop_start = self.current_instructions().len();
self.loop_contexts.push(LoopContext {
label: while_stmt.label.clone(),
continue_target: None,
break_jumps: Vec::new(),
continue_jumps: Vec::new(),
});
self.compile_expression(&while_stmt.condition)?;
let cond_exit = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.load_symbol(&counter_sym, span);
self.load_symbol(&bound_sym, span);
self.emit(Opcode::LessThan, &[], span);
let bound_exit = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.compile_block_statement(&while_stmt.body)?;
let continue_target = self.current_instructions().len();
self.load_symbol(&counter_sym, span);
let one_idx = self.add_constant(Value::Integer(Integer::I64(1)))?;
self.emit(Opcode::Constant, &[one_idx], span);
self.emit(Opcode::Add, &[], span);
self.emit_set_symbol(&counter_sym, span);
self.emit(Opcode::Jump, &[loop_start], span);
let loop_exit = self.current_instructions().len();
self.replace_operand(cond_exit, loop_exit)?;
self.replace_operand(bound_exit, loop_exit)?;
let ctx = self
.loop_contexts
.pop()
.expect("loop context was just pushed");
for jump_pos in ctx.break_jumps {
self.replace_operand(jump_pos, loop_exit)?;
}
for jump_pos in ctx.continue_jumps {
self.replace_operand(jump_pos, continue_target)?;
}
Ok(())
}
fn compile_for_range(&mut self, for_stmt: &ForStmt) -> Result<()> {
let span = for_stmt.span;
let Expr::Range(ref range) = *for_stmt.iterable else {
unreachable!("compile_for_range called with non-range iterable");
};
let inclusive = range.inclusive;
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let end_name = format!("__end_{id}");
let i_name = format!("__i_{id}");
self.compile_expression(&range.end)?;
let end_sym = self.define_and_set(&end_name, false, span)?;
self.compile_expression(&range.start)?;
let i_sym = self.define_and_set(&i_name, false, span)?;
let loop_start = self.current_instructions().len();
self.loop_contexts.push(LoopContext {
label: for_stmt.label.clone(),
continue_target: None,
break_jumps: Vec::new(),
continue_jumps: Vec::new(),
});
self.load_symbol(&i_sym, span);
self.load_symbol(&end_sym, span);
if inclusive {
self.emit(Opcode::GreaterThan, &[], span);
self.emit(Opcode::Bang, &[], span);
} else {
self.emit(Opcode::LessThan, &[], span);
}
let exit_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.load_symbol(&i_sym, span);
let _elem_sym = self.define_and_set(&for_stmt.ident, false, span)?;
self.compile_block_statement(&for_stmt.body)?;
let elem_kind = range.kind.as_ref();
self.finalize_counting_loop(&i_sym, loop_start, exit_jump, elem_kind, span)
}
fn compile_for_array(&mut self, for_stmt: &ForStmt) -> Result<()> {
let span = for_stmt.span;
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let iter_name = format!("__iter_{id}");
let len_name = format!("__len_{id}");
let i_name = format!("__i_{id}");
self.compile_expression(&for_stmt.iterable)?;
let iter_sym = self.define_and_set(&iter_name, false, span)?;
let len_builtin = self.resolve_or_error("Vector::len", span)?;
self.load_symbol(&len_builtin, span);
self.load_symbol(&iter_sym, span);
self.emit(Opcode::Call, &[1], span);
let len_sym = self.define_and_set(&len_name, false, span)?;
let zero_idx = self.add_constant(Value::Integer(Integer::I64(0)))?;
self.emit(Opcode::Constant, &[zero_idx], span);
let i_sym = self.define_and_set(&i_name, false, span)?;
let loop_start = self.current_instructions().len();
self.loop_contexts.push(LoopContext {
label: for_stmt.label.clone(),
continue_target: None,
break_jumps: Vec::new(),
continue_jumps: Vec::new(),
});
self.load_symbol(&i_sym, span);
self.load_symbol(&len_sym, span);
self.emit(Opcode::LessThan, &[], span);
let exit_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.load_symbol(&iter_sym, span);
self.load_symbol(&i_sym, span);
self.emit(Opcode::Index, &[], span);
let _elem_sym = self.define_and_set(&for_stmt.ident, false, span)?;
self.compile_block_statement(&for_stmt.body)?;
self.finalize_counting_loop(&i_sym, loop_start, exit_jump, None, span)
}
fn compile_for_map(&mut self, for_stmt: &ForStmt) -> Result<()> {
let span = for_stmt.span;
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let iter_name = format!("__miter_{id}");
let keys_name = format!("__mkeys_{id}");
let len_name = format!("__mlen_{id}");
let i_name = format!("__mi_{id}");
self.compile_expression(&for_stmt.iterable)?;
let iter_sym = self.define_and_set(&iter_name, false, span)?;
let keys_builtin = self.resolve_or_error("Map::keys", span)?;
self.load_symbol(&keys_builtin, span);
self.load_symbol(&iter_sym, span);
self.emit(Opcode::Call, &[1], span);
let keys_sym = self.define_and_set(&keys_name, false, span)?;
let len_builtin = self.resolve_or_error("Vector::len", span)?;
self.load_symbol(&len_builtin, span);
self.load_symbol(&keys_sym, span);
self.emit(Opcode::Call, &[1], span);
let len_sym = self.define_and_set(&len_name, false, span)?;
let zero_idx = self.add_constant(Value::Integer(Integer::I64(0)))?;
self.emit(Opcode::Constant, &[zero_idx], span);
let i_sym = self.define_and_set(&i_name, false, span)?;
let loop_start = self.current_instructions().len();
self.loop_contexts.push(LoopContext {
label: for_stmt.label.clone(),
continue_target: None,
break_jumps: Vec::new(),
continue_jumps: Vec::new(),
});
self.load_symbol(&i_sym, span);
self.load_symbol(&len_sym, span);
self.emit(Opcode::LessThan, &[], span);
let exit_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.load_symbol(&keys_sym, span);
self.load_symbol(&i_sym, span);
self.emit(Opcode::Index, &[], span);
let key_name = format!("__mkey_{id}");
let key_sym = self.define_and_set(&key_name, false, span)?;
self.load_symbol(&iter_sym, span);
self.load_symbol(&key_sym, span);
self.emit(Opcode::Index, &[], span);
let val_name = format!("__mval_{id}");
let val_sym = self.define_and_set(&val_name, false, span)?;
self.load_symbol(&key_sym, span);
self.load_symbol(&val_sym, span);
self.emit(Opcode::Tuple, &[2], span);
let pattern = for_stmt
.pattern
.as_ref()
.expect("compile_for_map requires a pattern");
self.compile_let_destructure(pattern, span)?;
self.compile_block_statement(&for_stmt.body)?;
self.finalize_counting_loop(&i_sym, loop_start, exit_jump, None, span)
}
fn finalize_counting_loop(
&mut self,
i_sym: &Symbol,
loop_start: usize,
exit_jump: usize,
elem_kind: Option<&NumKind>,
span: Span,
) -> Result<()> {
let continue_target = self.current_instructions().len();
self.load_symbol(i_sym, span);
let one = elem_kind
.map(Integer::one_of_kind)
.unwrap_or(Integer::I64(1));
let one_idx = self.add_constant(Value::Integer(one))?;
self.emit(Opcode::Constant, &[one_idx], span);
self.emit(Opcode::Add, &[], span);
self.emit_set_symbol(i_sym, span);
self.emit(Opcode::Jump, &[loop_start], span);
let loop_exit = self.current_instructions().len();
self.replace_operand(exit_jump, loop_exit)?;
let ctx = self
.loop_contexts
.pop()
.expect("loop context was just pushed");
for jump_pos in ctx.break_jumps {
self.replace_operand(jump_pos, loop_exit)?;
}
for jump_pos in ctx.continue_jumps {
self.replace_operand(jump_pos, continue_target)?;
}
Ok(())
}
fn compile_block_statement(&mut self, block: &BlockStmt) -> Result<()> {
self.symbols_table.push_block_scope();
for stmt in &block.statements {
self.compile_statement(stmt)?;
}
self.symbols_table.pop_block_scope();
Ok(())
}
fn compile_numeric_constant(&mut self, val: Value, span: Span) -> Result<()> {
let index = self.add_constant(val)?;
self.emit(Opcode::Constant, &[index], span);
Ok(())
}
fn compile_expression(&mut self, expr: &Expr) -> Result<()> {
let span = expr.span();
match expr {
Expr::Number(lit) => {
let val = Value::from_number_literal(lit)
.map_err(|msg| CompileErrorKind::UnsupportedExpr { expr_type: msg }.at(span))?;
self.compile_numeric_constant(val, span)
}
Expr::Bool(b) => {
let opcode = if b.value { Opcode::True } else { Opcode::False };
self.emit(opcode, &[], span);
Ok(())
}
Expr::Prefix(prefix_expr) => {
self.compile_expression(&prefix_expr.operand)?;
let opcode = match prefix_expr.operator.as_str() {
"!" => Opcode::Bang,
"-" => Opcode::Minus,
op => {
return Err(CompileErrorKind::UnsupportedOperator {
operator: op.to_string(),
context: "prefix expression".to_string(),
}
.at(span)
.into());
}
};
self.emit(opcode, &[], span);
Ok(())
}
Expr::Infix(infix_expr) => self.compile_infix(infix_expr, span),
Expr::Cond(cond) => self.compile_conditional(cond),
Expr::Ident(ident) => {
if let Some(symbol) = self.symbols_table.resolve_symbol(&ident.value) {
self.load_symbol(&symbol, span);
Ok(())
} else if let Some((registry_index, variant_tag, field_count)) =
self.find_variant_in_registry(&ident.value)
{
self.emit_variant_constructor(registry_index, variant_tag, field_count, span)
} else {
Err(CompileErrorKind::UndefinedVariable {
name: ident.value.clone(),
}
.at(ident.span)
.into())
}
}
Expr::Char(c) => {
let index = self.add_constant(Value::Char(c.value))?;
self.emit(Opcode::Constant, &[index], span);
Ok(())
}
Expr::Tuple(tuple) => {
for element in &tuple.elements {
self.compile_expression(element)?;
}
self.emit(Opcode::Tuple, &[tuple.elements.len()], span);
Ok(())
}
Expr::Str(s) => {
let constant = Value::Str(unescape_string(&s.value));
let index = self.add_constant(constant)?;
self.emit(Opcode::Constant, &[index], span);
Ok(())
}
Expr::Vector(array) => {
for element in &array.elements {
self.compile_expression(element)?;
}
self.emit(Opcode::Vector, &[array.elements.len()], span);
Ok(())
}
Expr::Array(arr) => self.compile_array_literal(arr, span),
Expr::Map(map) => {
for (key, value) in &map.pairs {
self.compile_expression(key)?;
self.compile_expression(value)?;
}
self.emit(Opcode::Map, &[map.pairs.len() * 2], span);
Ok(())
}
Expr::Index(index_expr) => self.compile_index_expression(index_expr, span),
Expr::Lambda(lambda) => {
self.compile_fn_body(
None,
lambda.param_names(),
lambda.params.len(),
&lambda.body,
lambda.span,
)?;
Ok(())
}
Expr::Call(call) => {
self.compile_expression(&call.function)?;
for arg in &call.arguments {
self.compile_expression(arg)?;
}
self.emit(Opcode::Call, &[call.arguments.len()], span);
Ok(())
}
Expr::Cast(cast) => {
self.compile_expression(&cast.expr)?;
let tag = TypeTag::from_cast_target(cast.target);
self.emit(Opcode::Convert, &[tag.to_byte() as usize], span);
Ok(())
}
Expr::Break(break_expr) => self.compile_break(break_expr),
Expr::Continue(cont_expr) => self.compile_continue(cont_expr),
Expr::MacroCall(mc) => self.compile_macro_call(mc),
Expr::MacroLit(_) => Err(CompileErrorKind::UnsupportedExpr {
expr_type: "macro literal".to_string(),
}
.at(span)
.into()),
Expr::StructLit(struct_lit) => self.compile_struct_literal(struct_lit),
Expr::PathExpr(path_expr) => self.compile_path_expression(path_expr),
Expr::Try(try_expr) => self.compile_try(try_expr),
Expr::Match(match_expr) => self.compile_match(match_expr),
Expr::FieldAccess(field_access) => self.compile_field_access(field_access),
Expr::MethodCall(method_call) => self.compile_method_call(method_call),
Expr::Range(range) => {
self.compile_expression(&range.start)?;
self.compile_expression(&range.end)?;
let opcode = if range.inclusive {
Opcode::MakeRangeInclusive
} else {
Opcode::MakeRange
};
self.emit(opcode, &[], span);
Ok(())
}
}
}
fn compile_break(&mut self, expr: &BreakExpr) -> Result<()> {
if self.loop_contexts.is_empty() {
return Err(CompileErrorKind::BreakOutsideLoop.at(expr.span).into());
}
let ctx_index = self.resolve_loop_label(&expr.label, expr.span)?;
match &expr.value {
Some(val) => self.compile_expression(val)?,
None => {
self.emit(Opcode::Unit, &[], expr.span);
}
}
let jump_pos = self.emit(Opcode::Jump, &[Self::JUMP], expr.span);
self.loop_contexts[ctx_index].break_jumps.push(jump_pos);
Ok(())
}
fn compile_continue(&mut self, expr: &ContinueExpr) -> Result<()> {
if self.loop_contexts.is_empty() {
return Err(CompileErrorKind::ContinueOutsideLoop.at(expr.span).into());
}
let ctx_index = self.resolve_loop_label(&expr.label, expr.span)?;
match self.loop_contexts[ctx_index].continue_target {
Some(target) => {
self.emit(Opcode::Jump, &[target], expr.span);
}
None => {
let jump_pos = self.emit(Opcode::Jump, &[Self::JUMP], expr.span);
self.loop_contexts[ctx_index].continue_jumps.push(jump_pos);
}
}
Ok(())
}
fn resolve_loop_label(&self, label: &Option<String>, span: Span) -> Result<usize> {
match label {
None => Ok(self.loop_contexts.len() - 1),
Some(name) => self
.loop_contexts
.iter()
.rposition(|ctx| ctx.label.as_deref() == Some(name))
.ok_or_else(|| {
CompileErrorKind::UndeclaredLabel {
label: name.clone(),
}
.at(span)
.into()
}),
}
}
fn compile_infix(&mut self, infix_expr: &InfixExpr, span: Span) -> Result<()> {
match infix_expr.operator.as_str() {
"&&" => {
self.compile_expression(&infix_expr.lhs)?;
let cond_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.compile_expression(&infix_expr.rhs)?;
let end_jump = self.emit(Opcode::Jump, &[Self::JUMP], span);
let false_pos = self.current_instructions().len();
self.replace_operand(cond_jump, false_pos)?;
self.emit(Opcode::False, &[], span);
let end_pos = self.current_instructions().len();
self.replace_operand(end_jump, end_pos)?;
Ok(())
}
"||" => {
self.compile_expression(&infix_expr.lhs)?;
let cond_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.emit(Opcode::True, &[], span);
let end_jump = self.emit(Opcode::Jump, &[Self::JUMP], span);
let rhs_pos = self.current_instructions().len();
self.replace_operand(cond_jump, rhs_pos)?;
self.compile_expression(&infix_expr.rhs)?;
let end_pos = self.current_instructions().len();
self.replace_operand(end_jump, end_pos)?;
Ok(())
}
_ => {
if let Some(n) = infix_expr.array_eq_len
&& matches!(infix_expr.operator.as_str(), "==" | "!=")
{
return self.compile_array_equality(infix_expr, n, span);
}
self.compile_expression(&infix_expr.lhs)?;
self.compile_expression(&infix_expr.rhs)?;
if matches!(infix_expr.op_class, BinOpClass::Felt) {
return self.emit_felt_infix(infix_expr.operator.as_str(), span);
}
match infix_expr.operator.as_str() {
">=" => {
self.emit(Opcode::LessThan, &[], span);
self.emit(Opcode::Bang, &[], span);
}
"<=" => {
self.emit(Opcode::GreaterThan, &[], span);
self.emit(Opcode::Bang, &[], span);
}
op => {
let opcode = match op {
"+" => Opcode::Add,
"-" => Opcode::Sub,
"*" => Opcode::Mul,
"/" => Opcode::Div,
"%" => Opcode::Mod,
">" => Opcode::GreaterThan,
"<" => Opcode::LessThan,
"==" => Opcode::Equal,
"!=" => Opcode::NotEqual,
"&" => Opcode::BitAnd,
"|" => Opcode::BitOr,
"^" => Opcode::BitXor,
"<<" => Opcode::Shl,
">>" => Opcode::Shr,
_ => {
return Err(CompileErrorKind::UnsupportedOperator {
operator: op.to_string(),
context: "infix expression".to_string(),
}
.at(span)
.into());
}
};
self.emit(opcode, &[], span);
}
}
Ok(())
}
}
}
fn emit_felt_infix(&mut self, op: &str, span: Span) -> Result<()> {
match op {
"+" => {
self.emit(Opcode::FeltAdd, &[], span);
}
"-" => {
self.emit(Opcode::FeltSub, &[], span);
}
"*" => {
self.emit(Opcode::FeltMul, &[], span);
}
"/" => {
self.emit(Opcode::FeltInv, &[], span);
self.emit(Opcode::FeltMul, &[], span);
}
"==" => {
self.emit(Opcode::Equal, &[], span);
}
"!=" => {
self.emit(Opcode::NotEqual, &[], span);
}
other => {
return Err(CompileErrorKind::UnsupportedOperator {
operator: other.to_string(),
context: "Felt infix expression".to_string(),
}
.at(span)
.into());
}
}
Ok(())
}
fn compile_conditional(&mut self, cond: &CondExpr) -> Result<()> {
self.compile_expression(&cond.condition)?;
let cond_jump_pos = self.emit(Opcode::CondJump, &[Self::JUMP], cond.span);
self.compile_block_statement(&cond.consequence)?;
if self.last_instruction_is(Opcode::Pop) {
self.remove_last_pop();
} else {
self.emit(Opcode::Unit, &[], cond.span);
}
let jump_pos = self.emit(Opcode::Jump, &[Self::JUMP], cond.span);
let cons_pos = self.current_instructions().len();
self.replace_operand(cond_jump_pos, cons_pos)?;
match &cond.alternative {
None => {
self.emit(Opcode::Unit, &[], cond.span);
}
Some(alt_block) => {
self.compile_block_statement(alt_block)?;
if self.last_instruction_is(Opcode::Pop) {
self.remove_last_pop();
} else {
self.emit(Opcode::Unit, &[], cond.span);
}
}
}
let alt_pos = self.current_instructions().len();
self.replace_operand(jump_pos, alt_pos)?;
Ok(())
}
fn compile_struct_literal(&mut self, lit: &StructLitExpr) -> Result<()> {
let span = lit.span;
let (registry_index, field_names) = self
.type_registry
.iter()
.enumerate()
.find_map(|(i, td)| match td {
TypeDef::Struct { name, field_names } if name == &lit.name => {
Some((i, field_names.clone()))
}
_ => None,
})
.ok_or_else(|| {
CompileErrorKind::UndefinedVariable {
name: lit.name.clone(),
}
.at(span)
})?;
let base_sym = lit
.base
.as_ref()
.map(|base_expr| {
self.compile_expression(base_expr)?;
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let hidden = format!("__struct_base_{id}");
self.define_and_set(&hidden, false, span)
})
.transpose()?;
for (field_index, field_name) in field_names.iter().enumerate() {
match lit.fields.iter().find(|(name, _)| name == field_name) {
Some((_, expr)) => self.compile_expression(expr)?,
None => {
let sym = base_sym.as_ref().ok_or_else(|| {
CompileErrorKind::UndefinedVariable {
name: format!(
"missing field `{}` in struct `{}`",
field_name, lit.name
),
}
.at(span)
})?;
self.load_symbol(sym, span);
self.emit(Opcode::GetField, &[field_index], span);
}
}
}
let type_index = registry_index << 8;
self.emit(Opcode::Construct, &[type_index, field_names.len()], span);
Ok(())
}
fn compile_path_expression(&mut self, path: &PathExpr) -> Result<()> {
let span = path.span;
if path.segments.len() == 2 {
let type_name = &path.segments[0];
let variant_name = &path.segments[1];
if let Some((registry_index, variant_tag, field_count)) =
self.resolve_enum_variant(type_name, variant_name)
{
return self.emit_variant_constructor(
registry_index,
variant_tag,
field_count,
span,
);
}
let qualified_name = format!("{type_name}::{variant_name}");
if let Some(symbol) = self.symbols_table.resolve_symbol(&qualified_name) {
self.load_symbol(&symbol, span);
return Ok(());
}
}
let full_name = path.segments.join("::");
let symbol = self.resolve_or_error(&full_name, span)?;
self.load_symbol(&symbol, span);
Ok(())
}
fn emit_variant_constructor(
&mut self,
registry_index: usize,
variant_tag: usize,
field_count: usize,
span: Span,
) -> Result<()> {
let type_index = (registry_index << 8) | (variant_tag & 0xFF);
if field_count == 0 {
self.emit(Opcode::Construct, &[type_index, 0], span);
} else {
self.enter_scope();
let mut param_names = Vec::with_capacity(field_count);
for i in 0..field_count {
let name = format!("__field_{i}");
if let Err(e) = self.symbols_table.define_symbol(&name, false) {
return Err(self.attach_span(e, span));
}
param_names.push(name);
}
for name in ¶m_names {
let sym = self.resolve_or_error(name, span)?;
self.load_symbol(&sym, span);
}
self.emit(Opcode::Construct, &[type_index, field_count], span);
self.emit(Opcode::ReturnValue, &[], span);
let num_locals = self.symbols_table.max_definitions();
let (instructions, inner_source_map) = self.leave_scope()?;
let compiled_fn = Value::CompiledFn(CompiledFn {
instructions: Rc::from(instructions.as_bytes()),
num_locals,
num_parameters: field_count,
source_map: inner_source_map,
});
let index = self.add_constant(compiled_fn)?;
self.emit(Opcode::Closure, &[index, 0], span);
}
Ok(())
}
fn resolve_enum_variant(
&self,
type_name: &str,
variant_name: &str,
) -> Option<(usize, usize, usize)> {
self.type_registry
.iter()
.enumerate()
.find_map(|(i, td)| match td {
TypeDef::Enum { name, variants } if name == type_name => variants
.iter()
.enumerate()
.find(|(_, v)| v.name == variant_name)
.map(|(tag, v)| (i, tag, v.field_count as usize)),
_ => None,
})
}
fn compile_match(&mut self, match_expr: &MatchExpr) -> Result<()> {
let span = match_expr.span;
self.compile_expression(&match_expr.scrutinee)?;
let mut end_jumps = Vec::with_capacity(match_expr.arms.len());
for arm in &match_expr.arms {
match &arm.pattern {
Pattern::TupleStruct { path, fields, .. } => {
if let Some((_, variant_tag, _)) = self.find_variant_in_registry(path) {
let match_tag_pos =
self.emit(Opcode::MatchTag, &[variant_tag, Self::JUMP], span);
let (nested_positions, scrutinee_var) =
self.bind_variant_fields(fields, span)?;
end_jumps.push(self.compile_match_arm_body(&arm.body, span)?);
if nested_positions.is_empty() {
let next_arm = self.current_instructions().len();
self.replace_match_tag_target(match_tag_pos, next_arm)?;
} else {
let cleanup = self.current_instructions().len();
for nested_pos in nested_positions {
self.replace_match_tag_target(nested_pos, cleanup)?;
}
self.emit(Opcode::Pop, &[], span);
if let Some(ref var_name) = scrutinee_var {
let sym = self.resolve_or_error(var_name, span)?;
self.load_symbol(&sym, span);
}
let next_arm = self.current_instructions().len();
self.replace_match_tag_target(match_tag_pos, next_arm)?;
}
}
}
Pattern::Ident { name, mutable, .. } if name != "_" => {
if let Some((_, variant_tag, _)) = self.find_variant_in_registry(name) {
let match_tag_pos =
self.emit(Opcode::MatchTag, &[variant_tag, Self::JUMP], span);
self.emit(Opcode::Pop, &[], span);
end_jumps.push(self.compile_match_arm_body(&arm.body, span)?);
let next_arm = self.current_instructions().len();
self.replace_match_tag_target(match_tag_pos, next_arm)?;
} else {
self.define_and_set(name, *mutable, span)?;
end_jumps.push(self.compile_match_arm_body(&arm.body, span)?);
}
}
Pattern::Wildcard(_) | Pattern::Ident { .. } => {
self.emit(Opcode::Pop, &[], span);
end_jumps.push(self.compile_match_arm_body(&arm.body, span)?);
}
Pattern::Tuple(..) => {
self.compile_let_destructure(&arm.pattern, span)?;
self.emit(Opcode::Pop, &[], span);
end_jumps.push(self.compile_match_arm_body(&arm.body, span)?);
}
Pattern::Literal(lit_expr) => {
self.compile_expression(lit_expr)?;
self.emit(Opcode::Equal, &[], span);
let cond_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
end_jumps.push(self.compile_match_arm_body(&arm.body, span)?);
let next_arm = self.current_instructions().len();
self.replace_operand(cond_jump, next_arm)?;
}
_ => {
self.emit(Opcode::Pop, &[], span);
self.emit(Opcode::Unit, &[], span);
end_jumps.push(self.emit(Opcode::Jump, &[Self::JUMP], span));
}
}
}
self.emit(Opcode::Unit, &[], span);
let exit = self.current_instructions().len();
for jump_pos in end_jumps {
self.replace_operand(jump_pos, exit)?;
}
Ok(())
}
fn compile_match_arm_body(&mut self, body: &Expr, span: Span) -> Result<usize> {
self.compile_expression(body)?;
if self.last_instruction_is(Opcode::Pop) {
self.remove_last_pop();
}
Ok(self.emit(Opcode::Jump, &[Self::JUMP], span))
}
fn find_variant_in_registry(&self, variant: &str) -> Option<(usize, usize, usize)> {
self.variant_index
.get(variant)
.map(|entry| (entry.registry_index, entry.tag, entry.field_count))
}
fn bind_variant_fields(
&mut self,
fields: &[Pattern],
span: Span,
) -> Result<(Vec<usize>, Option<String>)> {
let mut nested_match_positions = Vec::new();
let mut scrutinee_var = None;
for (i, field) in fields.iter().enumerate() {
if let Pattern::Ident { name, mutable, .. } = field {
if i == 0 {
let hidden = format!("__match_scrutinee_{}", self.for_loop_counter);
self.for_loop_counter += 1;
let hidden_sym = self.define_and_set(&hidden, false, span)?;
scrutinee_var = Some(hidden.clone());
self.load_symbol(&hidden_sym, span);
self.emit(Opcode::GetField, &[i], span);
} else {
let hidden = format!("__match_scrutinee_{}", self.for_loop_counter - 1);
let hidden_sym = self.resolve_or_error(&hidden, span)?;
self.load_symbol(&hidden_sym, span);
self.emit(Opcode::GetField, &[i], span);
}
if name == "_" {
self.emit(Opcode::Pop, &[], span);
} else if let Some((_, variant_tag, _)) = self.find_variant_in_registry(name) {
let pos = self.emit(Opcode::MatchTag, &[variant_tag, Self::JUMP], span);
self.emit(Opcode::Pop, &[], span);
nested_match_positions.push(pos);
} else {
self.define_and_set(name, *mutable, span)?;
}
}
}
Ok((nested_match_positions, scrutinee_var))
}
fn replace_match_tag_target(&mut self, op_pos: usize, target: usize) -> Result<()> {
let scope = &mut self.scopes[self.scope_index];
let target_bytes = (target as u16).to_be_bytes();
scope.instructions.replace_bytes(op_pos + 3, &target_bytes);
Ok(())
}
fn compile_field_access(&mut self, fa: &FieldAccessExpr) -> Result<()> {
let span = fa.span;
self.compile_expression(&fa.object)?;
let field_index = fa
.field
.parse::<usize>()
.ok()
.or_else(|| {
self.type_registry.iter().find_map(|td| match td {
TypeDef::Struct { field_names, .. } => {
field_names.iter().position(|f| f == &fa.field)
}
_ => None,
})
})
.ok_or_else(|| {
CompileErrorKind::UndefinedVariable {
name: format!("unknown field `{}`", fa.field),
}
.at(span)
})?;
self.emit(Opcode::GetField, &[field_index], span);
Ok(())
}
fn compile_let_destructure(&mut self, pattern: &Pattern, span: Span) -> Result<()> {
match pattern {
Pattern::Tuple(fields, _) => {
let temp = self.define_anonymous_local(span)?;
for (i, field) in fields.iter().enumerate() {
match field {
Pattern::Ident { name, mutable, .. } => {
self.load_symbol(&temp, span);
self.emit(Opcode::GetField, &[i], span);
self.define_and_set(name, *mutable, span)?;
}
Pattern::Wildcard(_) => {}
Pattern::Tuple(..) => {
self.load_symbol(&temp, span);
self.emit(Opcode::GetField, &[i], span);
self.compile_let_destructure(field, span)?;
}
_ => {
return Err(CompileErrorKind::UnsupportedExpr {
expr_type: "unsupported pattern in tuple destructuring".to_string(),
}
.at(span)
.into());
}
}
}
Ok(())
}
_ => Err(CompileErrorKind::UnsupportedExpr {
expr_type: "expected tuple pattern in let destructuring".to_string(),
}
.at(span)
.into()),
}
}
fn define_anonymous_local(&mut self, span: Span) -> Result<Symbol> {
use std::sync::atomic::{AtomicUsize, Ordering};
static COUNTER: AtomicUsize = AtomicUsize::new(0);
let id = COUNTER.fetch_add(1, Ordering::Relaxed);
let name = format!("__destructure_{id}");
self.define_and_set(&name, false, span)
}
fn compile_method_call(&mut self, mc: &MethodCallExpr) -> Result<()> {
let span = mc.span;
if self.is_desugared_higher_order(mc) {
return self.compile_desugared_higher_order(mc);
}
if let Some(n) = mc.array_len
&& (mc.method == "len" || mc.method == "length")
&& mc.arguments.is_empty()
{
return self.compile_array_len_constant(mc, n, span);
}
let qualified_name = self.resolve_method_name(mc).ok_or_else(|| {
CompileErrorKind::UndefinedVariable {
name: format!("unknown method `{}`", mc.method),
}
.at(span)
})?;
let symbol = self.resolve_or_error(&qualified_name, span)?;
self.load_symbol(&symbol, span);
self.compile_expression(&mc.object)?;
for arg in &mc.arguments {
self.compile_expression(arg)?;
}
let total_args = 1 + mc.arguments.len();
self.emit(Opcode::Call, &[total_args], span);
Ok(())
}
fn compile_array_literal(&mut self, arr: &ArrayLit, span: Span) -> Result<()> {
if arr.elements.is_empty() {
let zero_idx = self.add_constant(Value::Integer(Integer::I64(0)))?;
self.emit(Opcode::Constant, &[zero_idx], span);
self.emit(Opcode::HeapAlloc, &[], span);
return Ok(());
}
for element in &arr.elements {
self.compile_expression(element)?;
self.emit(Opcode::HeapAlloc, &[], span);
}
for _ in 1..arr.elements.len() {
self.emit(Opcode::Pop, &[], span);
}
Ok(())
}
fn compile_index_expression(&mut self, index_expr: &IndexExpr, span: Span) -> Result<()> {
if index_expr.array_len.is_some() {
self.compile_expression(&index_expr.expr)?;
self.compile_expression(&index_expr.index)?;
self.emit(Opcode::Convert, &[TypeTag::U64.to_byte() as usize], span);
self.emit(Opcode::Add, &[], span);
self.emit(Opcode::HeapRead, &[], span);
return Ok(());
}
self.compile_expression(&index_expr.expr)?;
self.compile_expression(&index_expr.index)?;
self.emit(Opcode::Index, &[], span);
Ok(())
}
fn compile_array_equality(
&mut self,
infix_expr: &InfixExpr,
n: usize,
span: Span,
) -> Result<()> {
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let lhs_name = format!("__arr_eq_lhs_{id}");
let rhs_name = format!("__arr_eq_rhs_{id}");
self.compile_expression(&infix_expr.lhs)?;
let lhs_sym = self.define_and_set(&lhs_name, false, span)?;
self.compile_expression(&infix_expr.rhs)?;
let rhs_sym = self.define_and_set(&rhs_name, false, span)?;
if n == 0 {
self.emit(Opcode::True, &[], span);
} else {
self.emit_array_element_equal(&lhs_sym, &rhs_sym, 0, span)?;
let mut end_jumps: Vec<usize> = Vec::with_capacity(n.saturating_sub(1));
for i in 1..n {
let cond_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.emit_array_element_equal(&lhs_sym, &rhs_sym, i, span)?;
let jump_to_end = self.emit(Opcode::Jump, &[Self::JUMP], span);
let false_branch = self.current_instructions().len();
self.replace_operand(cond_jump, false_branch)?;
self.emit(Opcode::False, &[], span);
end_jumps.push(jump_to_end);
}
let end_pos = self.current_instructions().len();
for jump in end_jumps {
self.replace_operand(jump, end_pos)?;
}
}
if infix_expr.operator == "!=" {
self.emit(Opcode::Bang, &[], span);
}
Ok(())
}
fn emit_array_element_equal(
&mut self,
lhs_sym: &Symbol,
rhs_sym: &Symbol,
i: usize,
span: Span,
) -> Result<()> {
let i_const = self.add_constant(Value::Integer(Integer::U64(i as u64)))?;
self.load_symbol(lhs_sym, span);
self.emit(Opcode::Constant, &[i_const], span);
self.emit(Opcode::Add, &[], span);
self.emit(Opcode::HeapRead, &[], span);
self.load_symbol(rhs_sym, span);
self.emit(Opcode::Constant, &[i_const], span);
self.emit(Opcode::Add, &[], span);
self.emit(Opcode::HeapRead, &[], span);
self.emit(Opcode::Equal, &[], span);
Ok(())
}
fn compile_array_len_constant(
&mut self,
mc: &MethodCallExpr,
n: usize,
span: Span,
) -> Result<()> {
self.compile_expression(&mc.object)?;
self.emit(Opcode::Pop, &[], span);
let idx = self.add_constant(Value::Integer(Integer::Usize(n)))?;
self.emit(Opcode::Constant, &[idx], span);
Ok(())
}
fn is_desugared_higher_order(&self, mc: &MethodCallExpr) -> bool {
matches!(
(mc.receiver.as_deref(), mc.method.as_str()),
(Some("Option"), "map" | "and_then" | "unwrap_or_else")
| (
Some("Result"),
"map" | "and_then" | "map_err" | "unwrap_or_else" | "or_else"
)
| (
Some("Vector"),
"map"
| "filter"
| "fold"
| "any"
| "all"
| "find"
| "position"
| "for_each"
| "flat_map"
)
)
}
fn compile_desugared_higher_order(&mut self, mc: &MethodCallExpr) -> Result<()> {
match mc.receiver.as_deref() {
Some("Option" | "Result") => self.compile_option_result_higher_order(mc),
Some("Vector") => self.compile_vector_iterator(mc),
_ => unreachable!("is_desugared_higher_order already validated receiver"),
}
}
fn compile_option_result_higher_order(&mut self, mc: &MethodCallExpr) -> Result<()> {
match mc.method.as_str() {
"map" | "and_then" => self.compile_option_result_map_like(mc),
"unwrap_or_else" => self.compile_option_result_unwrap_or_else(mc),
"map_err" => self.compile_result_map_err(mc),
"or_else" => self.compile_result_or_else(mc),
_ => unreachable!("unhandled Option/Result HOF: {}", mc.method),
}
}
fn compile_option_result_map_like(&mut self, mc: &MethodCallExpr) -> Result<()> {
let span = mc.span;
let is_option = mc.receiver.as_deref() == Some("Option");
let is_map = mc.method == "map";
let success_tag: usize = 0;
let type_index: usize = if is_option { 0 } else { 1 };
let some_or_ok = type_index << 8;
let none_or_err = (type_index << 8) | 1;
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let fn_name = format!("__hof_fn_{id}");
let val_name = format!("__hof_val_{id}");
self.compile_expression(&mc.arguments[0])?;
let fn_sym = self.define_and_set(&fn_name, false, span)?;
self.compile_expression(&mc.object)?;
let match_tag_pos = self.emit(Opcode::MatchTag, &[success_tag, Self::JUMP], span);
self.emit(Opcode::GetField, &[0], span);
let val_sym = self.define_and_set(&val_name, false, span)?;
self.load_symbol(&fn_sym, span);
self.load_symbol(&val_sym, span);
self.emit(Opcode::Call, &[1], span);
if is_map {
self.emit(Opcode::Construct, &[some_or_ok, 1], span);
}
let jump_to_end = self.emit(Opcode::Jump, &[Self::JUMP], span);
let fail_arm = self.current_instructions().len();
self.replace_match_tag_target(match_tag_pos, fail_arm)?;
if is_option {
self.emit(Opcode::Pop, &[], span);
self.emit(Opcode::Construct, &[none_or_err, 0], span);
} else {
}
let end = self.current_instructions().len();
self.replace_operand(jump_to_end, end)?;
Ok(())
}
fn compile_option_result_unwrap_or_else(&mut self, mc: &MethodCallExpr) -> Result<()> {
let span = mc.span;
let is_option = mc.receiver.as_deref() == Some("Option");
let success_tag: usize = 0;
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let fn_name = format!("__hof_fn_{id}");
let val_name = format!("__hof_val_{id}");
self.compile_expression(&mc.arguments[0])?;
let fn_sym = self.define_and_set(&fn_name, false, span)?;
self.compile_expression(&mc.object)?;
let match_tag_pos = self.emit(Opcode::MatchTag, &[success_tag, Self::JUMP], span);
self.emit(Opcode::GetField, &[0], span);
let jump_to_end = self.emit(Opcode::Jump, &[Self::JUMP], span);
let fail_arm = self.current_instructions().len();
self.replace_match_tag_target(match_tag_pos, fail_arm)?;
if is_option {
self.emit(Opcode::Pop, &[], span);
self.load_symbol(&fn_sym, span);
self.emit(Opcode::Call, &[0], span);
} else {
self.emit(Opcode::GetField, &[0], span);
let val_sym = self.define_and_set(&val_name, false, span)?;
self.load_symbol(&fn_sym, span);
self.load_symbol(&val_sym, span);
self.emit(Opcode::Call, &[1], span);
}
let end = self.current_instructions().len();
self.replace_operand(jump_to_end, end)?;
Ok(())
}
fn compile_result_map_err(&mut self, mc: &MethodCallExpr) -> Result<()> {
let span = mc.span;
let ok_tag: usize = 0;
let err_construct: usize = (1 << 8) | 1;
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let fn_name = format!("__hof_fn_{id}");
let val_name = format!("__hof_val_{id}");
self.compile_expression(&mc.arguments[0])?;
let fn_sym = self.define_and_set(&fn_name, false, span)?;
self.compile_expression(&mc.object)?;
let match_tag_pos = self.emit(Opcode::MatchTag, &[ok_tag, Self::JUMP], span);
let jump_to_end = self.emit(Opcode::Jump, &[Self::JUMP], span);
let err_arm = self.current_instructions().len();
self.replace_match_tag_target(match_tag_pos, err_arm)?;
self.emit(Opcode::GetField, &[0], span);
let val_sym = self.define_and_set(&val_name, false, span)?;
self.load_symbol(&fn_sym, span);
self.load_symbol(&val_sym, span);
self.emit(Opcode::Call, &[1], span);
self.emit(Opcode::Construct, &[err_construct, 1], span);
let end = self.current_instructions().len();
self.replace_operand(jump_to_end, end)?;
Ok(())
}
fn compile_result_or_else(&mut self, mc: &MethodCallExpr) -> Result<()> {
let span = mc.span;
let ok_tag: usize = 0;
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let fn_name = format!("__hof_fn_{id}");
let val_name = format!("__hof_val_{id}");
self.compile_expression(&mc.arguments[0])?;
let fn_sym = self.define_and_set(&fn_name, false, span)?;
self.compile_expression(&mc.object)?;
let match_tag_pos = self.emit(Opcode::MatchTag, &[ok_tag, Self::JUMP], span);
let jump_to_end = self.emit(Opcode::Jump, &[Self::JUMP], span);
let err_arm = self.current_instructions().len();
self.replace_match_tag_target(match_tag_pos, err_arm)?;
self.emit(Opcode::GetField, &[0], span);
let val_sym = self.define_and_set(&val_name, false, span)?;
self.load_symbol(&fn_sym, span);
self.load_symbol(&val_sym, span);
self.emit(Opcode::Call, &[1], span);
let end = self.current_instructions().len();
self.replace_operand(jump_to_end, end)?;
Ok(())
}
fn compile_vector_iterator(&mut self, mc: &MethodCallExpr) -> Result<()> {
match mc.method.as_str() {
"map" => self.compile_vector_map(mc),
"filter" => self.compile_vector_filter(mc),
"fold" => self.compile_vector_fold(mc),
"any" => self.compile_vector_any_all(mc, true),
"all" => self.compile_vector_any_all(mc, false),
"find" => self.compile_vector_find(mc),
"position" => self.compile_vector_position(mc),
"for_each" => self.compile_vector_for_each(mc),
"flat_map" => self.compile_vector_flat_map(mc),
_ => unreachable!("is_desugared_higher_order already validated method"),
}
}
fn compile_vector_map(&mut self, mc: &MethodCallExpr) -> Result<()> {
let span = mc.span;
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let fn_name = format!("__vmap_fn_{id}");
let iter_name = format!("__vmap_iter_{id}");
let len_name = format!("__vmap_len_{id}");
let result_name = format!("__vmap_result_{id}");
let i_name = format!("__vmap_i_{id}");
let elem_name = format!("__vmap_elem_{id}");
self.compile_expression(&mc.arguments[0])?;
let fn_sym = self.define_and_set(&fn_name, false, span)?;
self.compile_expression(&mc.object)?;
let iter_sym = self.define_and_set(&iter_name, false, span)?;
let len_builtin = self.resolve_or_error("Vector::len", span)?;
self.load_symbol(&len_builtin, span);
self.load_symbol(&iter_sym, span);
self.emit(Opcode::Call, &[1], span);
let len_sym = self.define_and_set(&len_name, false, span)?;
self.emit(Opcode::Vector, &[0], span);
let result_sym = self.define_and_set(&result_name, true, span)?;
let zero_idx = self.add_constant(Value::Integer(Integer::I64(0)))?;
self.emit(Opcode::Constant, &[zero_idx], span);
let i_sym = self.define_and_set(&i_name, true, span)?;
let loop_start = self.current_instructions().len();
self.load_symbol(&i_sym, span);
self.load_symbol(&len_sym, span);
self.emit(Opcode::LessThan, &[], span);
let exit_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.load_symbol(&iter_sym, span);
self.load_symbol(&i_sym, span);
self.emit(Opcode::Index, &[], span);
let elem_sym = self.define_and_set(&elem_name, false, span)?;
let push_builtin = self.resolve_or_error("Vector::push", span)?;
self.load_symbol(&push_builtin, span);
self.load_symbol(&result_sym, span);
self.load_symbol(&fn_sym, span);
self.load_symbol(&elem_sym, span);
self.emit(Opcode::Call, &[1], span);
self.emit(Opcode::Call, &[2], span);
self.emit_set_symbol(&result_sym, span);
self.load_symbol(&i_sym, span);
let one_idx = self.add_constant(Value::Integer(Integer::I64(1)))?;
self.emit(Opcode::Constant, &[one_idx], span);
self.emit(Opcode::Add, &[], span);
self.emit_set_symbol(&i_sym, span);
self.emit(Opcode::Jump, &[loop_start], span);
let loop_exit = self.current_instructions().len();
self.replace_operand(exit_jump, loop_exit)?;
self.load_symbol(&result_sym, span);
Ok(())
}
fn compile_vector_filter(&mut self, mc: &MethodCallExpr) -> Result<()> {
let span = mc.span;
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let fn_name = format!("__vfilt_fn_{id}");
let iter_name = format!("__vfilt_iter_{id}");
let len_name = format!("__vfilt_len_{id}");
let result_name = format!("__vfilt_result_{id}");
let i_name = format!("__vfilt_i_{id}");
let elem_name = format!("__vfilt_elem_{id}");
self.compile_expression(&mc.arguments[0])?;
let fn_sym = self.define_and_set(&fn_name, false, span)?;
self.compile_expression(&mc.object)?;
let iter_sym = self.define_and_set(&iter_name, false, span)?;
let len_builtin = self.resolve_or_error("Vector::len", span)?;
self.load_symbol(&len_builtin, span);
self.load_symbol(&iter_sym, span);
self.emit(Opcode::Call, &[1], span);
let len_sym = self.define_and_set(&len_name, false, span)?;
self.emit(Opcode::Vector, &[0], span);
let result_sym = self.define_and_set(&result_name, true, span)?;
let zero_idx = self.add_constant(Value::Integer(Integer::I64(0)))?;
self.emit(Opcode::Constant, &[zero_idx], span);
let i_sym = self.define_and_set(&i_name, true, span)?;
let loop_start = self.current_instructions().len();
self.load_symbol(&i_sym, span);
self.load_symbol(&len_sym, span);
self.emit(Opcode::LessThan, &[], span);
let exit_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.load_symbol(&iter_sym, span);
self.load_symbol(&i_sym, span);
self.emit(Opcode::Index, &[], span);
let elem_sym = self.define_and_set(&elem_name, false, span)?;
self.load_symbol(&fn_sym, span);
self.load_symbol(&elem_sym, span);
self.emit(Opcode::Call, &[1], span);
let skip_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
let push_builtin = self.resolve_or_error("Vector::push", span)?;
self.load_symbol(&push_builtin, span);
self.load_symbol(&result_sym, span);
self.load_symbol(&elem_sym, span);
self.emit(Opcode::Call, &[2], span);
self.emit_set_symbol(&result_sym, span);
let skip_target = self.current_instructions().len();
self.replace_operand(skip_jump, skip_target)?;
self.load_symbol(&i_sym, span);
let one_idx = self.add_constant(Value::Integer(Integer::I64(1)))?;
self.emit(Opcode::Constant, &[one_idx], span);
self.emit(Opcode::Add, &[], span);
self.emit_set_symbol(&i_sym, span);
self.emit(Opcode::Jump, &[loop_start], span);
let loop_exit = self.current_instructions().len();
self.replace_operand(exit_jump, loop_exit)?;
self.load_symbol(&result_sym, span);
Ok(())
}
fn compile_vector_fold(&mut self, mc: &MethodCallExpr) -> Result<()> {
let span = mc.span;
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let fn_name = format!("__vfold_fn_{id}");
let acc_name = format!("__vfold_acc_{id}");
let iter_name = format!("__vfold_iter_{id}");
let len_name = format!("__vfold_len_{id}");
let i_name = format!("__vfold_i_{id}");
let elem_name = format!("__vfold_elem_{id}");
self.compile_expression(&mc.arguments[1])?;
let fn_sym = self.define_and_set(&fn_name, false, span)?;
self.compile_expression(&mc.arguments[0])?;
let acc_sym = self.define_and_set(&acc_name, true, span)?;
self.compile_expression(&mc.object)?;
let iter_sym = self.define_and_set(&iter_name, false, span)?;
let len_builtin = self.resolve_or_error("Vector::len", span)?;
self.load_symbol(&len_builtin, span);
self.load_symbol(&iter_sym, span);
self.emit(Opcode::Call, &[1], span);
let len_sym = self.define_and_set(&len_name, false, span)?;
let zero_idx = self.add_constant(Value::Integer(Integer::I64(0)))?;
self.emit(Opcode::Constant, &[zero_idx], span);
let i_sym = self.define_and_set(&i_name, true, span)?;
let loop_start = self.current_instructions().len();
self.load_symbol(&i_sym, span);
self.load_symbol(&len_sym, span);
self.emit(Opcode::LessThan, &[], span);
let exit_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.load_symbol(&iter_sym, span);
self.load_symbol(&i_sym, span);
self.emit(Opcode::Index, &[], span);
let elem_sym = self.define_and_set(&elem_name, false, span)?;
self.load_symbol(&fn_sym, span);
self.load_symbol(&acc_sym, span);
self.load_symbol(&elem_sym, span);
self.emit(Opcode::Call, &[2], span);
self.emit_set_symbol(&acc_sym, span);
self.load_symbol(&i_sym, span);
let one_idx = self.add_constant(Value::Integer(Integer::I64(1)))?;
self.emit(Opcode::Constant, &[one_idx], span);
self.emit(Opcode::Add, &[], span);
self.emit_set_symbol(&i_sym, span);
self.emit(Opcode::Jump, &[loop_start], span);
let loop_exit = self.current_instructions().len();
self.replace_operand(exit_jump, loop_exit)?;
self.load_symbol(&acc_sym, span);
Ok(())
}
fn compile_vector_any_all(&mut self, mc: &MethodCallExpr, is_any: bool) -> Result<()> {
let span = mc.span;
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let fn_name = format!("__vaa_fn_{id}");
let iter_name = format!("__vaa_iter_{id}");
let len_name = format!("__vaa_len_{id}");
let i_name = format!("__vaa_i_{id}");
let elem_name = format!("__vaa_elem_{id}");
self.compile_expression(&mc.arguments[0])?;
let fn_sym = self.define_and_set(&fn_name, false, span)?;
self.compile_expression(&mc.object)?;
let iter_sym = self.define_and_set(&iter_name, false, span)?;
let len_builtin = self.resolve_or_error("Vector::len", span)?;
self.load_symbol(&len_builtin, span);
self.load_symbol(&iter_sym, span);
self.emit(Opcode::Call, &[1], span);
let len_sym = self.define_and_set(&len_name, false, span)?;
let zero_idx = self.add_constant(Value::Integer(Integer::I64(0)))?;
self.emit(Opcode::Constant, &[zero_idx], span);
let i_sym = self.define_and_set(&i_name, true, span)?;
let loop_start = self.current_instructions().len();
self.load_symbol(&i_sym, span);
self.load_symbol(&len_sym, span);
self.emit(Opcode::LessThan, &[], span);
let exit_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.load_symbol(&iter_sym, span);
self.load_symbol(&i_sym, span);
self.emit(Opcode::Index, &[], span);
let elem_sym = self.define_and_set(&elem_name, false, span)?;
self.load_symbol(&fn_sym, span);
self.load_symbol(&elem_sym, span);
self.emit(Opcode::Call, &[1], span);
let (early_value, default_value) = if is_any { (true, false) } else { (false, true) };
if is_any {
let continue_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
let early_idx = self.add_constant(Value::Bool(early_value))?;
self.emit(Opcode::Constant, &[early_idx], span);
let early_exit = self.emit(Opcode::Jump, &[Self::JUMP], span);
let continue_target = self.current_instructions().len();
self.replace_operand(continue_jump, continue_target)?;
self.load_symbol(&i_sym, span);
let one_idx = self.add_constant(Value::Integer(Integer::I64(1)))?;
self.emit(Opcode::Constant, &[one_idx], span);
self.emit(Opcode::Add, &[], span);
self.emit_set_symbol(&i_sym, span);
self.emit(Opcode::Jump, &[loop_start], span);
let default_target = self.current_instructions().len();
self.replace_operand(exit_jump, default_target)?;
let default_idx = self.add_constant(Value::Bool(default_value))?;
self.emit(Opcode::Constant, &[default_idx], span);
let end = self.current_instructions().len();
self.replace_operand(early_exit, end)?;
} else {
let early_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.load_symbol(&i_sym, span);
let one_idx = self.add_constant(Value::Integer(Integer::I64(1)))?;
self.emit(Opcode::Constant, &[one_idx], span);
self.emit(Opcode::Add, &[], span);
self.emit_set_symbol(&i_sym, span);
self.emit(Opcode::Jump, &[loop_start], span);
let early_target = self.current_instructions().len();
self.replace_operand(early_jump, early_target)?;
let early_idx = self.add_constant(Value::Bool(early_value))?;
self.emit(Opcode::Constant, &[early_idx], span);
let early_exit = self.emit(Opcode::Jump, &[Self::JUMP], span);
let default_target = self.current_instructions().len();
self.replace_operand(exit_jump, default_target)?;
let default_idx = self.add_constant(Value::Bool(default_value))?;
self.emit(Opcode::Constant, &[default_idx], span);
let end = self.current_instructions().len();
self.replace_operand(early_exit, end)?;
}
Ok(())
}
fn compile_vector_find(&mut self, mc: &MethodCallExpr) -> Result<()> {
let span = mc.span;
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let fn_name = format!("__vfind_fn_{id}");
let iter_name = format!("__vfind_iter_{id}");
let len_name = format!("__vfind_len_{id}");
let i_name = format!("__vfind_i_{id}");
let elem_name = format!("__vfind_elem_{id}");
self.compile_expression(&mc.arguments[0])?;
let fn_sym = self.define_and_set(&fn_name, false, span)?;
self.compile_expression(&mc.object)?;
let iter_sym = self.define_and_set(&iter_name, false, span)?;
let len_builtin = self.resolve_or_error("Vector::len", span)?;
self.load_symbol(&len_builtin, span);
self.load_symbol(&iter_sym, span);
self.emit(Opcode::Call, &[1], span);
let len_sym = self.define_and_set(&len_name, false, span)?;
let zero_idx = self.add_constant(Value::Integer(Integer::I64(0)))?;
self.emit(Opcode::Constant, &[zero_idx], span);
let i_sym = self.define_and_set(&i_name, true, span)?;
let loop_start = self.current_instructions().len();
self.load_symbol(&i_sym, span);
self.load_symbol(&len_sym, span);
self.emit(Opcode::LessThan, &[], span);
let exit_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.load_symbol(&iter_sym, span);
self.load_symbol(&i_sym, span);
self.emit(Opcode::Index, &[], span);
let elem_sym = self.define_and_set(&elem_name, false, span)?;
self.load_symbol(&fn_sym, span);
self.load_symbol(&elem_sym, span);
self.emit(Opcode::Call, &[1], span);
let continue_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.load_symbol(&elem_sym, span);
let some_tag = 0usize; self.emit(Opcode::Construct, &[some_tag, 1], span);
let early_exit = self.emit(Opcode::Jump, &[Self::JUMP], span);
let continue_target = self.current_instructions().len();
self.replace_operand(continue_jump, continue_target)?;
self.load_symbol(&i_sym, span);
let one_idx = self.add_constant(Value::Integer(Integer::I64(1)))?;
self.emit(Opcode::Constant, &[one_idx], span);
self.emit(Opcode::Add, &[], span);
self.emit_set_symbol(&i_sym, span);
self.emit(Opcode::Jump, &[loop_start], span);
let default_target = self.current_instructions().len();
self.replace_operand(exit_jump, default_target)?;
let none_tag = 1usize; self.emit(Opcode::Construct, &[none_tag, 0], span);
let end = self.current_instructions().len();
self.replace_operand(early_exit, end)?;
Ok(())
}
fn compile_vector_position(&mut self, mc: &MethodCallExpr) -> Result<()> {
let span = mc.span;
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let fn_name = format!("__vpos_fn_{id}");
let iter_name = format!("__vpos_iter_{id}");
let len_name = format!("__vpos_len_{id}");
let i_name = format!("__vpos_i_{id}");
let elem_name = format!("__vpos_elem_{id}");
self.compile_expression(&mc.arguments[0])?;
let fn_sym = self.define_and_set(&fn_name, false, span)?;
self.compile_expression(&mc.object)?;
let iter_sym = self.define_and_set(&iter_name, false, span)?;
let len_builtin = self.resolve_or_error("Vector::len", span)?;
self.load_symbol(&len_builtin, span);
self.load_symbol(&iter_sym, span);
self.emit(Opcode::Call, &[1], span);
let len_sym = self.define_and_set(&len_name, false, span)?;
let zero_idx = self.add_constant(Value::Integer(Integer::Usize(0)))?;
self.emit(Opcode::Constant, &[zero_idx], span);
let i_sym = self.define_and_set(&i_name, true, span)?;
let loop_start = self.current_instructions().len();
self.load_symbol(&i_sym, span);
self.load_symbol(&len_sym, span);
self.emit(Opcode::LessThan, &[], span);
let exit_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.load_symbol(&iter_sym, span);
self.load_symbol(&i_sym, span);
self.emit(Opcode::Index, &[], span);
let elem_sym = self.define_and_set(&elem_name, false, span)?;
self.load_symbol(&fn_sym, span);
self.load_symbol(&elem_sym, span);
self.emit(Opcode::Call, &[1], span);
let continue_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.load_symbol(&i_sym, span);
let some_tag = 0usize;
self.emit(Opcode::Construct, &[some_tag, 1], span);
let early_exit = self.emit(Opcode::Jump, &[Self::JUMP], span);
let continue_target = self.current_instructions().len();
self.replace_operand(continue_jump, continue_target)?;
self.load_symbol(&i_sym, span);
let one_idx = self.add_constant(Value::Integer(Integer::Usize(1)))?;
self.emit(Opcode::Constant, &[one_idx], span);
self.emit(Opcode::Add, &[], span);
self.emit_set_symbol(&i_sym, span);
self.emit(Opcode::Jump, &[loop_start], span);
let default_target = self.current_instructions().len();
self.replace_operand(exit_jump, default_target)?;
let none_tag = 1usize;
self.emit(Opcode::Construct, &[none_tag, 0], span);
let end = self.current_instructions().len();
self.replace_operand(early_exit, end)?;
Ok(())
}
fn compile_vector_for_each(&mut self, mc: &MethodCallExpr) -> Result<()> {
let span = mc.span;
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let fn_name = format!("__vfe_fn_{id}");
let iter_name = format!("__vfe_iter_{id}");
let len_name = format!("__vfe_len_{id}");
let i_name = format!("__vfe_i_{id}");
let elem_name = format!("__vfe_elem_{id}");
self.compile_expression(&mc.arguments[0])?;
let fn_sym = self.define_and_set(&fn_name, false, span)?;
self.compile_expression(&mc.object)?;
let iter_sym = self.define_and_set(&iter_name, false, span)?;
let len_builtin = self.resolve_or_error("Vector::len", span)?;
self.load_symbol(&len_builtin, span);
self.load_symbol(&iter_sym, span);
self.emit(Opcode::Call, &[1], span);
let len_sym = self.define_and_set(&len_name, false, span)?;
let zero_idx = self.add_constant(Value::Integer(Integer::I64(0)))?;
self.emit(Opcode::Constant, &[zero_idx], span);
let i_sym = self.define_and_set(&i_name, true, span)?;
let loop_start = self.current_instructions().len();
self.load_symbol(&i_sym, span);
self.load_symbol(&len_sym, span);
self.emit(Opcode::LessThan, &[], span);
let exit_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.load_symbol(&iter_sym, span);
self.load_symbol(&i_sym, span);
self.emit(Opcode::Index, &[], span);
let elem_sym = self.define_and_set(&elem_name, false, span)?;
self.load_symbol(&fn_sym, span);
self.load_symbol(&elem_sym, span);
self.emit(Opcode::Call, &[1], span);
self.emit(Opcode::Pop, &[], span);
self.load_symbol(&i_sym, span);
let one_idx = self.add_constant(Value::Integer(Integer::I64(1)))?;
self.emit(Opcode::Constant, &[one_idx], span);
self.emit(Opcode::Add, &[], span);
self.emit_set_symbol(&i_sym, span);
self.emit(Opcode::Jump, &[loop_start], span);
let loop_exit = self.current_instructions().len();
self.replace_operand(exit_jump, loop_exit)?;
let unit_idx = self.add_constant(Value::Unit)?;
self.emit(Opcode::Constant, &[unit_idx], span);
Ok(())
}
fn compile_vector_flat_map(&mut self, mc: &MethodCallExpr) -> Result<()> {
let span = mc.span;
let id = self.for_loop_counter;
self.for_loop_counter += 1;
let fn_name = format!("__vfm_fn_{id}");
let iter_name = format!("__vfm_iter_{id}");
let len_name = format!("__vfm_len_{id}");
let result_name = format!("__vfm_result_{id}");
let i_name = format!("__vfm_i_{id}");
let elem_name = format!("__vfm_elem_{id}");
self.compile_expression(&mc.arguments[0])?;
let fn_sym = self.define_and_set(&fn_name, false, span)?;
self.compile_expression(&mc.object)?;
let iter_sym = self.define_and_set(&iter_name, false, span)?;
let len_builtin = self.resolve_or_error("Vector::len", span)?;
self.load_symbol(&len_builtin, span);
self.load_symbol(&iter_sym, span);
self.emit(Opcode::Call, &[1], span);
let len_sym = self.define_and_set(&len_name, false, span)?;
self.emit(Opcode::Vector, &[0], span);
let result_sym = self.define_and_set(&result_name, true, span)?;
let zero_idx = self.add_constant(Value::Integer(Integer::I64(0)))?;
self.emit(Opcode::Constant, &[zero_idx], span);
let i_sym = self.define_and_set(&i_name, true, span)?;
let loop_start = self.current_instructions().len();
self.load_symbol(&i_sym, span);
self.load_symbol(&len_sym, span);
self.emit(Opcode::LessThan, &[], span);
let exit_jump = self.emit(Opcode::CondJump, &[Self::JUMP], span);
self.load_symbol(&iter_sym, span);
self.load_symbol(&i_sym, span);
self.emit(Opcode::Index, &[], span);
let elem_sym = self.define_and_set(&elem_name, false, span)?;
let chain_builtin = self.resolve_or_error("Vector::chain", span)?;
self.load_symbol(&chain_builtin, span);
self.load_symbol(&result_sym, span);
self.load_symbol(&fn_sym, span);
self.load_symbol(&elem_sym, span);
self.emit(Opcode::Call, &[1], span);
self.emit(Opcode::Call, &[2], span);
self.emit_set_symbol(&result_sym, span);
self.load_symbol(&i_sym, span);
let one_idx = self.add_constant(Value::Integer(Integer::I64(1)))?;
self.emit(Opcode::Constant, &[one_idx], span);
self.emit(Opcode::Add, &[], span);
self.emit_set_symbol(&i_sym, span);
self.emit(Opcode::Jump, &[loop_start], span);
let loop_exit = self.current_instructions().len();
self.replace_operand(exit_jump, loop_exit)?;
self.load_symbol(&result_sym, span);
Ok(())
}
fn compile_try(&mut self, try_expr: &TryExpr) -> Result<()> {
let span = try_expr.span;
let is_option = try_expr.kind == TryKind::Option;
let success_tag: usize = 0;
let type_index: usize = if is_option { 0 } else { 1 };
let none_or_err = (type_index << 8) | 1;
self.compile_expression(&try_expr.expr)?;
let match_tag_pos = self.emit(Opcode::MatchTag, &[success_tag, Self::JUMP], span);
self.emit(Opcode::GetField, &[0], span);
let jump_to_end = self.emit(Opcode::Jump, &[Self::JUMP], span);
let fail_arm = self.current_instructions().len();
self.replace_match_tag_target(match_tag_pos, fail_arm)?;
if is_option {
self.emit(Opcode::Pop, &[], span);
self.emit(Opcode::Construct, &[none_or_err, 0], span);
}
self.emit(Opcode::ReturnValue, &[], span);
let end = self.current_instructions().len();
self.replace_operand(jump_to_end, end)?;
Ok(())
}
fn resolve_method_name(&mut self, mc: &MethodCallExpr) -> Option<String> {
if let Some(ref receiver) = mc.receiver {
let candidate = format!("{receiver}::{}", mc.method);
if self.symbols_table.resolve_symbol(&candidate).is_some() {
return Some(candidate);
}
}
self.type_registry
.iter()
.map(|td| match td {
TypeDef::Struct { name, .. } | TypeDef::Enum { name, .. } => name.as_str(),
})
.chain(BUILTIN_METHOD_PREFIXES.iter().copied())
.find_map(|type_name| {
let candidate = format!("{type_name}::{}", mc.method);
self.symbols_table
.resolve_symbol(&candidate)
.map(|_| candidate)
})
}
fn resolve_or_error(&mut self, name: &str, span: Span) -> Result<Symbol> {
self.symbols_table.resolve_symbol(name).ok_or_else(|| {
CompileErrorKind::UndefinedVariable {
name: name.to_string(),
}
.at(span)
.into()
})
}
fn attach_span(&self, err: Error, span: Span) -> Error {
match err {
Error::Compile(ce) if ce.span.is_none() => CompileError {
kind: ce.kind,
span: Some(span),
}
.into(),
other => other,
}
}
fn add_constant(&mut self, val: Value) -> Result<usize> {
let index = self.constants.len();
if index > MAX_CONSTANT_POOL_SIZE {
return Err(CompileError::new(CompileErrorKind::ConstantPoolOverflow {
max: MAX_CONSTANT_POOL_SIZE,
attempted: index,
})
.into());
}
self.constants.push(val);
Ok(index)
}
fn current_instructions(&self) -> &Instructions {
&self.scopes[self.scope_index].instructions
}
fn compile_fn_body<'a>(
&mut self,
name: Option<&str>,
param_names: impl Iterator<Item = &'a str>,
num_params: usize,
body: &BlockStmt,
span: Span,
) -> Result<()> {
self.enter_scope();
if let Some(name) = name {
self.symbols_table.define_function_name(name);
}
for param in param_names {
if let Err(e) = self.symbols_table.define_symbol(param, false) {
return Err(self.attach_span(e, span));
}
}
self.compile_block_statement(body)?;
if self.last_instruction_is(Opcode::Pop) {
self.replace_last_pop_with_return_value();
}
if !self.last_instruction_is(Opcode::ReturnValue) {
self.emit(Opcode::Return, &[], span);
}
let free_vars = self.symbols_table.free_vars().to_vec();
let num_free = free_vars.len();
let num_locals = self.symbols_table.max_definitions();
let (instructions, inner_source_map) = self.leave_scope()?;
for sym in &free_vars {
self.load_symbol(sym, span);
}
let compiled_fn = Value::CompiledFn(CompiledFn {
instructions: Rc::from(instructions.as_bytes()),
num_locals,
num_parameters: num_params,
source_map: inner_source_map,
});
let index = self.add_constant(compiled_fn)?;
self.emit(Opcode::Closure, &[index, num_free], span);
Ok(())
}
fn compile_impl_block(&mut self, impl_block: &ImplBlock) -> Result<()> {
let type_name = match &impl_block.self_type {
TypeExpr::Named(n) => &n.name,
TypeExpr::Generic(name, _, _) => name,
_ => return Ok(()),
};
for method in &impl_block.methods {
let span = method.span;
let qualified_name = format!("{}::{}", type_name, method.name);
self.compile_fn_body(
Some(&qualified_name),
method.param_names(),
method.params.len(),
&method.body,
span,
)?;
self.define_and_set(&qualified_name, false, span)?;
}
Ok(())
}
fn enter_scope(&mut self) {
self.scopes.push(CompilationScope::new());
self.scope_index += 1;
let outer = std::mem::take(&mut self.symbols_table);
self.symbols_table = SymbolsTable::new_enclosed(outer);
}
fn leave_scope(&mut self) -> Result<(Instructions, SourceMap)> {
if self.scope_index == 0 {
return Err(CompileError::new(CompileErrorKind::ScopeUnderflow).into());
}
let scope = self
.scopes
.pop()
.ok_or(CompileError::new(CompileErrorKind::ScopeUnderflow))?;
self.scope_index -= 1;
let current = std::mem::take(&mut self.symbols_table);
self.symbols_table = current
.take_outer()
.ok_or(CompileError::new(CompileErrorKind::ScopeUnderflow))?;
Ok((scope.instructions, scope.source_map))
}
fn define_and_set(&mut self, name: &str, mutable: bool, span: Span) -> Result<Symbol> {
let symbol = match self.symbols_table.define_symbol(name, mutable) {
Ok(s) => s.clone(),
Err(e) => return Err(self.attach_span(e, span)),
};
self.emit_set_symbol(&symbol, span);
Ok(symbol)
}
fn emit_set_symbol(&mut self, symbol: &Symbol, span: Span) {
match symbol.scope {
SymbolScope::Global => self.emit(Opcode::SetGlobal, &[symbol.index], span),
SymbolScope::Local => self.emit(Opcode::SetLocal, &[symbol.index], span),
SymbolScope::Builtin | SymbolScope::Free | SymbolScope::Function => {
unreachable!("define_symbol never produces this scope")
}
};
}
fn load_symbol(&mut self, symbol: &Symbol, span: Span) {
match symbol.scope {
SymbolScope::Global => self.emit(Opcode::GetGlobal, &[symbol.index], span),
SymbolScope::Local => self.emit(Opcode::GetLocal, &[symbol.index], span),
SymbolScope::Builtin => self.emit(Opcode::GetBuiltin, &[symbol.index], span),
SymbolScope::Free => self.emit(Opcode::GetFree, &[symbol.index], span),
SymbolScope::Function => self.emit(Opcode::CurrentClosure, &[], span),
};
}
fn emit(&mut self, opcode: Opcode, operands: &[usize], span: Span) -> usize {
let instruction = encode(opcode, operands);
let pos = self.add_instruction(&instruction);
self.scopes[self.scope_index].source_map.add(pos, span);
self.set_last_instruction(opcode, pos);
pos
}
fn add_instruction(&mut self, instruction: &[u8]) -> usize {
let scope = &mut self.scopes[self.scope_index];
let pos = scope.instructions.len();
scope.instructions.extend_from_bytes(instruction);
pos
}
fn set_last_instruction(&mut self, opcode: Opcode, position: usize) {
let scope = &mut self.scopes[self.scope_index];
scope.previous_instruction = scope.last_instruction;
scope.last_instruction = Some(Instruction { opcode, position });
}
fn last_instruction_is(&self, opcode: Opcode) -> bool {
self.scopes[self.scope_index]
.last_instruction
.is_some_and(|last| last.opcode == opcode)
}
fn remove_last_pop(&mut self) {
let scope = &mut self.scopes[self.scope_index];
if let Some(last) = scope.last_instruction {
scope.instructions.truncate(last.position);
scope.last_instruction = scope.previous_instruction;
}
}
fn replace_last_pop_with_return_value(&mut self) {
let scope = &mut self.scopes[self.scope_index];
if let Some(last) = scope.last_instruction {
let new_inst = encode(Opcode::ReturnValue, &[]);
scope.instructions.replace_bytes(last.position, &new_inst);
scope.last_instruction = Some(Instruction {
opcode: Opcode::ReturnValue,
position: last.position,
});
}
}
fn replace_operand(&mut self, op_pos: usize, operand: usize) -> Result<()> {
let scope = &mut self.scopes[self.scope_index];
let byte = scope.instructions.as_bytes()[op_pos];
let op =
Opcode::from_byte(byte).ok_or(CompileError::new(CompileErrorKind::InvalidOpcode {
opcode: byte,
position: op_pos,
}))?;
let new_inst = encode(op, &[operand]);
scope.instructions.replace_bytes(op_pos, &new_inst);
Ok(())
}
fn compile_macro_call(&mut self, mc: &MacroCallExpr) -> Result<()> {
let span = mc.span;
match mc.name.as_str() {
"format" => self.compile_format_macro(&mc.arguments, span),
"print" => self.compile_print_macro(&mc.arguments, false, span),
"println" => self.compile_print_macro(&mc.arguments, true, span),
"assert" => self.compile_assert_macro(&mc.arguments, span),
"assert_eq" => self.compile_assert_eq_macro(&mc.arguments, span),
"panic" => self.compile_panic_macro(&mc.arguments, span),
"todo" => self.emit_builtin_call(
"__panic",
&[Value::Str("not yet implemented".to_string())],
span,
),
"unimplemented" => self.emit_builtin_call(
"__panic",
&[Value::Str("not implemented".to_string())],
span,
),
_ => Err(CompileErrorKind::UnknownMacro {
name: mc.name.clone(),
}
.at(span)
.into()),
}
}
fn compile_format_macro(&mut self, args: &[Expr], span: Span) -> Result<()> {
if args.is_empty() {
let idx = self.add_constant(Value::Str(String::new()))?;
self.emit(Opcode::Constant, &[idx], span);
return Ok(());
}
let fmt = match &args[0] {
Expr::Str(s) => unescape_string(&s.value),
_ => {
return Err(CompileErrorKind::MacroExpectsFormatString {
macro_name: "format".to_string(),
}
.at(span)
.into());
}
};
let segments = parse_format_string(&fmt);
let placeholder_count = segments
.iter()
.filter(|s| matches!(s, FmtSegment::Arg))
.count();
let value_args = &args[1..];
if placeholder_count != value_args.len() {
return Err(CompileErrorKind::FormatArgCountMismatch {
placeholders: placeholder_count,
arguments: value_args.len(),
}
.at(span)
.into());
}
let mut arg_idx = 0;
let mut pieces = 0usize;
for segment in &segments {
match segment {
FmtSegment::Literal(text) => {
let idx = self.add_constant(Value::Str(text.clone()))?;
self.emit(Opcode::Constant, &[idx], span);
pieces += 1;
}
FmtSegment::Arg => {
self.emit_builtin_call_expr("__to_string", &value_args[arg_idx], span)?;
arg_idx += 1;
pieces += 1;
}
FmtSegment::Capture(name) => {
let ident_expr = Expr::Ident(Ident {
value: name.clone(),
span,
});
self.emit_builtin_call_expr("__to_string", &ident_expr, span)?;
pieces += 1;
}
}
if pieces > 1 {
self.emit(Opcode::Add, &[], span);
}
}
if pieces == 0 {
let idx = self.add_constant(Value::Str(String::new()))?;
self.emit(Opcode::Constant, &[idx], span);
}
Ok(())
}
fn compile_print_macro(&mut self, args: &[Expr], newline: bool, span: Span) -> Result<()> {
let macro_name = if newline { "println" } else { "print" };
if args.is_empty() {
if newline {
return self.emit_builtin_call(
"__print_str_ln",
&[Value::Str(String::new())],
span,
);
}
return self.emit_builtin_call("__print_str", &[Value::Str(String::new())], span);
}
let fmt = match &args[0] {
Expr::Str(s) => unescape_string(&s.value),
_ => {
return Err(CompileErrorKind::MacroExpectsFormatString {
macro_name: macro_name.to_string(),
}
.at(span)
.into());
}
};
let segments = parse_format_string(&fmt);
let placeholder_count = segments
.iter()
.filter(|s| matches!(s, FmtSegment::Arg))
.count();
let value_args = &args[1..];
if placeholder_count != value_args.len() {
return Err(CompileErrorKind::FormatArgCountMismatch {
placeholders: placeholder_count,
arguments: value_args.len(),
}
.at(span)
.into());
}
let mut arg_idx = 0;
let mut emitted_calls = 0usize;
for segment in &segments {
if emitted_calls > 0 {
self.emit(Opcode::Pop, &[], span);
}
match segment {
FmtSegment::Literal(text) => {
self.emit_builtin_call("__print_str", &[Value::Str(text.clone())], span)?;
emitted_calls += 1;
}
FmtSegment::Arg => {
self.emit_builtin_call_expr("__to_string", &value_args[arg_idx], span)?;
self.emit_builtin_call_stack("__print_str", span)?;
arg_idx += 1;
emitted_calls += 1;
}
FmtSegment::Capture(name) => {
let ident_expr = Expr::Ident(Ident {
value: name.clone(),
span,
});
self.emit_builtin_call_expr("__to_string", &ident_expr, span)?;
self.emit_builtin_call_stack("__print_str", span)?;
emitted_calls += 1;
}
}
}
if newline {
if emitted_calls > 0 {
self.emit(Opcode::Pop, &[], span);
}
self.emit_builtin_call("__print_str_ln", &[Value::Str(String::new())], span)?;
}
Ok(())
}
fn compile_assert_macro(&mut self, args: &[Expr], span: Span) -> Result<()> {
if args.is_empty() || args.len() > 2 {
return Err(CompileErrorKind::FormatArgCountMismatch {
placeholders: 1,
arguments: args.len(),
}
.at(span)
.into());
}
self.compile_expression(&args[0])?;
let cond_jump_pos = self.emit(Opcode::CondJump, &[Self::JUMP], span);
let skip_pos = self.emit(Opcode::Jump, &[Self::JUMP], span);
let panic_start = self.current_instructions().len();
self.replace_operand(cond_jump_pos, panic_start)?;
if args.len() == 2 {
self.compile_expression(&args[1])?;
self.emit_builtin_call_stack("__panic", span)?;
} else {
self.emit_builtin_call(
"__panic",
&[Value::Str("assertion failed".to_string())],
span,
)?;
}
self.emit(Opcode::Pop, &[], span);
let after = self.current_instructions().len();
self.replace_operand(skip_pos, after)?;
self.emit(Opcode::Unit, &[], span);
Ok(())
}
fn compile_assert_eq_macro(&mut self, args: &[Expr], span: Span) -> Result<()> {
if args.len() != 2 {
return Err(CompileErrorKind::FormatArgCountMismatch {
placeholders: 2,
arguments: args.len(),
}
.at(span)
.into());
}
self.compile_expression(&args[0])?;
self.compile_expression(&args[1])?;
self.emit(Opcode::Equal, &[], span);
let cond_jump_pos = self.emit(Opcode::CondJump, &[Self::JUMP], span);
let skip_pos = self.emit(Opcode::Jump, &[Self::JUMP], span);
let panic_start = self.current_instructions().len();
self.replace_operand(cond_jump_pos, panic_start)?;
self.emit_builtin_call(
"__panic",
&[Value::Str("assertion `left == right` failed".to_string())],
span,
)?;
self.emit(Opcode::Pop, &[], span);
let after = self.current_instructions().len();
self.replace_operand(skip_pos, after)?;
self.emit(Opcode::Unit, &[], span);
Ok(())
}
fn compile_panic_macro(&mut self, args: &[Expr], span: Span) -> Result<()> {
if args.is_empty() {
return self.emit_builtin_call(
"__panic",
&[Value::Str("explicit panic".to_string())],
span,
);
}
let fmt = match &args[0] {
Expr::Str(s) => unescape_string(&s.value),
_ => {
return Err(CompileErrorKind::MacroExpectsFormatString {
macro_name: "panic".to_string(),
}
.at(span)
.into());
}
};
let segments = parse_format_string(&fmt);
let placeholder_count = segments
.iter()
.filter(|s| matches!(s, FmtSegment::Arg))
.count();
let value_args = &args[1..];
if placeholder_count != value_args.len() {
return Err(CompileErrorKind::FormatArgCountMismatch {
placeholders: placeholder_count,
arguments: value_args.len(),
}
.at(span)
.into());
}
if placeholder_count == 0 {
return self.emit_builtin_call("__panic", &[Value::Str(fmt)], span);
}
let mut arg_idx = 0;
let mut first = true;
for segment in &segments {
let segment_str = match segment {
FmtSegment::Literal(text) => {
let idx = self.add_constant(Value::Str(text.clone()))?;
self.emit(Opcode::Constant, &[idx], span);
true
}
FmtSegment::Arg => {
self.emit_builtin_call_expr("__to_string", &value_args[arg_idx], span)?;
arg_idx += 1;
true
}
FmtSegment::Capture(name) => {
let ident_expr = Expr::Ident(Ident {
value: name.clone(),
span,
});
self.emit_builtin_call_expr("__to_string", &ident_expr, span)?;
true
}
};
if segment_str && !first {
let rhs_tmp = format!("__panic_rhs_{}", self.current_instructions().len());
let rhs_sym = self.define_and_set(&rhs_tmp, false, span)?;
let lhs_tmp = format!("__panic_lhs_{}", self.current_instructions().len());
let lhs_sym = self.define_and_set(&lhs_tmp, false, span)?;
let concat_idx = registry::resolve_builtin_index("__str_concat");
self.emit(Opcode::GetBuiltin, &[concat_idx], span);
self.load_symbol(&lhs_sym, span);
self.load_symbol(&rhs_sym, span);
self.emit(Opcode::Call, &[2], span);
}
if segment_str {
first = false;
}
}
self.emit_builtin_call_stack("__panic", span)?;
Ok(())
}
fn emit_builtin_call(&mut self, name: &str, const_args: &[Value], span: Span) -> Result<()> {
let builtin_idx = registry::resolve_builtin_index(name);
self.emit(Opcode::GetBuiltin, &[builtin_idx], span);
for arg in const_args {
let idx = self.add_constant(arg.clone())?;
self.emit(Opcode::Constant, &[idx], span);
}
self.emit(Opcode::Call, &[const_args.len()], span);
Ok(())
}
fn emit_builtin_call_expr(&mut self, name: &str, arg: &Expr, span: Span) -> Result<()> {
let builtin_idx = registry::resolve_builtin_index(name);
self.emit(Opcode::GetBuiltin, &[builtin_idx], span);
self.compile_expression(arg)?;
self.emit(Opcode::Call, &[1], span);
Ok(())
}
fn emit_builtin_call_stack(&mut self, name: &str, span: Span) -> Result<()> {
let temp_name = format!("__macro_tmp_{}", self.current_instructions().len());
let symbol = self.define_and_set(&temp_name, false, span)?;
let builtin_idx = registry::resolve_builtin_index(name);
self.emit(Opcode::GetBuiltin, &[builtin_idx], span);
self.load_symbol(&symbol, span);
self.emit(Opcode::Call, &[1], span);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constant_pool_overflow() {
let mut compiler = Compiler::new();
for i in 0..=MAX_CONSTANT_POOL_SIZE as i64 {
let result = compiler.add_constant(Value::Integer(Integer::I64(i)));
assert!(result.is_ok(), "should succeed for index {i}");
}
let result = compiler.add_constant(Value::Integer(Integer::I64(999)));
assert!(
result.is_err(),
"should fail when exceeding MAX_CONSTANT_POOL_SIZE"
);
match result.unwrap_err() {
Error::Compile(CompileError {
kind: CompileErrorKind::ConstantPoolOverflow { max, attempted },
..
}) => {
assert_eq!(max, MAX_CONSTANT_POOL_SIZE);
assert_eq!(attempted, MAX_CONSTANT_POOL_SIZE + 1);
}
other => panic!("expected ConstantPoolOverflow, got {:?}", other),
}
}
#[test]
fn unsupported_prefix_operator() {
use maat_ast::{ExprStmt, NumKind, Number, PrefixExpr, Radix};
let expr = Expr::Prefix(PrefixExpr {
operator: "~".to_string(),
operand: Box::new(Expr::Number(Number {
kind: NumKind::I64,
value: 5,
radix: Radix::Dec,
span: Span::ZERO,
})),
span: Span::ZERO,
});
let program = Program {
statements: vec![Stmt::Expr(ExprStmt {
value: expr,
span: Span::ZERO,
})],
};
let mut compiler = Compiler::new();
let result = compiler.compile(&Node::Program(program));
assert!(
result.is_err(),
"should fail on unsupported prefix operator"
);
match result.unwrap_err() {
Error::Compile(CompileError {
kind: CompileErrorKind::UnsupportedOperator { operator, context },
..
}) => {
assert_eq!(operator, "~");
assert_eq!(context, "prefix expression");
}
other => panic!("expected UnsupportedOperator, got {:?}", other),
}
}
#[test]
fn scopes() {
let mut compiler = Compiler::new();
assert_eq!(compiler.scope_index, 0);
compiler.emit(Opcode::Mul, &[], Span::ZERO);
compiler.enter_scope();
assert_eq!(compiler.scope_index, 1);
compiler.emit(Opcode::Sub, &[], Span::ZERO);
assert_eq!(compiler.scopes[compiler.scope_index].instructions.len(), 1);
assert_eq!(
compiler.scopes[compiler.scope_index]
.last_instruction
.unwrap()
.opcode,
Opcode::Sub
);
let (instructions, _source_map) = compiler.leave_scope().expect("should leave scope");
assert_eq!(compiler.scope_index, 0);
assert_eq!(instructions.len(), 1);
assert_eq!(
compiler.scopes[compiler.scope_index]
.last_instruction
.unwrap()
.opcode,
Opcode::Mul
);
}
}