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 if contains_pipe_placeholder(right) {
326 let replaced = replace_pipe_placeholder(right);
327 let closure_node = SNode::dummy(Node::Closure {
328 params: vec![TypedParam {
329 name: "__pipe".into(),
330 type_expr: None,
331 }],
332 body: vec![replaced],
333 });
334 self.compile_node(&closure_node)?;
335 } else {
336 self.compile_node(right)?;
337 }
338 self.chunk.emit(Op::Pipe, self.line);
339 return Ok(());
340 }
341 _ => {}
342 }
343
344 self.compile_node(left)?;
345 self.compile_node(right)?;
346 match op.as_str() {
347 "+" => self.chunk.emit(Op::Add, self.line),
348 "-" => self.chunk.emit(Op::Sub, self.line),
349 "*" => self.chunk.emit(Op::Mul, self.line),
350 "/" => self.chunk.emit(Op::Div, self.line),
351 "%" => self.chunk.emit(Op::Mod, self.line),
352 "==" => self.chunk.emit(Op::Equal, self.line),
353 "!=" => self.chunk.emit(Op::NotEqual, self.line),
354 "<" => self.chunk.emit(Op::Less, self.line),
355 ">" => self.chunk.emit(Op::Greater, self.line),
356 "<=" => self.chunk.emit(Op::LessEqual, self.line),
357 ">=" => self.chunk.emit(Op::GreaterEqual, self.line),
358 _ => {
359 return Err(CompileError {
360 message: format!("Unknown operator: {op}"),
361 line: self.line,
362 })
363 }
364 }
365 }
366
367 Node::UnaryOp { op, operand } => {
368 self.compile_node(operand)?;
369 match op.as_str() {
370 "-" => self.chunk.emit(Op::Negate, self.line),
371 "!" => self.chunk.emit(Op::Not, self.line),
372 _ => {}
373 }
374 }
375
376 Node::Ternary {
377 condition,
378 true_expr,
379 false_expr,
380 } => {
381 self.compile_node(condition)?;
382 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
383 self.chunk.emit(Op::Pop, self.line);
384 self.compile_node(true_expr)?;
385 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
386 self.chunk.patch_jump(else_jump);
387 self.chunk.emit(Op::Pop, self.line);
388 self.compile_node(false_expr)?;
389 self.chunk.patch_jump(end_jump);
390 }
391
392 Node::FunctionCall { name, args } => {
393 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
395 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
396 for arg in args {
398 self.compile_node(arg)?;
399 }
400 self.chunk.emit_u8(Op::Call, args.len() as u8, self.line);
401 }
402
403 Node::MethodCall {
404 object,
405 method,
406 args,
407 } => {
408 if let Node::Identifier(name) = &object.node {
410 if self.enum_names.contains(name) {
411 for arg in args {
413 self.compile_node(arg)?;
414 }
415 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
416 let var_idx = self.chunk.add_constant(Constant::String(method.clone()));
417 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
418 let hi = (var_idx >> 8) as u8;
419 let lo = var_idx as u8;
420 self.chunk.code.push(hi);
421 self.chunk.code.push(lo);
422 self.chunk.lines.push(self.line);
423 self.chunk.columns.push(self.column);
424 self.chunk.lines.push(self.line);
425 self.chunk.columns.push(self.column);
426 let fc = args.len() as u16;
427 let fhi = (fc >> 8) as u8;
428 let flo = fc as u8;
429 self.chunk.code.push(fhi);
430 self.chunk.code.push(flo);
431 self.chunk.lines.push(self.line);
432 self.chunk.columns.push(self.column);
433 self.chunk.lines.push(self.line);
434 self.chunk.columns.push(self.column);
435 return Ok(());
436 }
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(name_idx, args.len() as u8, self.line);
445 }
446
447 Node::OptionalMethodCall {
448 object,
449 method,
450 args,
451 } => {
452 self.compile_node(object)?;
453 for arg in args {
454 self.compile_node(arg)?;
455 }
456 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
457 self.chunk
458 .emit_method_call_opt(name_idx, args.len() as u8, self.line);
459 }
460
461 Node::PropertyAccess { object, property } => {
462 if let Node::Identifier(name) = &object.node {
464 if self.enum_names.contains(name) {
465 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
467 let var_idx = self.chunk.add_constant(Constant::String(property.clone()));
468 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
469 let hi = (var_idx >> 8) as u8;
470 let lo = var_idx as u8;
471 self.chunk.code.push(hi);
472 self.chunk.code.push(lo);
473 self.chunk.lines.push(self.line);
474 self.chunk.columns.push(self.column);
475 self.chunk.lines.push(self.line);
476 self.chunk.columns.push(self.column);
477 self.chunk.code.push(0);
479 self.chunk.code.push(0);
480 self.chunk.lines.push(self.line);
481 self.chunk.columns.push(self.column);
482 self.chunk.lines.push(self.line);
483 self.chunk.columns.push(self.column);
484 return Ok(());
485 }
486 }
487 self.compile_node(object)?;
488 let idx = self.chunk.add_constant(Constant::String(property.clone()));
489 self.chunk.emit_u16(Op::GetProperty, idx, self.line);
490 }
491
492 Node::OptionalPropertyAccess { object, property } => {
493 self.compile_node(object)?;
494 let idx = self.chunk.add_constant(Constant::String(property.clone()));
495 self.chunk.emit_u16(Op::GetPropertyOpt, idx, self.line);
496 }
497
498 Node::SubscriptAccess { object, index } => {
499 self.compile_node(object)?;
500 self.compile_node(index)?;
501 self.chunk.emit(Op::Subscript, self.line);
502 }
503
504 Node::SliceAccess { object, start, end } => {
505 self.compile_node(object)?;
506 if let Some(s) = start {
507 self.compile_node(s)?;
508 } else {
509 self.chunk.emit(Op::Nil, self.line);
510 }
511 if let Some(e) = end {
512 self.compile_node(e)?;
513 } else {
514 self.chunk.emit(Op::Nil, self.line);
515 }
516 self.chunk.emit(Op::Slice, self.line);
517 }
518
519 Node::IfElse {
520 condition,
521 then_body,
522 else_body,
523 } => {
524 self.compile_node(condition)?;
525 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
526 self.chunk.emit(Op::Pop, self.line);
527 self.compile_block(then_body)?;
528 if let Some(else_body) = else_body {
529 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
530 self.chunk.patch_jump(else_jump);
531 self.chunk.emit(Op::Pop, self.line);
532 self.compile_block(else_body)?;
533 self.chunk.patch_jump(end_jump);
534 } else {
535 self.chunk.patch_jump(else_jump);
536 self.chunk.emit(Op::Pop, self.line);
537 self.chunk.emit(Op::Nil, self.line);
538 }
539 }
540
541 Node::WhileLoop { condition, body } => {
542 let loop_start = self.chunk.current_offset();
543 self.loop_stack.push(LoopContext {
544 start_offset: loop_start,
545 break_patches: Vec::new(),
546 has_iterator: false,
547 handler_depth: self.handler_depth,
548 });
549 self.compile_node(condition)?;
550 let exit_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
551 self.chunk.emit(Op::Pop, self.line); for sn in body {
554 self.compile_node(sn)?;
555 if Self::produces_value(&sn.node) {
556 self.chunk.emit(Op::Pop, self.line);
557 }
558 }
559 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
561 self.chunk.patch_jump(exit_jump);
562 self.chunk.emit(Op::Pop, self.line); let ctx = self.loop_stack.pop().unwrap();
565 for patch_pos in ctx.break_patches {
566 self.chunk.patch_jump(patch_pos);
567 }
568 self.chunk.emit(Op::Nil, self.line);
569 }
570
571 Node::ForIn {
572 variable,
573 iterable,
574 body,
575 } => {
576 self.compile_node(iterable)?;
578 let var_idx = self.chunk.add_constant(Constant::String(variable.clone()));
580 self.chunk.emit(Op::IterInit, self.line);
582 let loop_start = self.chunk.current_offset();
583 self.loop_stack.push(LoopContext {
584 start_offset: loop_start,
585 break_patches: Vec::new(),
586 has_iterator: true,
587 handler_depth: self.handler_depth,
588 });
589 let exit_jump_pos = self.chunk.emit_jump(Op::IterNext, self.line);
591 self.chunk.emit_u16(Op::DefVar, var_idx, self.line);
593 for sn in body {
595 self.compile_node(sn)?;
596 if Self::produces_value(&sn.node) {
597 self.chunk.emit(Op::Pop, self.line);
598 }
599 }
600 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
602 self.chunk.patch_jump(exit_jump_pos);
603 let ctx = self.loop_stack.pop().unwrap();
605 for patch_pos in ctx.break_patches {
606 self.chunk.patch_jump(patch_pos);
607 }
608 self.chunk.emit(Op::Nil, self.line);
610 }
611
612 Node::ReturnStmt { value } => {
613 if let Some(val) = value {
614 if let Node::FunctionCall { name, args } = &val.node {
617 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
618 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
619 for arg in args {
620 self.compile_node(arg)?;
621 }
622 self.chunk
623 .emit_u8(Op::TailCall, args.len() as u8, self.line);
624 } else if let Node::BinaryOp { op, left, right } = &val.node {
625 if op == "|>" {
626 self.compile_node(left)?;
629 self.compile_node(right)?;
631 self.chunk.emit(Op::Swap, self.line);
634 self.chunk.emit_u8(Op::TailCall, 1, self.line);
635 } else {
636 self.compile_node(val)?;
637 }
638 } else {
639 self.compile_node(val)?;
640 }
641 } else {
642 self.chunk.emit(Op::Nil, self.line);
643 }
644 self.chunk.emit(Op::Return, self.line);
645 }
646
647 Node::BreakStmt => {
648 if self.loop_stack.is_empty() {
649 return Err(CompileError {
650 message: "break outside of loop".to_string(),
651 line: self.line,
652 });
653 }
654 let ctx = self.loop_stack.last().unwrap();
655 for _ in ctx.handler_depth..self.handler_depth {
657 self.chunk.emit(Op::PopHandler, self.line);
658 }
659 if ctx.has_iterator {
661 self.chunk.emit(Op::PopIterator, self.line);
662 }
663 let patch = self.chunk.emit_jump(Op::Jump, self.line);
664 self.loop_stack
665 .last_mut()
666 .unwrap()
667 .break_patches
668 .push(patch);
669 }
670
671 Node::ContinueStmt => {
672 if self.loop_stack.is_empty() {
673 return Err(CompileError {
674 message: "continue outside of loop".to_string(),
675 line: self.line,
676 });
677 }
678 let ctx = self.loop_stack.last().unwrap();
679 for _ in ctx.handler_depth..self.handler_depth {
681 self.chunk.emit(Op::PopHandler, self.line);
682 }
683 let loop_start = ctx.start_offset;
684 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
685 }
686
687 Node::ListLiteral(elements) => {
688 for el in elements {
689 self.compile_node(el)?;
690 }
691 self.chunk
692 .emit_u16(Op::BuildList, elements.len() as u16, self.line);
693 }
694
695 Node::DictLiteral(entries) => {
696 for entry in entries {
697 self.compile_node(&entry.key)?;
698 self.compile_node(&entry.value)?;
699 }
700 self.chunk
701 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
702 }
703
704 Node::InterpolatedString(segments) => {
705 let mut part_count = 0u16;
706 for seg in segments {
707 match seg {
708 StringSegment::Literal(s) => {
709 let idx = self.chunk.add_constant(Constant::String(s.clone()));
710 self.chunk.emit_u16(Op::Constant, idx, self.line);
711 part_count += 1;
712 }
713 StringSegment::Expression(expr_str) => {
714 let mut lexer = harn_lexer::Lexer::new(expr_str);
716 if let Ok(tokens) = lexer.tokenize() {
717 let mut parser = harn_parser::Parser::new(tokens);
718 if let Ok(snode) = parser.parse_single_expression() {
719 self.compile_node(&snode)?;
720 let to_str = self
722 .chunk
723 .add_constant(Constant::String("to_string".into()));
724 self.chunk.emit_u16(Op::Constant, to_str, self.line);
725 self.chunk.emit(Op::Swap, self.line);
726 self.chunk.emit_u8(Op::Call, 1, self.line);
727 part_count += 1;
728 } else {
729 let idx =
731 self.chunk.add_constant(Constant::String(expr_str.clone()));
732 self.chunk.emit_u16(Op::Constant, idx, self.line);
733 part_count += 1;
734 }
735 }
736 }
737 }
738 }
739 if part_count > 1 {
740 self.chunk.emit_u16(Op::Concat, part_count, self.line);
741 }
742 }
743
744 Node::FnDecl {
745 name, params, body, ..
746 } => {
747 let mut fn_compiler = Compiler::new();
749 fn_compiler.enum_names = self.enum_names.clone();
750 fn_compiler.compile_block(body)?;
751 fn_compiler.chunk.emit(Op::Nil, self.line);
752 fn_compiler.chunk.emit(Op::Return, self.line);
753
754 let func = CompiledFunction {
755 name: name.clone(),
756 params: TypedParam::names(params),
757 chunk: fn_compiler.chunk,
758 };
759 let fn_idx = self.chunk.functions.len();
760 self.chunk.functions.push(func);
761
762 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
763 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
764 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
765 }
766
767 Node::Closure { params, body } => {
768 let mut fn_compiler = Compiler::new();
769 fn_compiler.enum_names = self.enum_names.clone();
770 fn_compiler.compile_block(body)?;
771 fn_compiler.chunk.emit(Op::Return, self.line);
773
774 let func = CompiledFunction {
775 name: "<closure>".to_string(),
776 params: TypedParam::names(params),
777 chunk: fn_compiler.chunk,
778 };
779 let fn_idx = self.chunk.functions.len();
780 self.chunk.functions.push(func);
781
782 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
783 }
784
785 Node::ThrowStmt { value } => {
786 self.compile_node(value)?;
787 self.chunk.emit(Op::Throw, self.line);
788 }
789
790 Node::MatchExpr { value, arms } => {
791 self.compile_node(value)?;
792 let mut end_jumps = Vec::new();
793 for arm in arms {
794 match &arm.pattern.node {
795 Node::Identifier(name) if name == "_" => {
797 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
799 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
800 }
801 Node::EnumConstruct {
803 enum_name,
804 variant,
805 args: pat_args,
806 } => {
807 self.chunk.emit(Op::Dup, self.line);
809 let en_idx =
810 self.chunk.add_constant(Constant::String(enum_name.clone()));
811 let vn_idx = self.chunk.add_constant(Constant::String(variant.clone()));
812 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
813 let hi = (vn_idx >> 8) as u8;
814 let lo = vn_idx as u8;
815 self.chunk.code.push(hi);
816 self.chunk.code.push(lo);
817 self.chunk.lines.push(self.line);
818 self.chunk.columns.push(self.column);
819 self.chunk.lines.push(self.line);
820 self.chunk.columns.push(self.column);
821 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
823 self.chunk.emit(Op::Pop, self.line); for (i, pat_arg) in pat_args.iter().enumerate() {
828 if let Node::Identifier(binding_name) = &pat_arg.node {
829 self.chunk.emit(Op::Dup, self.line);
831 let fields_idx = self
832 .chunk
833 .add_constant(Constant::String("fields".to_string()));
834 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
835 let idx_const =
836 self.chunk.add_constant(Constant::Int(i as i64));
837 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
838 self.chunk.emit(Op::Subscript, self.line);
839 let name_idx = self
840 .chunk
841 .add_constant(Constant::String(binding_name.clone()));
842 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
843 }
844 }
845
846 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
848 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
849 self.chunk.patch_jump(skip);
850 self.chunk.emit(Op::Pop, self.line); }
852 Node::PropertyAccess { object, property } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
854 {
855 let enum_name = if let Node::Identifier(n) = &object.node {
856 n.clone()
857 } else {
858 unreachable!()
859 };
860 self.chunk.emit(Op::Dup, self.line);
861 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
862 let vn_idx =
863 self.chunk.add_constant(Constant::String(property.clone()));
864 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
865 let hi = (vn_idx >> 8) as u8;
866 let lo = vn_idx as u8;
867 self.chunk.code.push(hi);
868 self.chunk.code.push(lo);
869 self.chunk.lines.push(self.line);
870 self.chunk.columns.push(self.column);
871 self.chunk.lines.push(self.line);
872 self.chunk.columns.push(self.column);
873 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
874 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
877 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
878 self.chunk.patch_jump(skip);
879 self.chunk.emit(Op::Pop, self.line); }
881 Node::MethodCall {
884 object,
885 method,
886 args: pat_args,
887 } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
888 {
889 let enum_name = if let Node::Identifier(n) = &object.node {
890 n.clone()
891 } else {
892 unreachable!()
893 };
894 self.chunk.emit(Op::Dup, self.line);
896 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
897 let vn_idx = self.chunk.add_constant(Constant::String(method.clone()));
898 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
899 let hi = (vn_idx >> 8) as u8;
900 let lo = vn_idx as u8;
901 self.chunk.code.push(hi);
902 self.chunk.code.push(lo);
903 self.chunk.lines.push(self.line);
904 self.chunk.columns.push(self.column);
905 self.chunk.lines.push(self.line);
906 self.chunk.columns.push(self.column);
907 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
908 self.chunk.emit(Op::Pop, self.line); for (i, pat_arg) in pat_args.iter().enumerate() {
912 if let Node::Identifier(binding_name) = &pat_arg.node {
913 self.chunk.emit(Op::Dup, self.line);
914 let fields_idx = self
915 .chunk
916 .add_constant(Constant::String("fields".to_string()));
917 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
918 let idx_const =
919 self.chunk.add_constant(Constant::Int(i as i64));
920 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
921 self.chunk.emit(Op::Subscript, self.line);
922 let name_idx = self
923 .chunk
924 .add_constant(Constant::String(binding_name.clone()));
925 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
926 }
927 }
928
929 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
931 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
932 self.chunk.patch_jump(skip);
933 self.chunk.emit(Op::Pop, self.line); }
935 Node::Identifier(name) => {
937 self.chunk.emit(Op::Dup, self.line); let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
940 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
941 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
943 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
944 }
945 _ => {
947 self.chunk.emit(Op::Dup, self.line);
948 self.compile_node(&arm.pattern)?;
949 self.chunk.emit(Op::Equal, self.line);
950 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
951 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
954 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
955 self.chunk.patch_jump(skip);
956 self.chunk.emit(Op::Pop, self.line); }
958 }
959 }
960 self.chunk.emit(Op::Pop, self.line);
962 self.chunk.emit(Op::Nil, self.line);
963 for j in end_jumps {
964 self.chunk.patch_jump(j);
965 }
966 }
967
968 Node::RangeExpr {
969 start,
970 end,
971 inclusive,
972 } => {
973 let name_idx = self
975 .chunk
976 .add_constant(Constant::String("__range__".to_string()));
977 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
978 self.compile_node(start)?;
979 self.compile_node(end)?;
980 if *inclusive {
981 self.chunk.emit(Op::True, self.line);
982 } else {
983 self.chunk.emit(Op::False, self.line);
984 }
985 self.chunk.emit_u8(Op::Call, 3, self.line);
986 }
987
988 Node::GuardStmt {
989 condition,
990 else_body,
991 } => {
992 self.compile_node(condition)?;
995 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
996 self.chunk.emit(Op::Pop, self.line); self.compile_block(else_body)?;
999 if !else_body.is_empty() && Self::produces_value(&else_body.last().unwrap().node) {
1001 self.chunk.emit(Op::Pop, self.line);
1002 }
1003 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1004 self.chunk.patch_jump(skip_jump);
1005 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end_jump);
1007 self.chunk.emit(Op::Nil, self.line);
1008 }
1009
1010 Node::Block(stmts) => {
1011 if stmts.is_empty() {
1012 self.chunk.emit(Op::Nil, self.line);
1013 } else {
1014 self.compile_block(stmts)?;
1015 }
1016 }
1017
1018 Node::DeadlineBlock { duration, body } => {
1019 self.compile_node(duration)?;
1020 self.chunk.emit(Op::DeadlineSetup, self.line);
1021 if body.is_empty() {
1022 self.chunk.emit(Op::Nil, self.line);
1023 } else {
1024 self.compile_block(body)?;
1025 }
1026 self.chunk.emit(Op::DeadlineEnd, self.line);
1027 }
1028
1029 Node::MutexBlock { body } => {
1030 if body.is_empty() {
1032 self.chunk.emit(Op::Nil, self.line);
1033 } else {
1034 for sn in body {
1037 self.compile_node(sn)?;
1038 if Self::produces_value(&sn.node) {
1039 self.chunk.emit(Op::Pop, self.line);
1040 }
1041 }
1042 self.chunk.emit(Op::Nil, self.line);
1043 }
1044 }
1045
1046 Node::YieldExpr { .. } => {
1047 self.chunk.emit(Op::Nil, self.line);
1049 }
1050
1051 Node::AskExpr { fields } => {
1052 for entry in fields {
1055 self.compile_node(&entry.key)?;
1056 self.compile_node(&entry.value)?;
1057 }
1058 self.chunk
1059 .emit_u16(Op::BuildDict, fields.len() as u16, self.line);
1060 }
1061
1062 Node::EnumConstruct {
1063 enum_name,
1064 variant,
1065 args,
1066 } => {
1067 for arg in args {
1069 self.compile_node(arg)?;
1070 }
1071 let enum_idx = self.chunk.add_constant(Constant::String(enum_name.clone()));
1072 let var_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1073 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
1075 let hi = (var_idx >> 8) as u8;
1076 let lo = var_idx as u8;
1077 self.chunk.code.push(hi);
1078 self.chunk.code.push(lo);
1079 self.chunk.lines.push(self.line);
1080 self.chunk.columns.push(self.column);
1081 self.chunk.lines.push(self.line);
1082 self.chunk.columns.push(self.column);
1083 let fc = args.len() as u16;
1084 let fhi = (fc >> 8) as u8;
1085 let flo = fc as u8;
1086 self.chunk.code.push(fhi);
1087 self.chunk.code.push(flo);
1088 self.chunk.lines.push(self.line);
1089 self.chunk.columns.push(self.column);
1090 self.chunk.lines.push(self.line);
1091 self.chunk.columns.push(self.column);
1092 }
1093
1094 Node::StructConstruct {
1095 struct_name,
1096 fields,
1097 } => {
1098 let struct_key = self
1100 .chunk
1101 .add_constant(Constant::String("__struct__".to_string()));
1102 let struct_val = self
1103 .chunk
1104 .add_constant(Constant::String(struct_name.clone()));
1105 self.chunk.emit_u16(Op::Constant, struct_key, self.line);
1106 self.chunk.emit_u16(Op::Constant, struct_val, self.line);
1107
1108 for entry in fields {
1109 self.compile_node(&entry.key)?;
1110 self.compile_node(&entry.value)?;
1111 }
1112 self.chunk
1113 .emit_u16(Op::BuildDict, (fields.len() + 1) as u16, self.line);
1114 }
1115
1116 Node::ImportDecl { path } => {
1117 let idx = self.chunk.add_constant(Constant::String(path.clone()));
1118 self.chunk.emit_u16(Op::Import, idx, self.line);
1119 }
1120
1121 Node::SelectiveImport { names, path } => {
1122 let path_idx = self.chunk.add_constant(Constant::String(path.clone()));
1123 let names_str = names.join(",");
1124 let names_idx = self.chunk.add_constant(Constant::String(names_str));
1125 self.chunk
1126 .emit_u16(Op::SelectiveImport, path_idx, self.line);
1127 let hi = (names_idx >> 8) as u8;
1128 let lo = names_idx as u8;
1129 self.chunk.code.push(hi);
1130 self.chunk.code.push(lo);
1131 self.chunk.lines.push(self.line);
1132 self.chunk.columns.push(self.column);
1133 self.chunk.lines.push(self.line);
1134 self.chunk.columns.push(self.column);
1135 }
1136
1137 Node::Pipeline { .. }
1139 | Node::OverrideDecl { .. }
1140 | Node::TypeDecl { .. }
1141 | Node::EnumDecl { .. }
1142 | Node::StructDecl { .. }
1143 | Node::InterfaceDecl { .. } => {
1144 self.chunk.emit(Op::Nil, self.line);
1145 }
1146
1147 Node::TryCatch {
1148 body,
1149 error_var,
1150 error_type,
1151 catch_body,
1152 } => {
1153 let type_name = error_type.as_ref().and_then(|te| {
1155 if let harn_parser::TypeExpr::Named(name) = te {
1157 Some(name.clone())
1158 } else {
1159 None
1160 }
1161 });
1162
1163 let type_name_idx = if let Some(ref tn) = type_name {
1165 self.chunk.add_constant(Constant::String(tn.clone()))
1166 } else {
1167 self.chunk.add_constant(Constant::String(String::new()))
1168 };
1169
1170 self.handler_depth += 1;
1172 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1173 let hi = (type_name_idx >> 8) as u8;
1175 let lo = type_name_idx as u8;
1176 self.chunk.code.push(hi);
1177 self.chunk.code.push(lo);
1178 self.chunk.lines.push(self.line);
1179 self.chunk.columns.push(self.column);
1180 self.chunk.lines.push(self.line);
1181 self.chunk.columns.push(self.column);
1182
1183 if body.is_empty() {
1185 self.chunk.emit(Op::Nil, self.line);
1186 } else {
1187 self.compile_block(body)?;
1188 if !Self::produces_value(&body.last().unwrap().node) {
1190 self.chunk.emit(Op::Nil, self.line);
1191 }
1192 }
1193
1194 self.handler_depth -= 1;
1196 self.chunk.emit(Op::PopHandler, self.line);
1197
1198 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1200
1201 self.chunk.patch_jump(catch_jump);
1203
1204 if let Some(var_name) = error_var {
1207 let idx = self.chunk.add_constant(Constant::String(var_name.clone()));
1208 self.chunk.emit_u16(Op::DefLet, idx, self.line);
1209 } else {
1210 self.chunk.emit(Op::Pop, self.line);
1211 }
1212
1213 if catch_body.is_empty() {
1215 self.chunk.emit(Op::Nil, self.line);
1216 } else {
1217 self.compile_block(catch_body)?;
1218 if !Self::produces_value(&catch_body.last().unwrap().node) {
1219 self.chunk.emit(Op::Nil, self.line);
1220 }
1221 }
1222
1223 self.chunk.patch_jump(end_jump);
1225 }
1226
1227 Node::Retry { count, body } => {
1228 self.compile_node(count)?;
1230 let counter_name = "__retry_counter__";
1231 let counter_idx = self
1232 .chunk
1233 .add_constant(Constant::String(counter_name.to_string()));
1234 self.chunk.emit_u16(Op::DefVar, counter_idx, self.line);
1235
1236 self.chunk.emit(Op::Nil, self.line);
1238 let err_name = "__retry_last_error__";
1239 let err_idx = self
1240 .chunk
1241 .add_constant(Constant::String(err_name.to_string()));
1242 self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
1243
1244 let loop_start = self.chunk.current_offset();
1246
1247 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1249 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1251 let hi = (empty_type >> 8) as u8;
1252 let lo = empty_type as u8;
1253 self.chunk.code.push(hi);
1254 self.chunk.code.push(lo);
1255 self.chunk.lines.push(self.line);
1256 self.chunk.columns.push(self.column);
1257 self.chunk.lines.push(self.line);
1258 self.chunk.columns.push(self.column);
1259
1260 self.compile_block(body)?;
1262
1263 self.chunk.emit(Op::PopHandler, self.line);
1265 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1266
1267 self.chunk.patch_jump(catch_jump);
1269 self.chunk.emit(Op::Dup, self.line);
1271 self.chunk.emit_u16(Op::SetVar, err_idx, self.line);
1272 self.chunk.emit(Op::Pop, self.line);
1274
1275 self.chunk.emit_u16(Op::GetVar, counter_idx, self.line);
1277 let one_idx = self.chunk.add_constant(Constant::Int(1));
1278 self.chunk.emit_u16(Op::Constant, one_idx, self.line);
1279 self.chunk.emit(Op::Sub, self.line);
1280 self.chunk.emit(Op::Dup, self.line);
1281 self.chunk.emit_u16(Op::SetVar, counter_idx, self.line);
1282
1283 let zero_idx = self.chunk.add_constant(Constant::Int(0));
1285 self.chunk.emit_u16(Op::Constant, zero_idx, self.line);
1286 self.chunk.emit(Op::Greater, self.line);
1287 let retry_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1288 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
1290
1291 self.chunk.patch_jump(retry_jump);
1293 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::GetVar, err_idx, self.line);
1295 self.chunk.emit(Op::Throw, self.line);
1296
1297 self.chunk.patch_jump(end_jump);
1298 self.chunk.emit(Op::Nil, self.line);
1300 }
1301
1302 Node::Parallel {
1303 count,
1304 variable,
1305 body,
1306 } => {
1307 self.compile_node(count)?;
1308 let mut fn_compiler = Compiler::new();
1309 fn_compiler.enum_names = self.enum_names.clone();
1310 fn_compiler.compile_block(body)?;
1311 fn_compiler.chunk.emit(Op::Return, self.line);
1312 let params = vec![variable.clone().unwrap_or_else(|| "__i__".to_string())];
1313 let func = CompiledFunction {
1314 name: "<parallel>".to_string(),
1315 params,
1316 chunk: fn_compiler.chunk,
1317 };
1318 let fn_idx = self.chunk.functions.len();
1319 self.chunk.functions.push(func);
1320 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1321 self.chunk.emit(Op::Parallel, self.line);
1322 }
1323
1324 Node::ParallelMap {
1325 list,
1326 variable,
1327 body,
1328 } => {
1329 self.compile_node(list)?;
1330 let mut fn_compiler = Compiler::new();
1331 fn_compiler.enum_names = self.enum_names.clone();
1332 fn_compiler.compile_block(body)?;
1333 fn_compiler.chunk.emit(Op::Return, self.line);
1334 let func = CompiledFunction {
1335 name: "<parallel_map>".to_string(),
1336 params: vec![variable.clone()],
1337 chunk: fn_compiler.chunk,
1338 };
1339 let fn_idx = self.chunk.functions.len();
1340 self.chunk.functions.push(func);
1341 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1342 self.chunk.emit(Op::ParallelMap, self.line);
1343 }
1344
1345 Node::SpawnExpr { body } => {
1346 let mut fn_compiler = Compiler::new();
1347 fn_compiler.enum_names = self.enum_names.clone();
1348 fn_compiler.compile_block(body)?;
1349 fn_compiler.chunk.emit(Op::Return, self.line);
1350 let func = CompiledFunction {
1351 name: "<spawn>".to_string(),
1352 params: vec![],
1353 chunk: fn_compiler.chunk,
1354 };
1355 let fn_idx = self.chunk.functions.len();
1356 self.chunk.functions.push(func);
1357 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1358 self.chunk.emit(Op::Spawn, self.line);
1359 }
1360 }
1361 Ok(())
1362 }
1363
1364 fn produces_value(node: &Node) -> bool {
1366 match node {
1367 Node::LetBinding { .. }
1369 | Node::VarBinding { .. }
1370 | Node::Assignment { .. }
1371 | Node::ReturnStmt { .. }
1372 | Node::FnDecl { .. }
1373 | Node::ThrowStmt { .. }
1374 | Node::BreakStmt
1375 | Node::ContinueStmt => false,
1376 Node::TryCatch { .. }
1378 | Node::Retry { .. }
1379 | Node::GuardStmt { .. }
1380 | Node::DeadlineBlock { .. }
1381 | Node::MutexBlock { .. } => true,
1382 _ => true,
1384 }
1385 }
1386}
1387
1388impl Compiler {
1389 pub fn compile_fn_body(
1391 &mut self,
1392 params: &[TypedParam],
1393 body: &[SNode],
1394 ) -> Result<CompiledFunction, CompileError> {
1395 let mut fn_compiler = Compiler::new();
1396 fn_compiler.compile_block(body)?;
1397 fn_compiler.chunk.emit(Op::Nil, 0);
1398 fn_compiler.chunk.emit(Op::Return, 0);
1399 Ok(CompiledFunction {
1400 name: String::new(),
1401 params: TypedParam::names(params),
1402 chunk: fn_compiler.chunk,
1403 })
1404 }
1405
1406 fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1408 if body.is_empty() {
1409 self.chunk.emit(Op::Nil, self.line);
1410 } else {
1411 self.compile_block(body)?;
1412 if !Self::produces_value(&body.last().unwrap().node) {
1414 self.chunk.emit(Op::Nil, self.line);
1415 }
1416 }
1417 Ok(())
1418 }
1419
1420 fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
1422 match op {
1423 "+" => self.chunk.emit(Op::Add, self.line),
1424 "-" => self.chunk.emit(Op::Sub, self.line),
1425 "*" => self.chunk.emit(Op::Mul, self.line),
1426 "/" => self.chunk.emit(Op::Div, self.line),
1427 "%" => self.chunk.emit(Op::Mod, self.line),
1428 _ => {
1429 return Err(CompileError {
1430 message: format!("Unknown compound operator: {op}"),
1431 line: self.line,
1432 })
1433 }
1434 }
1435 Ok(())
1436 }
1437
1438 fn root_var_name(&self, node: &SNode) -> Option<String> {
1440 match &node.node {
1441 Node::Identifier(name) => Some(name.clone()),
1442 Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
1443 self.root_var_name(object)
1444 }
1445 Node::SubscriptAccess { object, .. } => self.root_var_name(object),
1446 _ => None,
1447 }
1448 }
1449}
1450
1451impl Compiler {
1452 fn collect_enum_names(nodes: &[SNode], names: &mut std::collections::HashSet<String>) {
1454 for sn in nodes {
1455 match &sn.node {
1456 Node::EnumDecl { name, .. } => {
1457 names.insert(name.clone());
1458 }
1459 Node::Pipeline { body, .. } => {
1460 Self::collect_enum_names(body, names);
1461 }
1462 Node::FnDecl { body, .. } => {
1463 Self::collect_enum_names(body, names);
1464 }
1465 Node::Block(stmts) => {
1466 Self::collect_enum_names(stmts, names);
1467 }
1468 _ => {}
1469 }
1470 }
1471 }
1472}
1473
1474impl Default for Compiler {
1475 fn default() -> Self {
1476 Self::new()
1477 }
1478}
1479
1480fn contains_pipe_placeholder(node: &SNode) -> bool {
1482 match &node.node {
1483 Node::Identifier(name) if name == "_" => true,
1484 Node::FunctionCall { args, .. } => args.iter().any(contains_pipe_placeholder),
1485 Node::MethodCall { object, args, .. } => {
1486 contains_pipe_placeholder(object) || args.iter().any(contains_pipe_placeholder)
1487 }
1488 Node::BinaryOp { left, right, .. } => {
1489 contains_pipe_placeholder(left) || contains_pipe_placeholder(right)
1490 }
1491 Node::UnaryOp { operand, .. } => contains_pipe_placeholder(operand),
1492 Node::ListLiteral(items) => items.iter().any(contains_pipe_placeholder),
1493 Node::PropertyAccess { object, .. } => contains_pipe_placeholder(object),
1494 Node::SubscriptAccess { object, index } => {
1495 contains_pipe_placeholder(object) || contains_pipe_placeholder(index)
1496 }
1497 _ => false,
1498 }
1499}
1500
1501fn replace_pipe_placeholder(node: &SNode) -> SNode {
1503 let new_node = match &node.node {
1504 Node::Identifier(name) if name == "_" => Node::Identifier("__pipe".into()),
1505 Node::FunctionCall { name, args } => Node::FunctionCall {
1506 name: name.clone(),
1507 args: args.iter().map(replace_pipe_placeholder).collect(),
1508 },
1509 Node::MethodCall {
1510 object,
1511 method,
1512 args,
1513 } => Node::MethodCall {
1514 object: Box::new(replace_pipe_placeholder(object)),
1515 method: method.clone(),
1516 args: args.iter().map(replace_pipe_placeholder).collect(),
1517 },
1518 Node::BinaryOp { op, left, right } => Node::BinaryOp {
1519 op: op.clone(),
1520 left: Box::new(replace_pipe_placeholder(left)),
1521 right: Box::new(replace_pipe_placeholder(right)),
1522 },
1523 Node::UnaryOp { op, operand } => Node::UnaryOp {
1524 op: op.clone(),
1525 operand: Box::new(replace_pipe_placeholder(operand)),
1526 },
1527 Node::ListLiteral(items) => {
1528 Node::ListLiteral(items.iter().map(replace_pipe_placeholder).collect())
1529 }
1530 Node::PropertyAccess { object, property } => Node::PropertyAccess {
1531 object: Box::new(replace_pipe_placeholder(object)),
1532 property: property.clone(),
1533 },
1534 Node::SubscriptAccess { object, index } => Node::SubscriptAccess {
1535 object: Box::new(replace_pipe_placeholder(object)),
1536 index: Box::new(replace_pipe_placeholder(index)),
1537 },
1538 _ => return node.clone(),
1539 };
1540 SNode::new(new_node, node.span)
1541}
1542
1543#[cfg(test)]
1544mod tests {
1545 use super::*;
1546 use harn_lexer::Lexer;
1547 use harn_parser::Parser;
1548
1549 fn compile_source(source: &str) -> Chunk {
1550 let mut lexer = Lexer::new(source);
1551 let tokens = lexer.tokenize().unwrap();
1552 let mut parser = Parser::new(tokens);
1553 let program = parser.parse().unwrap();
1554 Compiler::new().compile(&program).unwrap()
1555 }
1556
1557 #[test]
1558 fn test_compile_arithmetic() {
1559 let chunk = compile_source("pipeline test(task) { let x = 2 + 3 }");
1560 assert!(!chunk.code.is_empty());
1561 assert!(chunk.constants.contains(&Constant::Int(2)));
1563 assert!(chunk.constants.contains(&Constant::Int(3)));
1564 }
1565
1566 #[test]
1567 fn test_compile_function_call() {
1568 let chunk = compile_source("pipeline test(task) { log(42) }");
1569 let disasm = chunk.disassemble("test");
1570 assert!(disasm.contains("CALL"));
1571 }
1572
1573 #[test]
1574 fn test_compile_if_else() {
1575 let chunk =
1576 compile_source(r#"pipeline test(task) { if true { log("yes") } else { log("no") } }"#);
1577 let disasm = chunk.disassemble("test");
1578 assert!(disasm.contains("JUMP_IF_FALSE"));
1579 assert!(disasm.contains("JUMP"));
1580 }
1581
1582 #[test]
1583 fn test_compile_while() {
1584 let chunk = compile_source("pipeline test(task) { var i = 0\n while i < 5 { i = i + 1 } }");
1585 let disasm = chunk.disassemble("test");
1586 assert!(disasm.contains("JUMP_IF_FALSE"));
1587 assert!(disasm.contains("JUMP"));
1589 }
1590
1591 #[test]
1592 fn test_compile_closure() {
1593 let chunk = compile_source("pipeline test(task) { let f = { x -> x * 2 } }");
1594 assert!(!chunk.functions.is_empty());
1595 assert_eq!(chunk.functions[0].params, vec!["x"]);
1596 }
1597
1598 #[test]
1599 fn test_compile_list() {
1600 let chunk = compile_source("pipeline test(task) { let a = [1, 2, 3] }");
1601 let disasm = chunk.disassemble("test");
1602 assert!(disasm.contains("BUILD_LIST"));
1603 }
1604
1605 #[test]
1606 fn test_compile_dict() {
1607 let chunk = compile_source(r#"pipeline test(task) { let d = {name: "test"} }"#);
1608 let disasm = chunk.disassemble("test");
1609 assert!(disasm.contains("BUILD_DICT"));
1610 }
1611
1612 #[test]
1613 fn test_disassemble() {
1614 let chunk = compile_source("pipeline test(task) { log(2 + 3) }");
1615 let disasm = chunk.disassemble("test");
1616 assert!(disasm.contains("CONSTANT"));
1618 assert!(disasm.contains("ADD"));
1619 assert!(disasm.contains("CALL"));
1620 }
1621}