1mod calls;
2mod classify;
3mod expr;
4mod patterns;
5
6use std::collections::HashMap;
7
8use crate::ast::{FnBody, FnDef, Stmt, TopLevel, TypeDef};
9use crate::nan_value::{Arena, NanValue};
10use crate::types::{option, result};
11use crate::visibility;
12
13use super::builtin::VmBuiltin;
14use super::opcode::*;
15use super::symbol::{VmSymbolTable, VmVariantCtor};
16use super::types::{CodeStore, FnChunk};
17
18pub fn compile_program(
21 items: &[TopLevel],
22 arena: &mut Arena,
23) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
24 compile_program_with_modules(items, arena, None, "")
25}
26
27pub fn compile_program_with_modules(
29 items: &[TopLevel],
30 arena: &mut Arena,
31 module_root: Option<&str>,
32 source_file: &str,
33) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
34 let mut compiler = ProgramCompiler::new();
35 compiler.source_file = source_file.to_string();
36 compiler.sync_record_field_symbols(arena);
37
38 if let Some(module_root) = module_root {
39 compiler.load_modules(items, module_root, arena)?;
40 }
41
42 for item in items {
43 if let TopLevel::Stmt(Stmt::Binding(name, _, _)) = item {
44 compiler.ensure_global(name);
45 }
46 }
47
48 for item in items {
49 match item {
50 TopLevel::FnDef(fndef) => {
51 compiler.ensure_global(&fndef.name);
52 let effect_ids: Vec<u32> = fndef
53 .effects
54 .iter()
55 .map(|effect| compiler.symbols.intern_name(&effect.node))
56 .collect();
57 let fn_id = compiler.code.add_function(FnChunk {
58 name: fndef.name.clone(),
59 arity: fndef.params.len() as u8,
60 local_count: 0,
61 code: Vec::new(),
62 constants: Vec::new(),
63 effects: effect_ids,
64 thin: false,
65 parent_thin: false,
66 leaf: false,
67 source_file: String::new(),
68 line_table: Vec::new(),
69 });
70 let symbol_id = compiler.symbols.intern_function(
71 &fndef.name,
72 fn_id,
73 &fndef
74 .effects
75 .iter()
76 .map(|e| e.node.clone())
77 .collect::<Vec<_>>(),
78 );
79 let global_idx = compiler.global_names[&fndef.name];
80 compiler.globals[global_idx as usize] = VmSymbolTable::symbol_ref(symbol_id);
81 }
82 TopLevel::TypeDef(td) => {
83 match td {
85 TypeDef::Product { name, fields, .. } => {
86 let field_names: Vec<String> =
87 fields.iter().map(|(n, _)| n.clone()).collect();
88 arena.register_record_type(name, field_names);
89 }
90 TypeDef::Sum { name, variants, .. } => {
91 let variant_names: Vec<String> =
92 variants.iter().map(|v| v.name.clone()).collect();
93 arena.register_sum_type(name, variant_names);
94 }
95 }
96 compiler.register_type_in_symbols(td, arena);
98 }
99 _ => {}
100 }
101 }
102
103 compiler.register_current_module_namespace(items);
104
105 for item in items {
106 if let TopLevel::FnDef(fndef) = item {
107 let fn_id = compiler.code.find(&fndef.name).unwrap();
108 let chunk = compiler.compile_fn(fndef, arena)?;
109 compiler.code.functions[fn_id as usize] = chunk;
110 }
111 }
112
113 compiler.compile_top_level(items, arena)?;
114 compiler.code.symbols = compiler.symbols.clone();
115 classify::classify_thin_functions(&mut compiler.code, arena)?;
116
117 Ok((compiler.code, compiler.globals))
118}
119
120#[derive(Debug)]
121pub struct CompileError {
122 pub msg: String,
123}
124
125impl std::fmt::Display for CompileError {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 write!(f, "Compile error: {}", self.msg)
128 }
129}
130
131struct ProgramCompiler {
132 code: CodeStore,
133 symbols: VmSymbolTable,
134 globals: Vec<NanValue>,
135 global_names: HashMap<String, u16>,
136 source_file: String,
138}
139
140impl ProgramCompiler {
141 fn new() -> Self {
142 let mut compiler = ProgramCompiler {
143 code: CodeStore::new(),
144 symbols: VmSymbolTable::default(),
145 globals: Vec::new(),
146 global_names: HashMap::new(),
147 source_file: String::new(),
148 };
149 compiler.bootstrap_core_symbols();
150 compiler
151 }
152
153 fn sync_record_field_symbols(&mut self, arena: &Arena) {
154 for type_id in 0..arena.type_count() {
155 let type_name = arena.get_type_name(type_id);
156 self.symbols.intern_namespace_path(type_name);
157 let field_names = arena.get_field_names(type_id);
158 if field_names.is_empty() {
159 continue;
160 }
161 let field_symbol_ids: Vec<u32> = field_names
162 .iter()
163 .map(|field_name| self.symbols.intern_name(field_name))
164 .collect();
165 self.code.register_record_fields(type_id, &field_symbol_ids);
166 }
167 }
168
169 fn load_modules(
172 &mut self,
173 items: &[TopLevel],
174 module_root: &str,
175 arena: &mut Arena,
176 ) -> Result<(), CompileError> {
177 let module = items.iter().find_map(|i| {
178 if let TopLevel::Module(m) = i {
179 Some(m)
180 } else {
181 None
182 }
183 });
184 let module = match module {
185 Some(m) => m,
186 None => return Ok(()),
187 };
188
189 let modules = crate::source::load_module_tree(&module.depends, module_root)
190 .map_err(|e| CompileError { msg: e })?;
191
192 for loaded in modules {
193 self.integrate_module(&loaded.dep_name, loaded.items, arena)?;
194 }
195 Ok(())
196 }
197
198 fn integrate_module(
201 &mut self,
202 dep_name: &str,
203 mut mod_items: Vec<TopLevel>,
204 arena: &mut Arena,
205 ) -> Result<(), CompileError> {
206 crate::tco::transform_program(&mut mod_items);
207 crate::resolver::resolve_program(&mut mod_items);
208
209 for mt in visibility::collect_module_types(&mod_items) {
211 let type_id = match &mt.kind {
212 visibility::ModuleTypeKind::Record { field_names } => {
213 arena.register_record_type(&mt.bare_name, field_names.clone())
214 }
215 visibility::ModuleTypeKind::Sum { variant_names } => {
216 arena.register_sum_type(&mt.bare_name, variant_names.clone())
217 }
218 };
219 arena.register_type_alias(
220 &visibility::qualified_name(dep_name, &mt.bare_name),
221 type_id,
222 );
223 }
224 for item in &mod_items {
225 if let TopLevel::TypeDef(td) = item {
226 self.register_type_in_symbols(td, arena);
227 }
228 }
229
230 let mut module_fn_ids: Vec<(String, u32)> = Vec::new();
232 for item in &mod_items {
233 if let TopLevel::FnDef(fndef) = item {
234 let qualified_name = visibility::qualified_name(dep_name, &fndef.name);
235 let effect_ids: Vec<u32> = fndef
236 .effects
237 .iter()
238 .map(|effect| self.symbols.intern_name(&effect.node))
239 .collect();
240 let fn_id = self.code.add_function(FnChunk {
241 name: qualified_name.clone(),
242 arity: fndef.params.len() as u8,
243 local_count: 0,
244 code: Vec::new(),
245 constants: Vec::new(),
246 effects: effect_ids,
247 thin: false,
248 parent_thin: false,
249 leaf: false,
250 source_file: String::new(),
251 line_table: Vec::new(),
252 });
253 self.symbols.intern_function(
254 &qualified_name,
255 fn_id,
256 &fndef
257 .effects
258 .iter()
259 .map(|e| e.node.clone())
260 .collect::<Vec<_>>(),
261 );
262 module_fn_ids.push((fndef.name.clone(), fn_id));
263 }
264 }
265
266 let module_scope: HashMap<String, u32> = module_fn_ids.iter().cloned().collect();
267 let mut fn_idx = 0;
268 for item in &mod_items {
269 if let TopLevel::FnDef(fndef) = item {
270 let (fn_name, fn_id) = &module_fn_ids[fn_idx];
271 let mut chunk = self.compile_fn_with_scope(fndef, arena, &module_scope)?;
272 chunk.name = visibility::qualified_name(dep_name, fn_name);
273 self.code.functions[*fn_id as usize] = chunk;
274 fn_idx += 1;
275 }
276 }
277
278 let exports = visibility::collect_module_exports(&mod_items);
280
281 for fd in &exports.functions {
282 let qualified = visibility::qualified_name(dep_name, &fd.name);
283 let global_idx = self.ensure_global(&qualified);
284 let symbol_id = self.symbols.find(&qualified).ok_or_else(|| CompileError {
285 msg: format!("missing VM symbol for exposed function {}", qualified),
286 })?;
287 self.globals[global_idx as usize] = VmSymbolTable::symbol_ref(symbol_id);
288 }
289
290 let module_symbol_id = self.symbols.intern_namespace_path(dep_name);
291 for et in &exports.types {
292 let type_name = match et.def {
293 TypeDef::Sum { name, .. } | TypeDef::Product { name, .. } => name,
294 };
295 if let Some(type_symbol_id) = self.symbols.find(type_name) {
296 let member_symbol_id = self.symbols.intern_name(type_name);
297 self.symbols.add_namespace_member_by_id(
298 module_symbol_id,
299 member_symbol_id,
300 VmSymbolTable::symbol_ref(type_symbol_id),
301 );
302 }
303 }
304 for fd in &exports.functions {
305 let qualified = visibility::qualified_name(dep_name, &fd.name);
306 if let Some(fn_symbol_id) = self.symbols.find(&qualified) {
307 let member_symbol_id = self.symbols.intern_name(&fd.name);
308 self.symbols.add_namespace_member_by_id(
309 module_symbol_id,
310 member_symbol_id,
311 VmSymbolTable::symbol_ref(fn_symbol_id),
312 );
313 }
314 }
315
316 Ok(())
317 }
318
319 fn ensure_global(&mut self, name: &str) -> u16 {
320 if let Some(&idx) = self.global_names.get(name) {
321 return idx;
322 }
323 let idx = self.globals.len() as u16;
324 self.global_names.insert(name.to_string(), idx);
325 self.globals.push(NanValue::UNIT);
326 idx
327 }
328
329 fn register_type_in_symbols(&mut self, td: &TypeDef, arena: &Arena) {
332 match td {
333 TypeDef::Product { name, fields, .. } => {
334 self.symbols.intern_namespace_path(name);
335 let type_id = arena
336 .find_type_id(name)
337 .expect("type already registered in Arena");
338 let field_symbol_ids: Vec<u32> = fields
339 .iter()
340 .map(|(field_name, _)| self.symbols.intern_name(field_name))
341 .collect();
342 self.code.register_record_fields(type_id, &field_symbol_ids);
343 }
344 TypeDef::Sum { name, variants, .. } => {
345 let type_symbol_id = self.symbols.intern_namespace_path(name);
346 let type_id = arena
347 .find_type_id(name)
348 .expect("type already registered in Arena");
349 for (variant_id, variant) in variants.iter().enumerate() {
350 let ctor_id = arena
351 .find_ctor_id(type_id, variant_id as u16)
352 .expect("ctor id");
353 let qualified_name = visibility::member_key(name, &variant.name);
354 let ctor_symbol_id = self.symbols.intern_variant_ctor(
355 &qualified_name,
356 VmVariantCtor {
357 type_id,
358 variant_id: variant_id as u16,
359 ctor_id,
360 field_count: variant.fields.len() as u8,
361 },
362 );
363 let member_symbol_id = self.symbols.intern_name(&variant.name);
364 self.symbols.add_namespace_member_by_id(
365 type_symbol_id,
366 member_symbol_id,
367 VmSymbolTable::symbol_ref(ctor_symbol_id),
368 );
369 }
370 }
371 }
372 }
373
374 fn bootstrap_core_symbols(&mut self) {
375 for builtin in VmBuiltin::ALL.iter().copied() {
376 let builtin_symbol_id = self.symbols.intern_builtin(builtin);
377 if let Some((namespace, member)) = builtin.name().split_once('.') {
378 let namespace_symbol_id = self.symbols.intern_namespace_path(namespace);
379 let member_symbol_id = self.symbols.intern_name(member);
380 self.symbols.add_namespace_member_by_id(
381 namespace_symbol_id,
382 member_symbol_id,
383 VmSymbolTable::symbol_ref(builtin_symbol_id),
384 );
385 }
386 }
387
388 let result_symbol_id = self.symbols.intern_namespace_path("Result");
389 let ok_symbol_id = self.symbols.intern_wrapper("Result.Ok", 0);
390 let err_symbol_id = self.symbols.intern_wrapper("Result.Err", 1);
391 let ok_member_symbol_id = self.symbols.intern_name("Ok");
392 self.symbols.add_namespace_member_by_id(
393 result_symbol_id,
394 ok_member_symbol_id,
395 VmSymbolTable::symbol_ref(ok_symbol_id),
396 );
397 let err_member_symbol_id = self.symbols.intern_name("Err");
398 self.symbols.add_namespace_member_by_id(
399 result_symbol_id,
400 err_member_symbol_id,
401 VmSymbolTable::symbol_ref(err_symbol_id),
402 );
403 for (member, builtin_name) in result::extra_members() {
404 if let Some(symbol_id) = self.symbols.find(&builtin_name) {
405 let member_symbol_id = self.symbols.intern_name(member);
406 self.symbols.add_namespace_member_by_id(
407 result_symbol_id,
408 member_symbol_id,
409 VmSymbolTable::symbol_ref(symbol_id),
410 );
411 }
412 }
413
414 let option_symbol_id = self.symbols.intern_namespace_path("Option");
415 let some_symbol_id = self.symbols.intern_wrapper("Option.Some", 2);
416 self.symbols.intern_constant("Option.None", NanValue::NONE);
417 let some_member_symbol_id = self.symbols.intern_name("Some");
418 self.symbols.add_namespace_member_by_id(
419 option_symbol_id,
420 some_member_symbol_id,
421 VmSymbolTable::symbol_ref(some_symbol_id),
422 );
423 let none_member_symbol_id = self.symbols.intern_name("None");
424 self.symbols.add_namespace_member_by_id(
425 option_symbol_id,
426 none_member_symbol_id,
427 NanValue::NONE,
428 );
429 for (member, builtin_name) in option::extra_members() {
430 if let Some(symbol_id) = self.symbols.find(&builtin_name) {
431 let member_symbol_id = self.symbols.intern_name(member);
432 self.symbols.add_namespace_member_by_id(
433 option_symbol_id,
434 member_symbol_id,
435 VmSymbolTable::symbol_ref(symbol_id),
436 );
437 }
438 }
439 }
440
441 fn compile_fn(&mut self, fndef: &FnDef, arena: &mut Arena) -> Result<FnChunk, CompileError> {
442 let empty_scope = HashMap::new();
443 self.compile_fn_with_scope(fndef, arena, &empty_scope)
444 }
445
446 fn compile_fn_with_scope(
447 &mut self,
448 fndef: &FnDef,
449 arena: &mut Arena,
450 module_scope: &HashMap<String, u32>,
451 ) -> Result<FnChunk, CompileError> {
452 let resolution = fndef.resolution.as_ref();
453 let local_count = resolution.map_or(fndef.params.len() as u16, |r| r.local_count);
454 let local_slots: HashMap<String, u16> = resolution
455 .map(|r| r.local_slots.as_ref().clone())
456 .unwrap_or_else(|| {
457 fndef
458 .params
459 .iter()
460 .enumerate()
461 .map(|(i, (name, _))| (name.clone(), i as u16))
462 .collect()
463 });
464
465 let mut fc = FnCompiler::new(
466 &fndef.name,
467 fndef.params.len() as u8,
468 local_count,
469 fndef
470 .effects
471 .iter()
472 .map(|effect| self.symbols.intern_name(&effect.node))
473 .collect(),
474 local_slots,
475 &self.global_names,
476 module_scope,
477 &self.code,
478 &mut self.symbols,
479 arena,
480 );
481 fc.source_file = self.source_file.clone();
482 fc.note_line(fndef.line);
483
484 match fndef.body.as_ref() {
485 FnBody::Block(stmts) => fc.compile_body(stmts)?,
486 }
487
488 Ok(fc.finish())
489 }
490
491 fn compile_top_level(
492 &mut self,
493 items: &[TopLevel],
494 arena: &mut Arena,
495 ) -> Result<(), CompileError> {
496 let has_stmts = items.iter().any(|i| matches!(i, TopLevel::Stmt(_)));
497 if !has_stmts {
498 return Ok(());
499 }
500
501 for item in items {
502 if let TopLevel::Stmt(Stmt::Binding(name, _, _)) = item {
503 self.ensure_global(name);
504 }
505 }
506
507 let empty_mod_scope = HashMap::new();
508 let mut fc = FnCompiler::new(
509 "__top_level__",
510 0,
511 0,
512 Vec::new(),
513 HashMap::new(),
514 &self.global_names,
515 &empty_mod_scope,
516 &self.code,
517 &mut self.symbols,
518 arena,
519 );
520
521 for item in items {
522 if let TopLevel::Stmt(stmt) = item {
523 match stmt {
524 Stmt::Binding(name, _type_ann, expr) => {
525 fc.compile_expr(expr)?;
526 let idx = self.global_names[name.as_str()];
527 fc.emit_op(STORE_GLOBAL);
528 fc.emit_u16(idx);
529 }
530 Stmt::Expr(expr) => {
531 fc.compile_expr(expr)?;
532 fc.emit_op(POP);
533 }
534 }
535 }
536 }
537
538 fc.emit_op(LOAD_UNIT);
539 fc.emit_op(RETURN);
540
541 let chunk = fc.finish();
542 self.code.add_function(chunk);
543 Ok(())
544 }
545
546 fn register_current_module_namespace(&mut self, items: &[TopLevel]) {
547 let Some(module) = items.iter().find_map(|item| {
548 if let TopLevel::Module(module) = item {
549 Some(module)
550 } else {
551 None
552 }
553 }) else {
554 return;
555 };
556
557 let module_symbol_id = self.symbols.intern_namespace_path(&module.name);
558 let exposes_ref = if module.exposes.is_empty() {
559 None
560 } else {
561 Some(module.exposes.as_slice())
562 };
563
564 for item in items {
565 match item {
566 TopLevel::FnDef(fndef) => {
567 if visibility::is_exposed(&fndef.name, exposes_ref)
568 && let Some(symbol_id) = self.symbols.find(&fndef.name)
569 {
570 let member_symbol_id = self.symbols.intern_name(&fndef.name);
571 self.symbols.add_namespace_member_by_id(
572 module_symbol_id,
573 member_symbol_id,
574 VmSymbolTable::symbol_ref(symbol_id),
575 );
576 }
577 }
578 TopLevel::TypeDef(TypeDef::Product { name, .. } | TypeDef::Sum { name, .. }) => {
579 if visibility::is_exposed(name, exposes_ref)
580 && let Some(symbol_id) = self.symbols.find(name)
581 {
582 let member_symbol_id = self.symbols.intern_name(name);
583 self.symbols.add_namespace_member_by_id(
584 module_symbol_id,
585 member_symbol_id,
586 VmSymbolTable::symbol_ref(symbol_id),
587 );
588 }
589 }
590 _ => {}
591 }
592 }
593 }
594}
595
596enum CallTarget {
598 KnownFn(u32),
600 Wrapper(u8),
602 None_,
604 Variant(u32, u16),
606 Builtin(VmBuiltin),
608 UnknownQualified(String),
610}
611
612struct FnCompiler<'a> {
613 name: String,
614 arity: u8,
615 local_count: u16,
616 effects: Vec<u32>,
617 local_slots: HashMap<String, u16>,
618 global_names: &'a HashMap<String, u16>,
619 module_scope: &'a HashMap<String, u32>,
622 code_store: &'a CodeStore,
623 symbols: &'a mut VmSymbolTable,
624 arena: &'a mut Arena,
625 code: Vec<u8>,
626 constants: Vec<NanValue>,
627 last_op_pos: usize,
629 source_file: String,
631 line_table: Vec<(u16, u16)>,
633 last_noted_line: u16,
635}
636
637impl<'a> FnCompiler<'a> {
638 #[allow(clippy::too_many_arguments)]
639 fn new(
640 name: &str,
641 arity: u8,
642 local_count: u16,
643 effects: Vec<u32>,
644 local_slots: HashMap<String, u16>,
645 global_names: &'a HashMap<String, u16>,
646 module_scope: &'a HashMap<String, u32>,
647 code_store: &'a CodeStore,
648 symbols: &'a mut VmSymbolTable,
649 arena: &'a mut Arena,
650 ) -> Self {
651 FnCompiler {
652 name: name.to_string(),
653 arity,
654 local_count,
655 effects,
656 local_slots,
657 global_names,
658 module_scope,
659 code_store,
660 symbols,
661 arena,
662 code: Vec::new(),
663 constants: Vec::new(),
664 last_op_pos: usize::MAX,
665 source_file: String::new(),
666 line_table: Vec::new(),
667 last_noted_line: 0,
668 }
669 }
670
671 fn finish(self) -> FnChunk {
672 FnChunk {
673 name: self.name,
674 arity: self.arity,
675 local_count: self.local_count,
676 code: self.code,
677 constants: self.constants,
678 effects: self.effects,
679 thin: false,
680 parent_thin: false,
681 leaf: false,
682 source_file: self.source_file,
683 line_table: self.line_table,
684 }
685 }
686
687 fn note_line(&mut self, line: usize) {
691 if line == 0 {
692 return;
693 }
694 let line16 = line as u16;
695 if line16 == self.last_noted_line {
696 return; }
698 self.last_noted_line = line16;
699 self.line_table.push((self.code.len() as u16, line16));
700 }
701
702 fn emit_op(&mut self, op: u8) {
703 let prev_pos = self.last_op_pos;
704 let prev_op = if prev_pos < self.code.len() {
705 self.code[prev_pos]
706 } else {
707 0xFF
708 };
709
710 if op == LOAD_LOCAL && prev_op == LOAD_LOCAL && prev_pos + 2 == self.code.len() {
712 self.code[prev_pos] = LOAD_LOCAL_2;
713 return;
715 }
716 if op == LOAD_CONST && prev_op == LOAD_LOCAL && prev_pos + 2 == self.code.len() {
718 self.code[prev_pos] = LOAD_LOCAL_CONST;
719 return;
721 }
722 if op == UNWRAP_OR && self.code.len() >= 4 {
726 let len = self.code.len();
727 if self.code[len - 4] == VECTOR_GET && self.code[len - 3] == LOAD_CONST {
728 let hi = self.code[len - 2];
729 let lo = self.code[len - 1];
730 self.code[len - 4] = VECTOR_GET_OR;
731 self.code[len - 3] = hi;
732 self.code[len - 2] = lo;
733 self.code.pop(); self.last_op_pos = len - 4;
735 return;
736 }
737 }
738 self.last_op_pos = self.code.len();
739 self.code.push(op);
740 }
741
742 fn emit_u8(&mut self, val: u8) {
743 self.code.push(val);
744 }
745
746 fn emit_u16(&mut self, val: u16) {
747 self.code.push((val >> 8) as u8);
748 self.code.push((val & 0xFF) as u8);
749 }
750
751 fn emit_i16(&mut self, val: i16) {
752 self.emit_u16(val as u16);
753 }
754
755 fn emit_u32(&mut self, val: u32) {
756 self.code.push((val >> 24) as u8);
757 self.code.push(((val >> 16) & 0xFF) as u8);
758 self.code.push(((val >> 8) & 0xFF) as u8);
759 self.code.push((val & 0xFF) as u8);
760 }
761
762 fn emit_u64(&mut self, val: u64) {
763 self.code.extend_from_slice(&val.to_be_bytes());
764 }
765
766 fn add_constant(&mut self, val: NanValue) -> u16 {
767 for (i, c) in self.constants.iter().enumerate() {
768 if c.bits() == val.bits() {
769 return i as u16;
770 }
771 }
772 let idx = self.constants.len() as u16;
773 self.constants.push(val);
774 idx
775 }
776
777 fn offset(&self) -> usize {
778 self.code.len()
779 }
780
781 fn emit_jump(&mut self, op: u8) -> usize {
782 self.emit_op(op);
783 let patch_pos = self.code.len();
784 self.emit_i16(0);
785 patch_pos
786 }
787
788 fn patch_jump(&mut self, patch_pos: usize) {
789 let target = self.code.len();
790 let offset = (target as isize - patch_pos as isize - 2) as i16;
791 let bytes = (offset as u16).to_be_bytes();
792 self.code[patch_pos] = bytes[0];
793 self.code[patch_pos + 1] = bytes[1];
794 }
795
796 fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
797 let offset = (target as isize - patch_pos as isize - 2) as i16;
798 let bytes = (offset as u16).to_be_bytes();
799 self.code[patch_pos] = bytes[0];
800 self.code[patch_pos + 1] = bytes[1];
801 }
802
803 fn bind_top_to_local(&mut self, name: &str) {
804 if let Some(&slot) = self.local_slots.get(name) {
805 self.emit_op(STORE_LOCAL);
806 self.emit_u8(slot as u8);
807 } else {
808 self.emit_op(POP);
809 }
810 }
811
812 fn dup_and_bind_top_to_local(&mut self, name: &str) {
813 self.emit_op(DUP);
814 self.bind_top_to_local(name);
815 }
816}
817
818#[cfg(test)]
819mod tests {
820 use super::compile_program;
821 use crate::nan_value::Arena;
822 use crate::source::parse_source;
823 use crate::vm::opcode::{LT, NOT, VECTOR_GET_OR, VECTOR_SET_OR_KEEP};
824
825 #[test]
826 fn vector_get_with_literal_default_lowers_to_vector_get_or() {
827 let source = r#"
828module Demo
829
830fn cellAt(grid: Vector<Int>, idx: Int) -> Int
831 Option.withDefault(Vector.get(grid, idx), 0)
832"#;
833
834 let mut items = parse_source(source).expect("source should parse");
835 crate::tco::transform_program(&mut items);
836 crate::resolver::resolve_program(&mut items);
837
838 let mut arena = Arena::new();
839 let (code, _globals) = compile_program(&items, &mut arena).expect("vm compile should pass");
840 let fn_id = code.find("cellAt").expect("cellAt should exist");
841 let chunk = code.get(fn_id);
842
843 assert!(
844 chunk.code.contains(&VECTOR_GET_OR),
845 "expected VECTOR_GET_OR in bytecode, got {:?}",
846 chunk.code
847 );
848 }
849
850 #[test]
851 fn vector_set_with_same_default_lowers_to_vector_set_or_keep() {
852 let source = r#"
853module Demo
854
855fn updateOrKeep(vec: Vector<Int>, idx: Int, value: Int) -> Vector<Int>
856 Option.withDefault(Vector.set(vec, idx, value), vec)
857"#;
858
859 let mut items = parse_source(source).expect("source should parse");
860 crate::tco::transform_program(&mut items);
861 crate::resolver::resolve_program(&mut items);
862
863 let mut arena = Arena::new();
864 let (code, _globals) = compile_program(&items, &mut arena).expect("vm compile should pass");
865 let fn_id = code
866 .find("updateOrKeep")
867 .expect("updateOrKeep should exist");
868 let chunk = code.get(fn_id);
869
870 assert!(
871 chunk.code.contains(&VECTOR_SET_OR_KEEP),
872 "expected VECTOR_SET_OR_KEEP in bytecode, got {:?}",
873 chunk.code
874 );
875 }
876
877 #[test]
878 fn bool_match_on_gte_uses_base_compare_without_not() {
879 let source = r#"
880module Demo
881
882fn bucket(n: Int) -> Int
883 match n >= 10
884 true -> 7
885 false -> 3
886"#;
887
888 let mut items = parse_source(source).expect("source should parse");
889 crate::tco::transform_program(&mut items);
890 crate::resolver::resolve_program(&mut items);
891
892 let mut arena = Arena::new();
893 let (code, _globals) = compile_program(&items, &mut arena).expect("vm compile should pass");
894 let fn_id = code.find("bucket").expect("bucket should exist");
895 let chunk = code.get(fn_id);
896
897 assert!(
898 chunk.code.contains(<),
899 "expected LT in bytecode, got {:?}",
900 chunk.code
901 );
902 assert!(
903 !chunk.code.contains(&NOT),
904 "did not expect NOT in normalized bool-match bytecode, got {:?}",
905 chunk.code
906 );
907 }
908
909 #[test]
910 fn self_host_runtime_http_server_aliases_compile_in_vm() {
911 let source = r#"
912module Demo
913
914fn listen(handler: Int) -> Unit
915 SelfHostRuntime.httpServerListen(8080, handler)
916
917fn listenWith(context: Int, handler: Int) -> Unit
918 SelfHostRuntime.httpServerListenWith(8081, context, handler)
919"#;
920
921 let mut items = parse_source(source).expect("source should parse");
922 crate::tco::transform_program(&mut items);
923 crate::resolver::resolve_program(&mut items);
924
925 let mut arena = Arena::new();
926 let (code, _globals) = compile_program(&items, &mut arena).expect("vm compile should pass");
927 assert!(code.find("listen").is_some(), "listen should compile");
928 assert!(
929 code.find("listenWith").is_some(),
930 "listenWith should compile"
931 );
932 }
933}