extern crate alloc;
use alloc::boxed::Box;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::format;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use crate::ast::*;
use crate::bytecode::*;
use crate::token::Span;
#[derive(Debug, Clone)]
pub struct CompileError {
pub message: String,
pub span: Span,
}
struct Local {
name: String,
slot: u16,
depth: u32,
ty: Option<TypeExpr>,
}
#[derive(Default, Clone)]
struct TypeInfo {
structs: BTreeMap<String, BTreeMap<String, TypeExpr>>,
enums: BTreeMap<String, BTreeMap<String, Vec<TypeExpr>>>,
function_returns: BTreeMap<String, TypeExpr>,
data_field_types: BTreeMap<String, BTreeMap<String, TypeExpr>>,
}
struct FuncCompiler {
chunk: Chunk,
locals: Vec<Local>,
scope_depth: u32,
next_slot: u16,
loop_breaks: Vec<Vec<usize>>,
function_map: BTreeMap<String, u16>,
native_map: BTreeMap<String, u16>,
data_fields: BTreeMap<String, Vec<(String, u16)>>,
type_info: TypeInfo,
}
impl FuncCompiler {
fn new(
name: &str,
block_type: BlockType,
function_map: BTreeMap<String, u16>,
native_map: BTreeMap<String, u16>,
data_fields: BTreeMap<String, Vec<(String, u16)>>,
type_info: TypeInfo,
) -> Self {
Self {
chunk: Chunk {
name: String::from(name),
ops: Vec::new(),
constants: Vec::new(),
struct_templates: Vec::new(),
local_count: 0,
param_count: 0,
block_type,
},
locals: Vec::new(),
scope_depth: 0,
next_slot: 0,
loop_breaks: Vec::new(),
function_map,
native_map,
data_fields,
type_info,
}
}
fn static_for_in_length(&self, expr: &Expr) -> Option<i64> {
match expr {
Expr::ArrayLiteral { elements, .. } => Some(elements.len() as i64),
Expr::Call { name, .. } => {
let return_type = self.type_info.function_returns.get(name)?;
array_length_of_type(return_type)
}
Expr::FieldAccess { object, field, .. } => {
let owner = self.struct_name_of(object)?;
let field_type = self
.type_info
.structs
.get(&owner)
.or_else(|| self.type_info.data_field_types.get(&owner))
.and_then(|fields| fields.get(field))?;
array_length_of_type(field_type)
}
Expr::Ident { name, .. } => {
let ty = self.local_type(name)?;
array_length_of_type(ty)
}
Expr::ArrayIndex { object, .. } => {
let object_ty = infer_expr_type(self, object)?;
let elem_ty = element_type_of(&object_ty)?;
array_length_of_type(&elem_ty)
}
Expr::Match { arms, .. } => {
let first = arms.first()?;
let arm_ty = infer_expr_type(self, &first.expr)?;
array_length_of_type(&arm_ty)
}
_ => None,
}
}
fn struct_name_of(&self, expr: &Expr) -> Option<String> {
match expr {
Expr::Ident { name, .. } => {
if self.data_fields.contains_key(name) {
return Some(name.clone());
}
let ty = self.local_type(name)?;
if let TypeExpr::Named(struct_name, _, _) = ty {
return Some(struct_name.clone());
}
None
}
_ => None,
}
}
fn emit(&mut self, op: Op) -> usize {
let idx = self.chunk.ops.len();
self.chunk.ops.push(op);
idx
}
fn emit_jump(&mut self, placeholder: Op) -> usize {
self.emit(placeholder)
}
fn patch_jump(&mut self, addr: usize) {
let target = self.chunk.ops.len() as u32;
match &mut self.chunk.ops[addr] {
Op::If(a)
| Op::Else(a)
| Op::Loop(a)
| Op::EndLoop(a)
| Op::Break(a)
| Op::BreakIf(a) => *a = target,
_ => {}
}
}
fn add_constant(&mut self, value: Value) -> u16 {
let cv = ConstValue::try_from_value(value).expect("compile-time constant only");
for (i, c) in self.chunk.constants.iter().enumerate() {
if *c == cv {
return i as u16;
}
}
let idx = self.chunk.constants.len() as u16;
self.chunk.constants.push(cv);
idx
}
fn add_string_constant(&mut self, s: &str) -> u16 {
self.add_constant(Value::StaticStr(String::from(s)))
}
fn add_struct_template(&mut self, type_name: &str, field_names: Vec<String>) -> u16 {
let idx = self.chunk.struct_templates.len() as u16;
self.chunk.struct_templates.push(StructTemplate {
type_name: String::from(type_name),
field_names,
});
idx
}
fn resolve_local(&self, name: &str) -> Option<u16> {
for local in self.locals.iter().rev() {
if local.name == name {
return Some(local.slot);
}
}
Option::None
}
fn is_data_block(&self, name: &str) -> bool {
self.data_fields.contains_key(name)
}
fn resolve_data_field(&self, data_name: &str, field: &str) -> Option<u16> {
self.data_fields.get(data_name).and_then(|fields| {
fields
.iter()
.find(|(name, _)| name == field)
.map(|(_, slot)| *slot)
})
}
fn declare_local(&mut self, name: &str) -> u16 {
self.declare_local_typed(name, None)
}
fn declare_local_typed(&mut self, name: &str, ty: Option<TypeExpr>) -> u16 {
let slot = self.next_slot;
self.next_slot += 1;
if self.next_slot > self.chunk.local_count {
self.chunk.local_count = self.next_slot;
}
self.locals.push(Local {
name: String::from(name),
slot,
depth: self.scope_depth,
ty,
});
slot
}
fn local_type(&self, name: &str) -> Option<&TypeExpr> {
for local in self.locals.iter().rev() {
if local.name == name {
return local.ty.as_ref();
}
}
None
}
fn begin_scope(&mut self) {
self.scope_depth += 1;
}
fn end_scope(&mut self) {
while let Some(local) = self.locals.last() {
if local.depth < self.scope_depth {
break;
}
self.locals.pop();
}
self.scope_depth -= 1;
}
fn enter_loop(&mut self) {
self.loop_breaks.push(Vec::new());
}
fn exit_loop(&mut self) {
if let Some(breaks) = self.loop_breaks.pop() {
for addr in breaks {
self.patch_jump(addr);
}
}
}
fn finish(mut self) -> Chunk {
self.chunk.local_count = self.next_slot;
self.chunk
}
}
fn array_length_of_type(t: &TypeExpr) -> Option<i64> {
match t {
TypeExpr::Array(_, n, _) => Some(*n),
_ => None,
}
}
fn element_type_of(t: &TypeExpr) -> Option<TypeExpr> {
match t {
TypeExpr::Array(elem, _, _) => Some((**elem).clone()),
_ => None,
}
}
pub fn compile(program: &Program) -> Result<Module, CompileError> {
compile_with_target(program, &crate::target::Target::host())
}
pub fn compile_with_target(
program: &Program,
target: &crate::target::Target,
) -> Result<Module, CompileError> {
target.validate_against_runtime()?;
crate::target::validate_program_for_target(program, target)?;
crate::typecheck::check(program).map_err(|e| CompileError {
message: format!("type error: {}", e.message),
span: e.span,
})?;
let owned = crate::monomorphize::monomorphize(program.clone());
crate::typecheck::check(&owned).map_err(|e| CompileError {
message: format!("type error after monomorphization: {}", e.message),
span: e.span,
})?;
let owned = hoist_closures(owned);
let program = &owned;
let mut native_names: Vec<String> = Vec::new();
let mut native_map: BTreeMap<String, u16> = BTreeMap::new();
for use_decl in &program.uses {
match &use_decl.import {
ImportItem::Name(name) => {
let full = if use_decl.path.is_empty() {
name.clone()
} else {
let mut full = String::new();
for (i, seg) in use_decl.path.iter().enumerate() {
if i > 0 {
full.push_str("::");
}
full.push_str(seg);
}
full.push_str("::");
full.push_str(name);
full
};
let idx = native_names.len() as u16;
native_map.insert(full.clone(), idx);
native_names.push(full);
}
ImportItem::Wildcard => {
}
}
}
if program.data_decls.len() > 1 {
return Err(CompileError {
message: format!(
"at most one data block per program (R28), found {}",
program.data_decls.len()
),
span: program.data_decls[1].span,
});
}
let mut data_fields: BTreeMap<String, Vec<(String, u16)>> = BTreeMap::new();
let mut data_layout_slots: Vec<DataSlot> = Vec::new();
let mut data_slot_idx: u16 = 0;
for decl in &program.data_decls {
let mut fields = Vec::new();
for field in &decl.fields {
let mut visiting: BTreeSet<String> = BTreeSet::new();
validate_data_field_type(&field.type_expr, &program.types, &mut visiting)?;
fields.push((field.name.clone(), data_slot_idx));
data_layout_slots.push(DataSlot {
name: format!("{}.{}", decl.name, field.name),
});
data_slot_idx += 1;
}
data_fields.insert(decl.name.clone(), fields);
}
let data_layout = if data_layout_slots.is_empty() {
None
} else {
Some(DataLayout {
slots: data_layout_slots,
})
};
let mut synth_impl_methods: Vec<FunctionDef> = Vec::new();
for impl_block in &program.impls {
let head = match &impl_block.for_type {
TypeExpr::Prim(p, _) => match p {
PrimType::I64 => String::from("i64"),
PrimType::F64 => String::from("f64"),
PrimType::Bool => String::from("bool"),
PrimType::KString => String::from("String"),
},
TypeExpr::Unit(_) => String::from("()"),
TypeExpr::Named(name, _, _) => name.clone(),
TypeExpr::Tuple(_, _) => String::from("tuple"),
TypeExpr::Array(_, _, _) => String::from("array"),
TypeExpr::Option(_, _) => String::from("Option"),
};
for method in &impl_block.methods {
let mut renamed = method.clone();
renamed.name = format!("{}::{}::{}", impl_block.trait_name, head, method.name);
synth_impl_methods.push(renamed);
}
}
let mut groups: BTreeMap<String, Vec<&FunctionDef>> = BTreeMap::new();
for func in &program.functions {
groups.entry(func.name.clone()).or_default().push(func);
}
for func in &synth_impl_methods {
groups.entry(func.name.clone()).or_default().push(func);
}
let mut function_map: BTreeMap<String, u16> = BTreeMap::new();
for (chunk_idx, name) in groups.keys().enumerate() {
function_map.insert(name.clone(), chunk_idx as u16);
}
let mut type_info = TypeInfo::default();
for type_def in &program.types {
match type_def {
TypeDef::Struct(s) => {
let mut fields = BTreeMap::new();
for f in &s.fields {
fields.insert(f.name.clone(), f.type_expr.clone());
}
type_info.structs.insert(s.name.clone(), fields);
}
TypeDef::Enum(e) => {
let mut variants = BTreeMap::new();
for v in &e.variants {
variants.insert(v.name.clone(), v.fields.clone());
}
type_info.enums.insert(e.name.clone(), variants);
}
}
}
for func in &program.functions {
type_info
.function_returns
.insert(func.name.clone(), func.return_type.clone());
}
for decl in &program.data_decls {
let mut fields = BTreeMap::new();
for f in &decl.fields {
fields.insert(f.name.clone(), f.type_expr.clone());
}
type_info.data_field_types.insert(decl.name.clone(), fields);
}
let mut chunks: Vec<Chunk> = Vec::new();
for (name, defs) in &groups {
let chunk = compile_function_group(
name,
defs,
&function_map,
&native_map,
&data_fields,
&type_info,
)?;
chunks.push(chunk);
}
let entry_point = function_map.get("main").map(|&idx| idx as usize);
let mut module = Module {
chunks,
native_names,
entry_point,
data_layout,
word_bits_log2: target.word_bits_log2,
addr_bits_log2: target.addr_bits_log2,
float_bits_log2: target.float_bits_log2,
wcet_cycles: 0,
wcmu_bytes: 0,
};
crate::verify::verify(&module).map_err(|e| CompileError {
message: format!("structural verification: {}: {}", e.chunk_name, e.message),
span: crate::token::Span::default(),
})?;
for chunk in &module.chunks {
for op in &chunk.ops {
match op {
crate::bytecode::Op::CallIndirect(_) => {
return Err(CompileError {
message: format!(
"WCET verification: {}: CallIndirect resolves its target chunk at runtime and cannot be statically bounded for WCET/WCMU analysis. First-class function dispatch is not admitted by the safe build pipeline. Restrict the program to direct calls.",
chunk.name
),
span: crate::token::Span::default(),
});
}
crate::bytecode::Op::MakeRecursiveClosure(target, _) => {
return Err(CompileError {
message: format!(
"WCET verification: {}: MakeRecursiveClosure(chunk={}) introduces unbounded recursion that cannot be statically bounded for WCET/WCMU analysis. Recursive closures are not admitted by the safe build pipeline.",
chunk.name, target
),
span: crate::token::Span::default(),
});
}
_ => {}
}
}
}
let mut max_wcet: u32 = 0;
let mut max_wcmu: u32 = 0;
let mut wcet_overflow = false;
let mut wcmu_overflow = false;
for chunk in &module.chunks {
if matches!(chunk.block_type, crate::bytecode::BlockType::Stream) {
match crate::verify::wcet_stream_iteration(chunk) {
Ok(c) => {
max_wcet = max_wcet.max(c);
}
Err(_) => {
wcet_overflow = true;
}
}
match crate::verify::wcmu_stream_iteration(chunk) {
Ok((stack, heap)) => {
let total = stack.saturating_add(heap);
if total == u32::MAX {
wcmu_overflow = true;
} else {
max_wcmu = max_wcmu.max(total);
}
}
Err(_) => {
wcmu_overflow = true;
}
}
}
}
module.wcet_cycles = if wcet_overflow { u32::MAX } else { max_wcet };
module.wcmu_bytes = if wcmu_overflow { u32::MAX } else { max_wcmu };
Ok(module)
}
fn validate_data_field_type(
type_expr: &TypeExpr,
types: &[TypeDef],
visiting: &mut BTreeSet<String>,
) -> Result<(), CompileError> {
match type_expr {
TypeExpr::Prim(prim, span) => match prim {
PrimType::I64 | PrimType::F64 | PrimType::Bool => Ok(()),
PrimType::KString => Err(CompileError {
message: String::from(
"data field type String is not admissible: variable-length \
types cannot be inlined into the data segment",
),
span: *span,
}),
},
TypeExpr::Unit(_) => Ok(()),
TypeExpr::Tuple(elems, _) => {
for elem in elems {
validate_data_field_type(elem, types, visiting)?;
}
Ok(())
}
TypeExpr::Array(elem, _len, _) => validate_data_field_type(elem, types, visiting),
TypeExpr::Option(inner, _) => validate_data_field_type(inner, types, visiting),
TypeExpr::Named(name, _args, span) => {
if visiting.contains(name) {
return Err(CompileError {
message: format!(
"recursive type {} cannot appear in a data segment field: \
the data segment requires statically known fixed size",
name
),
span: *span,
});
}
let type_def = types.iter().find(|td| match td {
TypeDef::Struct(s) => &s.name == name,
TypeDef::Enum(e) => &e.name == name,
});
match type_def {
Some(TypeDef::Struct(s)) => {
visiting.insert(name.clone());
for field in &s.fields {
validate_data_field_type(&field.type_expr, types, visiting)?;
}
visiting.remove(name);
Ok(())
}
Some(TypeDef::Enum(e)) => {
visiting.insert(name.clone());
for variant in &e.variants {
for ftype in &variant.fields {
validate_data_field_type(ftype, types, visiting)?;
}
}
visiting.remove(name);
Ok(())
}
None => Err(CompileError {
message: format!(
"data field type {} is not a struct or enum: opaque types \
are not yet admissible in data segment fields",
name
),
span: *span,
}),
}
}
}
}
fn compile_function_group(
name: &str,
defs: &[&FunctionDef],
function_map: &BTreeMap<String, u16>,
native_map: &BTreeMap<String, u16>,
data_fields: &BTreeMap<String, Vec<(String, u16)>>,
type_info: &TypeInfo,
) -> Result<Chunk, CompileError> {
let first = defs[0];
let block_type = match first.category {
FunctionCategory::Fn => BlockType::Func,
FunctionCategory::Yield => BlockType::Reentrant,
FunctionCategory::Loop => BlockType::Stream,
};
let param_count = first.params.len() as u8;
let mut fc = FuncCompiler::new(
name,
block_type,
function_map.clone(),
native_map.clone(),
data_fields.clone(),
type_info.clone(),
);
fc.chunk.param_count = param_count;
let mut param_slots = Vec::new();
for i in 0..param_count {
let slot = fc.declare_local(&format!("__param{}", i));
param_slots.push(slot);
}
if defs.len() == 1 && !has_non_trivial_pattern(&first.params) && first.guard.is_none() {
for (i, param) in first.params.iter().enumerate() {
bind_param_pattern(
&mut fc,
¶m.pattern,
param_slots[i],
param.type_expr.clone(),
);
}
if block_type == BlockType::Stream {
fc.emit(Op::Stream);
compile_block(&mut fc, &first.body)?;
fc.emit(Op::Pop); fc.emit(Op::Reset);
} else {
compile_block(&mut fc, &first.body)?;
fc.emit(Op::Return);
}
} else {
if block_type == BlockType::Stream {
return Err(CompileError {
message: String::from("multiheaded stream (loop) functions are not supported"),
span: first.params.first().map_or(
Span {
start: 0,
end: 0,
line: 0,
column: 0,
},
|p| p.span,
),
});
}
let mut fail_jumps: Vec<usize> = Vec::new();
for def in defs {
for addr in fail_jumps.drain(..).rev() {
fc.patch_jump(addr);
fc.emit(Op::EndIf);
}
fc.begin_scope();
for (i, param) in def.params.iter().enumerate() {
let fail = compile_pattern_test(&mut fc, ¶m.pattern, param_slots[i])?;
fail_jumps.extend(fail);
}
for (i, param) in def.params.iter().enumerate() {
compile_pattern_bind_typed(
&mut fc,
¶m.pattern,
param_slots[i],
param.type_expr.clone(),
)?;
}
if let Some(guard) = &def.guard {
compile_expr(&mut fc, guard)?;
let fail = fc.emit_jump(Op::If(0));
fail_jumps.push(fail);
}
compile_block(&mut fc, &def.body)?;
fc.emit(Op::Return);
fc.end_scope();
}
for addr in fail_jumps.drain(..).rev() {
fc.patch_jump(addr);
fc.emit(Op::EndIf);
}
let msg = fc.add_string_constant(&format!("no matching head for {}", name));
fc.emit(Op::Trap(msg));
}
Ok(fc.finish())
}
fn has_non_trivial_pattern(params: &[Param]) -> bool {
params
.iter()
.any(|p| !matches!(p.pattern, Pattern::Variable(_, _)))
}
fn bind_param_pattern(fc: &mut FuncCompiler, pattern: &Pattern, slot: u16, ty: Option<TypeExpr>) {
if let Pattern::Variable(name, _) = pattern {
fc.locals.push(Local {
name: name.clone(),
slot,
depth: fc.scope_depth,
ty,
});
}
}
fn compile_block(fc: &mut FuncCompiler, block: &Block) -> Result<(), CompileError> {
fc.begin_scope();
for stmt in &block.stmts {
compile_stmt(fc, stmt)?;
}
if let Some(tail) = &block.tail_expr {
compile_expr(fc, tail)?;
} else {
fc.emit(Op::PushUnit);
}
fc.end_scope();
Ok(())
}
fn compile_data_field_assign(
fc: &mut FuncCompiler,
data_name: &str,
field: &str,
value: &Expr,
span: Span,
) -> Result<(), CompileError> {
let slot = fc
.resolve_data_field(data_name, field)
.ok_or_else(|| CompileError {
message: format!("unknown data field: {}.{}", data_name, field),
span,
})?;
compile_expr(fc, value)?;
fc.emit(Op::SetData(slot));
Ok(())
}
fn compile_stmt(fc: &mut FuncCompiler, stmt: &Stmt) -> Result<(), CompileError> {
match stmt {
Stmt::Let(let_stmt) => {
let ty = let_stmt
.type_expr
.clone()
.or_else(|| infer_expr_type(fc, &let_stmt.value));
compile_expr(fc, &let_stmt.value)?;
compile_let_pattern_typed(fc, &let_stmt.pattern, ty)?;
}
Stmt::For(for_stmt) => {
compile_for(fc, for_stmt)?;
}
Stmt::Break(span) => {
if fc.loop_breaks.is_empty() {
return Err(CompileError {
message: String::from("break outside of loop"),
span: *span,
});
}
let addr = fc.emit_jump(Op::Break(0));
if let Some(breaks) = fc.loop_breaks.last_mut() {
breaks.push(addr);
}
}
Stmt::DataFieldAssign {
data_name,
field,
value,
span,
} => {
compile_data_field_assign(fc, data_name, field, value, *span)?;
}
Stmt::Expr(expr) => {
compile_expr(fc, expr)?;
fc.emit(Op::Pop);
}
}
Ok(())
}
fn infer_expr_type(fc: &FuncCompiler, expr: &Expr) -> Option<TypeExpr> {
match expr {
Expr::StructInit { name, span, .. } => {
Some(TypeExpr::Named(name.clone(), Vec::new(), *span))
}
Expr::Call { name, .. } => fc.type_info.function_returns.get(name).cloned(),
Expr::Ident { name, .. } => fc.local_type(name).cloned(),
Expr::FieldAccess { object, field, .. } => {
let owner = fc.struct_name_of(object)?;
let field_type = fc
.type_info
.structs
.get(&owner)
.or_else(|| fc.type_info.data_field_types.get(&owner))
.and_then(|fields| fields.get(field))?;
Some(field_type.clone())
}
Expr::ArrayLiteral { elements, span } => {
let elem_ty = elements.first().and_then(|e| infer_expr_type(fc, e))?;
Some(TypeExpr::Array(
Box::new(elem_ty),
elements.len() as i64,
*span,
))
}
Expr::ArrayIndex { object, .. } => {
let object_ty = infer_expr_type(fc, object)?;
element_type_of(&object_ty)
}
Expr::Match { arms, .. } => {
let first = arms.first()?;
infer_expr_type(fc, &first.expr)
}
Expr::Literal { value, span } => Some(match value {
Literal::Int(_) => TypeExpr::Prim(PrimType::I64, *span),
Literal::Float(_) => TypeExpr::Prim(PrimType::F64, *span),
Literal::Bool(_) => TypeExpr::Prim(PrimType::Bool, *span),
Literal::String(_) => TypeExpr::Prim(PrimType::KString, *span),
Literal::Unit => TypeExpr::Unit(*span),
}),
_ => None,
}
}
fn type_expr_head(ty: &TypeExpr) -> Option<String> {
use alloc::string::ToString;
match ty {
TypeExpr::Prim(p, _) => Some(
match p {
PrimType::I64 => "i64",
PrimType::F64 => "f64",
PrimType::Bool => "bool",
PrimType::KString => "String",
}
.to_string(),
),
TypeExpr::Unit(_) => Some("()".to_string()),
TypeExpr::Tuple(_, _) => Some("tuple".to_string()),
TypeExpr::Array(_, _, _) => Some("array".to_string()),
TypeExpr::Option(_, _) => Some("Option".to_string()),
TypeExpr::Named(name, _, _) => Some(name.clone()),
}
}
fn compile_let_pattern(fc: &mut FuncCompiler, pattern: &Pattern) -> Result<(), CompileError> {
compile_let_pattern_typed(fc, pattern, None)
}
fn compile_let_pattern_typed(
fc: &mut FuncCompiler,
pattern: &Pattern,
ty: Option<TypeExpr>,
) -> Result<(), CompileError> {
match pattern {
Pattern::Variable(name, _) => {
let slot = fc.declare_local_typed(name, ty);
fc.emit(Op::SetLocal(slot));
}
Pattern::Wildcard(_) => {
fc.emit(Op::Pop);
}
Pattern::Tuple(pats, _) => {
let temp = fc.declare_local("__let_tmp");
fc.emit(Op::SetLocal(temp));
for (i, pat) in pats.iter().enumerate() {
fc.emit(Op::GetLocal(temp));
fc.emit(Op::GetTupleField(i as u8));
compile_let_pattern(fc, pat)?;
}
}
_ => {
let slot = fc.declare_local("_");
fc.emit(Op::SetLocal(slot));
}
}
Ok(())
}
fn compile_for(fc: &mut FuncCompiler, for_stmt: &ForStmt) -> Result<(), CompileError> {
match &for_stmt.iterable {
Iterable::Range(start, end) => {
compile_expr(fc, start)?;
let var_slot = fc.declare_local(&for_stmt.var);
fc.emit(Op::SetLocal(var_slot));
compile_expr(fc, end)?;
let end_slot = fc.declare_local("__for_end");
fc.emit(Op::SetLocal(end_slot));
let loop_addr = fc.emit(Op::Loop(0)); fc.enter_loop();
fc.emit(Op::GetLocal(var_slot));
fc.emit(Op::GetLocal(end_slot));
fc.emit(Op::CmpGe);
let break_addr = fc.emit(Op::BreakIf(0));
fc.begin_scope();
compile_block(fc, &for_stmt.body)?;
fc.emit(Op::Pop); fc.end_scope();
fc.emit(Op::GetLocal(var_slot));
let one_const = fc.add_constant(Value::Int(1));
fc.emit(Op::Const(one_const));
fc.emit(Op::Add);
fc.emit(Op::SetLocal(var_slot));
let endloop_addr = fc.emit(Op::EndLoop(0));
let after_endloop = fc.chunk.ops.len() as u32;
if let Op::Loop(a) = &mut fc.chunk.ops[loop_addr] {
*a = after_endloop;
}
if let Op::BreakIf(a) = &mut fc.chunk.ops[break_addr] {
*a = after_endloop;
}
let after_loop = (loop_addr + 1) as u32;
if let Op::EndLoop(a) = &mut fc.chunk.ops[endloop_addr] {
*a = after_loop;
}
fc.exit_loop(); }
Iterable::Expr(expr) => {
let static_length = fc.static_for_in_length(expr);
let element_ty = infer_expr_type(fc, expr).and_then(|t| element_type_of(&t));
compile_expr(fc, expr)?;
let arr_slot = fc.declare_local("__for_arr");
fc.emit(Op::SetLocal(arr_slot));
let end_slot = fc.declare_local("__for_end");
if let Some(n) = static_length {
let n_const = fc.add_constant(Value::Int(n));
fc.emit(Op::Const(n_const));
fc.emit(Op::SetLocal(end_slot));
} else {
fc.emit(Op::GetLocal(arr_slot));
fc.emit(Op::Len);
fc.emit(Op::SetLocal(end_slot));
}
let zero_const = fc.add_constant(Value::Int(0));
fc.emit(Op::Const(zero_const));
let idx_slot = fc.declare_local("__for_idx");
fc.emit(Op::SetLocal(idx_slot));
let loop_addr = fc.emit(Op::Loop(0));
fc.enter_loop();
fc.emit(Op::GetLocal(idx_slot));
fc.emit(Op::GetLocal(end_slot));
fc.emit(Op::CmpGe);
let break_addr = fc.emit(Op::BreakIf(0));
fc.emit(Op::GetLocal(arr_slot));
fc.emit(Op::GetLocal(idx_slot));
fc.emit(Op::GetIndex);
let var_slot = fc.declare_local_typed(&for_stmt.var, element_ty);
fc.emit(Op::SetLocal(var_slot));
fc.begin_scope();
compile_block(fc, &for_stmt.body)?;
fc.emit(Op::Pop);
fc.end_scope();
fc.emit(Op::GetLocal(idx_slot));
let one_const = fc.add_constant(Value::Int(1));
fc.emit(Op::Const(one_const));
fc.emit(Op::Add);
fc.emit(Op::SetLocal(idx_slot));
let endloop_addr = fc.emit(Op::EndLoop(0));
let after_endloop = fc.chunk.ops.len() as u32;
if let Op::Loop(a) = &mut fc.chunk.ops[loop_addr] {
*a = after_endloop;
}
if let Op::BreakIf(a) = &mut fc.chunk.ops[break_addr] {
*a = after_endloop;
}
let after_loop = (loop_addr + 1) as u32;
if let Op::EndLoop(a) = &mut fc.chunk.ops[endloop_addr] {
*a = after_loop;
}
fc.exit_loop();
}
}
Ok(())
}
fn collect_pattern_names(
pattern: &Pattern,
out: &mut alloc::collections::BTreeSet<alloc::string::String>,
) {
match pattern {
Pattern::Variable(name, _) => {
out.insert(name.clone());
}
Pattern::Wildcard(_) | Pattern::Literal(_, _) => {}
Pattern::Tuple(parts, _) => {
for p in parts {
collect_pattern_names(p, out);
}
}
Pattern::Enum(_, _, sub_pats, _) => {
for p in sub_pats {
collect_pattern_names(p, out);
}
}
Pattern::Struct(_, field_pats, _) => {
for fp in field_pats {
if let Some(p) = &fp.pattern {
collect_pattern_names(p, out);
} else {
out.insert(fp.name.clone());
}
}
}
}
}
struct FreeVarCollector {
bound: alloc::collections::BTreeSet<alloc::string::String>,
free: Vec<alloc::string::String>,
}
impl FreeVarCollector {
fn record_if_free(&mut self, name: &str) {
if !self.bound.contains(name) && !self.free.iter().any(|n| n.as_str() == name) {
self.free.push(alloc::string::String::from(name));
}
}
}
impl crate::visitor::Visitor for FreeVarCollector {
fn visit_block(&mut self, block: &Block) {
let saved = self.bound.clone();
self.walk_block(block);
self.bound = saved;
}
fn visit_stmt(&mut self, stmt: &Stmt) {
match stmt {
Stmt::Let(l) => {
self.visit_expr(&l.value);
collect_pattern_names(&l.pattern, &mut self.bound);
}
Stmt::For(f) => {
match &f.iterable {
Iterable::Range(s, e) => {
self.visit_expr(s);
self.visit_expr(e);
}
Iterable::Expr(e) => self.visit_expr(e),
}
let saved = self.bound.clone();
self.bound.insert(f.var.clone());
self.visit_block(&f.body);
self.bound = saved;
}
_ => self.walk_stmt(stmt),
}
}
fn visit_expr(&mut self, expr: &Expr) {
match expr {
Expr::Ident { name, .. } => self.record_if_free(name),
Expr::Call { name, args, .. } => {
self.record_if_free(name);
for a in args {
self.visit_expr(a);
}
}
Expr::Match {
scrutinee, arms, ..
} => {
self.visit_expr(scrutinee);
for arm in arms {
let saved = self.bound.clone();
collect_pattern_names(&arm.pattern, &mut self.bound);
self.visit_expr(&arm.expr);
self.bound = saved;
}
}
Expr::Closure { .. } => {
}
Expr::ClosureRef { captures, .. } => {
for capture in captures {
self.record_if_free(capture);
}
}
_ => self.walk_expr(expr),
}
}
}
fn collect_free_in_block(
block: &Block,
bound: &alloc::collections::BTreeSet<alloc::string::String>,
out: &mut Vec<alloc::string::String>,
) {
use crate::visitor::Visitor;
let mut collector = FreeVarCollector {
bound: bound.clone(),
free: core::mem::take(out),
};
collector.visit_block(block);
*out = collector.free;
}
fn hoist_closures(mut program: Program) -> Program {
let mut natives: alloc::collections::BTreeSet<alloc::string::String> =
alloc::collections::BTreeSet::new();
for use_decl in &program.uses {
if let ImportItem::Name(name) = &use_decl.import {
let full = if use_decl.path.is_empty() {
name.clone()
} else {
let mut full = String::new();
for (i, seg) in use_decl.path.iter().enumerate() {
if i > 0 {
full.push_str("::");
}
full.push_str(seg);
}
full.push_str("::");
full.push_str(name);
full
};
natives.insert(full);
natives.insert(name.clone());
}
}
use crate::visitor::MutVisitor;
let mut counter: usize = 0;
let mut new_functions: Vec<FunctionDef> = Vec::new();
{
let mut hoister = ClosureHoister {
counter: &mut counter,
out: &mut new_functions,
natives: &natives,
};
for func in program.functions.iter_mut() {
hoister.visit_block(&mut func.body);
}
for impl_block in program.impls.iter_mut() {
for method in impl_block.methods.iter_mut() {
hoister.visit_block(&mut method.body);
}
}
}
program.functions.extend(new_functions);
program
}
struct ClosureHoister<'a> {
counter: &'a mut usize,
out: &'a mut Vec<FunctionDef>,
natives: &'a alloc::collections::BTreeSet<alloc::string::String>,
}
impl ClosureHoister<'_> {
fn hoist_let_bound_closure(&mut self, let_name: alloc::string::String, expr: &mut Expr) {
use crate::visitor::MutVisitor;
let (params, return_type, body, span) = match expr {
Expr::Closure {
params,
return_type,
body,
span,
} => (params, return_type, body, *span),
_ => return,
};
self.visit_block(body);
let mut bound: alloc::collections::BTreeSet<alloc::string::String> =
alloc::collections::BTreeSet::new();
for p in params.iter() {
collect_pattern_names(&p.pattern, &mut bound);
}
let mut free: Vec<alloc::string::String> = Vec::new();
collect_free_in_block(body, &bound, &mut free);
let recursive = free.iter().any(|n| n == &let_name);
if !recursive {
self.visit_expr(expr);
return;
}
free.retain(|n| n != &let_name);
let name = format!("__closure_{}", *self.counter);
*self.counter += 1;
let mut all_params: Vec<Param> = Vec::with_capacity(free.len() + 1 + params.len());
for capture in &free {
all_params.push(Param {
pattern: Pattern::Variable(capture.clone(), span),
type_expr: None,
span,
});
}
all_params.push(Param {
pattern: Pattern::Variable(let_name.clone(), span),
type_expr: None,
span,
});
all_params.extend(params.iter().cloned());
let body_owned = core::mem::replace(
body,
Block {
stmts: Vec::new(),
tail_expr: None,
span,
},
);
let synth = FunctionDef {
category: FunctionCategory::Fn,
name: name.clone(),
type_params: Vec::new(),
params: all_params,
return_type: return_type.clone().unwrap_or(TypeExpr::Unit(span)),
guard: None,
body: body_owned,
span,
};
self.out.push(synth);
*expr = Expr::ClosureRef {
name,
captures: free,
recursive: true,
span,
};
}
}
impl crate::visitor::MutVisitor for ClosureHoister<'_> {
fn visit_stmt(&mut self, stmt: &mut Stmt) {
if let Stmt::Let(l) = stmt
&& let Pattern::Variable(let_name, _) = &l.pattern
&& let Expr::Closure { .. } = &l.value
{
let name = let_name.clone();
self.hoist_let_bound_closure(name, &mut l.value);
return;
}
self.walk_stmt(stmt);
}
fn visit_expr(&mut self, expr: &mut Expr) {
if matches!(expr, Expr::Closure { .. }) {
if let Expr::Closure { body, .. } = expr {
self.visit_block(body);
}
self.hoist_closure_in_place(expr);
return;
}
self.walk_expr(expr);
}
}
impl ClosureHoister<'_> {
fn hoist_closure_in_place(&mut self, expr: &mut Expr) {
let (params, return_type, body, span) = match expr {
Expr::Closure {
params,
return_type,
body,
span,
} => (params, return_type, body, *span),
_ => return,
};
let mut bound: alloc::collections::BTreeSet<alloc::string::String> =
alloc::collections::BTreeSet::new();
for p in params.iter() {
collect_pattern_names(&p.pattern, &mut bound);
}
let mut free: Vec<alloc::string::String> = Vec::new();
collect_free_in_block(body, &bound, &mut free);
free.retain(|n| !self.natives.contains(n));
let name = format!("__closure_{}", *self.counter);
*self.counter += 1;
let mut all_params: Vec<Param> = Vec::with_capacity(free.len() + params.len());
for capture in &free {
all_params.push(Param {
pattern: Pattern::Variable(capture.clone(), span),
type_expr: None,
span,
});
}
all_params.extend(params.iter().cloned());
let body_owned = core::mem::replace(
body,
Block {
stmts: Vec::new(),
tail_expr: None,
span,
},
);
let synth = FunctionDef {
category: FunctionCategory::Fn,
name: name.clone(),
type_params: Vec::new(),
params: all_params,
return_type: return_type.clone().unwrap_or(TypeExpr::Unit(span)),
guard: None,
body: body_owned,
span,
};
self.out.push(synth);
*expr = Expr::ClosureRef {
name,
captures: free,
recursive: false,
span,
};
}
}
fn compile_expr(fc: &mut FuncCompiler, expr: &Expr) -> Result<(), CompileError> {
match expr {
Expr::Literal { value, .. } => match value {
Literal::Int(v) => {
let idx = fc.add_constant(Value::Int(*v));
fc.emit(Op::Const(idx));
}
Literal::Float(v) => {
let idx = fc.add_constant(Value::Float(*v));
fc.emit(Op::Const(idx));
}
Literal::String(s) => {
let idx = fc.add_constant(Value::StaticStr(s.clone()));
fc.emit(Op::Const(idx));
}
Literal::Bool(true) => {
fc.emit(Op::PushTrue);
}
Literal::Bool(false) => {
fc.emit(Op::PushFalse);
}
Literal::Unit => {
fc.emit(Op::PushUnit);
}
},
Expr::Ident { name, span } => {
if let Some(slot) = fc.resolve_local(name) {
fc.emit(Op::GetLocal(slot));
} else if let Some(&idx) = fc.function_map.get(name) {
fc.emit(Op::PushFunc(idx));
} else if fc.is_data_block(name) {
return Err(CompileError {
message: format!(
"data block '{}' cannot be used as a value; access individual fields with {}.field_name",
name, name
),
span: *span,
});
} else {
return Err(CompileError {
message: format!("undefined variable: {}", name),
span: *span,
});
}
}
Expr::BinOp {
op, left, right, ..
} => {
match op {
BinOp::And => {
compile_expr(fc, left)?;
fc.emit(Op::Dup);
let if_addr = fc.emit_jump(Op::If(0));
fc.emit(Op::Pop);
compile_expr(fc, right)?;
let else_addr = fc.emit_jump(Op::Else(0));
fc.patch_jump(if_addr);
fc.patch_jump(else_addr);
fc.emit(Op::EndIf);
return Ok(());
}
BinOp::Or => {
compile_expr(fc, left)?;
fc.emit(Op::Dup);
fc.emit(Op::Not);
let if_addr = fc.emit_jump(Op::If(0));
fc.emit(Op::Pop);
compile_expr(fc, right)?;
let else_addr = fc.emit_jump(Op::Else(0));
fc.patch_jump(if_addr);
fc.patch_jump(else_addr);
fc.emit(Op::EndIf);
return Ok(());
}
_ => {}
}
compile_expr(fc, left)?;
compile_expr(fc, right)?;
match op {
BinOp::Add => {
fc.emit(Op::Add);
}
BinOp::Sub => {
fc.emit(Op::Sub);
}
BinOp::Mul => {
fc.emit(Op::Mul);
}
BinOp::Div => {
fc.emit(Op::Div);
}
BinOp::Mod => {
fc.emit(Op::Mod);
}
BinOp::Eq => {
fc.emit(Op::CmpEq);
}
BinOp::NotEq => {
fc.emit(Op::CmpNe);
}
BinOp::Lt => {
fc.emit(Op::CmpLt);
}
BinOp::Gt => {
fc.emit(Op::CmpGt);
}
BinOp::LtEq => {
fc.emit(Op::CmpLe);
}
BinOp::GtEq => {
fc.emit(Op::CmpGe);
}
BinOp::And | BinOp::Or => unreachable!(),
}
}
Expr::UnaryOp { op, operand, .. } => {
compile_expr(fc, operand)?;
match op {
UnaryOp::Neg => {
fc.emit(Op::Neg);
}
UnaryOp::Not => {
fc.emit(Op::Not);
}
}
}
Expr::Call { name, args, span } => {
compile_call(fc, name, args, span)?;
}
Expr::MethodCall {
receiver,
method,
args,
span,
} => {
let head = match infer_expr_type(fc, receiver).and_then(|t| type_expr_head(&t)) {
Some(h) => h,
None => {
return Err(CompileError {
message: format!(
"method `{}` receiver type cannot be statically resolved; \
this currently requires monomorphization (B2.4)",
method
),
span: *span,
});
}
};
let suffix = format!("::{}::{}", head, method);
let resolved = fc
.function_map
.iter()
.find(|(k, _)| k.ends_with(&suffix))
.map(|(k, &idx)| (k.clone(), idx));
let (mangled, chunk_idx) = match resolved {
Some(p) => p,
None => {
return Err(CompileError {
message: format!(
"type `{}` has no method `{}` from any trait in scope",
head, method
),
span: *span,
});
}
};
let _ = mangled;
compile_expr(fc, receiver)?;
for arg in args {
compile_expr(fc, arg)?;
}
let arg_count = (args.len() + 1) as u8;
fc.emit(Op::Call(chunk_idx, arg_count));
}
Expr::Pipeline {
left,
func,
args,
span,
} => {
let mut call_args: Vec<&Expr> = Vec::new();
let mut placeholder_found = false;
for arg in args {
if matches!(arg, Expr::Placeholder { .. }) {
call_args.push(left);
placeholder_found = true;
} else {
call_args.push(arg);
}
}
if !placeholder_found {
let mut new_args = vec![left.as_ref()];
for arg in args {
new_args.push(arg);
}
call_args = new_args;
}
for arg in &call_args {
compile_expr(fc, arg)?;
}
let arg_count = call_args.len() as u8;
if let Some(&idx) = fc.function_map.get(func.as_str()) {
fc.emit(Op::Call(idx, arg_count));
} else if let Some(&idx) = fc.native_map.get(func.as_str()) {
fc.emit(Op::CallNative(idx, arg_count));
} else {
return Err(CompileError {
message: format!("undefined function: {}", func),
span: *span,
});
}
}
Expr::Yield { value, .. } => {
compile_expr(fc, value)?;
fc.emit(Op::Yield);
}
Expr::If {
condition,
then_block,
else_block,
..
} => {
compile_expr(fc, condition)?;
let if_addr = fc.emit_jump(Op::If(0));
compile_block(fc, then_block)?;
if let Some(else_blk) = else_block {
let else_addr = fc.emit_jump(Op::Else(0));
fc.patch_jump(if_addr);
compile_block(fc, else_blk)?;
fc.patch_jump(else_addr);
fc.emit(Op::EndIf);
} else {
let else_addr = fc.emit_jump(Op::Else(0));
fc.patch_jump(if_addr);
fc.emit(Op::PushUnit);
fc.patch_jump(else_addr);
fc.emit(Op::EndIf);
}
}
Expr::Match {
scrutinee, arms, ..
} => {
compile_expr(fc, scrutinee)?;
let temp = fc.declare_local("__match");
fc.emit(Op::SetLocal(temp));
let loop_addr = fc.emit(Op::Loop(0));
fc.enter_loop();
for arm in arms {
fc.begin_scope();
let fail_addrs = compile_pattern_test(fc, &arm.pattern, temp)?;
compile_pattern_bind(fc, &arm.pattern, temp)?;
compile_expr(fc, &arm.expr)?;
let break_addr = fc.emit(Op::Break(0));
if let Some(breaks) = fc.loop_breaks.last_mut() {
breaks.push(break_addr);
}
fc.end_scope();
for addr in fail_addrs.into_iter().rev() {
fc.patch_jump(addr);
fc.emit(Op::EndIf);
}
}
let msg = fc.add_string_constant("no matching arm in match expression");
fc.emit(Op::Trap(msg));
let endloop_addr = fc.emit(Op::EndLoop(0));
let after_loop = (loop_addr + 1) as u32;
if let Op::EndLoop(a) = &mut fc.chunk.ops[endloop_addr] {
*a = after_loop;
}
let after_endloop = fc.chunk.ops.len() as u32;
if let Op::Loop(a) = &mut fc.chunk.ops[loop_addr] {
*a = after_endloop;
}
fc.exit_loop();
}
Expr::Loop { body, .. } => {
let loop_addr = fc.emit(Op::Loop(0));
fc.enter_loop();
compile_block(fc, body)?;
fc.emit(Op::Pop);
let endloop_addr = fc.emit(Op::EndLoop(0));
let after_loop = (loop_addr + 1) as u32;
if let Op::EndLoop(a) = &mut fc.chunk.ops[endloop_addr] {
*a = after_loop;
}
let after_endloop = fc.chunk.ops.len() as u32;
if let Op::Loop(a) = &mut fc.chunk.ops[loop_addr] {
*a = after_endloop;
}
fc.exit_loop();
fc.emit(Op::PushUnit);
}
Expr::FieldAccess {
object,
field,
span,
} => {
if let Expr::Ident { name, .. } = object.as_ref()
&& fc.is_data_block(name)
{
let slot = fc
.resolve_data_field(name, field)
.ok_or_else(|| CompileError {
message: format!("unknown data field: {}.{}", name, field),
span: *span,
})?;
fc.emit(Op::GetData(slot));
return Ok(());
}
compile_expr(fc, object)?;
let name_const = fc.add_string_constant(field);
fc.emit(Op::GetField(name_const));
}
Expr::TupleIndex { object, index, .. } => {
compile_expr(fc, object)?;
fc.emit(Op::GetTupleField(*index as u8));
}
Expr::ArrayIndex { object, index, .. } => {
compile_expr(fc, object)?;
compile_expr(fc, index)?;
fc.emit(Op::GetIndex);
}
Expr::StructInit { name, fields, .. } => {
let field_names: Vec<String> = fields.iter().map(|f| f.name.clone()).collect();
let template_idx = fc.add_struct_template(name, field_names);
for field in fields {
compile_expr(fc, &field.value)?;
}
fc.emit(Op::NewStruct(template_idx));
}
Expr::EnumVariant {
enum_name,
variant,
args,
..
} => {
for arg in args {
compile_expr(fc, arg)?;
}
let enum_const = fc.add_string_constant(enum_name);
let var_const = fc.add_string_constant(variant);
fc.emit(Op::NewEnum(enum_const, var_const, args.len() as u8));
}
Expr::ArrayLiteral { elements, .. } => {
for elem in elements {
compile_expr(fc, elem)?;
}
fc.emit(Op::NewArray(elements.len() as u16));
}
Expr::TupleLiteral { elements, .. } => {
for elem in elements {
compile_expr(fc, elem)?;
}
fc.emit(Op::NewTuple(elements.len() as u8));
}
Expr::Cast {
expr: inner,
target,
..
} => {
compile_expr(fc, inner)?;
match target {
TypeExpr::Prim(PrimType::F64, _) => {
fc.emit(Op::IntToFloat);
}
TypeExpr::Prim(PrimType::I64, _) => {
fc.emit(Op::FloatToInt);
}
_ => {
}
}
}
Expr::Placeholder { span } => {
return Err(CompileError {
message: String::from("placeholder _ outside of pipeline"),
span: *span,
});
}
Expr::Closure { span, .. } => {
return Err(CompileError {
message: String::from("internal: closure expression encountered after hoist pass"),
span: *span,
});
}
Expr::ClosureRef {
name,
captures,
recursive,
span,
} => {
let chunk_idx = match fc.function_map.get(name) {
Some(&i) => i,
None => {
return Err(CompileError {
message: format!("internal: synthetic closure name {} not found", name),
span: *span,
});
}
};
for capture in captures {
if let Some(slot) = fc.resolve_local(capture) {
fc.emit(Op::GetLocal(slot));
} else if let Some(&fn_idx) = fc.function_map.get(capture) {
fc.emit(Op::PushFunc(fn_idx));
} else {
return Err(CompileError {
message: format!(
"closure captures `{}` which is not a local in the enclosing scope",
capture
),
span: *span,
});
}
}
let n = captures.len() as u8;
if *recursive {
fc.emit(Op::MakeRecursiveClosure(chunk_idx, n));
} else if n == 0 {
fc.emit(Op::PushFunc(chunk_idx));
} else {
fc.emit(Op::MakeClosure(chunk_idx, n));
}
}
}
Ok(())
}
fn compile_call(
fc: &mut FuncCompiler,
name: &str,
args: &[Expr],
span: &Span,
) -> Result<(), CompileError> {
if let Some(slot) = fc.resolve_local(name) {
fc.emit(Op::GetLocal(slot));
for arg in args {
compile_expr(fc, arg)?;
}
fc.emit(Op::CallIndirect(args.len() as u8));
return Ok(());
}
for arg in args {
compile_expr(fc, arg)?;
}
let arg_count = args.len() as u8;
if let Some(&idx) = fc.function_map.get(name) {
fc.emit(Op::Call(idx, arg_count));
} else if let Some(&idx) = fc.native_map.get(name) {
fc.emit(Op::CallNative(idx, arg_count));
} else {
return Err(CompileError {
message: format!("undefined function: {}", name),
span: *span,
});
}
Ok(())
}
fn compile_pattern_test(
fc: &mut FuncCompiler,
pattern: &Pattern,
value_slot: u16,
) -> Result<Vec<usize>, CompileError> {
let mut fail_addrs = Vec::new();
match pattern {
Pattern::Variable(_, _) | Pattern::Wildcard(_) => {
}
Pattern::Literal(lit, _) => {
fc.emit(Op::GetLocal(value_slot));
match lit {
Literal::Int(v) => {
let idx = fc.add_constant(Value::Int(*v));
fc.emit(Op::Const(idx));
}
Literal::Float(v) => {
let idx = fc.add_constant(Value::Float(*v));
fc.emit(Op::Const(idx));
}
Literal::String(s) => {
let idx = fc.add_constant(Value::StaticStr(s.clone()));
fc.emit(Op::Const(idx));
}
Literal::Bool(true) => {
fc.emit(Op::PushTrue);
}
Literal::Bool(false) => {
fc.emit(Op::PushFalse);
}
Literal::Unit => {
fc.emit(Op::PushUnit);
}
}
fc.emit(Op::CmpEq);
fail_addrs.push(fc.emit_jump(Op::If(0)));
}
Pattern::Enum(enum_name, variant, sub_pats, _) => {
fc.emit(Op::GetLocal(value_slot));
let e_const = fc.add_string_constant(enum_name);
let v_const = fc.add_string_constant(variant);
fc.emit(Op::IsEnum(e_const, v_const));
fail_addrs.push(fc.emit_jump(Op::If(0)));
fc.emit(Op::Pop);
for (i, sub_pat) in sub_pats.iter().enumerate() {
if matches!(sub_pat, Pattern::Variable(_, _) | Pattern::Wildcard(_)) {
continue; }
let temp = fc.declare_local(&format!("__enum_field{}", i));
fc.emit(Op::GetLocal(value_slot));
fc.emit(Op::GetEnumField(i as u8));
fc.emit(Op::SetLocal(temp));
let sub_fails = compile_pattern_test(fc, sub_pat, temp)?;
fail_addrs.extend(sub_fails);
}
}
Pattern::Struct(type_name, field_pats, _) => {
fc.emit(Op::GetLocal(value_slot));
let t_const = fc.add_string_constant(type_name);
fc.emit(Op::IsStruct(t_const));
fail_addrs.push(fc.emit_jump(Op::If(0)));
fc.emit(Op::Pop);
for field_pat in field_pats {
if let Some(pat) = &field_pat.pattern {
if matches!(pat, Pattern::Variable(_, _) | Pattern::Wildcard(_)) {
continue;
}
let temp = fc.declare_local(&format!("__struct_{}", field_pat.name));
fc.emit(Op::GetLocal(value_slot));
let name_const = fc.add_string_constant(&field_pat.name);
fc.emit(Op::GetField(name_const));
fc.emit(Op::SetLocal(temp));
let sub_fails = compile_pattern_test(fc, pat, temp)?;
fail_addrs.extend(sub_fails);
}
}
}
Pattern::Tuple(pats, _) => {
for (i, pat) in pats.iter().enumerate() {
if matches!(pat, Pattern::Variable(_, _) | Pattern::Wildcard(_)) {
continue;
}
let temp = fc.declare_local(&format!("__tuple_{}", i));
fc.emit(Op::GetLocal(value_slot));
fc.emit(Op::GetTupleField(i as u8));
fc.emit(Op::SetLocal(temp));
let sub_fails = compile_pattern_test(fc, pat, temp)?;
fail_addrs.extend(sub_fails);
}
}
}
Ok(fail_addrs)
}
fn compile_pattern_bind(
fc: &mut FuncCompiler,
pattern: &Pattern,
value_slot: u16,
) -> Result<(), CompileError> {
compile_pattern_bind_typed(fc, pattern, value_slot, None)
}
fn compile_pattern_bind_typed(
fc: &mut FuncCompiler,
pattern: &Pattern,
value_slot: u16,
ty: Option<TypeExpr>,
) -> Result<(), CompileError> {
match pattern {
Pattern::Variable(name, _) => {
fc.emit(Op::GetLocal(value_slot));
let slot = fc.declare_local_typed(name, ty);
fc.emit(Op::SetLocal(slot));
}
Pattern::Wildcard(_) | Pattern::Literal(_, _) => {
}
Pattern::Enum(enum_name, variant, sub_pats, _) => {
let payload_types: Vec<Option<TypeExpr>> = fc
.type_info
.enums
.get(enum_name)
.and_then(|variants| variants.get(variant))
.map(|tys| tys.iter().cloned().map(Some).collect())
.unwrap_or_else(|| sub_pats.iter().map(|_| None).collect());
for (i, sub_pat) in sub_pats.iter().enumerate() {
if matches!(sub_pat, Pattern::Wildcard(_) | Pattern::Literal(_, _)) {
continue;
}
fc.emit(Op::GetLocal(value_slot));
fc.emit(Op::GetEnumField(i as u8));
let sub_ty = payload_types.get(i).cloned().unwrap_or(None);
if let Pattern::Variable(name, _) = sub_pat {
let slot = fc.declare_local_typed(name, sub_ty);
fc.emit(Op::SetLocal(slot));
} else {
let temp = fc.declare_local(&format!("__bind_tmp{}", i));
fc.emit(Op::SetLocal(temp));
compile_pattern_bind_typed(fc, sub_pat, temp, sub_ty)?;
}
}
}
Pattern::Struct(struct_name, field_pats, _) => {
let field_types: BTreeMap<String, TypeExpr> = fc
.type_info
.structs
.get(struct_name)
.cloned()
.unwrap_or_default();
for field_pat in field_pats {
let name_const = fc.add_string_constant(&field_pat.name);
fc.emit(Op::GetLocal(value_slot));
fc.emit(Op::GetField(name_const));
let field_ty = field_types.get(&field_pat.name).cloned();
if let Some(pat) = &field_pat.pattern {
if let Pattern::Variable(vname, _) = pat {
let slot = fc.declare_local_typed(vname, field_ty);
fc.emit(Op::SetLocal(slot));
} else if matches!(pat, Pattern::Wildcard(_)) {
fc.emit(Op::Pop);
} else {
let temp = fc.declare_local(&format!("__sf_{}", field_pat.name));
fc.emit(Op::SetLocal(temp));
compile_pattern_bind_typed(fc, pat, temp, field_ty)?;
}
} else {
let slot = fc.declare_local_typed(&field_pat.name, field_ty);
fc.emit(Op::SetLocal(slot));
}
}
}
Pattern::Tuple(pats, _) => {
let elem_types: Vec<Option<TypeExpr>> = match &ty {
Some(TypeExpr::Tuple(ts, _)) if ts.len() == pats.len() => {
ts.iter().cloned().map(Some).collect()
}
_ => pats.iter().map(|_| None).collect(),
};
for (i, pat) in pats.iter().enumerate() {
if matches!(pat, Pattern::Wildcard(_) | Pattern::Literal(_, _)) {
continue;
}
fc.emit(Op::GetLocal(value_slot));
fc.emit(Op::GetTupleField(i as u8));
let sub_ty = elem_types.get(i).cloned().unwrap_or(None);
if let Pattern::Variable(name, _) = pat {
let slot = fc.declare_local_typed(name, sub_ty);
fc.emit(Op::SetLocal(slot));
} else {
let temp = fc.declare_local(&format!("__tup_bind{}", i));
fc.emit(Op::SetLocal(temp));
compile_pattern_bind_typed(fc, pat, temp, sub_ty)?;
}
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lexer::tokenize;
use crate::parser::parse;
fn compile_str(src: &str) -> Result<Module, CompileError> {
let tokens = tokenize(src).expect("lex error");
let program = parse(&tokens).expect("parse error");
compile(&program)
}
#[test]
fn compile_simple_fn() {
let module = compile_str("fn add(a: i64, b: i64) -> i64 { a + b }").unwrap();
assert_eq!(module.chunks.len(), 1);
assert_eq!(module.chunks[0].name, "add");
assert_eq!(module.chunks[0].param_count, 2);
}
#[test]
fn compile_literal_fn() {
let module = compile_str("fn fortytwo() -> i64 { 42 }").unwrap();
assert_eq!(module.chunks.len(), 1);
assert!(module.chunks[0].ops.contains(&Op::Return));
}
#[test]
fn compile_if_else() {
let module =
compile_str("fn max(a: i64, b: i64) -> i64 { if a > b { a } else { b } }").unwrap();
assert_eq!(module.chunks.len(), 1);
assert!(
module.chunks[0]
.ops
.iter()
.any(|op| matches!(op, Op::If(_)))
);
}
#[test]
fn compile_let_binding() {
let module = compile_str("fn double(x: i64) -> i64 { let y = x * 2; y }").unwrap();
assert_eq!(module.chunks.len(), 1);
}
#[test]
fn compile_for_range() {
let module = compile_str(
"fn sum_to(n: i64) -> i64 { let total = 0; for i in 0..n { let x = total + i; } total }"
).unwrap();
assert_eq!(module.chunks.len(), 1);
assert!(
module.chunks[0]
.ops
.iter()
.any(|op| matches!(op, Op::Loop(_)))
);
assert!(
module.chunks[0]
.ops
.iter()
.any(|op| matches!(op, Op::EndLoop(_)))
);
}
#[test]
fn compile_function_call() {
let module = compile_str(
"fn double(x: i64) -> i64 { x * 2 }\nfn quad(x: i64) -> i64 { double(double(x)) }",
)
.unwrap();
assert_eq!(module.chunks.len(), 2);
let quad = &module.chunks[1];
assert!(quad.ops.iter().any(|op| matches!(op, Op::Call(_, 1))));
}
#[test]
fn compile_multiheaded() {
let module = compile_str(
"fn classify(0) -> String { \"zero\" }\nfn classify(x: i64) -> String { \"other\" }",
)
.unwrap();
assert_eq!(module.chunks.len(), 1);
}
#[test]
fn compile_enum_variant() {
let module = compile_str(
"enum Color { Red, Green, Blue }\nfn make() -> Color { let x = Color::Red(); x }",
)
.unwrap();
assert_eq!(module.chunks.len(), 1);
assert!(
module.chunks[0]
.ops
.iter()
.any(|op| matches!(op, Op::NewEnum(_, _, 0)))
);
}
#[test]
fn compile_struct_init() {
let module = compile_str(
"struct Point { x: i64, y: i64 }\nfn make() -> Point { let p = Point { x: 1, y: 2 }; p }",
)
.unwrap();
assert_eq!(module.chunks.len(), 1);
assert!(
module.chunks[0]
.ops
.iter()
.any(|op| matches!(op, Op::NewStruct(_)))
);
}
#[test]
fn compile_yield_function() {
let module = compile_str("yield process(input: i64) -> i64 { yield input * 2 }").unwrap();
assert_eq!(module.chunks.len(), 1);
assert!(
module.chunks[0]
.ops
.iter()
.any(|op| matches!(op, Op::Yield))
);
assert_eq!(module.chunks[0].block_type, BlockType::Reentrant);
}
#[test]
fn compile_loop_function() {
let module =
compile_str("loop main(input: i64) -> i64 { let input = yield input + 1; input }")
.unwrap();
assert_eq!(module.chunks.len(), 1);
assert_eq!(module.chunks[0].block_type, BlockType::Stream);
assert!(
module.chunks[0]
.ops
.iter()
.any(|op| matches!(op, Op::Stream))
);
assert!(
module.chunks[0]
.ops
.iter()
.any(|op| matches!(op, Op::Reset))
);
}
#[test]
fn compile_entry_point() {
let module = compile_str("fn main(x: i64) -> i64 { x }").unwrap();
assert!(module.entry_point.is_some());
}
#[test]
fn compile_pipeline() {
let module = compile_str(
"fn double(x: i64) -> i64 { x * 2 }\nfn apply(x: i64) -> i64 { x |> double() }",
)
.unwrap();
assert_eq!(module.chunks.len(), 2);
}
#[test]
fn error_undefined_variable() {
let result = compile_str("fn bad() -> i64 { unknown }");
assert!(result.is_err());
}
#[test]
fn error_undefined_function() {
let result = compile_str("fn bad() -> i64 { missing(1) }");
assert!(result.is_err());
}
#[test]
fn error_break_outside_loop() {
let result = compile_str("fn bad() -> () { break; }");
assert!(result.is_err());
}
#[test]
fn compile_for_in_array() {
let module =
compile_str("fn main() -> i64 { let s = 0; for x in [1, 2, 3] { let s = s + x; } s }")
.unwrap();
assert_eq!(module.chunks.len(), 1);
assert!(
module.chunks[0]
.ops
.iter()
.any(|op| matches!(op, Op::Loop(_)))
);
assert!(!module.chunks[0].ops.iter().any(|op| matches!(op, Op::Len)));
assert!(
module.chunks[0]
.ops
.iter()
.any(|op| matches!(op, Op::GetIndex))
);
}
#[test]
fn compile_tuple_literal() {
let module = compile_str("fn main() -> (i64, i64, i64) { let t = (1, 2, 3); t }").unwrap();
assert_eq!(module.chunks.len(), 1);
assert!(
module.chunks[0]
.ops
.iter()
.any(|op| matches!(op, Op::NewTuple(3)))
);
}
#[test]
fn compile_block_structured_control() {
let module = compile_str("fn main() -> i64 { if true { 1 } else { 2 } }").unwrap();
for op in &module.chunks[0].ops {
assert!(
!matches!(
op,
Op::Loop(_) | Op::EndLoop(_) | Op::Break(_) | Op::BreakIf(_)
),
"unexpected loop instruction in simple if/else"
);
}
assert!(
module.chunks[0]
.ops
.iter()
.any(|op| matches!(op, Op::If(_)))
);
assert!(
module.chunks[0]
.ops
.iter()
.any(|op| matches!(op, Op::Else(_)))
);
assert!(
module.chunks[0]
.ops
.iter()
.any(|op| matches!(op, Op::EndIf))
);
}
#[test]
fn data_block_admits_primitives() {
let src = "data ctx { score: i64, level: i64, ratio: f64, alive: bool }\n\
fn main() -> i64 { ctx.score }";
let module = compile_str(src).unwrap();
let layout = module.data_layout.expect("expected data layout");
assert_eq!(layout.slots.len(), 4);
}
#[test]
fn data_block_admits_unit() {
let src = "data ctx { tick: () }\n\
fn main() -> () { ctx.tick }";
let module = compile_str(src).unwrap();
assert!(module.data_layout.is_some());
}
#[test]
fn data_block_admits_tuple_of_admissible() {
let src = "data ctx { pos: (f64, f64) }\n\
fn main() -> (f64, f64) { ctx.pos }";
assert!(compile_str(src).is_ok());
}
#[test]
fn data_block_admits_array_of_admissible() {
let src = "data ctx { samples: [f64; 4] }\n\
fn main() -> () { () }";
assert!(compile_str(src).is_ok());
}
#[test]
fn data_block_admits_option_of_admissible() {
let src = "data ctx { last: Option<i64> }\n\
fn main() -> () { () }";
assert!(compile_str(src).is_ok());
}
#[test]
fn data_block_admits_struct_of_admissible() {
let src = "struct Point { x: f64, y: f64 }\n\
data ctx { origin: Point }\n\
fn main() -> () { () }";
assert!(compile_str(src).is_ok());
}
#[test]
fn data_block_admits_enum_of_admissible() {
let src = "enum Status { Idle, Active(i64), Error(i64, i64) }\n\
data ctx { state: Status }\n\
fn main() -> () { () }";
assert!(compile_str(src).is_ok());
}
#[test]
fn data_block_rejects_string() {
let src = "data ctx { name: String }\n\
fn main() -> () { () }";
let err = compile_str(src).unwrap_err();
assert!(err.message.contains("String"));
}
#[test]
fn data_block_rejects_string_in_tuple() {
let src = "data ctx { pair: (i64, String) }\n\
fn main() -> () { () }";
let err = compile_str(src).unwrap_err();
assert!(err.message.contains("String"));
}
#[test]
fn data_block_rejects_string_in_array() {
let src = "data ctx { names: [String; 4] }\n\
fn main() -> () { () }";
let err = compile_str(src).unwrap_err();
assert!(err.message.contains("String"));
}
#[test]
fn data_block_rejects_string_in_option() {
let src = "data ctx { last: Option<String> }\n\
fn main() -> () { () }";
let err = compile_str(src).unwrap_err();
assert!(err.message.contains("String"));
}
#[test]
fn data_block_rejects_string_in_struct() {
let src = "struct Tag { label: String }\n\
data ctx { t: Tag }\n\
fn main() -> () { () }";
let err = compile_str(src).unwrap_err();
assert!(err.message.contains("String"));
}
#[test]
fn data_block_rejects_string_in_enum() {
let src = "enum Tag { Named(String), Unnamed }\n\
data ctx { t: Tag }\n\
fn main() -> () { () }";
let err = compile_str(src).unwrap_err();
assert!(err.message.contains("String"));
}
#[test]
fn data_block_rejects_unknown_named_type() {
let src = "data ctx { handle: Mystery }\n\
fn main() -> () { () }";
let err = compile_str(src).unwrap_err();
assert!(err.message.contains("Mystery") || err.message.contains("opaque"));
}
#[test]
fn multiple_data_blocks_rejected() {
let src = "data ctx_a { x: i64 }\n\
data ctx_b { y: i64 }\n\
fn main() -> () { () }";
let err = compile_str(src).unwrap_err();
assert!(err.message.contains("R28") || err.message.contains("one data block"));
}
#[test]
fn no_data_block_compiles() {
let module = compile_str("fn main() -> i64 { 42 }").unwrap();
assert!(module.data_layout.is_none());
}
}