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