1use harn_lexer::StringSegment;
2use harn_parser::{Node, SNode, TypedParam};
3
4use crate::chunk::{Chunk, CompiledFunction, Constant, Op};
5
6#[derive(Debug)]
8pub struct CompileError {
9 pub message: String,
10 pub line: u32,
11}
12
13impl std::fmt::Display for CompileError {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 write!(f, "Compile error at line {}: {}", self.line, self.message)
16 }
17}
18
19impl std::error::Error for CompileError {}
20
21struct LoopContext {
23 start_offset: usize,
25 break_patches: Vec<usize>,
27 has_iterator: bool,
29 handler_depth: usize,
31}
32
33pub struct Compiler {
35 chunk: Chunk,
36 line: u32,
37 enum_names: std::collections::HashSet<String>,
39 loop_stack: Vec<LoopContext>,
41 handler_depth: usize,
43}
44
45impl Compiler {
46 pub fn new() -> Self {
47 Self {
48 chunk: Chunk::new(),
49 line: 1,
50 enum_names: std::collections::HashSet::new(),
51 loop_stack: Vec::new(),
52 handler_depth: 0,
53 }
54 }
55
56 pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
59 Self::collect_enum_names(program, &mut self.enum_names);
62
63 for sn in program {
65 match &sn.node {
66 Node::ImportDecl { .. } | Node::SelectiveImport { .. } => {
67 self.compile_node(sn)?;
68 }
69 _ => {}
70 }
71 }
72
73 let main = program
75 .iter()
76 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == "default"))
77 .or_else(|| {
78 program
79 .iter()
80 .find(|sn| matches!(&sn.node, Node::Pipeline { .. }))
81 });
82
83 if let Some(sn) = main {
84 if let Node::Pipeline { body, extends, .. } = &sn.node {
85 if let Some(parent_name) = extends {
87 self.compile_parent_pipeline(program, parent_name)?;
88 }
89 self.compile_block(body)?;
90 }
91 }
92
93 self.chunk.emit(Op::Nil, self.line);
94 self.chunk.emit(Op::Return, self.line);
95 Ok(self.chunk)
96 }
97
98 pub fn compile_named(
100 mut self,
101 program: &[SNode],
102 pipeline_name: &str,
103 ) -> Result<Chunk, CompileError> {
104 Self::collect_enum_names(program, &mut self.enum_names);
105
106 for sn in program {
107 if matches!(
108 &sn.node,
109 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
110 ) {
111 self.compile_node(sn)?;
112 }
113 }
114
115 let target = program
116 .iter()
117 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == pipeline_name));
118
119 if let Some(sn) = target {
120 if let Node::Pipeline { body, extends, .. } = &sn.node {
121 if let Some(parent_name) = extends {
122 self.compile_parent_pipeline(program, parent_name)?;
123 }
124 self.compile_block(body)?;
125 }
126 }
127
128 self.chunk.emit(Op::Nil, self.line);
129 self.chunk.emit(Op::Return, self.line);
130 Ok(self.chunk)
131 }
132
133 fn compile_parent_pipeline(
135 &mut self,
136 program: &[SNode],
137 parent_name: &str,
138 ) -> Result<(), CompileError> {
139 let parent = program
140 .iter()
141 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == parent_name));
142 if let Some(sn) = parent {
143 if let Node::Pipeline { body, extends, .. } = &sn.node {
144 if let Some(grandparent) = extends {
146 self.compile_parent_pipeline(program, grandparent)?;
147 }
148 for stmt in body {
150 self.compile_node(stmt)?;
151 if Self::produces_value(&stmt.node) {
152 self.chunk.emit(Op::Pop, self.line);
153 }
154 }
155 }
156 }
157 Ok(())
158 }
159
160 fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
161 for (i, snode) in stmts.iter().enumerate() {
162 self.compile_node(snode)?;
163 if i < stmts.len() - 1 {
165 if Self::produces_value(&snode.node) {
167 self.chunk.emit(Op::Pop, self.line);
168 }
169 }
170 }
171 Ok(())
172 }
173
174 fn compile_node(&mut self, snode: &SNode) -> Result<(), CompileError> {
175 self.line = snode.span.line as u32;
176 match &snode.node {
177 Node::IntLiteral(n) => {
178 let idx = self.chunk.add_constant(Constant::Int(*n));
179 self.chunk.emit_u16(Op::Constant, idx, self.line);
180 }
181 Node::FloatLiteral(n) => {
182 let idx = self.chunk.add_constant(Constant::Float(*n));
183 self.chunk.emit_u16(Op::Constant, idx, self.line);
184 }
185 Node::StringLiteral(s) => {
186 let idx = self.chunk.add_constant(Constant::String(s.clone()));
187 self.chunk.emit_u16(Op::Constant, idx, self.line);
188 }
189 Node::BoolLiteral(true) => self.chunk.emit(Op::True, self.line),
190 Node::BoolLiteral(false) => self.chunk.emit(Op::False, self.line),
191 Node::NilLiteral => self.chunk.emit(Op::Nil, self.line),
192 Node::DurationLiteral(ms) => {
193 let idx = self.chunk.add_constant(Constant::Duration(*ms));
194 self.chunk.emit_u16(Op::Constant, idx, self.line);
195 }
196
197 Node::Identifier(name) => {
198 let idx = self.chunk.add_constant(Constant::String(name.clone()));
199 self.chunk.emit_u16(Op::GetVar, idx, self.line);
200 }
201
202 Node::LetBinding { name, value, .. } => {
203 self.compile_node(value)?;
204 let idx = self.chunk.add_constant(Constant::String(name.clone()));
205 self.chunk.emit_u16(Op::DefLet, idx, self.line);
206 }
207
208 Node::VarBinding { name, value, .. } => {
209 self.compile_node(value)?;
210 let idx = self.chunk.add_constant(Constant::String(name.clone()));
211 self.chunk.emit_u16(Op::DefVar, idx, self.line);
212 }
213
214 Node::Assignment {
215 target, value, op, ..
216 } => {
217 if let Node::Identifier(name) = &target.node {
218 let idx = self.chunk.add_constant(Constant::String(name.clone()));
219 if let Some(op) = op {
220 self.chunk.emit_u16(Op::GetVar, idx, self.line);
221 self.compile_node(value)?;
222 self.emit_compound_op(op)?;
223 self.chunk.emit_u16(Op::SetVar, idx, self.line);
224 } else {
225 self.compile_node(value)?;
226 self.chunk.emit_u16(Op::SetVar, idx, self.line);
227 }
228 } else if let Node::PropertyAccess { object, property } = &target.node {
229 if let Some(var_name) = self.root_var_name(object) {
231 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
232 let prop_idx = self.chunk.add_constant(Constant::String(property.clone()));
233 if let Some(op) = op {
234 self.compile_node(target)?; self.compile_node(value)?;
237 self.emit_compound_op(op)?;
238 } else {
239 self.compile_node(value)?;
240 }
241 self.chunk.emit_u16(Op::SetProperty, prop_idx, self.line);
244 let hi = (var_idx >> 8) as u8;
246 let lo = var_idx as u8;
247 self.chunk.code.push(hi);
248 self.chunk.code.push(lo);
249 self.chunk.lines.push(self.line);
250 self.chunk.lines.push(self.line);
251 }
252 } else if let Node::SubscriptAccess { object, index } = &target.node {
253 if let Some(var_name) = self.root_var_name(object) {
255 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
256 if let Some(op) = op {
257 self.compile_node(target)?;
258 self.compile_node(value)?;
259 self.emit_compound_op(op)?;
260 } else {
261 self.compile_node(value)?;
262 }
263 self.compile_node(index)?;
264 self.chunk.emit_u16(Op::SetSubscript, var_idx, self.line);
265 }
266 }
267 }
268
269 Node::BinaryOp { op, left, right } => {
270 match op.as_str() {
272 "&&" => {
273 self.compile_node(left)?;
274 let jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
275 self.chunk.emit(Op::Pop, self.line);
276 self.compile_node(right)?;
277 self.chunk.patch_jump(jump);
278 self.chunk.emit(Op::Not, self.line);
280 self.chunk.emit(Op::Not, self.line);
281 return Ok(());
282 }
283 "||" => {
284 self.compile_node(left)?;
285 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
286 self.chunk.emit(Op::Pop, self.line);
287 self.compile_node(right)?;
288 self.chunk.patch_jump(jump);
289 self.chunk.emit(Op::Not, self.line);
290 self.chunk.emit(Op::Not, self.line);
291 return Ok(());
292 }
293 "??" => {
294 self.compile_node(left)?;
295 self.chunk.emit(Op::Dup, self.line);
296 self.chunk.emit(Op::Nil, self.line);
298 self.chunk.emit(Op::NotEqual, self.line);
299 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
300 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_node(right)?;
303 let end = self.chunk.emit_jump(Op::Jump, self.line);
304 self.chunk.patch_jump(jump);
305 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end);
307 return Ok(());
308 }
309 "|>" => {
310 self.compile_node(left)?;
311 self.compile_node(right)?;
312 self.chunk.emit(Op::Pipe, self.line);
313 return Ok(());
314 }
315 _ => {}
316 }
317
318 self.compile_node(left)?;
319 self.compile_node(right)?;
320 match op.as_str() {
321 "+" => self.chunk.emit(Op::Add, self.line),
322 "-" => self.chunk.emit(Op::Sub, self.line),
323 "*" => self.chunk.emit(Op::Mul, self.line),
324 "/" => self.chunk.emit(Op::Div, self.line),
325 "%" => self.chunk.emit(Op::Mod, self.line),
326 "==" => self.chunk.emit(Op::Equal, self.line),
327 "!=" => self.chunk.emit(Op::NotEqual, self.line),
328 "<" => self.chunk.emit(Op::Less, self.line),
329 ">" => self.chunk.emit(Op::Greater, self.line),
330 "<=" => self.chunk.emit(Op::LessEqual, self.line),
331 ">=" => self.chunk.emit(Op::GreaterEqual, self.line),
332 _ => {
333 return Err(CompileError {
334 message: format!("Unknown operator: {op}"),
335 line: self.line,
336 })
337 }
338 }
339 }
340
341 Node::UnaryOp { op, operand } => {
342 self.compile_node(operand)?;
343 match op.as_str() {
344 "-" => self.chunk.emit(Op::Negate, self.line),
345 "!" => self.chunk.emit(Op::Not, self.line),
346 _ => {}
347 }
348 }
349
350 Node::Ternary {
351 condition,
352 true_expr,
353 false_expr,
354 } => {
355 self.compile_node(condition)?;
356 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
357 self.chunk.emit(Op::Pop, self.line);
358 self.compile_node(true_expr)?;
359 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
360 self.chunk.patch_jump(else_jump);
361 self.chunk.emit(Op::Pop, self.line);
362 self.compile_node(false_expr)?;
363 self.chunk.patch_jump(end_jump);
364 }
365
366 Node::FunctionCall { name, args } => {
367 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
369 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
370 for arg in args {
372 self.compile_node(arg)?;
373 }
374 self.chunk.emit_u8(Op::Call, args.len() as u8, self.line);
375 }
376
377 Node::MethodCall {
378 object,
379 method,
380 args,
381 } => {
382 if let Node::Identifier(name) = &object.node {
384 if self.enum_names.contains(name) {
385 for arg in args {
387 self.compile_node(arg)?;
388 }
389 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
390 let var_idx = self.chunk.add_constant(Constant::String(method.clone()));
391 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
392 let hi = (var_idx >> 8) as u8;
393 let lo = var_idx as u8;
394 self.chunk.code.push(hi);
395 self.chunk.code.push(lo);
396 self.chunk.lines.push(self.line);
397 self.chunk.lines.push(self.line);
398 let fc = args.len() as u16;
399 let fhi = (fc >> 8) as u8;
400 let flo = fc as u8;
401 self.chunk.code.push(fhi);
402 self.chunk.code.push(flo);
403 self.chunk.lines.push(self.line);
404 self.chunk.lines.push(self.line);
405 return Ok(());
406 }
407 }
408 self.compile_node(object)?;
409 for arg in args {
410 self.compile_node(arg)?;
411 }
412 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
413 self.chunk
414 .emit_method_call(name_idx, args.len() as u8, self.line);
415 }
416
417 Node::PropertyAccess { object, property } => {
418 if let Node::Identifier(name) = &object.node {
420 if self.enum_names.contains(name) {
421 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
423 let var_idx = self.chunk.add_constant(Constant::String(property.clone()));
424 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
425 let hi = (var_idx >> 8) as u8;
426 let lo = var_idx as u8;
427 self.chunk.code.push(hi);
428 self.chunk.code.push(lo);
429 self.chunk.lines.push(self.line);
430 self.chunk.lines.push(self.line);
431 self.chunk.code.push(0);
433 self.chunk.code.push(0);
434 self.chunk.lines.push(self.line);
435 self.chunk.lines.push(self.line);
436 return Ok(());
437 }
438 }
439 self.compile_node(object)?;
440 let idx = self.chunk.add_constant(Constant::String(property.clone()));
441 self.chunk.emit_u16(Op::GetProperty, idx, self.line);
442 }
443
444 Node::SubscriptAccess { object, index } => {
445 self.compile_node(object)?;
446 self.compile_node(index)?;
447 self.chunk.emit(Op::Subscript, self.line);
448 }
449
450 Node::IfElse {
451 condition,
452 then_body,
453 else_body,
454 } => {
455 self.compile_node(condition)?;
456 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
457 self.chunk.emit(Op::Pop, self.line);
458 self.compile_block(then_body)?;
459 if let Some(else_body) = else_body {
460 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
461 self.chunk.patch_jump(else_jump);
462 self.chunk.emit(Op::Pop, self.line);
463 self.compile_block(else_body)?;
464 self.chunk.patch_jump(end_jump);
465 } else {
466 self.chunk.patch_jump(else_jump);
467 self.chunk.emit(Op::Pop, self.line);
468 self.chunk.emit(Op::Nil, self.line);
469 }
470 }
471
472 Node::WhileLoop { condition, body } => {
473 let loop_start = self.chunk.current_offset();
474 self.loop_stack.push(LoopContext {
475 start_offset: loop_start,
476 break_patches: Vec::new(),
477 has_iterator: false,
478 handler_depth: self.handler_depth,
479 });
480 self.compile_node(condition)?;
481 let exit_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
482 self.chunk.emit(Op::Pop, self.line); for sn in body {
485 self.compile_node(sn)?;
486 if Self::produces_value(&sn.node) {
487 self.chunk.emit(Op::Pop, self.line);
488 }
489 }
490 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
492 self.chunk.patch_jump(exit_jump);
493 self.chunk.emit(Op::Pop, self.line); let ctx = self.loop_stack.pop().unwrap();
496 for patch_pos in ctx.break_patches {
497 self.chunk.patch_jump(patch_pos);
498 }
499 self.chunk.emit(Op::Nil, self.line);
500 }
501
502 Node::ForIn {
503 variable,
504 iterable,
505 body,
506 } => {
507 self.compile_node(iterable)?;
509 let var_idx = self.chunk.add_constant(Constant::String(variable.clone()));
511 self.chunk.emit(Op::IterInit, self.line);
513 let loop_start = self.chunk.current_offset();
514 self.loop_stack.push(LoopContext {
515 start_offset: loop_start,
516 break_patches: Vec::new(),
517 has_iterator: true,
518 handler_depth: self.handler_depth,
519 });
520 let exit_jump_pos = self.chunk.emit_jump(Op::IterNext, self.line);
522 self.chunk.emit_u16(Op::DefVar, var_idx, self.line);
524 for sn in body {
526 self.compile_node(sn)?;
527 if Self::produces_value(&sn.node) {
528 self.chunk.emit(Op::Pop, self.line);
529 }
530 }
531 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
533 self.chunk.patch_jump(exit_jump_pos);
534 let ctx = self.loop_stack.pop().unwrap();
536 for patch_pos in ctx.break_patches {
537 self.chunk.patch_jump(patch_pos);
538 }
539 self.chunk.emit(Op::Nil, self.line);
541 }
542
543 Node::ReturnStmt { value } => {
544 if let Some(val) = value {
545 if let Node::FunctionCall { name, args } = &val.node {
548 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
549 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
550 for arg in args {
551 self.compile_node(arg)?;
552 }
553 self.chunk
554 .emit_u8(Op::TailCall, args.len() as u8, self.line);
555 } else {
556 self.compile_node(val)?;
557 }
558 } else {
559 self.chunk.emit(Op::Nil, self.line);
560 }
561 self.chunk.emit(Op::Return, self.line);
562 }
563
564 Node::BreakStmt => {
565 if self.loop_stack.is_empty() {
566 return Err(CompileError {
567 message: "break outside of loop".to_string(),
568 line: self.line,
569 });
570 }
571 let ctx = self.loop_stack.last().unwrap();
572 for _ in ctx.handler_depth..self.handler_depth {
574 self.chunk.emit(Op::PopHandler, self.line);
575 }
576 if ctx.has_iterator {
578 self.chunk.emit(Op::PopIterator, self.line);
579 }
580 let patch = self.chunk.emit_jump(Op::Jump, self.line);
581 self.loop_stack
582 .last_mut()
583 .unwrap()
584 .break_patches
585 .push(patch);
586 }
587
588 Node::ContinueStmt => {
589 if self.loop_stack.is_empty() {
590 return Err(CompileError {
591 message: "continue outside of loop".to_string(),
592 line: self.line,
593 });
594 }
595 let ctx = self.loop_stack.last().unwrap();
596 for _ in ctx.handler_depth..self.handler_depth {
598 self.chunk.emit(Op::PopHandler, self.line);
599 }
600 let loop_start = ctx.start_offset;
601 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
602 }
603
604 Node::ListLiteral(elements) => {
605 for el in elements {
606 self.compile_node(el)?;
607 }
608 self.chunk
609 .emit_u16(Op::BuildList, elements.len() as u16, self.line);
610 }
611
612 Node::DictLiteral(entries) => {
613 for entry in entries {
614 self.compile_node(&entry.key)?;
615 self.compile_node(&entry.value)?;
616 }
617 self.chunk
618 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
619 }
620
621 Node::InterpolatedString(segments) => {
622 let mut part_count = 0u16;
623 for seg in segments {
624 match seg {
625 StringSegment::Literal(s) => {
626 let idx = self.chunk.add_constant(Constant::String(s.clone()));
627 self.chunk.emit_u16(Op::Constant, idx, self.line);
628 part_count += 1;
629 }
630 StringSegment::Expression(expr_str) => {
631 let mut lexer = harn_lexer::Lexer::new(expr_str);
633 if let Ok(tokens) = lexer.tokenize() {
634 let mut parser = harn_parser::Parser::new(tokens);
635 if let Ok(snode) = parser.parse_single_expression() {
636 self.compile_node(&snode)?;
637 let to_str = self
639 .chunk
640 .add_constant(Constant::String("to_string".into()));
641 self.chunk.emit_u16(Op::Constant, to_str, self.line);
642 self.chunk.emit(Op::Swap, self.line);
643 self.chunk.emit_u8(Op::Call, 1, self.line);
644 part_count += 1;
645 } else {
646 let idx =
648 self.chunk.add_constant(Constant::String(expr_str.clone()));
649 self.chunk.emit_u16(Op::Constant, idx, self.line);
650 part_count += 1;
651 }
652 }
653 }
654 }
655 }
656 if part_count > 1 {
657 self.chunk.emit_u16(Op::Concat, part_count, self.line);
658 }
659 }
660
661 Node::FnDecl {
662 name, params, body, ..
663 } => {
664 let mut fn_compiler = Compiler::new();
666 fn_compiler.enum_names = self.enum_names.clone();
667 fn_compiler.compile_block(body)?;
668 fn_compiler.chunk.emit(Op::Nil, self.line);
669 fn_compiler.chunk.emit(Op::Return, self.line);
670
671 let func = CompiledFunction {
672 name: name.clone(),
673 params: TypedParam::names(params),
674 chunk: fn_compiler.chunk,
675 };
676 let fn_idx = self.chunk.functions.len();
677 self.chunk.functions.push(func);
678
679 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
680 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
681 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
682 }
683
684 Node::Closure { params, body } => {
685 let mut fn_compiler = Compiler::new();
686 fn_compiler.enum_names = self.enum_names.clone();
687 fn_compiler.compile_block(body)?;
688 fn_compiler.chunk.emit(Op::Return, self.line);
690
691 let func = CompiledFunction {
692 name: "<closure>".to_string(),
693 params: TypedParam::names(params),
694 chunk: fn_compiler.chunk,
695 };
696 let fn_idx = self.chunk.functions.len();
697 self.chunk.functions.push(func);
698
699 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
700 }
701
702 Node::ThrowStmt { value } => {
703 self.compile_node(value)?;
704 self.chunk.emit(Op::Throw, self.line);
705 }
706
707 Node::MatchExpr { value, arms } => {
708 self.compile_node(value)?;
709 let mut end_jumps = Vec::new();
710 for arm in arms {
711 match &arm.pattern.node {
712 Node::Identifier(name) if name == "_" => {
714 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
716 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
717 }
718 Node::EnumConstruct {
720 enum_name,
721 variant,
722 args: pat_args,
723 } => {
724 self.chunk.emit(Op::Dup, self.line);
726 let en_idx =
727 self.chunk.add_constant(Constant::String(enum_name.clone()));
728 let vn_idx = self.chunk.add_constant(Constant::String(variant.clone()));
729 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
730 let hi = (vn_idx >> 8) as u8;
731 let lo = vn_idx as u8;
732 self.chunk.code.push(hi);
733 self.chunk.code.push(lo);
734 self.chunk.lines.push(self.line);
735 self.chunk.lines.push(self.line);
736 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
738 self.chunk.emit(Op::Pop, self.line); for (i, pat_arg) in pat_args.iter().enumerate() {
743 if let Node::Identifier(binding_name) = &pat_arg.node {
744 self.chunk.emit(Op::Dup, self.line);
746 let fields_idx = self
747 .chunk
748 .add_constant(Constant::String("fields".to_string()));
749 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
750 let idx_const =
751 self.chunk.add_constant(Constant::Int(i as i64));
752 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
753 self.chunk.emit(Op::Subscript, self.line);
754 let name_idx = self
755 .chunk
756 .add_constant(Constant::String(binding_name.clone()));
757 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
758 }
759 }
760
761 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
763 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
764 self.chunk.patch_jump(skip);
765 self.chunk.emit(Op::Pop, self.line); }
767 Node::PropertyAccess { object, property } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
769 {
770 let enum_name = if let Node::Identifier(n) = &object.node {
771 n.clone()
772 } else {
773 unreachable!()
774 };
775 self.chunk.emit(Op::Dup, self.line);
776 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
777 let vn_idx =
778 self.chunk.add_constant(Constant::String(property.clone()));
779 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
780 let hi = (vn_idx >> 8) as u8;
781 let lo = vn_idx as u8;
782 self.chunk.code.push(hi);
783 self.chunk.code.push(lo);
784 self.chunk.lines.push(self.line);
785 self.chunk.lines.push(self.line);
786 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
787 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
790 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
791 self.chunk.patch_jump(skip);
792 self.chunk.emit(Op::Pop, self.line); }
794 Node::MethodCall {
797 object,
798 method,
799 args: pat_args,
800 } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
801 {
802 let enum_name = if let Node::Identifier(n) = &object.node {
803 n.clone()
804 } else {
805 unreachable!()
806 };
807 self.chunk.emit(Op::Dup, self.line);
809 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
810 let vn_idx = self.chunk.add_constant(Constant::String(method.clone()));
811 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
812 let hi = (vn_idx >> 8) as u8;
813 let lo = vn_idx as u8;
814 self.chunk.code.push(hi);
815 self.chunk.code.push(lo);
816 self.chunk.lines.push(self.line);
817 self.chunk.lines.push(self.line);
818 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
819 self.chunk.emit(Op::Pop, self.line); for (i, pat_arg) in pat_args.iter().enumerate() {
823 if let Node::Identifier(binding_name) = &pat_arg.node {
824 self.chunk.emit(Op::Dup, self.line);
825 let fields_idx = self
826 .chunk
827 .add_constant(Constant::String("fields".to_string()));
828 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
829 let idx_const =
830 self.chunk.add_constant(Constant::Int(i as i64));
831 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
832 self.chunk.emit(Op::Subscript, self.line);
833 let name_idx = self
834 .chunk
835 .add_constant(Constant::String(binding_name.clone()));
836 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
837 }
838 }
839
840 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
842 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
843 self.chunk.patch_jump(skip);
844 self.chunk.emit(Op::Pop, self.line); }
846 Node::Identifier(name) => {
848 self.chunk.emit(Op::Dup, self.line); let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
851 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
852 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
854 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
855 }
856 _ => {
858 self.chunk.emit(Op::Dup, self.line);
859 self.compile_node(&arm.pattern)?;
860 self.chunk.emit(Op::Equal, self.line);
861 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
862 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
865 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
866 self.chunk.patch_jump(skip);
867 self.chunk.emit(Op::Pop, self.line); }
869 }
870 }
871 self.chunk.emit(Op::Pop, self.line);
873 self.chunk.emit(Op::Nil, self.line);
874 for j in end_jumps {
875 self.chunk.patch_jump(j);
876 }
877 }
878
879 Node::RangeExpr {
880 start,
881 end,
882 inclusive,
883 } => {
884 let name_idx = self
886 .chunk
887 .add_constant(Constant::String("__range__".to_string()));
888 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
889 self.compile_node(start)?;
890 self.compile_node(end)?;
891 if *inclusive {
892 self.chunk.emit(Op::True, self.line);
893 } else {
894 self.chunk.emit(Op::False, self.line);
895 }
896 self.chunk.emit_u8(Op::Call, 3, self.line);
897 }
898
899 Node::GuardStmt {
900 condition,
901 else_body,
902 } => {
903 self.compile_node(condition)?;
906 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
907 self.chunk.emit(Op::Pop, self.line); self.compile_block(else_body)?;
910 if !else_body.is_empty() && Self::produces_value(&else_body.last().unwrap().node) {
912 self.chunk.emit(Op::Pop, self.line);
913 }
914 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
915 self.chunk.patch_jump(skip_jump);
916 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end_jump);
918 self.chunk.emit(Op::Nil, self.line);
919 }
920
921 Node::Block(stmts) => {
922 if stmts.is_empty() {
923 self.chunk.emit(Op::Nil, self.line);
924 } else {
925 self.compile_block(stmts)?;
926 }
927 }
928
929 Node::DeadlineBlock { duration, body } => {
930 self.compile_node(duration)?;
931 self.chunk.emit(Op::DeadlineSetup, self.line);
932 if body.is_empty() {
933 self.chunk.emit(Op::Nil, self.line);
934 } else {
935 self.compile_block(body)?;
936 }
937 self.chunk.emit(Op::DeadlineEnd, self.line);
938 }
939
940 Node::MutexBlock { body } => {
941 if body.is_empty() {
943 self.chunk.emit(Op::Nil, self.line);
944 } else {
945 for sn in body {
948 self.compile_node(sn)?;
949 if Self::produces_value(&sn.node) {
950 self.chunk.emit(Op::Pop, self.line);
951 }
952 }
953 self.chunk.emit(Op::Nil, self.line);
954 }
955 }
956
957 Node::YieldExpr { .. } => {
958 self.chunk.emit(Op::Nil, self.line);
960 }
961
962 Node::AskExpr { fields } => {
963 for entry in fields {
966 self.compile_node(&entry.key)?;
967 self.compile_node(&entry.value)?;
968 }
969 self.chunk
970 .emit_u16(Op::BuildDict, fields.len() as u16, self.line);
971 }
972
973 Node::EnumConstruct {
974 enum_name,
975 variant,
976 args,
977 } => {
978 for arg in args {
980 self.compile_node(arg)?;
981 }
982 let enum_idx = self.chunk.add_constant(Constant::String(enum_name.clone()));
983 let var_idx = self.chunk.add_constant(Constant::String(variant.clone()));
984 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
986 let hi = (var_idx >> 8) as u8;
987 let lo = var_idx as u8;
988 self.chunk.code.push(hi);
989 self.chunk.code.push(lo);
990 self.chunk.lines.push(self.line);
991 self.chunk.lines.push(self.line);
992 let fc = args.len() as u16;
993 let fhi = (fc >> 8) as u8;
994 let flo = fc as u8;
995 self.chunk.code.push(fhi);
996 self.chunk.code.push(flo);
997 self.chunk.lines.push(self.line);
998 self.chunk.lines.push(self.line);
999 }
1000
1001 Node::StructConstruct {
1002 struct_name,
1003 fields,
1004 } => {
1005 let struct_key = self
1007 .chunk
1008 .add_constant(Constant::String("__struct__".to_string()));
1009 let struct_val = self
1010 .chunk
1011 .add_constant(Constant::String(struct_name.clone()));
1012 self.chunk.emit_u16(Op::Constant, struct_key, self.line);
1013 self.chunk.emit_u16(Op::Constant, struct_val, self.line);
1014
1015 for entry in fields {
1016 self.compile_node(&entry.key)?;
1017 self.compile_node(&entry.value)?;
1018 }
1019 self.chunk
1020 .emit_u16(Op::BuildDict, (fields.len() + 1) as u16, self.line);
1021 }
1022
1023 Node::ImportDecl { path } => {
1024 let idx = self.chunk.add_constant(Constant::String(path.clone()));
1025 self.chunk.emit_u16(Op::Import, idx, self.line);
1026 }
1027
1028 Node::SelectiveImport { names, path } => {
1029 let path_idx = self.chunk.add_constant(Constant::String(path.clone()));
1030 let names_str = names.join(",");
1031 let names_idx = self.chunk.add_constant(Constant::String(names_str));
1032 self.chunk
1033 .emit_u16(Op::SelectiveImport, path_idx, self.line);
1034 let hi = (names_idx >> 8) as u8;
1035 let lo = names_idx as u8;
1036 self.chunk.code.push(hi);
1037 self.chunk.code.push(lo);
1038 self.chunk.lines.push(self.line);
1039 self.chunk.lines.push(self.line);
1040 }
1041
1042 Node::Pipeline { .. }
1044 | Node::OverrideDecl { .. }
1045 | Node::TypeDecl { .. }
1046 | Node::EnumDecl { .. }
1047 | Node::StructDecl { .. }
1048 | Node::InterfaceDecl { .. } => {
1049 self.chunk.emit(Op::Nil, self.line);
1050 }
1051
1052 Node::TryCatch {
1053 body,
1054 error_var,
1055 error_type,
1056 catch_body,
1057 } => {
1058 let type_name = error_type.as_ref().and_then(|te| {
1060 if let harn_parser::TypeExpr::Named(name) = te {
1062 Some(name.clone())
1063 } else {
1064 None
1065 }
1066 });
1067
1068 let type_name_idx = if let Some(ref tn) = type_name {
1070 self.chunk.add_constant(Constant::String(tn.clone()))
1071 } else {
1072 self.chunk.add_constant(Constant::String(String::new()))
1073 };
1074
1075 self.handler_depth += 1;
1077 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1078 let hi = (type_name_idx >> 8) as u8;
1080 let lo = type_name_idx as u8;
1081 self.chunk.code.push(hi);
1082 self.chunk.code.push(lo);
1083 self.chunk.lines.push(self.line);
1084 self.chunk.lines.push(self.line);
1085
1086 if body.is_empty() {
1088 self.chunk.emit(Op::Nil, self.line);
1089 } else {
1090 self.compile_block(body)?;
1091 if !Self::produces_value(&body.last().unwrap().node) {
1093 self.chunk.emit(Op::Nil, self.line);
1094 }
1095 }
1096
1097 self.handler_depth -= 1;
1099 self.chunk.emit(Op::PopHandler, self.line);
1100
1101 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1103
1104 self.chunk.patch_jump(catch_jump);
1106
1107 if let Some(var_name) = error_var {
1110 let idx = self.chunk.add_constant(Constant::String(var_name.clone()));
1111 self.chunk.emit_u16(Op::DefLet, idx, self.line);
1112 } else {
1113 self.chunk.emit(Op::Pop, self.line);
1114 }
1115
1116 if catch_body.is_empty() {
1118 self.chunk.emit(Op::Nil, self.line);
1119 } else {
1120 self.compile_block(catch_body)?;
1121 if !Self::produces_value(&catch_body.last().unwrap().node) {
1122 self.chunk.emit(Op::Nil, self.line);
1123 }
1124 }
1125
1126 self.chunk.patch_jump(end_jump);
1128 }
1129
1130 Node::Retry { count, body } => {
1131 self.compile_node(count)?;
1133 let counter_name = "__retry_counter__";
1134 let counter_idx = self
1135 .chunk
1136 .add_constant(Constant::String(counter_name.to_string()));
1137 self.chunk.emit_u16(Op::DefVar, counter_idx, self.line);
1138
1139 self.chunk.emit(Op::Nil, self.line);
1141 let err_name = "__retry_last_error__";
1142 let err_idx = self
1143 .chunk
1144 .add_constant(Constant::String(err_name.to_string()));
1145 self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
1146
1147 let loop_start = self.chunk.current_offset();
1149
1150 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1152 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1154 let hi = (empty_type >> 8) as u8;
1155 let lo = empty_type as u8;
1156 self.chunk.code.push(hi);
1157 self.chunk.code.push(lo);
1158 self.chunk.lines.push(self.line);
1159 self.chunk.lines.push(self.line);
1160
1161 self.compile_block(body)?;
1163
1164 self.chunk.emit(Op::PopHandler, self.line);
1166 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1167
1168 self.chunk.patch_jump(catch_jump);
1170 self.chunk.emit(Op::Dup, self.line);
1172 self.chunk.emit_u16(Op::SetVar, err_idx, self.line);
1173 self.chunk.emit(Op::Pop, self.line);
1175
1176 self.chunk.emit_u16(Op::GetVar, counter_idx, self.line);
1178 let one_idx = self.chunk.add_constant(Constant::Int(1));
1179 self.chunk.emit_u16(Op::Constant, one_idx, self.line);
1180 self.chunk.emit(Op::Sub, self.line);
1181 self.chunk.emit(Op::Dup, self.line);
1182 self.chunk.emit_u16(Op::SetVar, counter_idx, self.line);
1183
1184 let zero_idx = self.chunk.add_constant(Constant::Int(0));
1186 self.chunk.emit_u16(Op::Constant, zero_idx, self.line);
1187 self.chunk.emit(Op::Greater, self.line);
1188 let retry_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1189 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
1191
1192 self.chunk.patch_jump(retry_jump);
1194 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::GetVar, err_idx, self.line);
1196 self.chunk.emit(Op::Throw, self.line);
1197
1198 self.chunk.patch_jump(end_jump);
1199 self.chunk.emit(Op::Nil, self.line);
1201 }
1202
1203 Node::Parallel {
1204 count,
1205 variable,
1206 body,
1207 } => {
1208 self.compile_node(count)?;
1209 let mut fn_compiler = Compiler::new();
1210 fn_compiler.enum_names = self.enum_names.clone();
1211 fn_compiler.compile_block(body)?;
1212 fn_compiler.chunk.emit(Op::Return, self.line);
1213 let params = vec![variable.clone().unwrap_or_else(|| "__i__".to_string())];
1214 let func = CompiledFunction {
1215 name: "<parallel>".to_string(),
1216 params,
1217 chunk: fn_compiler.chunk,
1218 };
1219 let fn_idx = self.chunk.functions.len();
1220 self.chunk.functions.push(func);
1221 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1222 self.chunk.emit(Op::Parallel, self.line);
1223 }
1224
1225 Node::ParallelMap {
1226 list,
1227 variable,
1228 body,
1229 } => {
1230 self.compile_node(list)?;
1231 let mut fn_compiler = Compiler::new();
1232 fn_compiler.enum_names = self.enum_names.clone();
1233 fn_compiler.compile_block(body)?;
1234 fn_compiler.chunk.emit(Op::Return, self.line);
1235 let func = CompiledFunction {
1236 name: "<parallel_map>".to_string(),
1237 params: vec![variable.clone()],
1238 chunk: fn_compiler.chunk,
1239 };
1240 let fn_idx = self.chunk.functions.len();
1241 self.chunk.functions.push(func);
1242 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1243 self.chunk.emit(Op::ParallelMap, self.line);
1244 }
1245
1246 Node::SpawnExpr { body } => {
1247 let mut fn_compiler = Compiler::new();
1248 fn_compiler.enum_names = self.enum_names.clone();
1249 fn_compiler.compile_block(body)?;
1250 fn_compiler.chunk.emit(Op::Return, self.line);
1251 let func = CompiledFunction {
1252 name: "<spawn>".to_string(),
1253 params: vec![],
1254 chunk: fn_compiler.chunk,
1255 };
1256 let fn_idx = self.chunk.functions.len();
1257 self.chunk.functions.push(func);
1258 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1259 self.chunk.emit(Op::Spawn, self.line);
1260 }
1261 }
1262 Ok(())
1263 }
1264
1265 fn produces_value(node: &Node) -> bool {
1267 match node {
1268 Node::LetBinding { .. }
1270 | Node::VarBinding { .. }
1271 | Node::Assignment { .. }
1272 | Node::ReturnStmt { .. }
1273 | Node::FnDecl { .. }
1274 | Node::ThrowStmt { .. }
1275 | Node::BreakStmt
1276 | Node::ContinueStmt => false,
1277 Node::TryCatch { .. }
1279 | Node::Retry { .. }
1280 | Node::GuardStmt { .. }
1281 | Node::DeadlineBlock { .. }
1282 | Node::MutexBlock { .. } => true,
1283 _ => true,
1285 }
1286 }
1287}
1288
1289impl Compiler {
1290 pub fn compile_fn_body(
1292 &mut self,
1293 params: &[TypedParam],
1294 body: &[SNode],
1295 ) -> Result<CompiledFunction, CompileError> {
1296 let mut fn_compiler = Compiler::new();
1297 fn_compiler.compile_block(body)?;
1298 fn_compiler.chunk.emit(Op::Nil, 0);
1299 fn_compiler.chunk.emit(Op::Return, 0);
1300 Ok(CompiledFunction {
1301 name: String::new(),
1302 params: TypedParam::names(params),
1303 chunk: fn_compiler.chunk,
1304 })
1305 }
1306
1307 fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1309 if body.is_empty() {
1310 self.chunk.emit(Op::Nil, self.line);
1311 } else {
1312 self.compile_block(body)?;
1313 if !Self::produces_value(&body.last().unwrap().node) {
1315 self.chunk.emit(Op::Nil, self.line);
1316 }
1317 }
1318 Ok(())
1319 }
1320
1321 fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
1323 match op {
1324 "+" => self.chunk.emit(Op::Add, self.line),
1325 "-" => self.chunk.emit(Op::Sub, self.line),
1326 "*" => self.chunk.emit(Op::Mul, self.line),
1327 "/" => self.chunk.emit(Op::Div, self.line),
1328 "%" => self.chunk.emit(Op::Mod, self.line),
1329 _ => {
1330 return Err(CompileError {
1331 message: format!("Unknown compound operator: {op}"),
1332 line: self.line,
1333 })
1334 }
1335 }
1336 Ok(())
1337 }
1338
1339 fn root_var_name(&self, node: &SNode) -> Option<String> {
1341 match &node.node {
1342 Node::Identifier(name) => Some(name.clone()),
1343 Node::PropertyAccess { object, .. } => self.root_var_name(object),
1344 Node::SubscriptAccess { object, .. } => self.root_var_name(object),
1345 _ => None,
1346 }
1347 }
1348}
1349
1350impl Compiler {
1351 fn collect_enum_names(nodes: &[SNode], names: &mut std::collections::HashSet<String>) {
1353 for sn in nodes {
1354 match &sn.node {
1355 Node::EnumDecl { name, .. } => {
1356 names.insert(name.clone());
1357 }
1358 Node::Pipeline { body, .. } => {
1359 Self::collect_enum_names(body, names);
1360 }
1361 Node::FnDecl { body, .. } => {
1362 Self::collect_enum_names(body, names);
1363 }
1364 Node::Block(stmts) => {
1365 Self::collect_enum_names(stmts, names);
1366 }
1367 _ => {}
1368 }
1369 }
1370 }
1371}
1372
1373impl Default for Compiler {
1374 fn default() -> Self {
1375 Self::new()
1376 }
1377}
1378
1379#[cfg(test)]
1380mod tests {
1381 use super::*;
1382 use harn_lexer::Lexer;
1383 use harn_parser::Parser;
1384
1385 fn compile_source(source: &str) -> Chunk {
1386 let mut lexer = Lexer::new(source);
1387 let tokens = lexer.tokenize().unwrap();
1388 let mut parser = Parser::new(tokens);
1389 let program = parser.parse().unwrap();
1390 Compiler::new().compile(&program).unwrap()
1391 }
1392
1393 #[test]
1394 fn test_compile_arithmetic() {
1395 let chunk = compile_source("pipeline test(task) { let x = 2 + 3 }");
1396 assert!(!chunk.code.is_empty());
1397 assert!(chunk.constants.contains(&Constant::Int(2)));
1399 assert!(chunk.constants.contains(&Constant::Int(3)));
1400 }
1401
1402 #[test]
1403 fn test_compile_function_call() {
1404 let chunk = compile_source("pipeline test(task) { log(42) }");
1405 let disasm = chunk.disassemble("test");
1406 assert!(disasm.contains("CALL"));
1407 }
1408
1409 #[test]
1410 fn test_compile_if_else() {
1411 let chunk =
1412 compile_source(r#"pipeline test(task) { if true { log("yes") } else { log("no") } }"#);
1413 let disasm = chunk.disassemble("test");
1414 assert!(disasm.contains("JUMP_IF_FALSE"));
1415 assert!(disasm.contains("JUMP"));
1416 }
1417
1418 #[test]
1419 fn test_compile_while() {
1420 let chunk = compile_source("pipeline test(task) { var i = 0\n while i < 5 { i = i + 1 } }");
1421 let disasm = chunk.disassemble("test");
1422 assert!(disasm.contains("JUMP_IF_FALSE"));
1423 assert!(disasm.contains("JUMP"));
1425 }
1426
1427 #[test]
1428 fn test_compile_closure() {
1429 let chunk = compile_source("pipeline test(task) { let f = { x -> x * 2 } }");
1430 assert!(!chunk.functions.is_empty());
1431 assert_eq!(chunk.functions[0].params, vec!["x"]);
1432 }
1433
1434 #[test]
1435 fn test_compile_list() {
1436 let chunk = compile_source("pipeline test(task) { let a = [1, 2, 3] }");
1437 let disasm = chunk.disassemble("test");
1438 assert!(disasm.contains("BUILD_LIST"));
1439 }
1440
1441 #[test]
1442 fn test_compile_dict() {
1443 let chunk = compile_source(r#"pipeline test(task) { let d = {name: "test"} }"#);
1444 let disasm = chunk.disassemble("test");
1445 assert!(disasm.contains("BUILD_DICT"));
1446 }
1447
1448 #[test]
1449 fn test_disassemble() {
1450 let chunk = compile_source("pipeline test(task) { log(2 + 3) }");
1451 let disasm = chunk.disassemble("test");
1452 assert!(disasm.contains("CONSTANT"));
1454 assert!(disasm.contains("ADD"));
1455 assert!(disasm.contains("CALL"));
1456 }
1457}