mod calls;
mod classify;
mod expr;
mod patterns;
use std::collections::{HashMap, HashSet};
use crate::ast::{FnBody, FnDef, Stmt, TopLevel, TypeDef};
use crate::nan_value::{Arena, NanValue};
use crate::source::find_module_file;
use crate::types::{option, result};
use super::builtin::VmBuiltin;
use super::opcode::*;
use super::symbol::{VmSymbolTable, VmVariantCtor};
use super::types::{CodeStore, FnChunk};
pub fn compile_program(
items: &[TopLevel],
arena: &mut Arena,
) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
compile_program_with_modules(items, arena, None, "")
}
pub fn compile_program_with_modules(
items: &[TopLevel],
arena: &mut Arena,
module_root: Option<&str>,
source_file: &str,
) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
let mut compiler = ProgramCompiler::new();
compiler.source_file = source_file.to_string();
compiler.sync_record_field_symbols(arena);
if let Some(module_root) = module_root {
compiler.load_modules(items, module_root, arena)?;
}
for item in items {
if let TopLevel::Stmt(Stmt::Binding(name, _, _)) = item {
compiler.ensure_global(name);
}
}
for item in items {
match item {
TopLevel::FnDef(fndef) => {
compiler.ensure_global(&fndef.name);
let effect_ids: Vec<u32> = fndef
.effects
.iter()
.map(|effect| compiler.symbols.intern_name(&effect.node))
.collect();
let fn_id = compiler.code.add_function(FnChunk {
name: fndef.name.clone(),
arity: fndef.params.len() as u8,
local_count: 0,
code: Vec::new(),
constants: Vec::new(),
effects: effect_ids,
thin: false,
parent_thin: false,
leaf: false,
source_file: String::new(),
line_table: Vec::new(),
});
let symbol_id = compiler.symbols.intern_function(
&fndef.name,
fn_id,
&fndef
.effects
.iter()
.map(|e| e.node.clone())
.collect::<Vec<_>>(),
);
let global_idx = compiler.global_names[&fndef.name];
compiler.globals[global_idx as usize] = VmSymbolTable::symbol_ref(symbol_id);
}
TopLevel::TypeDef(td) => {
compiler.register_type_def(td, arena);
}
_ => {}
}
}
compiler.register_current_module_namespace(items);
for item in items {
if let TopLevel::FnDef(fndef) = item {
let fn_id = compiler.code.find(&fndef.name).unwrap();
let chunk = compiler.compile_fn(fndef, arena)?;
compiler.code.functions[fn_id as usize] = chunk;
}
}
compiler.compile_top_level(items, arena)?;
compiler.code.symbols = compiler.symbols.clone();
classify::classify_thin_functions(&mut compiler.code, arena)?;
Ok((compiler.code, compiler.globals))
}
#[derive(Debug)]
pub struct CompileError {
pub msg: String,
}
impl std::fmt::Display for CompileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Compile error: {}", self.msg)
}
}
struct ProgramCompiler {
code: CodeStore,
symbols: VmSymbolTable,
globals: Vec<NanValue>,
global_names: HashMap<String, u16>,
source_file: String,
}
impl ProgramCompiler {
fn new() -> Self {
let mut compiler = ProgramCompiler {
code: CodeStore::new(),
symbols: VmSymbolTable::default(),
globals: Vec::new(),
global_names: HashMap::new(),
source_file: String::new(),
};
compiler.bootstrap_core_symbols();
compiler
}
fn sync_record_field_symbols(&mut self, arena: &Arena) {
for type_id in 0..arena.type_count() {
let type_name = arena.get_type_name(type_id);
self.symbols.intern_namespace_path(type_name);
let field_names = arena.get_field_names(type_id);
if field_names.is_empty() {
continue;
}
let field_symbol_ids: Vec<u32> = field_names
.iter()
.map(|field_name| self.symbols.intern_name(field_name))
.collect();
self.code.register_record_fields(type_id, &field_symbol_ids);
}
}
fn load_modules(
&mut self,
items: &[TopLevel],
module_root: &str,
arena: &mut Arena,
) -> Result<(), CompileError> {
let module = items.iter().find_map(|i| {
if let TopLevel::Module(m) = i {
Some(m)
} else {
None
}
});
let module = match module {
Some(m) => m,
None => return Ok(()),
};
let mut loaded = HashSet::new();
for dep_name in &module.depends {
self.load_module_recursive(dep_name, module_root, arena, &mut loaded)?;
}
Ok(())
}
fn load_module_recursive(
&mut self,
dep_name: &str,
module_root: &str,
arena: &mut Arena,
loaded: &mut HashSet<String>,
) -> Result<(), CompileError> {
if loaded.contains(dep_name) {
return Ok(());
}
loaded.insert(dep_name.to_string());
let file_path = find_module_file(dep_name, module_root).ok_or_else(|| CompileError {
msg: format!("module '{}' not found (root: {})", dep_name, module_root),
})?;
let source = std::fs::read_to_string(&file_path).map_err(|e| CompileError {
msg: format!("cannot read module '{}': {}", dep_name, e),
})?;
let mut mod_items = crate::source::parse_source(&source).map_err(|e| CompileError {
msg: format!("parse error in module '{}': {}", dep_name, e),
})?;
crate::tco::transform_program(&mut mod_items);
crate::resolver::resolve_program(&mut mod_items);
if let Some(sub_module) = mod_items.iter().find_map(|i| {
if let TopLevel::Module(m) = i {
Some(m)
} else {
None
}
}) {
for sub_dep in &sub_module.depends {
self.load_module_recursive(sub_dep, module_root, arena, loaded)?;
}
}
for item in &mod_items {
if let TopLevel::TypeDef(td) = item {
self.register_type_def(td, arena);
}
}
let exposes: Option<Vec<String>> = mod_items.iter().find_map(|i| {
if let TopLevel::Module(m) = i {
if m.exposes.is_empty() {
None
} else {
Some(m.exposes.clone())
}
} else {
None
}
});
let mut module_fn_ids: Vec<(String, u32)> = Vec::new();
for item in &mod_items {
if let TopLevel::FnDef(fndef) = item {
let qualified_name = format!("{}.{}", dep_name, fndef.name);
let effect_ids: Vec<u32> = fndef
.effects
.iter()
.map(|effect| self.symbols.intern_name(&effect.node))
.collect();
let fn_id = self.code.add_function(FnChunk {
name: qualified_name.clone(),
arity: fndef.params.len() as u8,
local_count: 0,
code: Vec::new(),
constants: Vec::new(),
effects: effect_ids,
thin: false,
parent_thin: false,
leaf: false,
source_file: String::new(),
line_table: Vec::new(),
});
self.symbols.intern_function(
&qualified_name,
fn_id,
&fndef
.effects
.iter()
.map(|e| e.node.clone())
.collect::<Vec<_>>(),
);
module_fn_ids.push((fndef.name.clone(), fn_id));
}
}
let module_scope: HashMap<String, u32> = module_fn_ids.iter().cloned().collect();
let mut fn_idx = 0;
for item in &mod_items {
if let TopLevel::FnDef(fndef) = item {
let (fn_name, fn_id) = &module_fn_ids[fn_idx];
let mut chunk = self.compile_fn_with_scope(fndef, arena, &module_scope)?;
chunk.name = format!("{}.{}", dep_name, fn_name);
self.code.functions[*fn_id as usize] = chunk;
fn_idx += 1;
}
}
for (fn_name, _fn_id) in &module_fn_ids {
let exposed = match &exposes {
Some(list) => list.iter().any(|e| e == fn_name),
None => !fn_name.starts_with('_'),
};
if exposed {
let qualified = format!("{}.{}", dep_name, fn_name);
let global_idx = self.ensure_global(&qualified);
let symbol_id = self.symbols.find(&qualified).ok_or_else(|| CompileError {
msg: format!("missing VM symbol for exposed function {}", qualified),
})?;
self.globals[global_idx as usize] = VmSymbolTable::symbol_ref(symbol_id);
}
}
let module_symbol_id = self.symbols.intern_namespace_path(dep_name);
for item in &mod_items {
if let TopLevel::TypeDef(td) = item {
let type_name = match td {
TypeDef::Sum { name, .. } | TypeDef::Product { name, .. } => name,
};
let exposed = match &exposes {
Some(list) => list.iter().any(|e| e == type_name),
None => !type_name.starts_with('_'),
};
if exposed && let Some(type_symbol_id) = self.symbols.find(type_name) {
let member_symbol_id = self.symbols.intern_name(type_name);
self.symbols.add_namespace_member_by_id(
module_symbol_id,
member_symbol_id,
VmSymbolTable::symbol_ref(type_symbol_id),
);
}
}
}
for (fn_name, _fn_id) in &module_fn_ids {
let exposed = match &exposes {
Some(list) => list.iter().any(|e| e == fn_name),
None => !fn_name.starts_with('_'),
};
if exposed {
let qualified = format!("{}.{}", dep_name, fn_name);
if let Some(fn_symbol_id) = self.symbols.find(&qualified) {
let member_symbol_id = self.symbols.intern_name(fn_name);
self.symbols.add_namespace_member_by_id(
module_symbol_id,
member_symbol_id,
VmSymbolTable::symbol_ref(fn_symbol_id),
);
}
}
}
Ok(())
}
fn ensure_global(&mut self, name: &str) -> u16 {
if let Some(&idx) = self.global_names.get(name) {
return idx;
}
let idx = self.globals.len() as u16;
self.global_names.insert(name.to_string(), idx);
self.globals.push(NanValue::UNIT);
idx
}
fn register_type_def(&mut self, td: &TypeDef, arena: &mut Arena) {
match td {
TypeDef::Product { name, fields, .. } => {
self.symbols.intern_namespace_path(name);
let field_names: Vec<String> = fields.iter().map(|(n, _)| n.clone()).collect();
let field_symbol_ids: Vec<u32> = field_names
.iter()
.map(|field_name| self.symbols.intern_name(field_name))
.collect();
let type_id = arena.register_record_type(name, field_names);
self.code.register_record_fields(type_id, &field_symbol_ids);
}
TypeDef::Sum { name, variants, .. } => {
let type_symbol_id = self.symbols.intern_namespace_path(name);
let variant_names: Vec<String> = variants.iter().map(|v| v.name.clone()).collect();
let type_id = arena.register_sum_type(name, variant_names);
for (variant_id, variant) in variants.iter().enumerate() {
let ctor_id = arena
.find_ctor_id(type_id, variant_id as u16)
.expect("ctor id");
let qualified_name = format!("{}.{}", name, variant.name);
let ctor_symbol_id = self.symbols.intern_variant_ctor(
&qualified_name,
VmVariantCtor {
type_id,
variant_id: variant_id as u16,
ctor_id,
field_count: variant.fields.len() as u8,
},
);
let member_symbol_id = self.symbols.intern_name(&variant.name);
self.symbols.add_namespace_member_by_id(
type_symbol_id,
member_symbol_id,
VmSymbolTable::symbol_ref(ctor_symbol_id),
);
}
}
}
}
fn bootstrap_core_symbols(&mut self) {
for builtin in VmBuiltin::ALL.iter().copied() {
let builtin_symbol_id = self.symbols.intern_builtin(builtin);
if let Some((namespace, member)) = builtin.name().split_once('.') {
let namespace_symbol_id = self.symbols.intern_namespace_path(namespace);
let member_symbol_id = self.symbols.intern_name(member);
self.symbols.add_namespace_member_by_id(
namespace_symbol_id,
member_symbol_id,
VmSymbolTable::symbol_ref(builtin_symbol_id),
);
}
}
let result_symbol_id = self.symbols.intern_namespace_path("Result");
let ok_symbol_id = self.symbols.intern_wrapper("Result.Ok", 0);
let err_symbol_id = self.symbols.intern_wrapper("Result.Err", 1);
let ok_member_symbol_id = self.symbols.intern_name("Ok");
self.symbols.add_namespace_member_by_id(
result_symbol_id,
ok_member_symbol_id,
VmSymbolTable::symbol_ref(ok_symbol_id),
);
let err_member_symbol_id = self.symbols.intern_name("Err");
self.symbols.add_namespace_member_by_id(
result_symbol_id,
err_member_symbol_id,
VmSymbolTable::symbol_ref(err_symbol_id),
);
for (member, builtin_name) in result::extra_members() {
if let Some(symbol_id) = self.symbols.find(&builtin_name) {
let member_symbol_id = self.symbols.intern_name(member);
self.symbols.add_namespace_member_by_id(
result_symbol_id,
member_symbol_id,
VmSymbolTable::symbol_ref(symbol_id),
);
}
}
let option_symbol_id = self.symbols.intern_namespace_path("Option");
let some_symbol_id = self.symbols.intern_wrapper("Option.Some", 2);
self.symbols.intern_constant("Option.None", NanValue::NONE);
let some_member_symbol_id = self.symbols.intern_name("Some");
self.symbols.add_namespace_member_by_id(
option_symbol_id,
some_member_symbol_id,
VmSymbolTable::symbol_ref(some_symbol_id),
);
let none_member_symbol_id = self.symbols.intern_name("None");
self.symbols.add_namespace_member_by_id(
option_symbol_id,
none_member_symbol_id,
NanValue::NONE,
);
for (member, builtin_name) in option::extra_members() {
if let Some(symbol_id) = self.symbols.find(&builtin_name) {
let member_symbol_id = self.symbols.intern_name(member);
self.symbols.add_namespace_member_by_id(
option_symbol_id,
member_symbol_id,
VmSymbolTable::symbol_ref(symbol_id),
);
}
}
}
fn compile_fn(&mut self, fndef: &FnDef, arena: &mut Arena) -> Result<FnChunk, CompileError> {
let empty_scope = HashMap::new();
self.compile_fn_with_scope(fndef, arena, &empty_scope)
}
fn compile_fn_with_scope(
&mut self,
fndef: &FnDef,
arena: &mut Arena,
module_scope: &HashMap<String, u32>,
) -> Result<FnChunk, CompileError> {
let resolution = fndef.resolution.as_ref();
let local_count = resolution.map_or(fndef.params.len() as u16, |r| r.local_count);
let local_slots: HashMap<String, u16> = resolution
.map(|r| r.local_slots.as_ref().clone())
.unwrap_or_else(|| {
fndef
.params
.iter()
.enumerate()
.map(|(i, (name, _))| (name.clone(), i as u16))
.collect()
});
let mut fc = FnCompiler::new(
&fndef.name,
fndef.params.len() as u8,
local_count,
fndef
.effects
.iter()
.map(|effect| self.symbols.intern_name(&effect.node))
.collect(),
local_slots,
&self.global_names,
module_scope,
&self.code,
&mut self.symbols,
arena,
);
fc.source_file = self.source_file.clone();
fc.note_line(fndef.line);
match fndef.body.as_ref() {
FnBody::Block(stmts) => fc.compile_body(stmts)?,
}
Ok(fc.finish())
}
fn compile_top_level(
&mut self,
items: &[TopLevel],
arena: &mut Arena,
) -> Result<(), CompileError> {
let has_stmts = items.iter().any(|i| matches!(i, TopLevel::Stmt(_)));
if !has_stmts {
return Ok(());
}
for item in items {
if let TopLevel::Stmt(Stmt::Binding(name, _, _)) = item {
self.ensure_global(name);
}
}
let empty_mod_scope = HashMap::new();
let mut fc = FnCompiler::new(
"__top_level__",
0,
0,
Vec::new(),
HashMap::new(),
&self.global_names,
&empty_mod_scope,
&self.code,
&mut self.symbols,
arena,
);
for item in items {
if let TopLevel::Stmt(stmt) = item {
match stmt {
Stmt::Binding(name, _type_ann, expr) => {
fc.compile_expr(expr)?;
let idx = self.global_names[name.as_str()];
fc.emit_op(STORE_GLOBAL);
fc.emit_u16(idx);
}
Stmt::Expr(expr) => {
fc.compile_expr(expr)?;
fc.emit_op(POP);
}
}
}
}
fc.emit_op(LOAD_UNIT);
fc.emit_op(RETURN);
let chunk = fc.finish();
self.code.add_function(chunk);
Ok(())
}
fn register_current_module_namespace(&mut self, items: &[TopLevel]) {
let Some(module) = items.iter().find_map(|item| {
if let TopLevel::Module(module) = item {
Some(module)
} else {
None
}
}) else {
return;
};
let module_symbol_id = self.symbols.intern_namespace_path(&module.name);
for item in items {
match item {
TopLevel::FnDef(fndef) => {
let exposed = if module.exposes.is_empty() {
!fndef.name.starts_with('_')
} else {
module.exposes.iter().any(|name| name == &fndef.name)
};
if exposed && let Some(symbol_id) = self.symbols.find(&fndef.name) {
let member_symbol_id = self.symbols.intern_name(&fndef.name);
self.symbols.add_namespace_member_by_id(
module_symbol_id,
member_symbol_id,
VmSymbolTable::symbol_ref(symbol_id),
);
}
}
TopLevel::TypeDef(TypeDef::Product { name, .. } | TypeDef::Sum { name, .. }) => {
let exposed = if module.exposes.is_empty() {
!name.starts_with('_')
} else {
module.exposes.iter().any(|member| member == name)
};
if exposed && let Some(symbol_id) = self.symbols.find(name) {
let member_symbol_id = self.symbols.intern_name(name);
self.symbols.add_namespace_member_by_id(
module_symbol_id,
member_symbol_id,
VmSymbolTable::symbol_ref(symbol_id),
);
}
}
_ => {}
}
}
}
}
enum CallTarget {
KnownFn(u32),
Wrapper(u8),
None_,
Variant(u32, u16),
Builtin(VmBuiltin),
UnknownQualified(String),
}
struct FnCompiler<'a> {
name: String,
arity: u8,
local_count: u16,
effects: Vec<u32>,
local_slots: HashMap<String, u16>,
global_names: &'a HashMap<String, u16>,
module_scope: &'a HashMap<String, u32>,
code_store: &'a CodeStore,
symbols: &'a mut VmSymbolTable,
arena: &'a mut Arena,
code: Vec<u8>,
constants: Vec<NanValue>,
last_op_pos: usize,
source_file: String,
line_table: Vec<(u16, u16)>,
last_noted_line: u16,
}
impl<'a> FnCompiler<'a> {
#[allow(clippy::too_many_arguments)]
fn new(
name: &str,
arity: u8,
local_count: u16,
effects: Vec<u32>,
local_slots: HashMap<String, u16>,
global_names: &'a HashMap<String, u16>,
module_scope: &'a HashMap<String, u32>,
code_store: &'a CodeStore,
symbols: &'a mut VmSymbolTable,
arena: &'a mut Arena,
) -> Self {
FnCompiler {
name: name.to_string(),
arity,
local_count,
effects,
local_slots,
global_names,
module_scope,
code_store,
symbols,
arena,
code: Vec::new(),
constants: Vec::new(),
last_op_pos: usize::MAX,
source_file: String::new(),
line_table: Vec::new(),
last_noted_line: 0,
}
}
fn finish(self) -> FnChunk {
FnChunk {
name: self.name,
arity: self.arity,
local_count: self.local_count,
code: self.code,
constants: self.constants,
effects: self.effects,
thin: false,
parent_thin: false,
leaf: false,
source_file: self.source_file,
line_table: self.line_table,
}
}
fn note_line(&mut self, line: usize) {
if line == 0 {
return;
}
let line16 = line as u16;
if line16 == self.last_noted_line {
return; }
self.last_noted_line = line16;
self.line_table.push((self.code.len() as u16, line16));
}
fn emit_op(&mut self, op: u8) {
let prev_pos = self.last_op_pos;
let prev_op = if prev_pos < self.code.len() {
self.code[prev_pos]
} else {
0xFF
};
if op == LOAD_LOCAL && prev_op == LOAD_LOCAL && prev_pos + 2 == self.code.len() {
self.code[prev_pos] = LOAD_LOCAL_2;
return;
}
if op == LOAD_CONST && prev_op == LOAD_LOCAL && prev_pos + 2 == self.code.len() {
self.code[prev_pos] = LOAD_LOCAL_CONST;
return;
}
if op == UNWRAP_OR && self.code.len() >= 4 {
let len = self.code.len();
if self.code[len - 4] == VECTOR_GET && self.code[len - 3] == LOAD_CONST {
let hi = self.code[len - 2];
let lo = self.code[len - 1];
self.code[len - 4] = VECTOR_GET_OR;
self.code[len - 3] = hi;
self.code[len - 2] = lo;
self.code.pop(); self.last_op_pos = len - 4;
return;
}
}
self.last_op_pos = self.code.len();
self.code.push(op);
}
fn emit_u8(&mut self, val: u8) {
self.code.push(val);
}
fn emit_u16(&mut self, val: u16) {
self.code.push((val >> 8) as u8);
self.code.push((val & 0xFF) as u8);
}
fn emit_i16(&mut self, val: i16) {
self.emit_u16(val as u16);
}
fn emit_u32(&mut self, val: u32) {
self.code.push((val >> 24) as u8);
self.code.push(((val >> 16) & 0xFF) as u8);
self.code.push(((val >> 8) & 0xFF) as u8);
self.code.push((val & 0xFF) as u8);
}
fn emit_u64(&mut self, val: u64) {
self.code.extend_from_slice(&val.to_be_bytes());
}
fn add_constant(&mut self, val: NanValue) -> u16 {
for (i, c) in self.constants.iter().enumerate() {
if c.bits() == val.bits() {
return i as u16;
}
}
let idx = self.constants.len() as u16;
self.constants.push(val);
idx
}
fn offset(&self) -> usize {
self.code.len()
}
fn emit_jump(&mut self, op: u8) -> usize {
self.emit_op(op);
let patch_pos = self.code.len();
self.emit_i16(0);
patch_pos
}
fn patch_jump(&mut self, patch_pos: usize) {
let target = self.code.len();
let offset = (target as isize - patch_pos as isize - 2) as i16;
let bytes = (offset as u16).to_be_bytes();
self.code[patch_pos] = bytes[0];
self.code[patch_pos + 1] = bytes[1];
}
fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
let offset = (target as isize - patch_pos as isize - 2) as i16;
let bytes = (offset as u16).to_be_bytes();
self.code[patch_pos] = bytes[0];
self.code[patch_pos + 1] = bytes[1];
}
fn bind_top_to_local(&mut self, name: &str) {
if let Some(&slot) = self.local_slots.get(name) {
self.emit_op(STORE_LOCAL);
self.emit_u8(slot as u8);
} else {
self.emit_op(POP);
}
}
fn dup_and_bind_top_to_local(&mut self, name: &str) {
self.emit_op(DUP);
self.bind_top_to_local(name);
}
}
#[cfg(test)]
mod tests {
use super::compile_program;
use crate::nan_value::Arena;
use crate::source::parse_source;
use crate::vm::opcode::{LT, NOT, VECTOR_GET_OR, VECTOR_SET_OR_KEEP};
#[test]
fn vector_get_with_literal_default_lowers_to_vector_get_or() {
let source = r#"
module Demo
fn cellAt(grid: Vector<Int>, idx: Int) -> Int
Option.withDefault(Vector.get(grid, idx), 0)
"#;
let mut items = parse_source(source).expect("source should parse");
crate::tco::transform_program(&mut items);
crate::resolver::resolve_program(&mut items);
let mut arena = Arena::new();
let (code, _globals) = compile_program(&items, &mut arena).expect("vm compile should pass");
let fn_id = code.find("cellAt").expect("cellAt should exist");
let chunk = code.get(fn_id);
assert!(
chunk.code.contains(&VECTOR_GET_OR),
"expected VECTOR_GET_OR in bytecode, got {:?}",
chunk.code
);
}
#[test]
fn vector_set_with_same_default_lowers_to_vector_set_or_keep() {
let source = r#"
module Demo
fn updateOrKeep(vec: Vector<Int>, idx: Int, value: Int) -> Vector<Int>
Option.withDefault(Vector.set(vec, idx, value), vec)
"#;
let mut items = parse_source(source).expect("source should parse");
crate::tco::transform_program(&mut items);
crate::resolver::resolve_program(&mut items);
let mut arena = Arena::new();
let (code, _globals) = compile_program(&items, &mut arena).expect("vm compile should pass");
let fn_id = code
.find("updateOrKeep")
.expect("updateOrKeep should exist");
let chunk = code.get(fn_id);
assert!(
chunk.code.contains(&VECTOR_SET_OR_KEEP),
"expected VECTOR_SET_OR_KEEP in bytecode, got {:?}",
chunk.code
);
}
#[test]
fn bool_match_on_gte_uses_base_compare_without_not() {
let source = r#"
module Demo
fn bucket(n: Int) -> Int
match n >= 10
true -> 7
false -> 3
"#;
let mut items = parse_source(source).expect("source should parse");
crate::tco::transform_program(&mut items);
crate::resolver::resolve_program(&mut items);
let mut arena = Arena::new();
let (code, _globals) = compile_program(&items, &mut arena).expect("vm compile should pass");
let fn_id = code.find("bucket").expect("bucket should exist");
let chunk = code.get(fn_id);
assert!(
chunk.code.contains(<),
"expected LT in bytecode, got {:?}",
chunk.code
);
assert!(
!chunk.code.contains(&NOT),
"did not expect NOT in normalized bool-match bytecode, got {:?}",
chunk.code
);
}
#[test]
fn self_host_runtime_http_server_aliases_compile_in_vm() {
let source = r#"
module Demo
fn listen(handler: Int) -> Unit
SelfHostRuntime.httpServerListen(8080, handler)
fn listenWith(context: Int, handler: Int) -> Unit
SelfHostRuntime.httpServerListenWith(8081, context, handler)
"#;
let mut items = parse_source(source).expect("source should parse");
crate::tco::transform_program(&mut items);
crate::resolver::resolve_program(&mut items);
let mut arena = Arena::new();
let (code, _globals) = compile_program(&items, &mut arena).expect("vm compile should pass");
assert!(code.find("listen").is_some(), "listen should compile");
assert!(
code.find("listenWith").is_some(),
"listenWith should compile"
);
}
}