1use harn_lexer::StringSegment;
2use harn_parser::{BindingPattern, 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 finally_depth: usize,
33}
34
35pub struct Compiler {
37 chunk: Chunk,
38 line: u32,
39 column: u32,
40 enum_names: std::collections::HashSet<String>,
42 interface_methods: std::collections::HashMap<String, Vec<String>>,
44 loop_stack: Vec<LoopContext>,
46 handler_depth: usize,
48 finally_bodies: Vec<Vec<SNode>>,
50 temp_counter: usize,
52}
53
54impl Compiler {
55 pub fn new() -> Self {
56 Self {
57 chunk: Chunk::new(),
58 line: 1,
59 column: 1,
60 enum_names: std::collections::HashSet::new(),
61 interface_methods: std::collections::HashMap::new(),
62 loop_stack: Vec::new(),
63 handler_depth: 0,
64 finally_bodies: Vec::new(),
65 temp_counter: 0,
66 }
67 }
68
69 pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
72 Self::collect_enum_names(program, &mut self.enum_names);
75 self.enum_names.insert("Result".to_string());
77 Self::collect_interface_methods(program, &mut self.interface_methods);
78
79 for sn in program {
81 match &sn.node {
82 Node::ImportDecl { .. } | Node::SelectiveImport { .. } => {
83 self.compile_node(sn)?;
84 }
85 _ => {}
86 }
87 }
88
89 let main = program
91 .iter()
92 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == "default"))
93 .or_else(|| {
94 program
95 .iter()
96 .find(|sn| matches!(&sn.node, Node::Pipeline { .. }))
97 });
98
99 if let Some(sn) = main {
100 if let Node::Pipeline { body, extends, .. } = &sn.node {
101 if let Some(parent_name) = extends {
103 self.compile_parent_pipeline(program, parent_name)?;
104 }
105 self.compile_block(body)?;
106 }
107 } else {
108 let top_level: Vec<&SNode> = program
111 .iter()
112 .filter(|sn| {
113 !matches!(
114 &sn.node,
115 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
116 )
117 })
118 .collect();
119 for sn in &top_level {
120 self.compile_node(sn)?;
121 if Self::produces_value(&sn.node) {
122 self.chunk.emit(Op::Pop, self.line);
123 }
124 }
125 }
126
127 self.chunk.emit(Op::Nil, self.line);
128 self.chunk.emit(Op::Return, self.line);
129 Ok(self.chunk)
130 }
131
132 pub fn compile_named(
134 mut self,
135 program: &[SNode],
136 pipeline_name: &str,
137 ) -> Result<Chunk, CompileError> {
138 Self::collect_enum_names(program, &mut self.enum_names);
139 Self::collect_interface_methods(program, &mut self.interface_methods);
140
141 for sn in program {
142 if matches!(
143 &sn.node,
144 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
145 ) {
146 self.compile_node(sn)?;
147 }
148 }
149
150 let target = program
151 .iter()
152 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == pipeline_name));
153
154 if let Some(sn) = target {
155 if let Node::Pipeline { body, extends, .. } = &sn.node {
156 if let Some(parent_name) = extends {
157 self.compile_parent_pipeline(program, parent_name)?;
158 }
159 self.compile_block(body)?;
160 }
161 }
162
163 self.chunk.emit(Op::Nil, self.line);
164 self.chunk.emit(Op::Return, self.line);
165 Ok(self.chunk)
166 }
167
168 fn compile_parent_pipeline(
170 &mut self,
171 program: &[SNode],
172 parent_name: &str,
173 ) -> Result<(), CompileError> {
174 let parent = program
175 .iter()
176 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == parent_name));
177 if let Some(sn) = parent {
178 if let Node::Pipeline { body, extends, .. } = &sn.node {
179 if let Some(grandparent) = extends {
181 self.compile_parent_pipeline(program, grandparent)?;
182 }
183 for stmt in body {
185 self.compile_node(stmt)?;
186 if Self::produces_value(&stmt.node) {
187 self.chunk.emit(Op::Pop, self.line);
188 }
189 }
190 }
191 }
192 Ok(())
193 }
194
195 fn emit_default_preamble(&mut self, params: &[TypedParam]) -> Result<(), CompileError> {
200 for (i, param) in params.iter().enumerate() {
201 if let Some(default_expr) = ¶m.default_value {
202 self.chunk.emit(Op::GetArgc, self.line);
203 let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
204 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
205 self.chunk.emit(Op::GreaterEqual, self.line);
207 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
208 self.chunk.emit(Op::Pop, self.line);
210 self.compile_node(default_expr)?;
212 let name_idx = self
213 .chunk
214 .add_constant(Constant::String(param.name.clone()));
215 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
216 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
217 self.chunk.patch_jump(skip_jump);
218 self.chunk.emit(Op::Pop, self.line);
220 self.chunk.patch_jump(end_jump);
221 }
222 }
223 Ok(())
224 }
225
226 fn emit_type_checks(&mut self, params: &[TypedParam]) {
230 for param in params {
231 if let Some(type_expr) = ¶m.type_expr {
232 if let harn_parser::TypeExpr::Shape(fields) = type_expr {
234 let spec = Self::shape_to_spec_string(fields);
235 let fn_idx = self
237 .chunk
238 .add_constant(Constant::String("__assert_shape".into()));
239 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
240 let var_idx = self
241 .chunk
242 .add_constant(Constant::String(param.name.clone()));
243 self.chunk.emit_u16(Op::GetVar, var_idx, self.line);
244 let name_idx = self
245 .chunk
246 .add_constant(Constant::String(param.name.clone()));
247 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
248 let spec_idx = self.chunk.add_constant(Constant::String(spec));
249 self.chunk.emit_u16(Op::Constant, spec_idx, self.line);
250 self.chunk.emit_u8(Op::Call, 3, self.line);
251 self.chunk.emit(Op::Pop, self.line);
252 continue;
253 }
254
255 if let harn_parser::TypeExpr::Named(name) = type_expr {
257 if let Some(methods) = self.interface_methods.get(name) {
258 let fn_idx = self
259 .chunk
260 .add_constant(Constant::String("__assert_interface".into()));
261 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
262 let var_idx = self
263 .chunk
264 .add_constant(Constant::String(param.name.clone()));
265 self.chunk.emit_u16(Op::GetVar, var_idx, self.line);
266 let name_idx = self
267 .chunk
268 .add_constant(Constant::String(param.name.clone()));
269 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
270 let iface_idx = self.chunk.add_constant(Constant::String(name.clone()));
271 self.chunk.emit_u16(Op::Constant, iface_idx, self.line);
272 let methods_str = methods.join(",");
273 let methods_idx = self.chunk.add_constant(Constant::String(methods_str));
274 self.chunk.emit_u16(Op::Constant, methods_idx, self.line);
275 self.chunk.emit_u8(Op::Call, 4, self.line);
276 self.chunk.emit(Op::Pop, self.line);
277 continue;
278 }
279 }
280
281 let type_name = Self::type_expr_to_runtime_name(type_expr);
282 if let Some(type_name) = type_name {
283 let var_idx = self
284 .chunk
285 .add_constant(Constant::String(param.name.clone()));
286 let type_idx = self.chunk.add_constant(Constant::String(type_name));
287 self.chunk.emit_u16(Op::CheckType, var_idx, self.line);
288 let hi = (type_idx >> 8) as u8;
290 let lo = type_idx as u8;
291 self.chunk.code.push(hi);
292 self.chunk.code.push(lo);
293 }
294 }
295 }
296 }
297
298 fn shape_to_spec_string(fields: &[harn_parser::ShapeField]) -> String {
301 fields
302 .iter()
303 .map(|f| {
304 let opt = if f.optional { "?" } else { "" };
305 let type_str = Self::type_expr_to_spec(&f.type_expr);
306 format!("{}:{}{}", f.name, opt, type_str)
307 })
308 .collect::<Vec<_>>()
309 .join(",")
310 }
311
312 fn type_expr_to_spec(type_expr: &harn_parser::TypeExpr) -> String {
314 match type_expr {
315 harn_parser::TypeExpr::Named(name) => name.clone(),
316 harn_parser::TypeExpr::Shape(fields) => {
317 let inner = Self::shape_to_spec_string(fields);
318 format!("{{{}}}", inner)
319 }
320 harn_parser::TypeExpr::List(_) => "list".to_string(),
321 harn_parser::TypeExpr::DictType(_, _) => "dict".to_string(),
322 harn_parser::TypeExpr::Union(members) => {
323 members
325 .iter()
326 .map(Self::type_expr_to_spec)
327 .collect::<Vec<_>>()
328 .join("|")
329 }
330 harn_parser::TypeExpr::FnType { .. } => "closure".to_string(),
331 }
332 }
333
334 fn type_expr_to_runtime_name(type_expr: &harn_parser::TypeExpr) -> Option<String> {
336 match type_expr {
337 harn_parser::TypeExpr::Named(name) => match name.as_str() {
338 "int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
339 | "closure" => Some(name.clone()),
340 _ => None, },
342 _ => None, }
344 }
345
346 fn emit_type_name_extra(&mut self, type_name_idx: u16) {
348 let hi = (type_name_idx >> 8) as u8;
349 let lo = type_name_idx as u8;
350 self.chunk.code.push(hi);
351 self.chunk.code.push(lo);
352 self.chunk.lines.push(self.line);
353 self.chunk.columns.push(self.column);
354 self.chunk.lines.push(self.line);
355 self.chunk.columns.push(self.column);
356 }
357
358 fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
360 if body.is_empty() {
361 self.chunk.emit(Op::Nil, self.line);
362 } else {
363 self.compile_block(body)?;
364 if !Self::produces_value(&body.last().unwrap().node) {
365 self.chunk.emit(Op::Nil, self.line);
366 }
367 }
368 Ok(())
369 }
370
371 fn compile_catch_binding(&mut self, error_var: &Option<String>) -> Result<(), CompileError> {
373 if let Some(var_name) = error_var {
374 let idx = self.chunk.add_constant(Constant::String(var_name.clone()));
375 self.chunk.emit_u16(Op::DefVar, idx, self.line);
379 } else {
380 self.chunk.emit(Op::Pop, self.line);
381 }
382 Ok(())
383 }
384
385 fn compile_finally_inline(&mut self, finally_body: &[SNode]) -> Result<(), CompileError> {
387 if !finally_body.is_empty() {
388 self.compile_block(finally_body)?;
389 if Self::produces_value(&finally_body.last().unwrap().node) {
391 self.chunk.emit(Op::Pop, self.line);
392 }
393 }
394 Ok(())
395 }
396
397 fn compile_rethrow_with_finally(&mut self, finally_body: &[SNode]) -> Result<(), CompileError> {
399 self.temp_counter += 1;
401 let temp_name = format!("__finally_err_{}__", self.temp_counter);
402 let err_idx = self.chunk.add_constant(Constant::String(temp_name.clone()));
403 self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
404 self.compile_finally_inline(finally_body)?;
405 let get_idx = self.chunk.add_constant(Constant::String(temp_name));
406 self.chunk.emit_u16(Op::GetVar, get_idx, self.line);
407 self.chunk.emit(Op::Throw, self.line);
408 Ok(())
409 }
410
411 fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
412 for (i, snode) in stmts.iter().enumerate() {
413 self.compile_node(snode)?;
414 let is_last = i == stmts.len() - 1;
415 if is_last {
416 if !Self::produces_value(&snode.node) {
419 self.chunk.emit(Op::Nil, self.line);
420 }
421 } else {
422 if Self::produces_value(&snode.node) {
424 self.chunk.emit(Op::Pop, self.line);
425 }
426 }
427 }
428 Ok(())
429 }
430
431 fn compile_node(&mut self, snode: &SNode) -> Result<(), CompileError> {
432 self.line = snode.span.line as u32;
433 self.column = snode.span.column as u32;
434 self.chunk.set_column(self.column);
435 match &snode.node {
436 Node::IntLiteral(n) => {
437 let idx = self.chunk.add_constant(Constant::Int(*n));
438 self.chunk.emit_u16(Op::Constant, idx, self.line);
439 }
440 Node::FloatLiteral(n) => {
441 let idx = self.chunk.add_constant(Constant::Float(*n));
442 self.chunk.emit_u16(Op::Constant, idx, self.line);
443 }
444 Node::StringLiteral(s) => {
445 let idx = self.chunk.add_constant(Constant::String(s.clone()));
446 self.chunk.emit_u16(Op::Constant, idx, self.line);
447 }
448 Node::BoolLiteral(true) => self.chunk.emit(Op::True, self.line),
449 Node::BoolLiteral(false) => self.chunk.emit(Op::False, self.line),
450 Node::NilLiteral => self.chunk.emit(Op::Nil, self.line),
451 Node::DurationLiteral(ms) => {
452 let idx = self.chunk.add_constant(Constant::Duration(*ms));
453 self.chunk.emit_u16(Op::Constant, idx, self.line);
454 }
455
456 Node::Identifier(name) => {
457 let idx = self.chunk.add_constant(Constant::String(name.clone()));
458 self.chunk.emit_u16(Op::GetVar, idx, self.line);
459 }
460
461 Node::LetBinding { pattern, value, .. } => {
462 self.compile_node(value)?;
463 self.compile_destructuring(pattern, false)?;
464 }
465
466 Node::VarBinding { pattern, value, .. } => {
467 self.compile_node(value)?;
468 self.compile_destructuring(pattern, true)?;
469 }
470
471 Node::Assignment {
472 target, value, op, ..
473 } => {
474 if let Node::Identifier(name) = &target.node {
475 let idx = self.chunk.add_constant(Constant::String(name.clone()));
476 if let Some(op) = op {
477 self.chunk.emit_u16(Op::GetVar, idx, self.line);
478 self.compile_node(value)?;
479 self.emit_compound_op(op)?;
480 self.chunk.emit_u16(Op::SetVar, idx, self.line);
481 } else {
482 self.compile_node(value)?;
483 self.chunk.emit_u16(Op::SetVar, idx, self.line);
484 }
485 } else if let Node::PropertyAccess { object, property } = &target.node {
486 if let Some(var_name) = self.root_var_name(object) {
488 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
489 let prop_idx = self.chunk.add_constant(Constant::String(property.clone()));
490 if let Some(op) = op {
491 self.compile_node(target)?; self.compile_node(value)?;
494 self.emit_compound_op(op)?;
495 } else {
496 self.compile_node(value)?;
497 }
498 self.chunk.emit_u16(Op::SetProperty, prop_idx, self.line);
501 let hi = (var_idx >> 8) as u8;
503 let lo = var_idx as u8;
504 self.chunk.code.push(hi);
505 self.chunk.code.push(lo);
506 self.chunk.lines.push(self.line);
507 self.chunk.columns.push(self.column);
508 self.chunk.lines.push(self.line);
509 self.chunk.columns.push(self.column);
510 }
511 } else if let Node::SubscriptAccess { object, index } = &target.node {
512 if let Some(var_name) = self.root_var_name(object) {
514 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
515 if let Some(op) = op {
516 self.compile_node(target)?;
517 self.compile_node(value)?;
518 self.emit_compound_op(op)?;
519 } else {
520 self.compile_node(value)?;
521 }
522 self.compile_node(index)?;
523 self.chunk.emit_u16(Op::SetSubscript, var_idx, self.line);
524 }
525 }
526 }
527
528 Node::BinaryOp { op, left, right } => {
529 match op.as_str() {
531 "&&" => {
532 self.compile_node(left)?;
533 let jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
534 self.chunk.emit(Op::Pop, self.line);
535 self.compile_node(right)?;
536 self.chunk.patch_jump(jump);
537 self.chunk.emit(Op::Not, self.line);
539 self.chunk.emit(Op::Not, self.line);
540 return Ok(());
541 }
542 "||" => {
543 self.compile_node(left)?;
544 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
545 self.chunk.emit(Op::Pop, self.line);
546 self.compile_node(right)?;
547 self.chunk.patch_jump(jump);
548 self.chunk.emit(Op::Not, self.line);
549 self.chunk.emit(Op::Not, self.line);
550 return Ok(());
551 }
552 "??" => {
553 self.compile_node(left)?;
554 self.chunk.emit(Op::Dup, self.line);
555 self.chunk.emit(Op::Nil, self.line);
557 self.chunk.emit(Op::NotEqual, self.line);
558 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
559 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_node(right)?;
562 let end = self.chunk.emit_jump(Op::Jump, self.line);
563 self.chunk.patch_jump(jump);
564 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end);
566 return Ok(());
567 }
568 "|>" => {
569 self.compile_node(left)?;
570 if contains_pipe_placeholder(right) {
573 let replaced = replace_pipe_placeholder(right);
574 let closure_node = SNode::dummy(Node::Closure {
575 params: vec![TypedParam {
576 name: "__pipe".into(),
577 type_expr: None,
578 default_value: None,
579 }],
580 body: vec![replaced],
581 fn_syntax: false,
582 });
583 self.compile_node(&closure_node)?;
584 } else {
585 self.compile_node(right)?;
586 }
587 self.chunk.emit(Op::Pipe, self.line);
588 return Ok(());
589 }
590 _ => {}
591 }
592
593 self.compile_node(left)?;
594 self.compile_node(right)?;
595 match op.as_str() {
596 "+" => self.chunk.emit(Op::Add, self.line),
597 "-" => self.chunk.emit(Op::Sub, self.line),
598 "*" => self.chunk.emit(Op::Mul, self.line),
599 "/" => self.chunk.emit(Op::Div, self.line),
600 "%" => self.chunk.emit(Op::Mod, self.line),
601 "==" => self.chunk.emit(Op::Equal, self.line),
602 "!=" => self.chunk.emit(Op::NotEqual, self.line),
603 "<" => self.chunk.emit(Op::Less, self.line),
604 ">" => self.chunk.emit(Op::Greater, self.line),
605 "<=" => self.chunk.emit(Op::LessEqual, self.line),
606 ">=" => self.chunk.emit(Op::GreaterEqual, self.line),
607 "in" => self.chunk.emit(Op::Contains, self.line),
608 "not_in" => {
609 self.chunk.emit(Op::Contains, self.line);
610 self.chunk.emit(Op::Not, self.line);
611 }
612 _ => {
613 return Err(CompileError {
614 message: format!("Unknown operator: {op}"),
615 line: self.line,
616 })
617 }
618 }
619 }
620
621 Node::UnaryOp { op, operand } => {
622 self.compile_node(operand)?;
623 match op.as_str() {
624 "-" => self.chunk.emit(Op::Negate, self.line),
625 "!" => self.chunk.emit(Op::Not, self.line),
626 _ => {}
627 }
628 }
629
630 Node::Ternary {
631 condition,
632 true_expr,
633 false_expr,
634 } => {
635 self.compile_node(condition)?;
636 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
637 self.chunk.emit(Op::Pop, self.line);
638 self.compile_node(true_expr)?;
639 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
640 self.chunk.patch_jump(else_jump);
641 self.chunk.emit(Op::Pop, self.line);
642 self.compile_node(false_expr)?;
643 self.chunk.patch_jump(end_jump);
644 }
645
646 Node::FunctionCall { name, args } => {
647 let has_spread = args.iter().any(|a| matches!(&a.node, Node::Spread(_)));
648 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
650 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
651
652 if has_spread {
653 self.chunk.emit_u16(Op::BuildList, 0, self.line);
656 let mut pending = 0u16;
657 for arg in args {
658 if let Node::Spread(inner) = &arg.node {
659 if pending > 0 {
660 self.chunk.emit_u16(Op::BuildList, pending, self.line);
661 self.chunk.emit(Op::Add, self.line);
662 pending = 0;
663 }
664 self.compile_node(inner)?;
665 self.chunk.emit(Op::Dup, self.line);
666 let assert_idx = self
667 .chunk
668 .add_constant(Constant::String("__assert_list".into()));
669 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
670 self.chunk.emit(Op::Swap, self.line);
671 self.chunk.emit_u8(Op::Call, 1, self.line);
672 self.chunk.emit(Op::Pop, self.line);
673 self.chunk.emit(Op::Add, self.line);
674 } else {
675 self.compile_node(arg)?;
676 pending += 1;
677 }
678 }
679 if pending > 0 {
680 self.chunk.emit_u16(Op::BuildList, pending, self.line);
681 self.chunk.emit(Op::Add, self.line);
682 }
683 self.chunk.emit(Op::CallSpread, self.line);
684 } else {
685 for arg in args {
687 self.compile_node(arg)?;
688 }
689 self.chunk.emit_u8(Op::Call, args.len() as u8, self.line);
690 }
691 }
692
693 Node::MethodCall {
694 object,
695 method,
696 args,
697 } => {
698 if let Node::Identifier(name) = &object.node {
700 if self.enum_names.contains(name) {
701 for arg in args {
703 self.compile_node(arg)?;
704 }
705 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
706 let var_idx = self.chunk.add_constant(Constant::String(method.clone()));
707 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
708 let hi = (var_idx >> 8) as u8;
709 let lo = var_idx as u8;
710 self.chunk.code.push(hi);
711 self.chunk.code.push(lo);
712 self.chunk.lines.push(self.line);
713 self.chunk.columns.push(self.column);
714 self.chunk.lines.push(self.line);
715 self.chunk.columns.push(self.column);
716 let fc = args.len() as u16;
717 let fhi = (fc >> 8) as u8;
718 let flo = fc as u8;
719 self.chunk.code.push(fhi);
720 self.chunk.code.push(flo);
721 self.chunk.lines.push(self.line);
722 self.chunk.columns.push(self.column);
723 self.chunk.lines.push(self.line);
724 self.chunk.columns.push(self.column);
725 return Ok(());
726 }
727 }
728 let has_spread = args.iter().any(|a| matches!(&a.node, Node::Spread(_)));
729 self.compile_node(object)?;
730 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
731 if has_spread {
732 self.chunk.emit_u16(Op::BuildList, 0, self.line);
734 let mut pending = 0u16;
735 for arg in args {
736 if let Node::Spread(inner) = &arg.node {
737 if pending > 0 {
738 self.chunk.emit_u16(Op::BuildList, pending, self.line);
739 self.chunk.emit(Op::Add, self.line);
740 pending = 0;
741 }
742 self.compile_node(inner)?;
743 self.chunk.emit(Op::Dup, self.line);
744 let assert_idx = self
745 .chunk
746 .add_constant(Constant::String("__assert_list".into()));
747 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
748 self.chunk.emit(Op::Swap, self.line);
749 self.chunk.emit_u8(Op::Call, 1, self.line);
750 self.chunk.emit(Op::Pop, self.line);
751 self.chunk.emit(Op::Add, self.line);
752 } else {
753 self.compile_node(arg)?;
754 pending += 1;
755 }
756 }
757 if pending > 0 {
758 self.chunk.emit_u16(Op::BuildList, pending, self.line);
759 self.chunk.emit(Op::Add, self.line);
760 }
761 self.chunk
762 .emit_u16(Op::MethodCallSpread, name_idx, self.line);
763 } else {
764 for arg in args {
765 self.compile_node(arg)?;
766 }
767 self.chunk
768 .emit_method_call(name_idx, args.len() as u8, self.line);
769 }
770 }
771
772 Node::OptionalMethodCall {
773 object,
774 method,
775 args,
776 } => {
777 self.compile_node(object)?;
778 for arg in args {
779 self.compile_node(arg)?;
780 }
781 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
782 self.chunk
783 .emit_method_call_opt(name_idx, args.len() as u8, self.line);
784 }
785
786 Node::PropertyAccess { object, property } => {
787 if let Node::Identifier(name) = &object.node {
789 if self.enum_names.contains(name) {
790 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
792 let var_idx = self.chunk.add_constant(Constant::String(property.clone()));
793 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
794 let hi = (var_idx >> 8) as u8;
795 let lo = var_idx as u8;
796 self.chunk.code.push(hi);
797 self.chunk.code.push(lo);
798 self.chunk.lines.push(self.line);
799 self.chunk.columns.push(self.column);
800 self.chunk.lines.push(self.line);
801 self.chunk.columns.push(self.column);
802 self.chunk.code.push(0);
804 self.chunk.code.push(0);
805 self.chunk.lines.push(self.line);
806 self.chunk.columns.push(self.column);
807 self.chunk.lines.push(self.line);
808 self.chunk.columns.push(self.column);
809 return Ok(());
810 }
811 }
812 self.compile_node(object)?;
813 let idx = self.chunk.add_constant(Constant::String(property.clone()));
814 self.chunk.emit_u16(Op::GetProperty, idx, self.line);
815 }
816
817 Node::OptionalPropertyAccess { object, property } => {
818 self.compile_node(object)?;
819 let idx = self.chunk.add_constant(Constant::String(property.clone()));
820 self.chunk.emit_u16(Op::GetPropertyOpt, idx, self.line);
821 }
822
823 Node::SubscriptAccess { object, index } => {
824 self.compile_node(object)?;
825 self.compile_node(index)?;
826 self.chunk.emit(Op::Subscript, self.line);
827 }
828
829 Node::SliceAccess { object, start, end } => {
830 self.compile_node(object)?;
831 if let Some(s) = start {
832 self.compile_node(s)?;
833 } else {
834 self.chunk.emit(Op::Nil, self.line);
835 }
836 if let Some(e) = end {
837 self.compile_node(e)?;
838 } else {
839 self.chunk.emit(Op::Nil, self.line);
840 }
841 self.chunk.emit(Op::Slice, self.line);
842 }
843
844 Node::IfElse {
845 condition,
846 then_body,
847 else_body,
848 } => {
849 self.compile_node(condition)?;
850 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
851 self.chunk.emit(Op::Pop, self.line);
852 self.compile_block(then_body)?;
853 if let Some(else_body) = else_body {
854 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
855 self.chunk.patch_jump(else_jump);
856 self.chunk.emit(Op::Pop, self.line);
857 self.compile_block(else_body)?;
858 self.chunk.patch_jump(end_jump);
859 } else {
860 self.chunk.patch_jump(else_jump);
861 self.chunk.emit(Op::Pop, self.line);
862 self.chunk.emit(Op::Nil, self.line);
863 }
864 }
865
866 Node::WhileLoop { condition, body } => {
867 let loop_start = self.chunk.current_offset();
868 self.loop_stack.push(LoopContext {
869 start_offset: loop_start,
870 break_patches: Vec::new(),
871 has_iterator: false,
872 handler_depth: self.handler_depth,
873 finally_depth: self.finally_bodies.len(),
874 });
875 self.compile_node(condition)?;
876 let exit_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
877 self.chunk.emit(Op::Pop, self.line); for sn in body {
880 self.compile_node(sn)?;
881 if Self::produces_value(&sn.node) {
882 self.chunk.emit(Op::Pop, self.line);
883 }
884 }
885 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
887 self.chunk.patch_jump(exit_jump);
888 self.chunk.emit(Op::Pop, self.line); let ctx = self.loop_stack.pop().unwrap();
891 for patch_pos in ctx.break_patches {
892 self.chunk.patch_jump(patch_pos);
893 }
894 self.chunk.emit(Op::Nil, self.line);
895 }
896
897 Node::ForIn {
898 pattern,
899 iterable,
900 body,
901 } => {
902 self.compile_node(iterable)?;
904 self.chunk.emit(Op::IterInit, self.line);
906 let loop_start = self.chunk.current_offset();
907 self.loop_stack.push(LoopContext {
908 start_offset: loop_start,
909 break_patches: Vec::new(),
910 has_iterator: true,
911 handler_depth: self.handler_depth,
912 finally_depth: self.finally_bodies.len(),
913 });
914 let exit_jump_pos = self.chunk.emit_jump(Op::IterNext, self.line);
916 self.compile_destructuring(pattern, true)?;
918 for sn in body {
920 self.compile_node(sn)?;
921 if Self::produces_value(&sn.node) {
922 self.chunk.emit(Op::Pop, self.line);
923 }
924 }
925 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
927 self.chunk.patch_jump(exit_jump_pos);
928 let ctx = self.loop_stack.pop().unwrap();
930 for patch_pos in ctx.break_patches {
931 self.chunk.patch_jump(patch_pos);
932 }
933 self.chunk.emit(Op::Nil, self.line);
935 }
936
937 Node::ReturnStmt { value } => {
938 let has_pending_finally = !self.finally_bodies.is_empty();
939
940 if has_pending_finally {
941 if let Some(val) = value {
944 self.compile_node(val)?;
945 } else {
946 self.chunk.emit(Op::Nil, self.line);
947 }
948 self.temp_counter += 1;
949 let temp_name = format!("__return_val_{}__", self.temp_counter);
950 let save_idx = self.chunk.add_constant(Constant::String(temp_name.clone()));
951 self.chunk.emit_u16(Op::DefVar, save_idx, self.line);
952 let finallys: Vec<_> = self.finally_bodies.iter().rev().cloned().collect();
954 for fb in &finallys {
955 self.compile_finally_inline(fb)?;
956 }
957 let restore_idx = self.chunk.add_constant(Constant::String(temp_name));
958 self.chunk.emit_u16(Op::GetVar, restore_idx, self.line);
959 self.chunk.emit(Op::Return, self.line);
960 } else {
961 if let Some(val) = value {
963 if let Node::FunctionCall { name, args } = &val.node {
964 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
965 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
966 for arg in args {
967 self.compile_node(arg)?;
968 }
969 self.chunk
970 .emit_u8(Op::TailCall, args.len() as u8, self.line);
971 } else if let Node::BinaryOp { op, left, right } = &val.node {
972 if op == "|>" {
973 self.compile_node(left)?;
974 self.compile_node(right)?;
975 self.chunk.emit(Op::Swap, self.line);
976 self.chunk.emit_u8(Op::TailCall, 1, self.line);
977 } else {
978 self.compile_node(val)?;
979 }
980 } else {
981 self.compile_node(val)?;
982 }
983 } else {
984 self.chunk.emit(Op::Nil, self.line);
985 }
986 self.chunk.emit(Op::Return, self.line);
987 }
988 }
989
990 Node::BreakStmt => {
991 if self.loop_stack.is_empty() {
992 return Err(CompileError {
993 message: "break outside of loop".to_string(),
994 line: self.line,
995 });
996 }
997 let ctx = self.loop_stack.last().unwrap();
999 let finally_depth = ctx.finally_depth;
1000 let handler_depth = ctx.handler_depth;
1001 let has_iterator = ctx.has_iterator;
1002 for _ in handler_depth..self.handler_depth {
1004 self.chunk.emit(Op::PopHandler, self.line);
1005 }
1006 if self.finally_bodies.len() > finally_depth {
1008 let finallys: Vec<_> = self.finally_bodies[finally_depth..]
1009 .iter()
1010 .rev()
1011 .cloned()
1012 .collect();
1013 for fb in &finallys {
1014 self.compile_finally_inline(fb)?;
1015 }
1016 }
1017 if has_iterator {
1018 self.chunk.emit(Op::PopIterator, self.line);
1019 }
1020 let patch = self.chunk.emit_jump(Op::Jump, self.line);
1021 self.loop_stack
1022 .last_mut()
1023 .unwrap()
1024 .break_patches
1025 .push(patch);
1026 }
1027
1028 Node::ContinueStmt => {
1029 if self.loop_stack.is_empty() {
1030 return Err(CompileError {
1031 message: "continue outside of loop".to_string(),
1032 line: self.line,
1033 });
1034 }
1035 let ctx = self.loop_stack.last().unwrap();
1036 let finally_depth = ctx.finally_depth;
1037 let handler_depth = ctx.handler_depth;
1038 let loop_start = ctx.start_offset;
1039 for _ in handler_depth..self.handler_depth {
1040 self.chunk.emit(Op::PopHandler, self.line);
1041 }
1042 if self.finally_bodies.len() > finally_depth {
1043 let finallys: Vec<_> = self.finally_bodies[finally_depth..]
1044 .iter()
1045 .rev()
1046 .cloned()
1047 .collect();
1048 for fb in &finallys {
1049 self.compile_finally_inline(fb)?;
1050 }
1051 }
1052 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
1053 }
1054
1055 Node::ListLiteral(elements) => {
1056 let has_spread = elements.iter().any(|e| matches!(&e.node, Node::Spread(_)));
1057 if !has_spread {
1058 for el in elements {
1059 self.compile_node(el)?;
1060 }
1061 self.chunk
1062 .emit_u16(Op::BuildList, elements.len() as u16, self.line);
1063 } else {
1064 self.chunk.emit_u16(Op::BuildList, 0, self.line);
1067 let mut pending = 0u16;
1068 for el in elements {
1069 if let Node::Spread(inner) = &el.node {
1070 if pending > 0 {
1072 self.chunk.emit_u16(Op::BuildList, pending, self.line);
1073 self.chunk.emit(Op::Add, self.line);
1075 pending = 0;
1076 }
1077 self.compile_node(inner)?;
1079 self.chunk.emit(Op::Dup, self.line);
1080 let assert_idx = self
1081 .chunk
1082 .add_constant(Constant::String("__assert_list".into()));
1083 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1084 self.chunk.emit(Op::Swap, self.line);
1085 self.chunk.emit_u8(Op::Call, 1, self.line);
1086 self.chunk.emit(Op::Pop, self.line);
1087 self.chunk.emit(Op::Add, self.line);
1088 } else {
1089 self.compile_node(el)?;
1090 pending += 1;
1091 }
1092 }
1093 if pending > 0 {
1094 self.chunk.emit_u16(Op::BuildList, pending, self.line);
1095 self.chunk.emit(Op::Add, self.line);
1096 }
1097 }
1098 }
1099
1100 Node::DictLiteral(entries) => {
1101 let has_spread = entries
1102 .iter()
1103 .any(|e| matches!(&e.value.node, Node::Spread(_)));
1104 if !has_spread {
1105 for entry in entries {
1106 self.compile_node(&entry.key)?;
1107 self.compile_node(&entry.value)?;
1108 }
1109 self.chunk
1110 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
1111 } else {
1112 self.chunk.emit_u16(Op::BuildDict, 0, self.line);
1114 let mut pending = 0u16;
1115 for entry in entries {
1116 if let Node::Spread(inner) = &entry.value.node {
1117 if pending > 0 {
1119 self.chunk.emit_u16(Op::BuildDict, pending, self.line);
1120 self.chunk.emit(Op::Add, self.line);
1121 pending = 0;
1122 }
1123 self.compile_node(inner)?;
1125 self.chunk.emit(Op::Dup, self.line);
1126 let assert_idx = self
1127 .chunk
1128 .add_constant(Constant::String("__assert_dict".into()));
1129 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1130 self.chunk.emit(Op::Swap, self.line);
1131 self.chunk.emit_u8(Op::Call, 1, self.line);
1132 self.chunk.emit(Op::Pop, self.line);
1133 self.chunk.emit(Op::Add, self.line);
1134 } else {
1135 self.compile_node(&entry.key)?;
1136 self.compile_node(&entry.value)?;
1137 pending += 1;
1138 }
1139 }
1140 if pending > 0 {
1141 self.chunk.emit_u16(Op::BuildDict, pending, self.line);
1142 self.chunk.emit(Op::Add, self.line);
1143 }
1144 }
1145 }
1146
1147 Node::InterpolatedString(segments) => {
1148 let mut part_count = 0u16;
1149 for seg in segments {
1150 match seg {
1151 StringSegment::Literal(s) => {
1152 let idx = self.chunk.add_constant(Constant::String(s.clone()));
1153 self.chunk.emit_u16(Op::Constant, idx, self.line);
1154 part_count += 1;
1155 }
1156 StringSegment::Expression(expr_str) => {
1157 let mut lexer = harn_lexer::Lexer::new(expr_str);
1159 if let Ok(tokens) = lexer.tokenize() {
1160 let mut parser = harn_parser::Parser::new(tokens);
1161 if let Ok(snode) = parser.parse_single_expression() {
1162 self.compile_node(&snode)?;
1163 let to_str = self
1165 .chunk
1166 .add_constant(Constant::String("to_string".into()));
1167 self.chunk.emit_u16(Op::Constant, to_str, self.line);
1168 self.chunk.emit(Op::Swap, self.line);
1169 self.chunk.emit_u8(Op::Call, 1, self.line);
1170 part_count += 1;
1171 } else {
1172 let idx =
1174 self.chunk.add_constant(Constant::String(expr_str.clone()));
1175 self.chunk.emit_u16(Op::Constant, idx, self.line);
1176 part_count += 1;
1177 }
1178 }
1179 }
1180 }
1181 }
1182 if part_count > 1 {
1183 self.chunk.emit_u16(Op::Concat, part_count, self.line);
1184 }
1185 }
1186
1187 Node::FnDecl {
1188 name, params, body, ..
1189 } => {
1190 let mut fn_compiler = Compiler::new();
1192 fn_compiler.enum_names = self.enum_names.clone();
1193 fn_compiler.emit_default_preamble(params)?;
1194 fn_compiler.emit_type_checks(params);
1195 let is_gen = body_contains_yield(body);
1196 fn_compiler.compile_block(body)?;
1197 fn_compiler.chunk.emit(Op::Nil, self.line);
1198 fn_compiler.chunk.emit(Op::Return, self.line);
1199
1200 let func = CompiledFunction {
1201 name: name.clone(),
1202 params: TypedParam::names(params),
1203 default_start: TypedParam::default_start(params),
1204 chunk: fn_compiler.chunk,
1205 is_generator: is_gen,
1206 };
1207 let fn_idx = self.chunk.functions.len();
1208 self.chunk.functions.push(func);
1209
1210 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1211 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1212 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1213 }
1214
1215 Node::Closure { params, body, .. } => {
1216 let mut fn_compiler = Compiler::new();
1217 fn_compiler.enum_names = self.enum_names.clone();
1218 fn_compiler.emit_default_preamble(params)?;
1219 fn_compiler.emit_type_checks(params);
1220 let is_gen = body_contains_yield(body);
1221 fn_compiler.compile_block(body)?;
1222 fn_compiler.chunk.emit(Op::Return, self.line);
1224
1225 let func = CompiledFunction {
1226 name: "<closure>".to_string(),
1227 params: TypedParam::names(params),
1228 default_start: TypedParam::default_start(params),
1229 chunk: fn_compiler.chunk,
1230 is_generator: is_gen,
1231 };
1232 let fn_idx = self.chunk.functions.len();
1233 self.chunk.functions.push(func);
1234
1235 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1236 }
1237
1238 Node::ThrowStmt { value } => {
1239 self.compile_node(value)?;
1240 self.chunk.emit(Op::Throw, self.line);
1241 }
1242
1243 Node::MatchExpr { value, arms } => {
1244 self.compile_node(value)?;
1245 let mut end_jumps = Vec::new();
1246 for arm in arms {
1247 match &arm.pattern.node {
1248 Node::Identifier(name) if name == "_" => {
1250 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1252 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1253 }
1254 Node::EnumConstruct {
1256 enum_name,
1257 variant,
1258 args: pat_args,
1259 } => {
1260 self.chunk.emit(Op::Dup, self.line);
1262 let en_idx =
1263 self.chunk.add_constant(Constant::String(enum_name.clone()));
1264 let vn_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1265 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1266 let hi = (vn_idx >> 8) as u8;
1267 let lo = vn_idx as u8;
1268 self.chunk.code.push(hi);
1269 self.chunk.code.push(lo);
1270 self.chunk.lines.push(self.line);
1271 self.chunk.columns.push(self.column);
1272 self.chunk.lines.push(self.line);
1273 self.chunk.columns.push(self.column);
1274 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1276 self.chunk.emit(Op::Pop, self.line); for (i, pat_arg) in pat_args.iter().enumerate() {
1281 if let Node::Identifier(binding_name) = &pat_arg.node {
1282 self.chunk.emit(Op::Dup, self.line);
1284 let fields_idx = self
1285 .chunk
1286 .add_constant(Constant::String("fields".to_string()));
1287 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
1288 let idx_const =
1289 self.chunk.add_constant(Constant::Int(i as i64));
1290 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1291 self.chunk.emit(Op::Subscript, self.line);
1292 let name_idx = self
1293 .chunk
1294 .add_constant(Constant::String(binding_name.clone()));
1295 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1296 }
1297 }
1298
1299 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1301 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1302 self.chunk.patch_jump(skip);
1303 self.chunk.emit(Op::Pop, self.line); }
1305 Node::PropertyAccess { object, property } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
1307 {
1308 let enum_name = if let Node::Identifier(n) = &object.node {
1309 n.clone()
1310 } else {
1311 unreachable!()
1312 };
1313 self.chunk.emit(Op::Dup, self.line);
1314 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
1315 let vn_idx =
1316 self.chunk.add_constant(Constant::String(property.clone()));
1317 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1318 let hi = (vn_idx >> 8) as u8;
1319 let lo = vn_idx as u8;
1320 self.chunk.code.push(hi);
1321 self.chunk.code.push(lo);
1322 self.chunk.lines.push(self.line);
1323 self.chunk.columns.push(self.column);
1324 self.chunk.lines.push(self.line);
1325 self.chunk.columns.push(self.column);
1326 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1327 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1330 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1331 self.chunk.patch_jump(skip);
1332 self.chunk.emit(Op::Pop, self.line); }
1334 Node::MethodCall {
1337 object,
1338 method,
1339 args: pat_args,
1340 } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
1341 {
1342 let enum_name = if let Node::Identifier(n) = &object.node {
1343 n.clone()
1344 } else {
1345 unreachable!()
1346 };
1347 self.chunk.emit(Op::Dup, self.line);
1349 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
1350 let vn_idx = self.chunk.add_constant(Constant::String(method.clone()));
1351 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1352 let hi = (vn_idx >> 8) as u8;
1353 let lo = vn_idx as u8;
1354 self.chunk.code.push(hi);
1355 self.chunk.code.push(lo);
1356 self.chunk.lines.push(self.line);
1357 self.chunk.columns.push(self.column);
1358 self.chunk.lines.push(self.line);
1359 self.chunk.columns.push(self.column);
1360 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1361 self.chunk.emit(Op::Pop, self.line); for (i, pat_arg) in pat_args.iter().enumerate() {
1365 if let Node::Identifier(binding_name) = &pat_arg.node {
1366 self.chunk.emit(Op::Dup, self.line);
1367 let fields_idx = self
1368 .chunk
1369 .add_constant(Constant::String("fields".to_string()));
1370 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
1371 let idx_const =
1372 self.chunk.add_constant(Constant::Int(i as i64));
1373 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1374 self.chunk.emit(Op::Subscript, self.line);
1375 let name_idx = self
1376 .chunk
1377 .add_constant(Constant::String(binding_name.clone()));
1378 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1379 }
1380 }
1381
1382 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1384 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1385 self.chunk.patch_jump(skip);
1386 self.chunk.emit(Op::Pop, self.line); }
1388 Node::Identifier(name) => {
1390 self.chunk.emit(Op::Dup, self.line); let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1393 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1394 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1396 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1397 }
1398 Node::DictLiteral(entries)
1400 if entries
1401 .iter()
1402 .all(|e| matches!(&e.key.node, Node::StringLiteral(_))) =>
1403 {
1404 self.chunk.emit(Op::Dup, self.line);
1406 let typeof_idx =
1407 self.chunk.add_constant(Constant::String("type_of".into()));
1408 self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
1409 self.chunk.emit(Op::Swap, self.line);
1410 self.chunk.emit_u8(Op::Call, 1, self.line);
1411 let dict_str = self.chunk.add_constant(Constant::String("dict".into()));
1412 self.chunk.emit_u16(Op::Constant, dict_str, self.line);
1413 self.chunk.emit(Op::Equal, self.line);
1414 let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1415 self.chunk.emit(Op::Pop, self.line); let mut constraint_skips = Vec::new();
1419 let mut bindings = Vec::new();
1420 for entry in entries {
1421 if let Node::StringLiteral(key) = &entry.key.node {
1422 match &entry.value.node {
1423 Node::StringLiteral(_)
1425 | Node::IntLiteral(_)
1426 | Node::FloatLiteral(_)
1427 | Node::BoolLiteral(_)
1428 | Node::NilLiteral => {
1429 self.chunk.emit(Op::Dup, self.line);
1430 let key_idx = self
1431 .chunk
1432 .add_constant(Constant::String(key.clone()));
1433 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1434 self.chunk.emit(Op::Subscript, self.line);
1435 self.compile_node(&entry.value)?;
1436 self.chunk.emit(Op::Equal, self.line);
1437 let skip =
1438 self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1439 self.chunk.emit(Op::Pop, self.line); constraint_skips.push(skip);
1441 }
1442 Node::Identifier(binding) => {
1444 bindings.push((key.clone(), binding.clone()));
1445 }
1446 _ => {
1447 self.chunk.emit(Op::Dup, self.line);
1449 let key_idx = self
1450 .chunk
1451 .add_constant(Constant::String(key.clone()));
1452 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1453 self.chunk.emit(Op::Subscript, self.line);
1454 self.compile_node(&entry.value)?;
1455 self.chunk.emit(Op::Equal, self.line);
1456 let skip =
1457 self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1458 self.chunk.emit(Op::Pop, self.line);
1459 constraint_skips.push(skip);
1460 }
1461 }
1462 }
1463 }
1464
1465 for (key, binding) in &bindings {
1467 self.chunk.emit(Op::Dup, self.line);
1468 let key_idx =
1469 self.chunk.add_constant(Constant::String(key.clone()));
1470 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1471 self.chunk.emit(Op::Subscript, self.line);
1472 let name_idx =
1473 self.chunk.add_constant(Constant::String(binding.clone()));
1474 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1475 }
1476
1477 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1479 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1480
1481 let fail_target = self.chunk.code.len();
1483 self.chunk.emit(Op::Pop, self.line); for skip in constraint_skips {
1486 self.chunk.patch_jump_to(skip, fail_target);
1487 }
1488 self.chunk.patch_jump_to(skip_type, fail_target);
1489 }
1490 Node::ListLiteral(elements) => {
1492 self.chunk.emit(Op::Dup, self.line);
1494 let typeof_idx =
1495 self.chunk.add_constant(Constant::String("type_of".into()));
1496 self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
1497 self.chunk.emit(Op::Swap, self.line);
1498 self.chunk.emit_u8(Op::Call, 1, self.line);
1499 let list_str = self.chunk.add_constant(Constant::String("list".into()));
1500 self.chunk.emit_u16(Op::Constant, list_str, self.line);
1501 self.chunk.emit(Op::Equal, self.line);
1502 let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1503 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Dup, self.line);
1507 let len_idx = self.chunk.add_constant(Constant::String("len".into()));
1508 self.chunk.emit_u16(Op::Constant, len_idx, self.line);
1509 self.chunk.emit(Op::Swap, self.line);
1510 self.chunk.emit_u8(Op::Call, 1, self.line);
1511 let count = self
1512 .chunk
1513 .add_constant(Constant::Int(elements.len() as i64));
1514 self.chunk.emit_u16(Op::Constant, count, self.line);
1515 self.chunk.emit(Op::GreaterEqual, self.line);
1516 let skip_len = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1517 self.chunk.emit(Op::Pop, self.line); let mut constraint_skips = Vec::new();
1521 let mut bindings = Vec::new();
1522 for (i, elem) in elements.iter().enumerate() {
1523 match &elem.node {
1524 Node::Identifier(name) if name != "_" => {
1525 bindings.push((i, name.clone()));
1526 }
1527 Node::Identifier(_) => {} _ => {
1530 self.chunk.emit(Op::Dup, self.line);
1531 let idx_const =
1532 self.chunk.add_constant(Constant::Int(i as i64));
1533 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1534 self.chunk.emit(Op::Subscript, self.line);
1535 self.compile_node(elem)?;
1536 self.chunk.emit(Op::Equal, self.line);
1537 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1538 self.chunk.emit(Op::Pop, self.line);
1539 constraint_skips.push(skip);
1540 }
1541 }
1542 }
1543
1544 for (i, name) in &bindings {
1546 self.chunk.emit(Op::Dup, self.line);
1547 let idx_const = self.chunk.add_constant(Constant::Int(*i as i64));
1548 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1549 self.chunk.emit(Op::Subscript, self.line);
1550 let name_idx =
1551 self.chunk.add_constant(Constant::String(name.clone()));
1552 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1553 }
1554
1555 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1557 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1558
1559 let fail_target = self.chunk.code.len();
1561 self.chunk.emit(Op::Pop, self.line); for skip in constraint_skips {
1563 self.chunk.patch_jump_to(skip, fail_target);
1564 }
1565 self.chunk.patch_jump_to(skip_len, fail_target);
1566 self.chunk.patch_jump_to(skip_type, fail_target);
1567 }
1568 _ => {
1570 self.chunk.emit(Op::Dup, self.line);
1571 self.compile_node(&arm.pattern)?;
1572 self.chunk.emit(Op::Equal, self.line);
1573 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1574 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1577 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1578 self.chunk.patch_jump(skip);
1579 self.chunk.emit(Op::Pop, self.line); }
1581 }
1582 }
1583 self.chunk.emit(Op::Pop, self.line);
1585 self.chunk.emit(Op::Nil, self.line);
1586 for j in end_jumps {
1587 self.chunk.patch_jump(j);
1588 }
1589 }
1590
1591 Node::RangeExpr {
1592 start,
1593 end,
1594 inclusive,
1595 } => {
1596 let name_idx = self
1598 .chunk
1599 .add_constant(Constant::String("__range__".to_string()));
1600 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
1601 self.compile_node(start)?;
1602 self.compile_node(end)?;
1603 if *inclusive {
1604 self.chunk.emit(Op::True, self.line);
1605 } else {
1606 self.chunk.emit(Op::False, self.line);
1607 }
1608 self.chunk.emit_u8(Op::Call, 3, self.line);
1609 }
1610
1611 Node::GuardStmt {
1612 condition,
1613 else_body,
1614 } => {
1615 self.compile_node(condition)?;
1618 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
1619 self.chunk.emit(Op::Pop, self.line); self.compile_block(else_body)?;
1622 if !else_body.is_empty() && Self::produces_value(&else_body.last().unwrap().node) {
1624 self.chunk.emit(Op::Pop, self.line);
1625 }
1626 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1627 self.chunk.patch_jump(skip_jump);
1628 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end_jump);
1630 self.chunk.emit(Op::Nil, self.line);
1631 }
1632
1633 Node::RequireStmt { condition, message } => {
1634 self.compile_node(condition)?;
1635 let ok_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
1636 self.chunk.emit(Op::Pop, self.line);
1637 if let Some(message) = message {
1638 self.compile_node(message)?;
1639 } else {
1640 let idx = self
1641 .chunk
1642 .add_constant(Constant::String("require condition failed".to_string()));
1643 self.chunk.emit_u16(Op::Constant, idx, self.line);
1644 }
1645 self.chunk.emit(Op::Throw, self.line);
1646 self.chunk.patch_jump(ok_jump);
1647 self.chunk.emit(Op::Pop, self.line);
1648 }
1649
1650 Node::Block(stmts) => {
1651 if stmts.is_empty() {
1652 self.chunk.emit(Op::Nil, self.line);
1653 } else {
1654 self.compile_block(stmts)?;
1655 }
1656 }
1657
1658 Node::DeadlineBlock { duration, body } => {
1659 self.compile_node(duration)?;
1660 self.chunk.emit(Op::DeadlineSetup, self.line);
1661 if body.is_empty() {
1662 self.chunk.emit(Op::Nil, self.line);
1663 } else {
1664 self.compile_block(body)?;
1665 }
1666 self.chunk.emit(Op::DeadlineEnd, self.line);
1667 }
1668
1669 Node::MutexBlock { body } => {
1670 if body.is_empty() {
1672 self.chunk.emit(Op::Nil, self.line);
1673 } else {
1674 for sn in body {
1677 self.compile_node(sn)?;
1678 if Self::produces_value(&sn.node) {
1679 self.chunk.emit(Op::Pop, self.line);
1680 }
1681 }
1682 self.chunk.emit(Op::Nil, self.line);
1683 }
1684 }
1685
1686 Node::YieldExpr { value } => {
1687 if let Some(val) = value {
1688 self.compile_node(val)?;
1689 } else {
1690 self.chunk.emit(Op::Nil, self.line);
1691 }
1692 self.chunk.emit(Op::Yield, self.line);
1693 }
1694
1695 Node::AskExpr { fields } => {
1696 for entry in fields {
1699 self.compile_node(&entry.key)?;
1700 self.compile_node(&entry.value)?;
1701 }
1702 self.chunk
1703 .emit_u16(Op::BuildDict, fields.len() as u16, self.line);
1704 }
1705
1706 Node::EnumConstruct {
1707 enum_name,
1708 variant,
1709 args,
1710 } => {
1711 for arg in args {
1713 self.compile_node(arg)?;
1714 }
1715 let enum_idx = self.chunk.add_constant(Constant::String(enum_name.clone()));
1716 let var_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1717 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
1719 let hi = (var_idx >> 8) as u8;
1720 let lo = var_idx as u8;
1721 self.chunk.code.push(hi);
1722 self.chunk.code.push(lo);
1723 self.chunk.lines.push(self.line);
1724 self.chunk.columns.push(self.column);
1725 self.chunk.lines.push(self.line);
1726 self.chunk.columns.push(self.column);
1727 let fc = args.len() as u16;
1728 let fhi = (fc >> 8) as u8;
1729 let flo = fc as u8;
1730 self.chunk.code.push(fhi);
1731 self.chunk.code.push(flo);
1732 self.chunk.lines.push(self.line);
1733 self.chunk.columns.push(self.column);
1734 self.chunk.lines.push(self.line);
1735 self.chunk.columns.push(self.column);
1736 }
1737
1738 Node::StructConstruct {
1739 struct_name,
1740 fields,
1741 } => {
1742 let struct_key = self
1744 .chunk
1745 .add_constant(Constant::String("__struct__".to_string()));
1746 let struct_val = self
1747 .chunk
1748 .add_constant(Constant::String(struct_name.clone()));
1749 self.chunk.emit_u16(Op::Constant, struct_key, self.line);
1750 self.chunk.emit_u16(Op::Constant, struct_val, self.line);
1751
1752 for entry in fields {
1753 self.compile_node(&entry.key)?;
1754 self.compile_node(&entry.value)?;
1755 }
1756 self.chunk
1757 .emit_u16(Op::BuildDict, (fields.len() + 1) as u16, self.line);
1758 }
1759
1760 Node::ImportDecl { path } => {
1761 let idx = self.chunk.add_constant(Constant::String(path.clone()));
1762 self.chunk.emit_u16(Op::Import, idx, self.line);
1763 }
1764
1765 Node::SelectiveImport { names, path } => {
1766 let path_idx = self.chunk.add_constant(Constant::String(path.clone()));
1767 let names_str = names.join(",");
1768 let names_idx = self.chunk.add_constant(Constant::String(names_str));
1769 self.chunk
1770 .emit_u16(Op::SelectiveImport, path_idx, self.line);
1771 let hi = (names_idx >> 8) as u8;
1772 let lo = names_idx as u8;
1773 self.chunk.code.push(hi);
1774 self.chunk.code.push(lo);
1775 self.chunk.lines.push(self.line);
1776 self.chunk.columns.push(self.column);
1777 self.chunk.lines.push(self.line);
1778 self.chunk.columns.push(self.column);
1779 }
1780
1781 Node::TryOperator { operand } => {
1782 self.compile_node(operand)?;
1783 self.chunk.emit(Op::TryUnwrap, self.line);
1784 }
1785
1786 Node::ImplBlock { type_name, methods } => {
1787 for method_sn in methods {
1790 if let Node::FnDecl {
1791 name, params, body, ..
1792 } = &method_sn.node
1793 {
1794 let key_idx = self.chunk.add_constant(Constant::String(name.clone()));
1796 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1797
1798 let mut fn_compiler = Compiler::new();
1800 fn_compiler.enum_names = self.enum_names.clone();
1801 fn_compiler.emit_default_preamble(params)?;
1802 fn_compiler.emit_type_checks(params);
1803 fn_compiler.compile_block(body)?;
1804 fn_compiler.chunk.emit(Op::Nil, self.line);
1805 fn_compiler.chunk.emit(Op::Return, self.line);
1806
1807 let func = CompiledFunction {
1808 name: format!("{}.{}", type_name, name),
1809 params: TypedParam::names(params),
1810 default_start: TypedParam::default_start(params),
1811 chunk: fn_compiler.chunk,
1812 is_generator: false,
1813 };
1814 let fn_idx = self.chunk.functions.len();
1815 self.chunk.functions.push(func);
1816 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1817 }
1818 }
1819 let method_count = methods
1820 .iter()
1821 .filter(|m| matches!(m.node, Node::FnDecl { .. }))
1822 .count();
1823 self.chunk
1824 .emit_u16(Op::BuildDict, method_count as u16, self.line);
1825 let impl_name = format!("__impl_{}", type_name);
1826 let name_idx = self.chunk.add_constant(Constant::String(impl_name));
1827 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1828 }
1829
1830 Node::StructDecl { name, .. } => {
1831 let mut fn_compiler = Compiler::new();
1833 fn_compiler.enum_names = self.enum_names.clone();
1834 let params = vec![TypedParam::untyped("__fields")];
1835 fn_compiler.emit_default_preamble(¶ms)?;
1836
1837 let make_idx = fn_compiler
1839 .chunk
1840 .add_constant(Constant::String("__make_struct".into()));
1841 fn_compiler
1842 .chunk
1843 .emit_u16(Op::Constant, make_idx, self.line);
1844 let sname_idx = fn_compiler
1845 .chunk
1846 .add_constant(Constant::String(name.clone()));
1847 fn_compiler
1848 .chunk
1849 .emit_u16(Op::Constant, sname_idx, self.line);
1850 let fields_idx = fn_compiler
1851 .chunk
1852 .add_constant(Constant::String("__fields".into()));
1853 fn_compiler
1854 .chunk
1855 .emit_u16(Op::GetVar, fields_idx, self.line);
1856 fn_compiler.chunk.emit_u8(Op::Call, 2, self.line);
1857 fn_compiler.chunk.emit(Op::Return, self.line);
1858
1859 let func = CompiledFunction {
1860 name: name.clone(),
1861 params: TypedParam::names(¶ms),
1862 default_start: None,
1863 chunk: fn_compiler.chunk,
1864 is_generator: false,
1865 };
1866 let fn_idx = self.chunk.functions.len();
1867 self.chunk.functions.push(func);
1868 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1869 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1870 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1871 }
1872
1873 Node::Pipeline { .. }
1875 | Node::OverrideDecl { .. }
1876 | Node::TypeDecl { .. }
1877 | Node::EnumDecl { .. }
1878 | Node::InterfaceDecl { .. } => {
1879 self.chunk.emit(Op::Nil, self.line);
1880 }
1881
1882 Node::TryCatch {
1883 body,
1884 error_var,
1885 error_type,
1886 catch_body,
1887 finally_body,
1888 } => {
1889 let type_name = error_type.as_ref().and_then(|te| {
1891 if let harn_parser::TypeExpr::Named(name) = te {
1892 Some(name.clone())
1893 } else {
1894 None
1895 }
1896 });
1897
1898 let type_name_idx = if let Some(ref tn) = type_name {
1899 self.chunk.add_constant(Constant::String(tn.clone()))
1900 } else {
1901 self.chunk.add_constant(Constant::String(String::new()))
1902 };
1903
1904 let has_catch = !catch_body.is_empty() || error_var.is_some();
1905 let has_finally = finally_body.is_some();
1906
1907 if has_catch && has_finally {
1908 let finally_body = finally_body.as_ref().unwrap();
1910
1911 self.finally_bodies.push(finally_body.clone());
1913
1914 self.handler_depth += 1;
1916 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1917 self.emit_type_name_extra(type_name_idx);
1918
1919 self.compile_try_body(body)?;
1921
1922 self.handler_depth -= 1;
1924 self.chunk.emit(Op::PopHandler, self.line);
1925 self.compile_finally_inline(finally_body)?;
1926 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1927
1928 self.chunk.patch_jump(catch_jump);
1930 self.compile_catch_binding(error_var)?;
1931
1932 self.handler_depth += 1;
1934 let rethrow_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1935 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1936 self.emit_type_name_extra(empty_type);
1937
1938 self.compile_try_body(catch_body)?;
1940
1941 self.handler_depth -= 1;
1943 self.chunk.emit(Op::PopHandler, self.line);
1944 self.compile_finally_inline(finally_body)?;
1945 let end_jump2 = self.chunk.emit_jump(Op::Jump, self.line);
1946
1947 self.chunk.patch_jump(rethrow_jump);
1949 self.compile_rethrow_with_finally(finally_body)?;
1950
1951 self.chunk.patch_jump(end_jump);
1952 self.chunk.patch_jump(end_jump2);
1953
1954 self.finally_bodies.pop();
1955 } else if has_finally {
1956 let finally_body = finally_body.as_ref().unwrap();
1958
1959 self.finally_bodies.push(finally_body.clone());
1960
1961 self.handler_depth += 1;
1963 let error_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1964 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1965 self.emit_type_name_extra(empty_type);
1966
1967 self.compile_try_body(body)?;
1969
1970 self.handler_depth -= 1;
1972 self.chunk.emit(Op::PopHandler, self.line);
1973 self.compile_finally_inline(finally_body)?;
1974 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1975
1976 self.chunk.patch_jump(error_jump);
1978 self.compile_rethrow_with_finally(finally_body)?;
1979
1980 self.chunk.patch_jump(end_jump);
1981
1982 self.finally_bodies.pop();
1983 } else {
1984 self.handler_depth += 1;
1988 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1989 self.emit_type_name_extra(type_name_idx);
1990
1991 self.compile_try_body(body)?;
1993
1994 self.handler_depth -= 1;
1996 self.chunk.emit(Op::PopHandler, self.line);
1997 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1998
1999 self.chunk.patch_jump(catch_jump);
2001 self.compile_catch_binding(error_var)?;
2002
2003 self.compile_try_body(catch_body)?;
2005
2006 self.chunk.patch_jump(end_jump);
2008 }
2009 }
2010
2011 Node::TryExpr { body } => {
2012 self.handler_depth += 1;
2016 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
2017 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
2018 self.emit_type_name_extra(empty_type);
2019
2020 self.compile_try_body(body)?;
2022
2023 self.handler_depth -= 1;
2025 self.chunk.emit(Op::PopHandler, self.line);
2026
2027 let ok_idx = self.chunk.add_constant(Constant::String("Ok".to_string()));
2029 self.chunk.emit_u16(Op::Constant, ok_idx, self.line);
2030 self.chunk.emit(Op::Swap, self.line);
2031 self.chunk.emit_u8(Op::Call, 1, self.line);
2032
2033 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
2035
2036 self.chunk.patch_jump(catch_jump);
2038
2039 let err_idx = self.chunk.add_constant(Constant::String("Err".to_string()));
2041 self.chunk.emit_u16(Op::Constant, err_idx, self.line);
2042 self.chunk.emit(Op::Swap, self.line);
2043 self.chunk.emit_u8(Op::Call, 1, self.line);
2044
2045 self.chunk.patch_jump(end_jump);
2047 }
2048
2049 Node::Retry { count, body } => {
2050 self.compile_node(count)?;
2052 let counter_name = "__retry_counter__";
2053 let counter_idx = self
2054 .chunk
2055 .add_constant(Constant::String(counter_name.to_string()));
2056 self.chunk.emit_u16(Op::DefVar, counter_idx, self.line);
2057
2058 self.chunk.emit(Op::Nil, self.line);
2060 let err_name = "__retry_last_error__";
2061 let err_idx = self
2062 .chunk
2063 .add_constant(Constant::String(err_name.to_string()));
2064 self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
2065
2066 let loop_start = self.chunk.current_offset();
2068
2069 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
2071 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
2073 let hi = (empty_type >> 8) as u8;
2074 let lo = empty_type as u8;
2075 self.chunk.code.push(hi);
2076 self.chunk.code.push(lo);
2077 self.chunk.lines.push(self.line);
2078 self.chunk.columns.push(self.column);
2079 self.chunk.lines.push(self.line);
2080 self.chunk.columns.push(self.column);
2081
2082 self.compile_block(body)?;
2084
2085 self.chunk.emit(Op::PopHandler, self.line);
2087 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
2088
2089 self.chunk.patch_jump(catch_jump);
2091 self.chunk.emit(Op::Dup, self.line);
2093 self.chunk.emit_u16(Op::SetVar, err_idx, self.line);
2094 self.chunk.emit(Op::Pop, self.line);
2096
2097 self.chunk.emit_u16(Op::GetVar, counter_idx, self.line);
2099 let one_idx = self.chunk.add_constant(Constant::Int(1));
2100 self.chunk.emit_u16(Op::Constant, one_idx, self.line);
2101 self.chunk.emit(Op::Sub, self.line);
2102 self.chunk.emit(Op::Dup, self.line);
2103 self.chunk.emit_u16(Op::SetVar, counter_idx, self.line);
2104
2105 let zero_idx = self.chunk.add_constant(Constant::Int(0));
2107 self.chunk.emit_u16(Op::Constant, zero_idx, self.line);
2108 self.chunk.emit(Op::Greater, self.line);
2109 let retry_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2110 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
2112
2113 self.chunk.patch_jump(retry_jump);
2115 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::GetVar, err_idx, self.line);
2117 self.chunk.emit(Op::Throw, self.line);
2118
2119 self.chunk.patch_jump(end_jump);
2120 self.chunk.emit(Op::Nil, self.line);
2122 }
2123
2124 Node::Parallel {
2125 count,
2126 variable,
2127 body,
2128 } => {
2129 self.compile_node(count)?;
2130 let mut fn_compiler = Compiler::new();
2131 fn_compiler.enum_names = self.enum_names.clone();
2132 fn_compiler.compile_block(body)?;
2133 fn_compiler.chunk.emit(Op::Return, self.line);
2134 let params = vec![variable.clone().unwrap_or_else(|| "__i__".to_string())];
2135 let func = CompiledFunction {
2136 name: "<parallel>".to_string(),
2137 params,
2138 default_start: None,
2139 chunk: fn_compiler.chunk,
2140 is_generator: false,
2141 };
2142 let fn_idx = self.chunk.functions.len();
2143 self.chunk.functions.push(func);
2144 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2145 self.chunk.emit(Op::Parallel, self.line);
2146 }
2147
2148 Node::ParallelMap {
2149 list,
2150 variable,
2151 body,
2152 } => {
2153 self.compile_node(list)?;
2154 let mut fn_compiler = Compiler::new();
2155 fn_compiler.enum_names = self.enum_names.clone();
2156 fn_compiler.compile_block(body)?;
2157 fn_compiler.chunk.emit(Op::Return, self.line);
2158 let func = CompiledFunction {
2159 name: "<parallel_map>".to_string(),
2160 params: vec![variable.clone()],
2161 default_start: None,
2162 chunk: fn_compiler.chunk,
2163 is_generator: false,
2164 };
2165 let fn_idx = self.chunk.functions.len();
2166 self.chunk.functions.push(func);
2167 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2168 self.chunk.emit(Op::ParallelMap, self.line);
2169 }
2170
2171 Node::ParallelSettle {
2172 list,
2173 variable,
2174 body,
2175 } => {
2176 self.compile_node(list)?;
2177 let mut fn_compiler = Compiler::new();
2178 fn_compiler.enum_names = self.enum_names.clone();
2179 fn_compiler.compile_block(body)?;
2180 fn_compiler.chunk.emit(Op::Return, self.line);
2181 let func = CompiledFunction {
2182 name: "<parallel_settle>".to_string(),
2183 params: vec![variable.clone()],
2184 default_start: None,
2185 chunk: fn_compiler.chunk,
2186 is_generator: false,
2187 };
2188 let fn_idx = self.chunk.functions.len();
2189 self.chunk.functions.push(func);
2190 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2191 self.chunk.emit(Op::ParallelSettle, self.line);
2192 }
2193
2194 Node::SpawnExpr { body } => {
2195 let mut fn_compiler = Compiler::new();
2196 fn_compiler.enum_names = self.enum_names.clone();
2197 fn_compiler.compile_block(body)?;
2198 fn_compiler.chunk.emit(Op::Return, self.line);
2199 let func = CompiledFunction {
2200 name: "<spawn>".to_string(),
2201 params: vec![],
2202 default_start: None,
2203 chunk: fn_compiler.chunk,
2204 is_generator: false,
2205 };
2206 let fn_idx = self.chunk.functions.len();
2207 self.chunk.functions.push(func);
2208 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2209 self.chunk.emit(Op::Spawn, self.line);
2210 }
2211 Node::SelectExpr {
2212 cases,
2213 timeout,
2214 default_body,
2215 } => {
2216 let builtin_name = if timeout.is_some() {
2223 "__select_timeout"
2224 } else if default_body.is_some() {
2225 "__select_try"
2226 } else {
2227 "__select_list"
2228 };
2229
2230 let name_idx = self
2232 .chunk
2233 .add_constant(Constant::String(builtin_name.into()));
2234 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
2235
2236 for case in cases {
2238 self.compile_node(&case.channel)?;
2239 }
2240 self.chunk
2241 .emit_u16(Op::BuildList, cases.len() as u16, self.line);
2242
2243 if let Some((duration_expr, _)) = timeout {
2245 self.compile_node(duration_expr)?;
2246 self.chunk.emit_u8(Op::Call, 2, self.line);
2247 } else {
2248 self.chunk.emit_u8(Op::Call, 1, self.line);
2249 }
2250
2251 self.temp_counter += 1;
2253 let result_name = format!("__sel_result_{}__", self.temp_counter);
2254 let result_idx = self
2255 .chunk
2256 .add_constant(Constant::String(result_name.clone()));
2257 self.chunk.emit_u16(Op::DefVar, result_idx, self.line);
2258
2259 let mut end_jumps = Vec::new();
2261
2262 for (i, case) in cases.iter().enumerate() {
2263 let get_r = self
2264 .chunk
2265 .add_constant(Constant::String(result_name.clone()));
2266 self.chunk.emit_u16(Op::GetVar, get_r, self.line);
2267 let idx_prop = self.chunk.add_constant(Constant::String("index".into()));
2268 self.chunk.emit_u16(Op::GetProperty, idx_prop, self.line);
2269 let case_i = self.chunk.add_constant(Constant::Int(i as i64));
2270 self.chunk.emit_u16(Op::Constant, case_i, self.line);
2271 self.chunk.emit(Op::Equal, self.line);
2272 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2273 self.chunk.emit(Op::Pop, self.line);
2274
2275 let get_r2 = self
2277 .chunk
2278 .add_constant(Constant::String(result_name.clone()));
2279 self.chunk.emit_u16(Op::GetVar, get_r2, self.line);
2280 let val_prop = self.chunk.add_constant(Constant::String("value".into()));
2281 self.chunk.emit_u16(Op::GetProperty, val_prop, self.line);
2282 let var_idx = self
2283 .chunk
2284 .add_constant(Constant::String(case.variable.clone()));
2285 self.chunk.emit_u16(Op::DefLet, var_idx, self.line);
2286
2287 self.compile_try_body(&case.body)?;
2288 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
2289 self.chunk.patch_jump(skip);
2290 self.chunk.emit(Op::Pop, self.line);
2291 }
2292
2293 if let Some((_, ref timeout_body)) = timeout {
2295 self.compile_try_body(timeout_body)?;
2296 } else if let Some(ref def_body) = default_body {
2297 self.compile_try_body(def_body)?;
2298 } else {
2299 self.chunk.emit(Op::Nil, self.line);
2300 }
2301
2302 for ej in end_jumps {
2303 self.chunk.patch_jump(ej);
2304 }
2305 }
2306 Node::Spread(_) => {
2307 return Err(CompileError {
2308 message: "spread (...) can only be used inside list literals, dict literals, or function call arguments".into(),
2309 line: self.line,
2310 });
2311 }
2312 }
2313 Ok(())
2314 }
2315
2316 fn compile_destructuring(
2320 &mut self,
2321 pattern: &BindingPattern,
2322 is_mutable: bool,
2323 ) -> Result<(), CompileError> {
2324 let def_op = if is_mutable { Op::DefVar } else { Op::DefLet };
2325 match pattern {
2326 BindingPattern::Identifier(name) => {
2327 let idx = self.chunk.add_constant(Constant::String(name.clone()));
2329 self.chunk.emit_u16(def_op, idx, self.line);
2330 }
2331 BindingPattern::Dict(fields) => {
2332 self.chunk.emit(Op::Dup, self.line);
2335 let assert_idx = self
2336 .chunk
2337 .add_constant(Constant::String("__assert_dict".into()));
2338 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
2339 self.chunk.emit(Op::Swap, self.line);
2340 self.chunk.emit_u8(Op::Call, 1, self.line);
2341 self.chunk.emit(Op::Pop, self.line); let non_rest: Vec<_> = fields.iter().filter(|f| !f.is_rest).collect();
2346 let rest_field = fields.iter().find(|f| f.is_rest);
2347
2348 for field in &non_rest {
2349 self.chunk.emit(Op::Dup, self.line);
2350 let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
2351 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
2352 self.chunk.emit(Op::Subscript, self.line);
2353 let binding_name = field.alias.as_deref().unwrap_or(&field.key);
2354 let name_idx = self
2355 .chunk
2356 .add_constant(Constant::String(binding_name.to_string()));
2357 self.chunk.emit_u16(def_op, name_idx, self.line);
2358 }
2359
2360 if let Some(rest) = rest_field {
2361 let fn_idx = self
2364 .chunk
2365 .add_constant(Constant::String("__dict_rest".into()));
2366 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
2367 self.chunk.emit(Op::Swap, self.line);
2369 for field in &non_rest {
2371 let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
2372 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
2373 }
2374 self.chunk
2375 .emit_u16(Op::BuildList, non_rest.len() as u16, self.line);
2376 self.chunk.emit_u8(Op::Call, 2, self.line);
2378 let rest_name = &rest.key;
2379 let rest_idx = self.chunk.add_constant(Constant::String(rest_name.clone()));
2380 self.chunk.emit_u16(def_op, rest_idx, self.line);
2381 } else {
2382 self.chunk.emit(Op::Pop, self.line);
2384 }
2385 }
2386 BindingPattern::List(elements) => {
2387 self.chunk.emit(Op::Dup, self.line);
2390 let assert_idx = self
2391 .chunk
2392 .add_constant(Constant::String("__assert_list".into()));
2393 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
2394 self.chunk.emit(Op::Swap, self.line);
2395 self.chunk.emit_u8(Op::Call, 1, self.line);
2396 self.chunk.emit(Op::Pop, self.line); let non_rest: Vec<_> = elements.iter().filter(|e| !e.is_rest).collect();
2399 let rest_elem = elements.iter().find(|e| e.is_rest);
2400
2401 for (i, elem) in non_rest.iter().enumerate() {
2402 self.chunk.emit(Op::Dup, self.line);
2403 let idx_const = self.chunk.add_constant(Constant::Int(i as i64));
2404 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
2405 self.chunk.emit(Op::Subscript, self.line);
2406 let name_idx = self.chunk.add_constant(Constant::String(elem.name.clone()));
2407 self.chunk.emit_u16(def_op, name_idx, self.line);
2408 }
2409
2410 if let Some(rest) = rest_elem {
2411 let start_idx = self
2415 .chunk
2416 .add_constant(Constant::Int(non_rest.len() as i64));
2417 self.chunk.emit_u16(Op::Constant, start_idx, self.line);
2418 self.chunk.emit(Op::Nil, self.line); self.chunk.emit(Op::Slice, self.line);
2420 let rest_name_idx =
2421 self.chunk.add_constant(Constant::String(rest.name.clone()));
2422 self.chunk.emit_u16(def_op, rest_name_idx, self.line);
2423 } else {
2424 self.chunk.emit(Op::Pop, self.line);
2426 }
2427 }
2428 }
2429 Ok(())
2430 }
2431
2432 fn produces_value(node: &Node) -> bool {
2434 match node {
2435 Node::LetBinding { .. }
2437 | Node::VarBinding { .. }
2438 | Node::Assignment { .. }
2439 | Node::ReturnStmt { .. }
2440 | Node::FnDecl { .. }
2441 | Node::ImplBlock { .. }
2442 | Node::StructDecl { .. }
2443 | Node::EnumDecl { .. }
2444 | Node::InterfaceDecl { .. }
2445 | Node::TypeDecl { .. }
2446 | Node::ThrowStmt { .. }
2447 | Node::BreakStmt
2448 | Node::ContinueStmt
2449 | Node::RequireStmt { .. } => false,
2450 Node::TryCatch { .. }
2452 | Node::TryExpr { .. }
2453 | Node::Retry { .. }
2454 | Node::GuardStmt { .. }
2455 | Node::DeadlineBlock { .. }
2456 | Node::MutexBlock { .. }
2457 | Node::Spread(_) => true,
2458 _ => true,
2460 }
2461 }
2462}
2463
2464impl Compiler {
2465 pub fn compile_fn_body(
2467 &mut self,
2468 params: &[TypedParam],
2469 body: &[SNode],
2470 ) -> Result<CompiledFunction, CompileError> {
2471 let mut fn_compiler = Compiler::new();
2472 fn_compiler.compile_block(body)?;
2473 fn_compiler.chunk.emit(Op::Nil, 0);
2474 fn_compiler.chunk.emit(Op::Return, 0);
2475 Ok(CompiledFunction {
2476 name: String::new(),
2477 params: TypedParam::names(params),
2478 default_start: TypedParam::default_start(params),
2479 chunk: fn_compiler.chunk,
2480 is_generator: false,
2481 })
2482 }
2483
2484 fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
2486 if body.is_empty() {
2487 self.chunk.emit(Op::Nil, self.line);
2488 } else {
2489 self.compile_block(body)?;
2490 if !Self::produces_value(&body.last().unwrap().node) {
2492 self.chunk.emit(Op::Nil, self.line);
2493 }
2494 }
2495 Ok(())
2496 }
2497
2498 fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
2500 match op {
2501 "+" => self.chunk.emit(Op::Add, self.line),
2502 "-" => self.chunk.emit(Op::Sub, self.line),
2503 "*" => self.chunk.emit(Op::Mul, self.line),
2504 "/" => self.chunk.emit(Op::Div, self.line),
2505 "%" => self.chunk.emit(Op::Mod, self.line),
2506 _ => {
2507 return Err(CompileError {
2508 message: format!("Unknown compound operator: {op}"),
2509 line: self.line,
2510 })
2511 }
2512 }
2513 Ok(())
2514 }
2515
2516 fn root_var_name(&self, node: &SNode) -> Option<String> {
2518 match &node.node {
2519 Node::Identifier(name) => Some(name.clone()),
2520 Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
2521 self.root_var_name(object)
2522 }
2523 Node::SubscriptAccess { object, .. } => self.root_var_name(object),
2524 _ => None,
2525 }
2526 }
2527}
2528
2529impl Compiler {
2530 fn collect_enum_names(nodes: &[SNode], names: &mut std::collections::HashSet<String>) {
2532 for sn in nodes {
2533 match &sn.node {
2534 Node::EnumDecl { name, .. } => {
2535 names.insert(name.clone());
2536 }
2537 Node::Pipeline { body, .. } => {
2538 Self::collect_enum_names(body, names);
2539 }
2540 Node::FnDecl { body, .. } => {
2541 Self::collect_enum_names(body, names);
2542 }
2543 Node::Block(stmts) => {
2544 Self::collect_enum_names(stmts, names);
2545 }
2546 _ => {}
2547 }
2548 }
2549 }
2550
2551 fn collect_interface_methods(
2552 nodes: &[SNode],
2553 interfaces: &mut std::collections::HashMap<String, Vec<String>>,
2554 ) {
2555 for sn in nodes {
2556 match &sn.node {
2557 Node::InterfaceDecl { name, methods } => {
2558 let method_names: Vec<String> =
2559 methods.iter().map(|m| m.name.clone()).collect();
2560 interfaces.insert(name.clone(), method_names);
2561 }
2562 Node::Pipeline { body, .. } | Node::FnDecl { body, .. } => {
2563 Self::collect_interface_methods(body, interfaces);
2564 }
2565 Node::Block(stmts) => {
2566 Self::collect_interface_methods(stmts, interfaces);
2567 }
2568 _ => {}
2569 }
2570 }
2571 }
2572}
2573
2574impl Default for Compiler {
2575 fn default() -> Self {
2576 Self::new()
2577 }
2578}
2579
2580fn body_contains_yield(nodes: &[SNode]) -> bool {
2582 nodes.iter().any(|sn| node_contains_yield(&sn.node))
2583}
2584
2585fn node_contains_yield(node: &Node) -> bool {
2586 match node {
2587 Node::YieldExpr { .. } => true,
2588 Node::FnDecl { .. } | Node::Closure { .. } => false,
2591 Node::Block(stmts) => body_contains_yield(stmts),
2592 Node::IfElse {
2593 condition,
2594 then_body,
2595 else_body,
2596 } => {
2597 node_contains_yield(&condition.node)
2598 || body_contains_yield(then_body)
2599 || else_body.as_ref().is_some_and(|b| body_contains_yield(b))
2600 }
2601 Node::WhileLoop { condition, body } => {
2602 node_contains_yield(&condition.node) || body_contains_yield(body)
2603 }
2604 Node::ForIn { iterable, body, .. } => {
2605 node_contains_yield(&iterable.node) || body_contains_yield(body)
2606 }
2607 Node::TryCatch {
2608 body, catch_body, ..
2609 } => body_contains_yield(body) || body_contains_yield(catch_body),
2610 Node::TryExpr { body } => body_contains_yield(body),
2611 _ => false,
2612 }
2613}
2614
2615fn contains_pipe_placeholder(node: &SNode) -> bool {
2617 match &node.node {
2618 Node::Identifier(name) if name == "_" => true,
2619 Node::FunctionCall { args, .. } => args.iter().any(contains_pipe_placeholder),
2620 Node::MethodCall { object, args, .. } => {
2621 contains_pipe_placeholder(object) || args.iter().any(contains_pipe_placeholder)
2622 }
2623 Node::BinaryOp { left, right, .. } => {
2624 contains_pipe_placeholder(left) || contains_pipe_placeholder(right)
2625 }
2626 Node::UnaryOp { operand, .. } => contains_pipe_placeholder(operand),
2627 Node::ListLiteral(items) => items.iter().any(contains_pipe_placeholder),
2628 Node::PropertyAccess { object, .. } => contains_pipe_placeholder(object),
2629 Node::SubscriptAccess { object, index } => {
2630 contains_pipe_placeholder(object) || contains_pipe_placeholder(index)
2631 }
2632 _ => false,
2633 }
2634}
2635
2636fn replace_pipe_placeholder(node: &SNode) -> SNode {
2638 let new_node = match &node.node {
2639 Node::Identifier(name) if name == "_" => Node::Identifier("__pipe".into()),
2640 Node::FunctionCall { name, args } => Node::FunctionCall {
2641 name: name.clone(),
2642 args: args.iter().map(replace_pipe_placeholder).collect(),
2643 },
2644 Node::MethodCall {
2645 object,
2646 method,
2647 args,
2648 } => Node::MethodCall {
2649 object: Box::new(replace_pipe_placeholder(object)),
2650 method: method.clone(),
2651 args: args.iter().map(replace_pipe_placeholder).collect(),
2652 },
2653 Node::BinaryOp { op, left, right } => Node::BinaryOp {
2654 op: op.clone(),
2655 left: Box::new(replace_pipe_placeholder(left)),
2656 right: Box::new(replace_pipe_placeholder(right)),
2657 },
2658 Node::UnaryOp { op, operand } => Node::UnaryOp {
2659 op: op.clone(),
2660 operand: Box::new(replace_pipe_placeholder(operand)),
2661 },
2662 Node::ListLiteral(items) => {
2663 Node::ListLiteral(items.iter().map(replace_pipe_placeholder).collect())
2664 }
2665 Node::PropertyAccess { object, property } => Node::PropertyAccess {
2666 object: Box::new(replace_pipe_placeholder(object)),
2667 property: property.clone(),
2668 },
2669 Node::SubscriptAccess { object, index } => Node::SubscriptAccess {
2670 object: Box::new(replace_pipe_placeholder(object)),
2671 index: Box::new(replace_pipe_placeholder(index)),
2672 },
2673 _ => return node.clone(),
2674 };
2675 SNode::new(new_node, node.span)
2676}
2677
2678#[cfg(test)]
2679mod tests {
2680 use super::*;
2681 use harn_lexer::Lexer;
2682 use harn_parser::Parser;
2683
2684 fn compile_source(source: &str) -> Chunk {
2685 let mut lexer = Lexer::new(source);
2686 let tokens = lexer.tokenize().unwrap();
2687 let mut parser = Parser::new(tokens);
2688 let program = parser.parse().unwrap();
2689 Compiler::new().compile(&program).unwrap()
2690 }
2691
2692 #[test]
2693 fn test_compile_arithmetic() {
2694 let chunk = compile_source("pipeline test(task) { let x = 2 + 3 }");
2695 assert!(!chunk.code.is_empty());
2696 assert!(chunk.constants.contains(&Constant::Int(2)));
2698 assert!(chunk.constants.contains(&Constant::Int(3)));
2699 }
2700
2701 #[test]
2702 fn test_compile_function_call() {
2703 let chunk = compile_source("pipeline test(task) { log(42) }");
2704 let disasm = chunk.disassemble("test");
2705 assert!(disasm.contains("CALL"));
2706 }
2707
2708 #[test]
2709 fn test_compile_if_else() {
2710 let chunk =
2711 compile_source(r#"pipeline test(task) { if true { log("yes") } else { log("no") } }"#);
2712 let disasm = chunk.disassemble("test");
2713 assert!(disasm.contains("JUMP_IF_FALSE"));
2714 assert!(disasm.contains("JUMP"));
2715 }
2716
2717 #[test]
2718 fn test_compile_while() {
2719 let chunk = compile_source("pipeline test(task) { var i = 0\n while i < 5 { i = i + 1 } }");
2720 let disasm = chunk.disassemble("test");
2721 assert!(disasm.contains("JUMP_IF_FALSE"));
2722 assert!(disasm.contains("JUMP"));
2724 }
2725
2726 #[test]
2727 fn test_compile_closure() {
2728 let chunk = compile_source("pipeline test(task) { let f = { x -> x * 2 } }");
2729 assert!(!chunk.functions.is_empty());
2730 assert_eq!(chunk.functions[0].params, vec!["x"]);
2731 }
2732
2733 #[test]
2734 fn test_compile_list() {
2735 let chunk = compile_source("pipeline test(task) { let a = [1, 2, 3] }");
2736 let disasm = chunk.disassemble("test");
2737 assert!(disasm.contains("BUILD_LIST"));
2738 }
2739
2740 #[test]
2741 fn test_compile_dict() {
2742 let chunk = compile_source(r#"pipeline test(task) { let d = {name: "test"} }"#);
2743 let disasm = chunk.disassemble("test");
2744 assert!(disasm.contains("BUILD_DICT"));
2745 }
2746
2747 #[test]
2748 fn test_disassemble() {
2749 let chunk = compile_source("pipeline test(task) { log(2 + 3) }");
2750 let disasm = chunk.disassemble("test");
2751 assert!(disasm.contains("CONSTANT"));
2753 assert!(disasm.contains("ADD"));
2754 assert!(disasm.contains("CALL"));
2755 }
2756}