1use harn_lexer::StringSegment;
2use harn_parser::{BindingPattern, Node, SNode, TypedParam};
3
4use crate::chunk::{Chunk, CompiledFunction, Constant, Op};
5
6#[derive(Debug)]
8pub struct CompileError {
9 pub message: String,
10 pub line: u32,
11}
12
13impl std::fmt::Display for CompileError {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 write!(f, "Compile error at line {}: {}", self.line, self.message)
16 }
17}
18
19impl std::error::Error for CompileError {}
20
21struct LoopContext {
23 start_offset: usize,
25 break_patches: Vec<usize>,
27 has_iterator: bool,
29 handler_depth: usize,
31}
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 { pattern, value, .. } => {
213 self.compile_node(value)?;
214 self.compile_destructuring(pattern, false)?;
215 }
216
217 Node::VarBinding { pattern, value, .. } => {
218 self.compile_node(value)?;
219 self.compile_destructuring(pattern, true)?;
220 }
221
222 Node::Assignment {
223 target, value, op, ..
224 } => {
225 if let Node::Identifier(name) = &target.node {
226 let idx = self.chunk.add_constant(Constant::String(name.clone()));
227 if let Some(op) = op {
228 self.chunk.emit_u16(Op::GetVar, idx, self.line);
229 self.compile_node(value)?;
230 self.emit_compound_op(op)?;
231 self.chunk.emit_u16(Op::SetVar, idx, self.line);
232 } else {
233 self.compile_node(value)?;
234 self.chunk.emit_u16(Op::SetVar, idx, self.line);
235 }
236 } else if let Node::PropertyAccess { object, property } = &target.node {
237 if let Some(var_name) = self.root_var_name(object) {
239 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
240 let prop_idx = self.chunk.add_constant(Constant::String(property.clone()));
241 if let Some(op) = op {
242 self.compile_node(target)?; self.compile_node(value)?;
245 self.emit_compound_op(op)?;
246 } else {
247 self.compile_node(value)?;
248 }
249 self.chunk.emit_u16(Op::SetProperty, prop_idx, self.line);
252 let hi = (var_idx >> 8) as u8;
254 let lo = var_idx as u8;
255 self.chunk.code.push(hi);
256 self.chunk.code.push(lo);
257 self.chunk.lines.push(self.line);
258 self.chunk.columns.push(self.column);
259 self.chunk.lines.push(self.line);
260 self.chunk.columns.push(self.column);
261 }
262 } else if let Node::SubscriptAccess { object, index } = &target.node {
263 if let Some(var_name) = self.root_var_name(object) {
265 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
266 if let Some(op) = op {
267 self.compile_node(target)?;
268 self.compile_node(value)?;
269 self.emit_compound_op(op)?;
270 } else {
271 self.compile_node(value)?;
272 }
273 self.compile_node(index)?;
274 self.chunk.emit_u16(Op::SetSubscript, var_idx, self.line);
275 }
276 }
277 }
278
279 Node::BinaryOp { op, left, right } => {
280 match op.as_str() {
282 "&&" => {
283 self.compile_node(left)?;
284 let jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
285 self.chunk.emit(Op::Pop, self.line);
286 self.compile_node(right)?;
287 self.chunk.patch_jump(jump);
288 self.chunk.emit(Op::Not, self.line);
290 self.chunk.emit(Op::Not, self.line);
291 return Ok(());
292 }
293 "||" => {
294 self.compile_node(left)?;
295 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
296 self.chunk.emit(Op::Pop, self.line);
297 self.compile_node(right)?;
298 self.chunk.patch_jump(jump);
299 self.chunk.emit(Op::Not, self.line);
300 self.chunk.emit(Op::Not, self.line);
301 return Ok(());
302 }
303 "??" => {
304 self.compile_node(left)?;
305 self.chunk.emit(Op::Dup, self.line);
306 self.chunk.emit(Op::Nil, self.line);
308 self.chunk.emit(Op::NotEqual, self.line);
309 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
310 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_node(right)?;
313 let end = self.chunk.emit_jump(Op::Jump, self.line);
314 self.chunk.patch_jump(jump);
315 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end);
317 return Ok(());
318 }
319 "|>" => {
320 self.compile_node(left)?;
321 if contains_pipe_placeholder(right) {
324 let replaced = replace_pipe_placeholder(right);
325 let closure_node = SNode::dummy(Node::Closure {
326 params: vec![TypedParam {
327 name: "__pipe".into(),
328 type_expr: None,
329 }],
330 body: vec![replaced],
331 });
332 self.compile_node(&closure_node)?;
333 } else {
334 self.compile_node(right)?;
335 }
336 self.chunk.emit(Op::Pipe, self.line);
337 return Ok(());
338 }
339 _ => {}
340 }
341
342 self.compile_node(left)?;
343 self.compile_node(right)?;
344 match op.as_str() {
345 "+" => self.chunk.emit(Op::Add, self.line),
346 "-" => self.chunk.emit(Op::Sub, self.line),
347 "*" => self.chunk.emit(Op::Mul, self.line),
348 "/" => self.chunk.emit(Op::Div, self.line),
349 "%" => self.chunk.emit(Op::Mod, self.line),
350 "==" => self.chunk.emit(Op::Equal, self.line),
351 "!=" => self.chunk.emit(Op::NotEqual, self.line),
352 "<" => self.chunk.emit(Op::Less, self.line),
353 ">" => self.chunk.emit(Op::Greater, self.line),
354 "<=" => self.chunk.emit(Op::LessEqual, self.line),
355 ">=" => self.chunk.emit(Op::GreaterEqual, self.line),
356 _ => {
357 return Err(CompileError {
358 message: format!("Unknown operator: {op}"),
359 line: self.line,
360 })
361 }
362 }
363 }
364
365 Node::UnaryOp { op, operand } => {
366 self.compile_node(operand)?;
367 match op.as_str() {
368 "-" => self.chunk.emit(Op::Negate, self.line),
369 "!" => self.chunk.emit(Op::Not, self.line),
370 _ => {}
371 }
372 }
373
374 Node::Ternary {
375 condition,
376 true_expr,
377 false_expr,
378 } => {
379 self.compile_node(condition)?;
380 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
381 self.chunk.emit(Op::Pop, self.line);
382 self.compile_node(true_expr)?;
383 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
384 self.chunk.patch_jump(else_jump);
385 self.chunk.emit(Op::Pop, self.line);
386 self.compile_node(false_expr)?;
387 self.chunk.patch_jump(end_jump);
388 }
389
390 Node::FunctionCall { name, args } => {
391 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
393 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
394 for arg in args {
396 self.compile_node(arg)?;
397 }
398 self.chunk.emit_u8(Op::Call, args.len() as u8, self.line);
399 }
400
401 Node::MethodCall {
402 object,
403 method,
404 args,
405 } => {
406 if let Node::Identifier(name) = &object.node {
408 if self.enum_names.contains(name) {
409 for arg in args {
411 self.compile_node(arg)?;
412 }
413 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
414 let var_idx = self.chunk.add_constant(Constant::String(method.clone()));
415 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
416 let hi = (var_idx >> 8) as u8;
417 let lo = var_idx as u8;
418 self.chunk.code.push(hi);
419 self.chunk.code.push(lo);
420 self.chunk.lines.push(self.line);
421 self.chunk.columns.push(self.column);
422 self.chunk.lines.push(self.line);
423 self.chunk.columns.push(self.column);
424 let fc = args.len() as u16;
425 let fhi = (fc >> 8) as u8;
426 let flo = fc as u8;
427 self.chunk.code.push(fhi);
428 self.chunk.code.push(flo);
429 self.chunk.lines.push(self.line);
430 self.chunk.columns.push(self.column);
431 self.chunk.lines.push(self.line);
432 self.chunk.columns.push(self.column);
433 return Ok(());
434 }
435 }
436 self.compile_node(object)?;
437 for arg in args {
438 self.compile_node(arg)?;
439 }
440 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
441 self.chunk
442 .emit_method_call(name_idx, args.len() as u8, self.line);
443 }
444
445 Node::OptionalMethodCall {
446 object,
447 method,
448 args,
449 } => {
450 self.compile_node(object)?;
451 for arg in args {
452 self.compile_node(arg)?;
453 }
454 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
455 self.chunk
456 .emit_method_call_opt(name_idx, args.len() as u8, self.line);
457 }
458
459 Node::PropertyAccess { object, property } => {
460 if let Node::Identifier(name) = &object.node {
462 if self.enum_names.contains(name) {
463 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
465 let var_idx = self.chunk.add_constant(Constant::String(property.clone()));
466 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
467 let hi = (var_idx >> 8) as u8;
468 let lo = var_idx as u8;
469 self.chunk.code.push(hi);
470 self.chunk.code.push(lo);
471 self.chunk.lines.push(self.line);
472 self.chunk.columns.push(self.column);
473 self.chunk.lines.push(self.line);
474 self.chunk.columns.push(self.column);
475 self.chunk.code.push(0);
477 self.chunk.code.push(0);
478 self.chunk.lines.push(self.line);
479 self.chunk.columns.push(self.column);
480 self.chunk.lines.push(self.line);
481 self.chunk.columns.push(self.column);
482 return Ok(());
483 }
484 }
485 self.compile_node(object)?;
486 let idx = self.chunk.add_constant(Constant::String(property.clone()));
487 self.chunk.emit_u16(Op::GetProperty, idx, self.line);
488 }
489
490 Node::OptionalPropertyAccess { object, property } => {
491 self.compile_node(object)?;
492 let idx = self.chunk.add_constant(Constant::String(property.clone()));
493 self.chunk.emit_u16(Op::GetPropertyOpt, idx, self.line);
494 }
495
496 Node::SubscriptAccess { object, index } => {
497 self.compile_node(object)?;
498 self.compile_node(index)?;
499 self.chunk.emit(Op::Subscript, self.line);
500 }
501
502 Node::SliceAccess { object, start, end } => {
503 self.compile_node(object)?;
504 if let Some(s) = start {
505 self.compile_node(s)?;
506 } else {
507 self.chunk.emit(Op::Nil, self.line);
508 }
509 if let Some(e) = end {
510 self.compile_node(e)?;
511 } else {
512 self.chunk.emit(Op::Nil, self.line);
513 }
514 self.chunk.emit(Op::Slice, self.line);
515 }
516
517 Node::IfElse {
518 condition,
519 then_body,
520 else_body,
521 } => {
522 self.compile_node(condition)?;
523 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
524 self.chunk.emit(Op::Pop, self.line);
525 self.compile_block(then_body)?;
526 if let Some(else_body) = else_body {
527 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
528 self.chunk.patch_jump(else_jump);
529 self.chunk.emit(Op::Pop, self.line);
530 self.compile_block(else_body)?;
531 self.chunk.patch_jump(end_jump);
532 } else {
533 self.chunk.patch_jump(else_jump);
534 self.chunk.emit(Op::Pop, self.line);
535 self.chunk.emit(Op::Nil, self.line);
536 }
537 }
538
539 Node::WhileLoop { condition, body } => {
540 let loop_start = self.chunk.current_offset();
541 self.loop_stack.push(LoopContext {
542 start_offset: loop_start,
543 break_patches: Vec::new(),
544 has_iterator: false,
545 handler_depth: self.handler_depth,
546 });
547 self.compile_node(condition)?;
548 let exit_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
549 self.chunk.emit(Op::Pop, self.line); for sn in body {
552 self.compile_node(sn)?;
553 if Self::produces_value(&sn.node) {
554 self.chunk.emit(Op::Pop, self.line);
555 }
556 }
557 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
559 self.chunk.patch_jump(exit_jump);
560 self.chunk.emit(Op::Pop, self.line); let ctx = self.loop_stack.pop().unwrap();
563 for patch_pos in ctx.break_patches {
564 self.chunk.patch_jump(patch_pos);
565 }
566 self.chunk.emit(Op::Nil, self.line);
567 }
568
569 Node::ForIn {
570 pattern,
571 iterable,
572 body,
573 } => {
574 self.compile_node(iterable)?;
576 self.chunk.emit(Op::IterInit, self.line);
578 let loop_start = self.chunk.current_offset();
579 self.loop_stack.push(LoopContext {
580 start_offset: loop_start,
581 break_patches: Vec::new(),
582 has_iterator: true,
583 handler_depth: self.handler_depth,
584 });
585 let exit_jump_pos = self.chunk.emit_jump(Op::IterNext, self.line);
587 self.compile_destructuring(pattern, true)?;
589 for sn in body {
591 self.compile_node(sn)?;
592 if Self::produces_value(&sn.node) {
593 self.chunk.emit(Op::Pop, self.line);
594 }
595 }
596 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
598 self.chunk.patch_jump(exit_jump_pos);
599 let ctx = self.loop_stack.pop().unwrap();
601 for patch_pos in ctx.break_patches {
602 self.chunk.patch_jump(patch_pos);
603 }
604 self.chunk.emit(Op::Nil, self.line);
606 }
607
608 Node::ReturnStmt { value } => {
609 if let Some(val) = value {
610 if let Node::FunctionCall { name, args } = &val.node {
613 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
614 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
615 for arg in args {
616 self.compile_node(arg)?;
617 }
618 self.chunk
619 .emit_u8(Op::TailCall, args.len() as u8, self.line);
620 } else if let Node::BinaryOp { op, left, right } = &val.node {
621 if op == "|>" {
622 self.compile_node(left)?;
625 self.compile_node(right)?;
627 self.chunk.emit(Op::Swap, self.line);
630 self.chunk.emit_u8(Op::TailCall, 1, self.line);
631 } else {
632 self.compile_node(val)?;
633 }
634 } else {
635 self.compile_node(val)?;
636 }
637 } else {
638 self.chunk.emit(Op::Nil, self.line);
639 }
640 self.chunk.emit(Op::Return, self.line);
641 }
642
643 Node::BreakStmt => {
644 if self.loop_stack.is_empty() {
645 return Err(CompileError {
646 message: "break outside of loop".to_string(),
647 line: self.line,
648 });
649 }
650 let ctx = self.loop_stack.last().unwrap();
651 for _ in ctx.handler_depth..self.handler_depth {
653 self.chunk.emit(Op::PopHandler, self.line);
654 }
655 if ctx.has_iterator {
657 self.chunk.emit(Op::PopIterator, self.line);
658 }
659 let patch = self.chunk.emit_jump(Op::Jump, self.line);
660 self.loop_stack
661 .last_mut()
662 .unwrap()
663 .break_patches
664 .push(patch);
665 }
666
667 Node::ContinueStmt => {
668 if self.loop_stack.is_empty() {
669 return Err(CompileError {
670 message: "continue outside of loop".to_string(),
671 line: self.line,
672 });
673 }
674 let ctx = self.loop_stack.last().unwrap();
675 for _ in ctx.handler_depth..self.handler_depth {
677 self.chunk.emit(Op::PopHandler, self.line);
678 }
679 let loop_start = ctx.start_offset;
680 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
681 }
682
683 Node::ListLiteral(elements) => {
684 for el in elements {
685 self.compile_node(el)?;
686 }
687 self.chunk
688 .emit_u16(Op::BuildList, elements.len() as u16, self.line);
689 }
690
691 Node::DictLiteral(entries) => {
692 for entry in entries {
693 self.compile_node(&entry.key)?;
694 self.compile_node(&entry.value)?;
695 }
696 self.chunk
697 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
698 }
699
700 Node::InterpolatedString(segments) => {
701 let mut part_count = 0u16;
702 for seg in segments {
703 match seg {
704 StringSegment::Literal(s) => {
705 let idx = self.chunk.add_constant(Constant::String(s.clone()));
706 self.chunk.emit_u16(Op::Constant, idx, self.line);
707 part_count += 1;
708 }
709 StringSegment::Expression(expr_str) => {
710 let mut lexer = harn_lexer::Lexer::new(expr_str);
712 if let Ok(tokens) = lexer.tokenize() {
713 let mut parser = harn_parser::Parser::new(tokens);
714 if let Ok(snode) = parser.parse_single_expression() {
715 self.compile_node(&snode)?;
716 let to_str = self
718 .chunk
719 .add_constant(Constant::String("to_string".into()));
720 self.chunk.emit_u16(Op::Constant, to_str, self.line);
721 self.chunk.emit(Op::Swap, self.line);
722 self.chunk.emit_u8(Op::Call, 1, self.line);
723 part_count += 1;
724 } else {
725 let idx =
727 self.chunk.add_constant(Constant::String(expr_str.clone()));
728 self.chunk.emit_u16(Op::Constant, idx, self.line);
729 part_count += 1;
730 }
731 }
732 }
733 }
734 }
735 if part_count > 1 {
736 self.chunk.emit_u16(Op::Concat, part_count, self.line);
737 }
738 }
739
740 Node::FnDecl {
741 name, params, body, ..
742 } => {
743 let mut fn_compiler = Compiler::new();
745 fn_compiler.enum_names = self.enum_names.clone();
746 fn_compiler.compile_block(body)?;
747 fn_compiler.chunk.emit(Op::Nil, self.line);
748 fn_compiler.chunk.emit(Op::Return, self.line);
749
750 let func = CompiledFunction {
751 name: name.clone(),
752 params: TypedParam::names(params),
753 chunk: fn_compiler.chunk,
754 };
755 let fn_idx = self.chunk.functions.len();
756 self.chunk.functions.push(func);
757
758 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
759 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
760 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
761 }
762
763 Node::Closure { params, body } => {
764 let mut fn_compiler = Compiler::new();
765 fn_compiler.enum_names = self.enum_names.clone();
766 fn_compiler.compile_block(body)?;
767 fn_compiler.chunk.emit(Op::Return, self.line);
769
770 let func = CompiledFunction {
771 name: "<closure>".to_string(),
772 params: TypedParam::names(params),
773 chunk: fn_compiler.chunk,
774 };
775 let fn_idx = self.chunk.functions.len();
776 self.chunk.functions.push(func);
777
778 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
779 }
780
781 Node::ThrowStmt { value } => {
782 self.compile_node(value)?;
783 self.chunk.emit(Op::Throw, self.line);
784 }
785
786 Node::MatchExpr { value, arms } => {
787 self.compile_node(value)?;
788 let mut end_jumps = Vec::new();
789 for arm in arms {
790 match &arm.pattern.node {
791 Node::Identifier(name) if name == "_" => {
793 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
795 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
796 }
797 Node::EnumConstruct {
799 enum_name,
800 variant,
801 args: pat_args,
802 } => {
803 self.chunk.emit(Op::Dup, self.line);
805 let en_idx =
806 self.chunk.add_constant(Constant::String(enum_name.clone()));
807 let vn_idx = self.chunk.add_constant(Constant::String(variant.clone()));
808 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
809 let hi = (vn_idx >> 8) as u8;
810 let lo = vn_idx as u8;
811 self.chunk.code.push(hi);
812 self.chunk.code.push(lo);
813 self.chunk.lines.push(self.line);
814 self.chunk.columns.push(self.column);
815 self.chunk.lines.push(self.line);
816 self.chunk.columns.push(self.column);
817 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
819 self.chunk.emit(Op::Pop, self.line); for (i, pat_arg) in pat_args.iter().enumerate() {
824 if let Node::Identifier(binding_name) = &pat_arg.node {
825 self.chunk.emit(Op::Dup, self.line);
827 let fields_idx = self
828 .chunk
829 .add_constant(Constant::String("fields".to_string()));
830 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
831 let idx_const =
832 self.chunk.add_constant(Constant::Int(i as i64));
833 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
834 self.chunk.emit(Op::Subscript, self.line);
835 let name_idx = self
836 .chunk
837 .add_constant(Constant::String(binding_name.clone()));
838 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
839 }
840 }
841
842 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
844 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
845 self.chunk.patch_jump(skip);
846 self.chunk.emit(Op::Pop, self.line); }
848 Node::PropertyAccess { object, property } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
850 {
851 let enum_name = if let Node::Identifier(n) = &object.node {
852 n.clone()
853 } else {
854 unreachable!()
855 };
856 self.chunk.emit(Op::Dup, self.line);
857 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
858 let vn_idx =
859 self.chunk.add_constant(Constant::String(property.clone()));
860 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
861 let hi = (vn_idx >> 8) as u8;
862 let lo = vn_idx as u8;
863 self.chunk.code.push(hi);
864 self.chunk.code.push(lo);
865 self.chunk.lines.push(self.line);
866 self.chunk.columns.push(self.column);
867 self.chunk.lines.push(self.line);
868 self.chunk.columns.push(self.column);
869 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
870 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
873 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
874 self.chunk.patch_jump(skip);
875 self.chunk.emit(Op::Pop, self.line); }
877 Node::MethodCall {
880 object,
881 method,
882 args: pat_args,
883 } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
884 {
885 let enum_name = if let Node::Identifier(n) = &object.node {
886 n.clone()
887 } else {
888 unreachable!()
889 };
890 self.chunk.emit(Op::Dup, self.line);
892 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
893 let vn_idx = self.chunk.add_constant(Constant::String(method.clone()));
894 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
895 let hi = (vn_idx >> 8) as u8;
896 let lo = vn_idx as u8;
897 self.chunk.code.push(hi);
898 self.chunk.code.push(lo);
899 self.chunk.lines.push(self.line);
900 self.chunk.columns.push(self.column);
901 self.chunk.lines.push(self.line);
902 self.chunk.columns.push(self.column);
903 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
904 self.chunk.emit(Op::Pop, self.line); for (i, pat_arg) in pat_args.iter().enumerate() {
908 if let Node::Identifier(binding_name) = &pat_arg.node {
909 self.chunk.emit(Op::Dup, self.line);
910 let fields_idx = self
911 .chunk
912 .add_constant(Constant::String("fields".to_string()));
913 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
914 let idx_const =
915 self.chunk.add_constant(Constant::Int(i as i64));
916 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
917 self.chunk.emit(Op::Subscript, self.line);
918 let name_idx = self
919 .chunk
920 .add_constant(Constant::String(binding_name.clone()));
921 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
922 }
923 }
924
925 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
927 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
928 self.chunk.patch_jump(skip);
929 self.chunk.emit(Op::Pop, self.line); }
931 Node::Identifier(name) => {
933 self.chunk.emit(Op::Dup, self.line); let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
936 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
937 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
939 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
940 }
941 _ => {
943 self.chunk.emit(Op::Dup, self.line);
944 self.compile_node(&arm.pattern)?;
945 self.chunk.emit(Op::Equal, self.line);
946 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
947 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
950 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
951 self.chunk.patch_jump(skip);
952 self.chunk.emit(Op::Pop, self.line); }
954 }
955 }
956 self.chunk.emit(Op::Pop, self.line);
958 self.chunk.emit(Op::Nil, self.line);
959 for j in end_jumps {
960 self.chunk.patch_jump(j);
961 }
962 }
963
964 Node::RangeExpr {
965 start,
966 end,
967 inclusive,
968 } => {
969 let name_idx = self
971 .chunk
972 .add_constant(Constant::String("__range__".to_string()));
973 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
974 self.compile_node(start)?;
975 self.compile_node(end)?;
976 if *inclusive {
977 self.chunk.emit(Op::True, self.line);
978 } else {
979 self.chunk.emit(Op::False, self.line);
980 }
981 self.chunk.emit_u8(Op::Call, 3, self.line);
982 }
983
984 Node::GuardStmt {
985 condition,
986 else_body,
987 } => {
988 self.compile_node(condition)?;
991 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
992 self.chunk.emit(Op::Pop, self.line); self.compile_block(else_body)?;
995 if !else_body.is_empty() && Self::produces_value(&else_body.last().unwrap().node) {
997 self.chunk.emit(Op::Pop, self.line);
998 }
999 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1000 self.chunk.patch_jump(skip_jump);
1001 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end_jump);
1003 self.chunk.emit(Op::Nil, self.line);
1004 }
1005
1006 Node::Block(stmts) => {
1007 if stmts.is_empty() {
1008 self.chunk.emit(Op::Nil, self.line);
1009 } else {
1010 self.compile_block(stmts)?;
1011 }
1012 }
1013
1014 Node::DeadlineBlock { duration, body } => {
1015 self.compile_node(duration)?;
1016 self.chunk.emit(Op::DeadlineSetup, self.line);
1017 if body.is_empty() {
1018 self.chunk.emit(Op::Nil, self.line);
1019 } else {
1020 self.compile_block(body)?;
1021 }
1022 self.chunk.emit(Op::DeadlineEnd, self.line);
1023 }
1024
1025 Node::MutexBlock { body } => {
1026 if body.is_empty() {
1028 self.chunk.emit(Op::Nil, self.line);
1029 } else {
1030 for sn in body {
1033 self.compile_node(sn)?;
1034 if Self::produces_value(&sn.node) {
1035 self.chunk.emit(Op::Pop, self.line);
1036 }
1037 }
1038 self.chunk.emit(Op::Nil, self.line);
1039 }
1040 }
1041
1042 Node::YieldExpr { .. } => {
1043 self.chunk.emit(Op::Nil, self.line);
1045 }
1046
1047 Node::AskExpr { fields } => {
1048 for entry in fields {
1051 self.compile_node(&entry.key)?;
1052 self.compile_node(&entry.value)?;
1053 }
1054 self.chunk
1055 .emit_u16(Op::BuildDict, fields.len() as u16, self.line);
1056 }
1057
1058 Node::EnumConstruct {
1059 enum_name,
1060 variant,
1061 args,
1062 } => {
1063 for arg in args {
1065 self.compile_node(arg)?;
1066 }
1067 let enum_idx = self.chunk.add_constant(Constant::String(enum_name.clone()));
1068 let var_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1069 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
1071 let hi = (var_idx >> 8) as u8;
1072 let lo = var_idx as u8;
1073 self.chunk.code.push(hi);
1074 self.chunk.code.push(lo);
1075 self.chunk.lines.push(self.line);
1076 self.chunk.columns.push(self.column);
1077 self.chunk.lines.push(self.line);
1078 self.chunk.columns.push(self.column);
1079 let fc = args.len() as u16;
1080 let fhi = (fc >> 8) as u8;
1081 let flo = fc as u8;
1082 self.chunk.code.push(fhi);
1083 self.chunk.code.push(flo);
1084 self.chunk.lines.push(self.line);
1085 self.chunk.columns.push(self.column);
1086 self.chunk.lines.push(self.line);
1087 self.chunk.columns.push(self.column);
1088 }
1089
1090 Node::StructConstruct {
1091 struct_name,
1092 fields,
1093 } => {
1094 let struct_key = self
1096 .chunk
1097 .add_constant(Constant::String("__struct__".to_string()));
1098 let struct_val = self
1099 .chunk
1100 .add_constant(Constant::String(struct_name.clone()));
1101 self.chunk.emit_u16(Op::Constant, struct_key, self.line);
1102 self.chunk.emit_u16(Op::Constant, struct_val, self.line);
1103
1104 for entry in fields {
1105 self.compile_node(&entry.key)?;
1106 self.compile_node(&entry.value)?;
1107 }
1108 self.chunk
1109 .emit_u16(Op::BuildDict, (fields.len() + 1) as u16, self.line);
1110 }
1111
1112 Node::ImportDecl { path } => {
1113 let idx = self.chunk.add_constant(Constant::String(path.clone()));
1114 self.chunk.emit_u16(Op::Import, idx, self.line);
1115 }
1116
1117 Node::SelectiveImport { names, path } => {
1118 let path_idx = self.chunk.add_constant(Constant::String(path.clone()));
1119 let names_str = names.join(",");
1120 let names_idx = self.chunk.add_constant(Constant::String(names_str));
1121 self.chunk
1122 .emit_u16(Op::SelectiveImport, path_idx, self.line);
1123 let hi = (names_idx >> 8) as u8;
1124 let lo = names_idx as u8;
1125 self.chunk.code.push(hi);
1126 self.chunk.code.push(lo);
1127 self.chunk.lines.push(self.line);
1128 self.chunk.columns.push(self.column);
1129 self.chunk.lines.push(self.line);
1130 self.chunk.columns.push(self.column);
1131 }
1132
1133 Node::Pipeline { .. }
1135 | Node::OverrideDecl { .. }
1136 | Node::TypeDecl { .. }
1137 | Node::EnumDecl { .. }
1138 | Node::StructDecl { .. }
1139 | Node::InterfaceDecl { .. } => {
1140 self.chunk.emit(Op::Nil, self.line);
1141 }
1142
1143 Node::TryCatch {
1144 body,
1145 error_var,
1146 error_type,
1147 catch_body,
1148 } => {
1149 let type_name = error_type.as_ref().and_then(|te| {
1151 if let harn_parser::TypeExpr::Named(name) = te {
1153 Some(name.clone())
1154 } else {
1155 None
1156 }
1157 });
1158
1159 let type_name_idx = if let Some(ref tn) = type_name {
1161 self.chunk.add_constant(Constant::String(tn.clone()))
1162 } else {
1163 self.chunk.add_constant(Constant::String(String::new()))
1164 };
1165
1166 self.handler_depth += 1;
1168 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1169 let hi = (type_name_idx >> 8) as u8;
1171 let lo = type_name_idx as u8;
1172 self.chunk.code.push(hi);
1173 self.chunk.code.push(lo);
1174 self.chunk.lines.push(self.line);
1175 self.chunk.columns.push(self.column);
1176 self.chunk.lines.push(self.line);
1177 self.chunk.columns.push(self.column);
1178
1179 if body.is_empty() {
1181 self.chunk.emit(Op::Nil, self.line);
1182 } else {
1183 self.compile_block(body)?;
1184 if !Self::produces_value(&body.last().unwrap().node) {
1186 self.chunk.emit(Op::Nil, self.line);
1187 }
1188 }
1189
1190 self.handler_depth -= 1;
1192 self.chunk.emit(Op::PopHandler, self.line);
1193
1194 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1196
1197 self.chunk.patch_jump(catch_jump);
1199
1200 if let Some(var_name) = error_var {
1203 let idx = self.chunk.add_constant(Constant::String(var_name.clone()));
1204 self.chunk.emit_u16(Op::DefLet, idx, self.line);
1205 } else {
1206 self.chunk.emit(Op::Pop, self.line);
1207 }
1208
1209 if catch_body.is_empty() {
1211 self.chunk.emit(Op::Nil, self.line);
1212 } else {
1213 self.compile_block(catch_body)?;
1214 if !Self::produces_value(&catch_body.last().unwrap().node) {
1215 self.chunk.emit(Op::Nil, self.line);
1216 }
1217 }
1218
1219 self.chunk.patch_jump(end_jump);
1221 }
1222
1223 Node::Retry { count, body } => {
1224 self.compile_node(count)?;
1226 let counter_name = "__retry_counter__";
1227 let counter_idx = self
1228 .chunk
1229 .add_constant(Constant::String(counter_name.to_string()));
1230 self.chunk.emit_u16(Op::DefVar, counter_idx, self.line);
1231
1232 self.chunk.emit(Op::Nil, self.line);
1234 let err_name = "__retry_last_error__";
1235 let err_idx = self
1236 .chunk
1237 .add_constant(Constant::String(err_name.to_string()));
1238 self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
1239
1240 let loop_start = self.chunk.current_offset();
1242
1243 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1245 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1247 let hi = (empty_type >> 8) as u8;
1248 let lo = empty_type as u8;
1249 self.chunk.code.push(hi);
1250 self.chunk.code.push(lo);
1251 self.chunk.lines.push(self.line);
1252 self.chunk.columns.push(self.column);
1253 self.chunk.lines.push(self.line);
1254 self.chunk.columns.push(self.column);
1255
1256 self.compile_block(body)?;
1258
1259 self.chunk.emit(Op::PopHandler, self.line);
1261 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1262
1263 self.chunk.patch_jump(catch_jump);
1265 self.chunk.emit(Op::Dup, self.line);
1267 self.chunk.emit_u16(Op::SetVar, err_idx, self.line);
1268 self.chunk.emit(Op::Pop, self.line);
1270
1271 self.chunk.emit_u16(Op::GetVar, counter_idx, self.line);
1273 let one_idx = self.chunk.add_constant(Constant::Int(1));
1274 self.chunk.emit_u16(Op::Constant, one_idx, self.line);
1275 self.chunk.emit(Op::Sub, self.line);
1276 self.chunk.emit(Op::Dup, self.line);
1277 self.chunk.emit_u16(Op::SetVar, counter_idx, self.line);
1278
1279 let zero_idx = self.chunk.add_constant(Constant::Int(0));
1281 self.chunk.emit_u16(Op::Constant, zero_idx, self.line);
1282 self.chunk.emit(Op::Greater, self.line);
1283 let retry_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1284 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
1286
1287 self.chunk.patch_jump(retry_jump);
1289 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::GetVar, err_idx, self.line);
1291 self.chunk.emit(Op::Throw, self.line);
1292
1293 self.chunk.patch_jump(end_jump);
1294 self.chunk.emit(Op::Nil, self.line);
1296 }
1297
1298 Node::Parallel {
1299 count,
1300 variable,
1301 body,
1302 } => {
1303 self.compile_node(count)?;
1304 let mut fn_compiler = Compiler::new();
1305 fn_compiler.enum_names = self.enum_names.clone();
1306 fn_compiler.compile_block(body)?;
1307 fn_compiler.chunk.emit(Op::Return, self.line);
1308 let params = vec![variable.clone().unwrap_or_else(|| "__i__".to_string())];
1309 let func = CompiledFunction {
1310 name: "<parallel>".to_string(),
1311 params,
1312 chunk: fn_compiler.chunk,
1313 };
1314 let fn_idx = self.chunk.functions.len();
1315 self.chunk.functions.push(func);
1316 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1317 self.chunk.emit(Op::Parallel, self.line);
1318 }
1319
1320 Node::ParallelMap {
1321 list,
1322 variable,
1323 body,
1324 } => {
1325 self.compile_node(list)?;
1326 let mut fn_compiler = Compiler::new();
1327 fn_compiler.enum_names = self.enum_names.clone();
1328 fn_compiler.compile_block(body)?;
1329 fn_compiler.chunk.emit(Op::Return, self.line);
1330 let func = CompiledFunction {
1331 name: "<parallel_map>".to_string(),
1332 params: vec![variable.clone()],
1333 chunk: fn_compiler.chunk,
1334 };
1335 let fn_idx = self.chunk.functions.len();
1336 self.chunk.functions.push(func);
1337 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1338 self.chunk.emit(Op::ParallelMap, self.line);
1339 }
1340
1341 Node::SpawnExpr { body } => {
1342 let mut fn_compiler = Compiler::new();
1343 fn_compiler.enum_names = self.enum_names.clone();
1344 fn_compiler.compile_block(body)?;
1345 fn_compiler.chunk.emit(Op::Return, self.line);
1346 let func = CompiledFunction {
1347 name: "<spawn>".to_string(),
1348 params: vec![],
1349 chunk: fn_compiler.chunk,
1350 };
1351 let fn_idx = self.chunk.functions.len();
1352 self.chunk.functions.push(func);
1353 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1354 self.chunk.emit(Op::Spawn, self.line);
1355 }
1356 }
1357 Ok(())
1358 }
1359
1360 fn compile_destructuring(
1364 &mut self,
1365 pattern: &BindingPattern,
1366 is_mutable: bool,
1367 ) -> Result<(), CompileError> {
1368 let def_op = if is_mutable { Op::DefVar } else { Op::DefLet };
1369 match pattern {
1370 BindingPattern::Identifier(name) => {
1371 let idx = self.chunk.add_constant(Constant::String(name.clone()));
1373 self.chunk.emit_u16(def_op, idx, self.line);
1374 }
1375 BindingPattern::Dict(fields) => {
1376 self.chunk.emit(Op::Dup, self.line);
1379 let assert_idx = self
1380 .chunk
1381 .add_constant(Constant::String("__assert_dict".into()));
1382 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1383 self.chunk.emit(Op::Swap, self.line);
1384 self.chunk.emit_u8(Op::Call, 1, self.line);
1385 self.chunk.emit(Op::Pop, self.line); let non_rest: Vec<_> = fields.iter().filter(|f| !f.is_rest).collect();
1390 let rest_field = fields.iter().find(|f| f.is_rest);
1391
1392 for field in &non_rest {
1393 self.chunk.emit(Op::Dup, self.line);
1394 let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
1395 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1396 self.chunk.emit(Op::Subscript, self.line);
1397 let binding_name = field.alias.as_deref().unwrap_or(&field.key);
1398 let name_idx = self
1399 .chunk
1400 .add_constant(Constant::String(binding_name.to_string()));
1401 self.chunk.emit_u16(def_op, name_idx, self.line);
1402 }
1403
1404 if let Some(rest) = rest_field {
1405 let fn_idx = self
1408 .chunk
1409 .add_constant(Constant::String("__dict_rest".into()));
1410 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
1411 self.chunk.emit(Op::Swap, self.line);
1413 for field in &non_rest {
1415 let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
1416 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1417 }
1418 self.chunk
1419 .emit_u16(Op::BuildList, non_rest.len() as u16, self.line);
1420 self.chunk.emit_u8(Op::Call, 2, self.line);
1422 let rest_name = &rest.key;
1423 let rest_idx = self.chunk.add_constant(Constant::String(rest_name.clone()));
1424 self.chunk.emit_u16(def_op, rest_idx, self.line);
1425 } else {
1426 self.chunk.emit(Op::Pop, self.line);
1428 }
1429 }
1430 BindingPattern::List(elements) => {
1431 self.chunk.emit(Op::Dup, self.line);
1434 let assert_idx = self
1435 .chunk
1436 .add_constant(Constant::String("__assert_list".into()));
1437 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1438 self.chunk.emit(Op::Swap, self.line);
1439 self.chunk.emit_u8(Op::Call, 1, self.line);
1440 self.chunk.emit(Op::Pop, self.line); let non_rest: Vec<_> = elements.iter().filter(|e| !e.is_rest).collect();
1443 let rest_elem = elements.iter().find(|e| e.is_rest);
1444
1445 for (i, elem) in non_rest.iter().enumerate() {
1446 self.chunk.emit(Op::Dup, self.line);
1447 let idx_const = self.chunk.add_constant(Constant::Int(i as i64));
1448 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1449 self.chunk.emit(Op::Subscript, self.line);
1450 let name_idx = self.chunk.add_constant(Constant::String(elem.name.clone()));
1451 self.chunk.emit_u16(def_op, name_idx, self.line);
1452 }
1453
1454 if let Some(rest) = rest_elem {
1455 let start_idx = self
1459 .chunk
1460 .add_constant(Constant::Int(non_rest.len() as i64));
1461 self.chunk.emit_u16(Op::Constant, start_idx, self.line);
1462 self.chunk.emit(Op::Nil, self.line); self.chunk.emit(Op::Slice, self.line);
1464 let rest_name_idx =
1465 self.chunk.add_constant(Constant::String(rest.name.clone()));
1466 self.chunk.emit_u16(def_op, rest_name_idx, self.line);
1467 } else {
1468 self.chunk.emit(Op::Pop, self.line);
1470 }
1471 }
1472 }
1473 Ok(())
1474 }
1475
1476 fn produces_value(node: &Node) -> bool {
1478 match node {
1479 Node::LetBinding { .. }
1481 | Node::VarBinding { .. }
1482 | Node::Assignment { .. }
1483 | Node::ReturnStmt { .. }
1484 | Node::FnDecl { .. }
1485 | Node::ThrowStmt { .. }
1486 | Node::BreakStmt
1487 | Node::ContinueStmt => false,
1488 Node::TryCatch { .. }
1490 | Node::Retry { .. }
1491 | Node::GuardStmt { .. }
1492 | Node::DeadlineBlock { .. }
1493 | Node::MutexBlock { .. } => true,
1494 _ => true,
1496 }
1497 }
1498}
1499
1500impl Compiler {
1501 pub fn compile_fn_body(
1503 &mut self,
1504 params: &[TypedParam],
1505 body: &[SNode],
1506 ) -> Result<CompiledFunction, CompileError> {
1507 let mut fn_compiler = Compiler::new();
1508 fn_compiler.compile_block(body)?;
1509 fn_compiler.chunk.emit(Op::Nil, 0);
1510 fn_compiler.chunk.emit(Op::Return, 0);
1511 Ok(CompiledFunction {
1512 name: String::new(),
1513 params: TypedParam::names(params),
1514 chunk: fn_compiler.chunk,
1515 })
1516 }
1517
1518 fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1520 if body.is_empty() {
1521 self.chunk.emit(Op::Nil, self.line);
1522 } else {
1523 self.compile_block(body)?;
1524 if !Self::produces_value(&body.last().unwrap().node) {
1526 self.chunk.emit(Op::Nil, self.line);
1527 }
1528 }
1529 Ok(())
1530 }
1531
1532 fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
1534 match op {
1535 "+" => self.chunk.emit(Op::Add, self.line),
1536 "-" => self.chunk.emit(Op::Sub, self.line),
1537 "*" => self.chunk.emit(Op::Mul, self.line),
1538 "/" => self.chunk.emit(Op::Div, self.line),
1539 "%" => self.chunk.emit(Op::Mod, self.line),
1540 _ => {
1541 return Err(CompileError {
1542 message: format!("Unknown compound operator: {op}"),
1543 line: self.line,
1544 })
1545 }
1546 }
1547 Ok(())
1548 }
1549
1550 fn root_var_name(&self, node: &SNode) -> Option<String> {
1552 match &node.node {
1553 Node::Identifier(name) => Some(name.clone()),
1554 Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
1555 self.root_var_name(object)
1556 }
1557 Node::SubscriptAccess { object, .. } => self.root_var_name(object),
1558 _ => None,
1559 }
1560 }
1561}
1562
1563impl Compiler {
1564 fn collect_enum_names(nodes: &[SNode], names: &mut std::collections::HashSet<String>) {
1566 for sn in nodes {
1567 match &sn.node {
1568 Node::EnumDecl { name, .. } => {
1569 names.insert(name.clone());
1570 }
1571 Node::Pipeline { body, .. } => {
1572 Self::collect_enum_names(body, names);
1573 }
1574 Node::FnDecl { body, .. } => {
1575 Self::collect_enum_names(body, names);
1576 }
1577 Node::Block(stmts) => {
1578 Self::collect_enum_names(stmts, names);
1579 }
1580 _ => {}
1581 }
1582 }
1583 }
1584}
1585
1586impl Default for Compiler {
1587 fn default() -> Self {
1588 Self::new()
1589 }
1590}
1591
1592fn contains_pipe_placeholder(node: &SNode) -> bool {
1594 match &node.node {
1595 Node::Identifier(name) if name == "_" => true,
1596 Node::FunctionCall { args, .. } => args.iter().any(contains_pipe_placeholder),
1597 Node::MethodCall { object, args, .. } => {
1598 contains_pipe_placeholder(object) || args.iter().any(contains_pipe_placeholder)
1599 }
1600 Node::BinaryOp { left, right, .. } => {
1601 contains_pipe_placeholder(left) || contains_pipe_placeholder(right)
1602 }
1603 Node::UnaryOp { operand, .. } => contains_pipe_placeholder(operand),
1604 Node::ListLiteral(items) => items.iter().any(contains_pipe_placeholder),
1605 Node::PropertyAccess { object, .. } => contains_pipe_placeholder(object),
1606 Node::SubscriptAccess { object, index } => {
1607 contains_pipe_placeholder(object) || contains_pipe_placeholder(index)
1608 }
1609 _ => false,
1610 }
1611}
1612
1613fn replace_pipe_placeholder(node: &SNode) -> SNode {
1615 let new_node = match &node.node {
1616 Node::Identifier(name) if name == "_" => Node::Identifier("__pipe".into()),
1617 Node::FunctionCall { name, args } => Node::FunctionCall {
1618 name: name.clone(),
1619 args: args.iter().map(replace_pipe_placeholder).collect(),
1620 },
1621 Node::MethodCall {
1622 object,
1623 method,
1624 args,
1625 } => Node::MethodCall {
1626 object: Box::new(replace_pipe_placeholder(object)),
1627 method: method.clone(),
1628 args: args.iter().map(replace_pipe_placeholder).collect(),
1629 },
1630 Node::BinaryOp { op, left, right } => Node::BinaryOp {
1631 op: op.clone(),
1632 left: Box::new(replace_pipe_placeholder(left)),
1633 right: Box::new(replace_pipe_placeholder(right)),
1634 },
1635 Node::UnaryOp { op, operand } => Node::UnaryOp {
1636 op: op.clone(),
1637 operand: Box::new(replace_pipe_placeholder(operand)),
1638 },
1639 Node::ListLiteral(items) => {
1640 Node::ListLiteral(items.iter().map(replace_pipe_placeholder).collect())
1641 }
1642 Node::PropertyAccess { object, property } => Node::PropertyAccess {
1643 object: Box::new(replace_pipe_placeholder(object)),
1644 property: property.clone(),
1645 },
1646 Node::SubscriptAccess { object, index } => Node::SubscriptAccess {
1647 object: Box::new(replace_pipe_placeholder(object)),
1648 index: Box::new(replace_pipe_placeholder(index)),
1649 },
1650 _ => return node.clone(),
1651 };
1652 SNode::new(new_node, node.span)
1653}
1654
1655#[cfg(test)]
1656mod tests {
1657 use super::*;
1658 use harn_lexer::Lexer;
1659 use harn_parser::Parser;
1660
1661 fn compile_source(source: &str) -> Chunk {
1662 let mut lexer = Lexer::new(source);
1663 let tokens = lexer.tokenize().unwrap();
1664 let mut parser = Parser::new(tokens);
1665 let program = parser.parse().unwrap();
1666 Compiler::new().compile(&program).unwrap()
1667 }
1668
1669 #[test]
1670 fn test_compile_arithmetic() {
1671 let chunk = compile_source("pipeline test(task) { let x = 2 + 3 }");
1672 assert!(!chunk.code.is_empty());
1673 assert!(chunk.constants.contains(&Constant::Int(2)));
1675 assert!(chunk.constants.contains(&Constant::Int(3)));
1676 }
1677
1678 #[test]
1679 fn test_compile_function_call() {
1680 let chunk = compile_source("pipeline test(task) { log(42) }");
1681 let disasm = chunk.disassemble("test");
1682 assert!(disasm.contains("CALL"));
1683 }
1684
1685 #[test]
1686 fn test_compile_if_else() {
1687 let chunk =
1688 compile_source(r#"pipeline test(task) { if true { log("yes") } else { log("no") } }"#);
1689 let disasm = chunk.disassemble("test");
1690 assert!(disasm.contains("JUMP_IF_FALSE"));
1691 assert!(disasm.contains("JUMP"));
1692 }
1693
1694 #[test]
1695 fn test_compile_while() {
1696 let chunk = compile_source("pipeline test(task) { var i = 0\n while i < 5 { i = i + 1 } }");
1697 let disasm = chunk.disassemble("test");
1698 assert!(disasm.contains("JUMP_IF_FALSE"));
1699 assert!(disasm.contains("JUMP"));
1701 }
1702
1703 #[test]
1704 fn test_compile_closure() {
1705 let chunk = compile_source("pipeline test(task) { let f = { x -> x * 2 } }");
1706 assert!(!chunk.functions.is_empty());
1707 assert_eq!(chunk.functions[0].params, vec!["x"]);
1708 }
1709
1710 #[test]
1711 fn test_compile_list() {
1712 let chunk = compile_source("pipeline test(task) { let a = [1, 2, 3] }");
1713 let disasm = chunk.disassemble("test");
1714 assert!(disasm.contains("BUILD_LIST"));
1715 }
1716
1717 #[test]
1718 fn test_compile_dict() {
1719 let chunk = compile_source(r#"pipeline test(task) { let d = {name: "test"} }"#);
1720 let disasm = chunk.disassemble("test");
1721 assert!(disasm.contains("BUILD_DICT"));
1722 }
1723
1724 #[test]
1725 fn test_disassemble() {
1726 let chunk = compile_source("pipeline test(task) { log(2 + 3) }");
1727 let disasm = chunk.disassemble("test");
1728 assert!(disasm.contains("CONSTANT"));
1730 assert!(disasm.contains("ADD"));
1731 assert!(disasm.contains("CALL"));
1732 }
1733}