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 let is_last = i == stmts.len() - 1;
166 if is_last {
167 if !Self::produces_value(&snode.node) {
170 self.chunk.emit(Op::Nil, self.line);
171 }
172 } else {
173 if Self::produces_value(&snode.node) {
175 self.chunk.emit(Op::Pop, self.line);
176 }
177 }
178 }
179 Ok(())
180 }
181
182 fn compile_node(&mut self, snode: &SNode) -> Result<(), CompileError> {
183 self.line = snode.span.line as u32;
184 self.column = snode.span.column as u32;
185 self.chunk.set_column(self.column);
186 match &snode.node {
187 Node::IntLiteral(n) => {
188 let idx = self.chunk.add_constant(Constant::Int(*n));
189 self.chunk.emit_u16(Op::Constant, idx, self.line);
190 }
191 Node::FloatLiteral(n) => {
192 let idx = self.chunk.add_constant(Constant::Float(*n));
193 self.chunk.emit_u16(Op::Constant, idx, self.line);
194 }
195 Node::StringLiteral(s) => {
196 let idx = self.chunk.add_constant(Constant::String(s.clone()));
197 self.chunk.emit_u16(Op::Constant, idx, self.line);
198 }
199 Node::BoolLiteral(true) => self.chunk.emit(Op::True, self.line),
200 Node::BoolLiteral(false) => self.chunk.emit(Op::False, self.line),
201 Node::NilLiteral => self.chunk.emit(Op::Nil, self.line),
202 Node::DurationLiteral(ms) => {
203 let idx = self.chunk.add_constant(Constant::Duration(*ms));
204 self.chunk.emit_u16(Op::Constant, idx, self.line);
205 }
206
207 Node::Identifier(name) => {
208 let idx = self.chunk.add_constant(Constant::String(name.clone()));
209 self.chunk.emit_u16(Op::GetVar, idx, self.line);
210 }
211
212 Node::LetBinding { name, value, .. } => {
213 self.compile_node(value)?;
214 let idx = self.chunk.add_constant(Constant::String(name.clone()));
215 self.chunk.emit_u16(Op::DefLet, idx, self.line);
216 }
217
218 Node::VarBinding { name, value, .. } => {
219 self.compile_node(value)?;
220 let idx = self.chunk.add_constant(Constant::String(name.clone()));
221 self.chunk.emit_u16(Op::DefVar, idx, self.line);
222 }
223
224 Node::Assignment {
225 target, value, op, ..
226 } => {
227 if let Node::Identifier(name) = &target.node {
228 let idx = self.chunk.add_constant(Constant::String(name.clone()));
229 if let Some(op) = op {
230 self.chunk.emit_u16(Op::GetVar, idx, self.line);
231 self.compile_node(value)?;
232 self.emit_compound_op(op)?;
233 self.chunk.emit_u16(Op::SetVar, idx, self.line);
234 } else {
235 self.compile_node(value)?;
236 self.chunk.emit_u16(Op::SetVar, idx, self.line);
237 }
238 } else if let Node::PropertyAccess { object, property } = &target.node {
239 if let Some(var_name) = self.root_var_name(object) {
241 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
242 let prop_idx = self.chunk.add_constant(Constant::String(property.clone()));
243 if let Some(op) = op {
244 self.compile_node(target)?; self.compile_node(value)?;
247 self.emit_compound_op(op)?;
248 } else {
249 self.compile_node(value)?;
250 }
251 self.chunk.emit_u16(Op::SetProperty, prop_idx, self.line);
254 let hi = (var_idx >> 8) as u8;
256 let lo = var_idx as u8;
257 self.chunk.code.push(hi);
258 self.chunk.code.push(lo);
259 self.chunk.lines.push(self.line);
260 self.chunk.columns.push(self.column);
261 self.chunk.lines.push(self.line);
262 self.chunk.columns.push(self.column);
263 }
264 } else if let Node::SubscriptAccess { object, index } = &target.node {
265 if let Some(var_name) = self.root_var_name(object) {
267 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
268 if let Some(op) = op {
269 self.compile_node(target)?;
270 self.compile_node(value)?;
271 self.emit_compound_op(op)?;
272 } else {
273 self.compile_node(value)?;
274 }
275 self.compile_node(index)?;
276 self.chunk.emit_u16(Op::SetSubscript, var_idx, self.line);
277 }
278 }
279 }
280
281 Node::BinaryOp { op, left, right } => {
282 match op.as_str() {
284 "&&" => {
285 self.compile_node(left)?;
286 let jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
287 self.chunk.emit(Op::Pop, self.line);
288 self.compile_node(right)?;
289 self.chunk.patch_jump(jump);
290 self.chunk.emit(Op::Not, self.line);
292 self.chunk.emit(Op::Not, self.line);
293 return Ok(());
294 }
295 "||" => {
296 self.compile_node(left)?;
297 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
298 self.chunk.emit(Op::Pop, self.line);
299 self.compile_node(right)?;
300 self.chunk.patch_jump(jump);
301 self.chunk.emit(Op::Not, self.line);
302 self.chunk.emit(Op::Not, self.line);
303 return Ok(());
304 }
305 "??" => {
306 self.compile_node(left)?;
307 self.chunk.emit(Op::Dup, self.line);
308 self.chunk.emit(Op::Nil, self.line);
310 self.chunk.emit(Op::NotEqual, self.line);
311 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
312 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_node(right)?;
315 let end = self.chunk.emit_jump(Op::Jump, self.line);
316 self.chunk.patch_jump(jump);
317 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end);
319 return Ok(());
320 }
321 "|>" => {
322 self.compile_node(left)?;
323 self.compile_node(right)?;
324 self.chunk.emit(Op::Pipe, self.line);
325 return Ok(());
326 }
327 _ => {}
328 }
329
330 self.compile_node(left)?;
331 self.compile_node(right)?;
332 match op.as_str() {
333 "+" => self.chunk.emit(Op::Add, self.line),
334 "-" => self.chunk.emit(Op::Sub, self.line),
335 "*" => self.chunk.emit(Op::Mul, self.line),
336 "/" => self.chunk.emit(Op::Div, self.line),
337 "%" => self.chunk.emit(Op::Mod, self.line),
338 "==" => self.chunk.emit(Op::Equal, self.line),
339 "!=" => self.chunk.emit(Op::NotEqual, self.line),
340 "<" => self.chunk.emit(Op::Less, self.line),
341 ">" => self.chunk.emit(Op::Greater, self.line),
342 "<=" => self.chunk.emit(Op::LessEqual, self.line),
343 ">=" => self.chunk.emit(Op::GreaterEqual, self.line),
344 _ => {
345 return Err(CompileError {
346 message: format!("Unknown operator: {op}"),
347 line: self.line,
348 })
349 }
350 }
351 }
352
353 Node::UnaryOp { op, operand } => {
354 self.compile_node(operand)?;
355 match op.as_str() {
356 "-" => self.chunk.emit(Op::Negate, self.line),
357 "!" => self.chunk.emit(Op::Not, self.line),
358 _ => {}
359 }
360 }
361
362 Node::Ternary {
363 condition,
364 true_expr,
365 false_expr,
366 } => {
367 self.compile_node(condition)?;
368 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
369 self.chunk.emit(Op::Pop, self.line);
370 self.compile_node(true_expr)?;
371 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
372 self.chunk.patch_jump(else_jump);
373 self.chunk.emit(Op::Pop, self.line);
374 self.compile_node(false_expr)?;
375 self.chunk.patch_jump(end_jump);
376 }
377
378 Node::FunctionCall { name, args } => {
379 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
381 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
382 for arg in args {
384 self.compile_node(arg)?;
385 }
386 self.chunk.emit_u8(Op::Call, args.len() as u8, self.line);
387 }
388
389 Node::MethodCall {
390 object,
391 method,
392 args,
393 } => {
394 if let Node::Identifier(name) = &object.node {
396 if self.enum_names.contains(name) {
397 for arg in args {
399 self.compile_node(arg)?;
400 }
401 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
402 let var_idx = self.chunk.add_constant(Constant::String(method.clone()));
403 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
404 let hi = (var_idx >> 8) as u8;
405 let lo = var_idx as u8;
406 self.chunk.code.push(hi);
407 self.chunk.code.push(lo);
408 self.chunk.lines.push(self.line);
409 self.chunk.columns.push(self.column);
410 self.chunk.lines.push(self.line);
411 self.chunk.columns.push(self.column);
412 let fc = args.len() as u16;
413 let fhi = (fc >> 8) as u8;
414 let flo = fc as u8;
415 self.chunk.code.push(fhi);
416 self.chunk.code.push(flo);
417 self.chunk.lines.push(self.line);
418 self.chunk.columns.push(self.column);
419 self.chunk.lines.push(self.line);
420 self.chunk.columns.push(self.column);
421 return Ok(());
422 }
423 }
424 self.compile_node(object)?;
425 for arg in args {
426 self.compile_node(arg)?;
427 }
428 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
429 self.chunk
430 .emit_method_call(name_idx, args.len() as u8, self.line);
431 }
432
433 Node::OptionalMethodCall {
434 object,
435 method,
436 args,
437 } => {
438 self.compile_node(object)?;
439 for arg in args {
440 self.compile_node(arg)?;
441 }
442 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
443 self.chunk
444 .emit_method_call_opt(name_idx, args.len() as u8, self.line);
445 }
446
447 Node::PropertyAccess { object, property } => {
448 if let Node::Identifier(name) = &object.node {
450 if self.enum_names.contains(name) {
451 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
453 let var_idx = self.chunk.add_constant(Constant::String(property.clone()));
454 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
455 let hi = (var_idx >> 8) as u8;
456 let lo = var_idx as u8;
457 self.chunk.code.push(hi);
458 self.chunk.code.push(lo);
459 self.chunk.lines.push(self.line);
460 self.chunk.columns.push(self.column);
461 self.chunk.lines.push(self.line);
462 self.chunk.columns.push(self.column);
463 self.chunk.code.push(0);
465 self.chunk.code.push(0);
466 self.chunk.lines.push(self.line);
467 self.chunk.columns.push(self.column);
468 self.chunk.lines.push(self.line);
469 self.chunk.columns.push(self.column);
470 return Ok(());
471 }
472 }
473 self.compile_node(object)?;
474 let idx = self.chunk.add_constant(Constant::String(property.clone()));
475 self.chunk.emit_u16(Op::GetProperty, idx, self.line);
476 }
477
478 Node::OptionalPropertyAccess { object, property } => {
479 self.compile_node(object)?;
480 let idx = self.chunk.add_constant(Constant::String(property.clone()));
481 self.chunk.emit_u16(Op::GetPropertyOpt, idx, self.line);
482 }
483
484 Node::SubscriptAccess { object, index } => {
485 self.compile_node(object)?;
486 self.compile_node(index)?;
487 self.chunk.emit(Op::Subscript, self.line);
488 }
489
490 Node::SliceAccess { object, start, end } => {
491 self.compile_node(object)?;
492 if let Some(s) = start {
493 self.compile_node(s)?;
494 } else {
495 self.chunk.emit(Op::Nil, self.line);
496 }
497 if let Some(e) = end {
498 self.compile_node(e)?;
499 } else {
500 self.chunk.emit(Op::Nil, self.line);
501 }
502 self.chunk.emit(Op::Slice, self.line);
503 }
504
505 Node::IfElse {
506 condition,
507 then_body,
508 else_body,
509 } => {
510 self.compile_node(condition)?;
511 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
512 self.chunk.emit(Op::Pop, self.line);
513 self.compile_block(then_body)?;
514 if let Some(else_body) = else_body {
515 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
516 self.chunk.patch_jump(else_jump);
517 self.chunk.emit(Op::Pop, self.line);
518 self.compile_block(else_body)?;
519 self.chunk.patch_jump(end_jump);
520 } else {
521 self.chunk.patch_jump(else_jump);
522 self.chunk.emit(Op::Pop, self.line);
523 self.chunk.emit(Op::Nil, self.line);
524 }
525 }
526
527 Node::WhileLoop { condition, body } => {
528 let loop_start = self.chunk.current_offset();
529 self.loop_stack.push(LoopContext {
530 start_offset: loop_start,
531 break_patches: Vec::new(),
532 has_iterator: false,
533 handler_depth: self.handler_depth,
534 });
535 self.compile_node(condition)?;
536 let exit_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
537 self.chunk.emit(Op::Pop, self.line); 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);
548 self.chunk.emit(Op::Pop, self.line); let ctx = self.loop_stack.pop().unwrap();
551 for patch_pos in ctx.break_patches {
552 self.chunk.patch_jump(patch_pos);
553 }
554 self.chunk.emit(Op::Nil, self.line);
555 }
556
557 Node::ForIn {
558 variable,
559 iterable,
560 body,
561 } => {
562 self.compile_node(iterable)?;
564 let var_idx = self.chunk.add_constant(Constant::String(variable.clone()));
566 self.chunk.emit(Op::IterInit, self.line);
568 let loop_start = self.chunk.current_offset();
569 self.loop_stack.push(LoopContext {
570 start_offset: loop_start,
571 break_patches: Vec::new(),
572 has_iterator: true,
573 handler_depth: self.handler_depth,
574 });
575 let exit_jump_pos = self.chunk.emit_jump(Op::IterNext, self.line);
577 self.chunk.emit_u16(Op::DefVar, var_idx, self.line);
579 for sn in body {
581 self.compile_node(sn)?;
582 if Self::produces_value(&sn.node) {
583 self.chunk.emit(Op::Pop, self.line);
584 }
585 }
586 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
588 self.chunk.patch_jump(exit_jump_pos);
589 let ctx = self.loop_stack.pop().unwrap();
591 for patch_pos in ctx.break_patches {
592 self.chunk.patch_jump(patch_pos);
593 }
594 self.chunk.emit(Op::Nil, self.line);
596 }
597
598 Node::ReturnStmt { value } => {
599 if let Some(val) = value {
600 if let Node::FunctionCall { name, args } = &val.node {
603 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
604 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
605 for arg in args {
606 self.compile_node(arg)?;
607 }
608 self.chunk
609 .emit_u8(Op::TailCall, args.len() as u8, self.line);
610 } else if let Node::BinaryOp { op, left, right } = &val.node {
611 if op == "|>" {
612 self.compile_node(left)?;
615 self.compile_node(right)?;
617 self.chunk.emit(Op::Swap, self.line);
620 self.chunk.emit_u8(Op::TailCall, 1, self.line);
621 } else {
622 self.compile_node(val)?;
623 }
624 } else {
625 self.compile_node(val)?;
626 }
627 } else {
628 self.chunk.emit(Op::Nil, self.line);
629 }
630 self.chunk.emit(Op::Return, self.line);
631 }
632
633 Node::BreakStmt => {
634 if self.loop_stack.is_empty() {
635 return Err(CompileError {
636 message: "break outside of loop".to_string(),
637 line: self.line,
638 });
639 }
640 let ctx = self.loop_stack.last().unwrap();
641 for _ in ctx.handler_depth..self.handler_depth {
643 self.chunk.emit(Op::PopHandler, self.line);
644 }
645 if ctx.has_iterator {
647 self.chunk.emit(Op::PopIterator, self.line);
648 }
649 let patch = self.chunk.emit_jump(Op::Jump, self.line);
650 self.loop_stack
651 .last_mut()
652 .unwrap()
653 .break_patches
654 .push(patch);
655 }
656
657 Node::ContinueStmt => {
658 if self.loop_stack.is_empty() {
659 return Err(CompileError {
660 message: "continue outside of loop".to_string(),
661 line: self.line,
662 });
663 }
664 let ctx = self.loop_stack.last().unwrap();
665 for _ in ctx.handler_depth..self.handler_depth {
667 self.chunk.emit(Op::PopHandler, self.line);
668 }
669 let loop_start = ctx.start_offset;
670 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
671 }
672
673 Node::ListLiteral(elements) => {
674 for el in elements {
675 self.compile_node(el)?;
676 }
677 self.chunk
678 .emit_u16(Op::BuildList, elements.len() as u16, self.line);
679 }
680
681 Node::DictLiteral(entries) => {
682 for entry in entries {
683 self.compile_node(&entry.key)?;
684 self.compile_node(&entry.value)?;
685 }
686 self.chunk
687 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
688 }
689
690 Node::InterpolatedString(segments) => {
691 let mut part_count = 0u16;
692 for seg in segments {
693 match seg {
694 StringSegment::Literal(s) => {
695 let idx = self.chunk.add_constant(Constant::String(s.clone()));
696 self.chunk.emit_u16(Op::Constant, idx, self.line);
697 part_count += 1;
698 }
699 StringSegment::Expression(expr_str) => {
700 let mut lexer = harn_lexer::Lexer::new(expr_str);
702 if let Ok(tokens) = lexer.tokenize() {
703 let mut parser = harn_parser::Parser::new(tokens);
704 if let Ok(snode) = parser.parse_single_expression() {
705 self.compile_node(&snode)?;
706 let to_str = self
708 .chunk
709 .add_constant(Constant::String("to_string".into()));
710 self.chunk.emit_u16(Op::Constant, to_str, self.line);
711 self.chunk.emit(Op::Swap, self.line);
712 self.chunk.emit_u8(Op::Call, 1, self.line);
713 part_count += 1;
714 } else {
715 let idx =
717 self.chunk.add_constant(Constant::String(expr_str.clone()));
718 self.chunk.emit_u16(Op::Constant, idx, self.line);
719 part_count += 1;
720 }
721 }
722 }
723 }
724 }
725 if part_count > 1 {
726 self.chunk.emit_u16(Op::Concat, part_count, self.line);
727 }
728 }
729
730 Node::FnDecl {
731 name, params, body, ..
732 } => {
733 let mut fn_compiler = Compiler::new();
735 fn_compiler.enum_names = self.enum_names.clone();
736 fn_compiler.compile_block(body)?;
737 fn_compiler.chunk.emit(Op::Nil, self.line);
738 fn_compiler.chunk.emit(Op::Return, self.line);
739
740 let func = CompiledFunction {
741 name: name.clone(),
742 params: TypedParam::names(params),
743 chunk: fn_compiler.chunk,
744 };
745 let fn_idx = self.chunk.functions.len();
746 self.chunk.functions.push(func);
747
748 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
749 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
750 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
751 }
752
753 Node::Closure { params, body } => {
754 let mut fn_compiler = Compiler::new();
755 fn_compiler.enum_names = self.enum_names.clone();
756 fn_compiler.compile_block(body)?;
757 fn_compiler.chunk.emit(Op::Return, self.line);
759
760 let func = CompiledFunction {
761 name: "<closure>".to_string(),
762 params: TypedParam::names(params),
763 chunk: fn_compiler.chunk,
764 };
765 let fn_idx = self.chunk.functions.len();
766 self.chunk.functions.push(func);
767
768 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
769 }
770
771 Node::ThrowStmt { value } => {
772 self.compile_node(value)?;
773 self.chunk.emit(Op::Throw, self.line);
774 }
775
776 Node::MatchExpr { value, arms } => {
777 self.compile_node(value)?;
778 let mut end_jumps = Vec::new();
779 for arm in arms {
780 match &arm.pattern.node {
781 Node::Identifier(name) if name == "_" => {
783 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
785 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
786 }
787 Node::EnumConstruct {
789 enum_name,
790 variant,
791 args: pat_args,
792 } => {
793 self.chunk.emit(Op::Dup, self.line);
795 let en_idx =
796 self.chunk.add_constant(Constant::String(enum_name.clone()));
797 let vn_idx = self.chunk.add_constant(Constant::String(variant.clone()));
798 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
799 let hi = (vn_idx >> 8) as u8;
800 let lo = vn_idx as u8;
801 self.chunk.code.push(hi);
802 self.chunk.code.push(lo);
803 self.chunk.lines.push(self.line);
804 self.chunk.columns.push(self.column);
805 self.chunk.lines.push(self.line);
806 self.chunk.columns.push(self.column);
807 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
809 self.chunk.emit(Op::Pop, self.line); for (i, pat_arg) in pat_args.iter().enumerate() {
814 if let Node::Identifier(binding_name) = &pat_arg.node {
815 self.chunk.emit(Op::Dup, self.line);
817 let fields_idx = self
818 .chunk
819 .add_constant(Constant::String("fields".to_string()));
820 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
821 let idx_const =
822 self.chunk.add_constant(Constant::Int(i as i64));
823 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
824 self.chunk.emit(Op::Subscript, self.line);
825 let name_idx = self
826 .chunk
827 .add_constant(Constant::String(binding_name.clone()));
828 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
829 }
830 }
831
832 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
834 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
835 self.chunk.patch_jump(skip);
836 self.chunk.emit(Op::Pop, self.line); }
838 Node::PropertyAccess { object, property } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
840 {
841 let enum_name = if let Node::Identifier(n) = &object.node {
842 n.clone()
843 } else {
844 unreachable!()
845 };
846 self.chunk.emit(Op::Dup, self.line);
847 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
848 let vn_idx =
849 self.chunk.add_constant(Constant::String(property.clone()));
850 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
851 let hi = (vn_idx >> 8) as u8;
852 let lo = vn_idx as u8;
853 self.chunk.code.push(hi);
854 self.chunk.code.push(lo);
855 self.chunk.lines.push(self.line);
856 self.chunk.columns.push(self.column);
857 self.chunk.lines.push(self.line);
858 self.chunk.columns.push(self.column);
859 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
860 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
863 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
864 self.chunk.patch_jump(skip);
865 self.chunk.emit(Op::Pop, self.line); }
867 Node::MethodCall {
870 object,
871 method,
872 args: pat_args,
873 } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
874 {
875 let enum_name = if let Node::Identifier(n) = &object.node {
876 n.clone()
877 } else {
878 unreachable!()
879 };
880 self.chunk.emit(Op::Dup, self.line);
882 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
883 let vn_idx = self.chunk.add_constant(Constant::String(method.clone()));
884 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
885 let hi = (vn_idx >> 8) as u8;
886 let lo = vn_idx as u8;
887 self.chunk.code.push(hi);
888 self.chunk.code.push(lo);
889 self.chunk.lines.push(self.line);
890 self.chunk.columns.push(self.column);
891 self.chunk.lines.push(self.line);
892 self.chunk.columns.push(self.column);
893 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
894 self.chunk.emit(Op::Pop, self.line); for (i, pat_arg) in pat_args.iter().enumerate() {
898 if let Node::Identifier(binding_name) = &pat_arg.node {
899 self.chunk.emit(Op::Dup, self.line);
900 let fields_idx = self
901 .chunk
902 .add_constant(Constant::String("fields".to_string()));
903 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
904 let idx_const =
905 self.chunk.add_constant(Constant::Int(i as i64));
906 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
907 self.chunk.emit(Op::Subscript, self.line);
908 let name_idx = self
909 .chunk
910 .add_constant(Constant::String(binding_name.clone()));
911 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
912 }
913 }
914
915 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
917 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
918 self.chunk.patch_jump(skip);
919 self.chunk.emit(Op::Pop, self.line); }
921 Node::Identifier(name) => {
923 self.chunk.emit(Op::Dup, self.line); let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
926 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
927 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
929 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
930 }
931 _ => {
933 self.chunk.emit(Op::Dup, self.line);
934 self.compile_node(&arm.pattern)?;
935 self.chunk.emit(Op::Equal, self.line);
936 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
937 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
940 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
941 self.chunk.patch_jump(skip);
942 self.chunk.emit(Op::Pop, self.line); }
944 }
945 }
946 self.chunk.emit(Op::Pop, self.line);
948 self.chunk.emit(Op::Nil, self.line);
949 for j in end_jumps {
950 self.chunk.patch_jump(j);
951 }
952 }
953
954 Node::RangeExpr {
955 start,
956 end,
957 inclusive,
958 } => {
959 let name_idx = self
961 .chunk
962 .add_constant(Constant::String("__range__".to_string()));
963 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
964 self.compile_node(start)?;
965 self.compile_node(end)?;
966 if *inclusive {
967 self.chunk.emit(Op::True, self.line);
968 } else {
969 self.chunk.emit(Op::False, self.line);
970 }
971 self.chunk.emit_u8(Op::Call, 3, self.line);
972 }
973
974 Node::GuardStmt {
975 condition,
976 else_body,
977 } => {
978 self.compile_node(condition)?;
981 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
982 self.chunk.emit(Op::Pop, self.line); self.compile_block(else_body)?;
985 if !else_body.is_empty() && Self::produces_value(&else_body.last().unwrap().node) {
987 self.chunk.emit(Op::Pop, self.line);
988 }
989 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
990 self.chunk.patch_jump(skip_jump);
991 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end_jump);
993 self.chunk.emit(Op::Nil, self.line);
994 }
995
996 Node::Block(stmts) => {
997 if stmts.is_empty() {
998 self.chunk.emit(Op::Nil, self.line);
999 } else {
1000 self.compile_block(stmts)?;
1001 }
1002 }
1003
1004 Node::DeadlineBlock { duration, body } => {
1005 self.compile_node(duration)?;
1006 self.chunk.emit(Op::DeadlineSetup, self.line);
1007 if body.is_empty() {
1008 self.chunk.emit(Op::Nil, self.line);
1009 } else {
1010 self.compile_block(body)?;
1011 }
1012 self.chunk.emit(Op::DeadlineEnd, self.line);
1013 }
1014
1015 Node::MutexBlock { body } => {
1016 if body.is_empty() {
1018 self.chunk.emit(Op::Nil, self.line);
1019 } else {
1020 for sn in body {
1023 self.compile_node(sn)?;
1024 if Self::produces_value(&sn.node) {
1025 self.chunk.emit(Op::Pop, self.line);
1026 }
1027 }
1028 self.chunk.emit(Op::Nil, self.line);
1029 }
1030 }
1031
1032 Node::YieldExpr { .. } => {
1033 self.chunk.emit(Op::Nil, self.line);
1035 }
1036
1037 Node::AskExpr { fields } => {
1038 for entry in fields {
1041 self.compile_node(&entry.key)?;
1042 self.compile_node(&entry.value)?;
1043 }
1044 self.chunk
1045 .emit_u16(Op::BuildDict, fields.len() as u16, self.line);
1046 }
1047
1048 Node::EnumConstruct {
1049 enum_name,
1050 variant,
1051 args,
1052 } => {
1053 for arg in args {
1055 self.compile_node(arg)?;
1056 }
1057 let enum_idx = self.chunk.add_constant(Constant::String(enum_name.clone()));
1058 let var_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1059 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
1061 let hi = (var_idx >> 8) as u8;
1062 let lo = var_idx as u8;
1063 self.chunk.code.push(hi);
1064 self.chunk.code.push(lo);
1065 self.chunk.lines.push(self.line);
1066 self.chunk.columns.push(self.column);
1067 self.chunk.lines.push(self.line);
1068 self.chunk.columns.push(self.column);
1069 let fc = args.len() as u16;
1070 let fhi = (fc >> 8) as u8;
1071 let flo = fc as u8;
1072 self.chunk.code.push(fhi);
1073 self.chunk.code.push(flo);
1074 self.chunk.lines.push(self.line);
1075 self.chunk.columns.push(self.column);
1076 self.chunk.lines.push(self.line);
1077 self.chunk.columns.push(self.column);
1078 }
1079
1080 Node::StructConstruct {
1081 struct_name,
1082 fields,
1083 } => {
1084 let struct_key = self
1086 .chunk
1087 .add_constant(Constant::String("__struct__".to_string()));
1088 let struct_val = self
1089 .chunk
1090 .add_constant(Constant::String(struct_name.clone()));
1091 self.chunk.emit_u16(Op::Constant, struct_key, self.line);
1092 self.chunk.emit_u16(Op::Constant, struct_val, self.line);
1093
1094 for entry in fields {
1095 self.compile_node(&entry.key)?;
1096 self.compile_node(&entry.value)?;
1097 }
1098 self.chunk
1099 .emit_u16(Op::BuildDict, (fields.len() + 1) as u16, self.line);
1100 }
1101
1102 Node::ImportDecl { path } => {
1103 let idx = self.chunk.add_constant(Constant::String(path.clone()));
1104 self.chunk.emit_u16(Op::Import, idx, self.line);
1105 }
1106
1107 Node::SelectiveImport { names, path } => {
1108 let path_idx = self.chunk.add_constant(Constant::String(path.clone()));
1109 let names_str = names.join(",");
1110 let names_idx = self.chunk.add_constant(Constant::String(names_str));
1111 self.chunk
1112 .emit_u16(Op::SelectiveImport, path_idx, self.line);
1113 let hi = (names_idx >> 8) as u8;
1114 let lo = names_idx as u8;
1115 self.chunk.code.push(hi);
1116 self.chunk.code.push(lo);
1117 self.chunk.lines.push(self.line);
1118 self.chunk.columns.push(self.column);
1119 self.chunk.lines.push(self.line);
1120 self.chunk.columns.push(self.column);
1121 }
1122
1123 Node::Pipeline { .. }
1125 | Node::OverrideDecl { .. }
1126 | Node::TypeDecl { .. }
1127 | Node::EnumDecl { .. }
1128 | Node::StructDecl { .. }
1129 | Node::InterfaceDecl { .. } => {
1130 self.chunk.emit(Op::Nil, self.line);
1131 }
1132
1133 Node::TryCatch {
1134 body,
1135 error_var,
1136 error_type,
1137 catch_body,
1138 } => {
1139 let type_name = error_type.as_ref().and_then(|te| {
1141 if let harn_parser::TypeExpr::Named(name) = te {
1143 Some(name.clone())
1144 } else {
1145 None
1146 }
1147 });
1148
1149 let type_name_idx = if let Some(ref tn) = type_name {
1151 self.chunk.add_constant(Constant::String(tn.clone()))
1152 } else {
1153 self.chunk.add_constant(Constant::String(String::new()))
1154 };
1155
1156 self.handler_depth += 1;
1158 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1159 let hi = (type_name_idx >> 8) as u8;
1161 let lo = type_name_idx as u8;
1162 self.chunk.code.push(hi);
1163 self.chunk.code.push(lo);
1164 self.chunk.lines.push(self.line);
1165 self.chunk.columns.push(self.column);
1166 self.chunk.lines.push(self.line);
1167 self.chunk.columns.push(self.column);
1168
1169 if body.is_empty() {
1171 self.chunk.emit(Op::Nil, self.line);
1172 } else {
1173 self.compile_block(body)?;
1174 if !Self::produces_value(&body.last().unwrap().node) {
1176 self.chunk.emit(Op::Nil, self.line);
1177 }
1178 }
1179
1180 self.handler_depth -= 1;
1182 self.chunk.emit(Op::PopHandler, self.line);
1183
1184 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1186
1187 self.chunk.patch_jump(catch_jump);
1189
1190 if let Some(var_name) = error_var {
1193 let idx = self.chunk.add_constant(Constant::String(var_name.clone()));
1194 self.chunk.emit_u16(Op::DefLet, idx, self.line);
1195 } else {
1196 self.chunk.emit(Op::Pop, self.line);
1197 }
1198
1199 if catch_body.is_empty() {
1201 self.chunk.emit(Op::Nil, self.line);
1202 } else {
1203 self.compile_block(catch_body)?;
1204 if !Self::produces_value(&catch_body.last().unwrap().node) {
1205 self.chunk.emit(Op::Nil, self.line);
1206 }
1207 }
1208
1209 self.chunk.patch_jump(end_jump);
1211 }
1212
1213 Node::Retry { count, body } => {
1214 self.compile_node(count)?;
1216 let counter_name = "__retry_counter__";
1217 let counter_idx = self
1218 .chunk
1219 .add_constant(Constant::String(counter_name.to_string()));
1220 self.chunk.emit_u16(Op::DefVar, counter_idx, self.line);
1221
1222 self.chunk.emit(Op::Nil, self.line);
1224 let err_name = "__retry_last_error__";
1225 let err_idx = self
1226 .chunk
1227 .add_constant(Constant::String(err_name.to_string()));
1228 self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
1229
1230 let loop_start = self.chunk.current_offset();
1232
1233 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1235 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1237 let hi = (empty_type >> 8) as u8;
1238 let lo = empty_type as u8;
1239 self.chunk.code.push(hi);
1240 self.chunk.code.push(lo);
1241 self.chunk.lines.push(self.line);
1242 self.chunk.columns.push(self.column);
1243 self.chunk.lines.push(self.line);
1244 self.chunk.columns.push(self.column);
1245
1246 self.compile_block(body)?;
1248
1249 self.chunk.emit(Op::PopHandler, self.line);
1251 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1252
1253 self.chunk.patch_jump(catch_jump);
1255 self.chunk.emit(Op::Dup, self.line);
1257 self.chunk.emit_u16(Op::SetVar, err_idx, self.line);
1258 self.chunk.emit(Op::Pop, self.line);
1260
1261 self.chunk.emit_u16(Op::GetVar, counter_idx, self.line);
1263 let one_idx = self.chunk.add_constant(Constant::Int(1));
1264 self.chunk.emit_u16(Op::Constant, one_idx, self.line);
1265 self.chunk.emit(Op::Sub, self.line);
1266 self.chunk.emit(Op::Dup, self.line);
1267 self.chunk.emit_u16(Op::SetVar, counter_idx, self.line);
1268
1269 let zero_idx = self.chunk.add_constant(Constant::Int(0));
1271 self.chunk.emit_u16(Op::Constant, zero_idx, self.line);
1272 self.chunk.emit(Op::Greater, self.line);
1273 let retry_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1274 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
1276
1277 self.chunk.patch_jump(retry_jump);
1279 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::GetVar, err_idx, self.line);
1281 self.chunk.emit(Op::Throw, self.line);
1282
1283 self.chunk.patch_jump(end_jump);
1284 self.chunk.emit(Op::Nil, self.line);
1286 }
1287
1288 Node::Parallel {
1289 count,
1290 variable,
1291 body,
1292 } => {
1293 self.compile_node(count)?;
1294 let mut fn_compiler = Compiler::new();
1295 fn_compiler.enum_names = self.enum_names.clone();
1296 fn_compiler.compile_block(body)?;
1297 fn_compiler.chunk.emit(Op::Return, self.line);
1298 let params = vec![variable.clone().unwrap_or_else(|| "__i__".to_string())];
1299 let func = CompiledFunction {
1300 name: "<parallel>".to_string(),
1301 params,
1302 chunk: fn_compiler.chunk,
1303 };
1304 let fn_idx = self.chunk.functions.len();
1305 self.chunk.functions.push(func);
1306 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1307 self.chunk.emit(Op::Parallel, self.line);
1308 }
1309
1310 Node::ParallelMap {
1311 list,
1312 variable,
1313 body,
1314 } => {
1315 self.compile_node(list)?;
1316 let mut fn_compiler = Compiler::new();
1317 fn_compiler.enum_names = self.enum_names.clone();
1318 fn_compiler.compile_block(body)?;
1319 fn_compiler.chunk.emit(Op::Return, self.line);
1320 let func = CompiledFunction {
1321 name: "<parallel_map>".to_string(),
1322 params: vec![variable.clone()],
1323 chunk: fn_compiler.chunk,
1324 };
1325 let fn_idx = self.chunk.functions.len();
1326 self.chunk.functions.push(func);
1327 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1328 self.chunk.emit(Op::ParallelMap, self.line);
1329 }
1330
1331 Node::SpawnExpr { body } => {
1332 let mut fn_compiler = Compiler::new();
1333 fn_compiler.enum_names = self.enum_names.clone();
1334 fn_compiler.compile_block(body)?;
1335 fn_compiler.chunk.emit(Op::Return, self.line);
1336 let func = CompiledFunction {
1337 name: "<spawn>".to_string(),
1338 params: vec![],
1339 chunk: fn_compiler.chunk,
1340 };
1341 let fn_idx = self.chunk.functions.len();
1342 self.chunk.functions.push(func);
1343 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1344 self.chunk.emit(Op::Spawn, self.line);
1345 }
1346 }
1347 Ok(())
1348 }
1349
1350 fn produces_value(node: &Node) -> bool {
1352 match node {
1353 Node::LetBinding { .. }
1355 | Node::VarBinding { .. }
1356 | Node::Assignment { .. }
1357 | Node::ReturnStmt { .. }
1358 | Node::FnDecl { .. }
1359 | Node::ThrowStmt { .. }
1360 | Node::BreakStmt
1361 | Node::ContinueStmt => false,
1362 Node::TryCatch { .. }
1364 | Node::Retry { .. }
1365 | Node::GuardStmt { .. }
1366 | Node::DeadlineBlock { .. }
1367 | Node::MutexBlock { .. } => true,
1368 _ => true,
1370 }
1371 }
1372}
1373
1374impl Compiler {
1375 pub fn compile_fn_body(
1377 &mut self,
1378 params: &[TypedParam],
1379 body: &[SNode],
1380 ) -> Result<CompiledFunction, CompileError> {
1381 let mut fn_compiler = Compiler::new();
1382 fn_compiler.compile_block(body)?;
1383 fn_compiler.chunk.emit(Op::Nil, 0);
1384 fn_compiler.chunk.emit(Op::Return, 0);
1385 Ok(CompiledFunction {
1386 name: String::new(),
1387 params: TypedParam::names(params),
1388 chunk: fn_compiler.chunk,
1389 })
1390 }
1391
1392 fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1394 if body.is_empty() {
1395 self.chunk.emit(Op::Nil, self.line);
1396 } else {
1397 self.compile_block(body)?;
1398 if !Self::produces_value(&body.last().unwrap().node) {
1400 self.chunk.emit(Op::Nil, self.line);
1401 }
1402 }
1403 Ok(())
1404 }
1405
1406 fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
1408 match op {
1409 "+" => self.chunk.emit(Op::Add, self.line),
1410 "-" => self.chunk.emit(Op::Sub, self.line),
1411 "*" => self.chunk.emit(Op::Mul, self.line),
1412 "/" => self.chunk.emit(Op::Div, self.line),
1413 "%" => self.chunk.emit(Op::Mod, self.line),
1414 _ => {
1415 return Err(CompileError {
1416 message: format!("Unknown compound operator: {op}"),
1417 line: self.line,
1418 })
1419 }
1420 }
1421 Ok(())
1422 }
1423
1424 fn root_var_name(&self, node: &SNode) -> Option<String> {
1426 match &node.node {
1427 Node::Identifier(name) => Some(name.clone()),
1428 Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
1429 self.root_var_name(object)
1430 }
1431 Node::SubscriptAccess { object, .. } => self.root_var_name(object),
1432 _ => None,
1433 }
1434 }
1435}
1436
1437impl Compiler {
1438 fn collect_enum_names(nodes: &[SNode], names: &mut std::collections::HashSet<String>) {
1440 for sn in nodes {
1441 match &sn.node {
1442 Node::EnumDecl { name, .. } => {
1443 names.insert(name.clone());
1444 }
1445 Node::Pipeline { body, .. } => {
1446 Self::collect_enum_names(body, names);
1447 }
1448 Node::FnDecl { body, .. } => {
1449 Self::collect_enum_names(body, names);
1450 }
1451 Node::Block(stmts) => {
1452 Self::collect_enum_names(stmts, names);
1453 }
1454 _ => {}
1455 }
1456 }
1457 }
1458}
1459
1460impl Default for Compiler {
1461 fn default() -> Self {
1462 Self::new()
1463 }
1464}
1465
1466#[cfg(test)]
1467mod tests {
1468 use super::*;
1469 use harn_lexer::Lexer;
1470 use harn_parser::Parser;
1471
1472 fn compile_source(source: &str) -> Chunk {
1473 let mut lexer = Lexer::new(source);
1474 let tokens = lexer.tokenize().unwrap();
1475 let mut parser = Parser::new(tokens);
1476 let program = parser.parse().unwrap();
1477 Compiler::new().compile(&program).unwrap()
1478 }
1479
1480 #[test]
1481 fn test_compile_arithmetic() {
1482 let chunk = compile_source("pipeline test(task) { let x = 2 + 3 }");
1483 assert!(!chunk.code.is_empty());
1484 assert!(chunk.constants.contains(&Constant::Int(2)));
1486 assert!(chunk.constants.contains(&Constant::Int(3)));
1487 }
1488
1489 #[test]
1490 fn test_compile_function_call() {
1491 let chunk = compile_source("pipeline test(task) { log(42) }");
1492 let disasm = chunk.disassemble("test");
1493 assert!(disasm.contains("CALL"));
1494 }
1495
1496 #[test]
1497 fn test_compile_if_else() {
1498 let chunk =
1499 compile_source(r#"pipeline test(task) { if true { log("yes") } else { log("no") } }"#);
1500 let disasm = chunk.disassemble("test");
1501 assert!(disasm.contains("JUMP_IF_FALSE"));
1502 assert!(disasm.contains("JUMP"));
1503 }
1504
1505 #[test]
1506 fn test_compile_while() {
1507 let chunk = compile_source("pipeline test(task) { var i = 0\n while i < 5 { i = i + 1 } }");
1508 let disasm = chunk.disassemble("test");
1509 assert!(disasm.contains("JUMP_IF_FALSE"));
1510 assert!(disasm.contains("JUMP"));
1512 }
1513
1514 #[test]
1515 fn test_compile_closure() {
1516 let chunk = compile_source("pipeline test(task) { let f = { x -> x * 2 } }");
1517 assert!(!chunk.functions.is_empty());
1518 assert_eq!(chunk.functions[0].params, vec!["x"]);
1519 }
1520
1521 #[test]
1522 fn test_compile_list() {
1523 let chunk = compile_source("pipeline test(task) { let a = [1, 2, 3] }");
1524 let disasm = chunk.disassemble("test");
1525 assert!(disasm.contains("BUILD_LIST"));
1526 }
1527
1528 #[test]
1529 fn test_compile_dict() {
1530 let chunk = compile_source(r#"pipeline test(task) { let d = {name: "test"} }"#);
1531 let disasm = chunk.disassemble("test");
1532 assert!(disasm.contains("BUILD_DICT"));
1533 }
1534
1535 #[test]
1536 fn test_disassemble() {
1537 let chunk = compile_source("pipeline test(task) { log(2 + 3) }");
1538 let disasm = chunk.disassemble("test");
1539 assert!(disasm.contains("CONSTANT"));
1541 assert!(disasm.contains("ADD"));
1542 assert!(disasm.contains("CALL"));
1543 }
1544}