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 {
213 pattern, value, ..
214 } => {
215 self.compile_node(value)?;
216 self.compile_destructuring(pattern, false)?;
217 }
218
219 Node::VarBinding {
220 pattern, value, ..
221 } => {
222 self.compile_node(value)?;
223 self.compile_destructuring(pattern, true)?;
224 }
225
226 Node::Assignment {
227 target, value, op, ..
228 } => {
229 if let Node::Identifier(name) = &target.node {
230 let idx = self.chunk.add_constant(Constant::String(name.clone()));
231 if let Some(op) = op {
232 self.chunk.emit_u16(Op::GetVar, idx, self.line);
233 self.compile_node(value)?;
234 self.emit_compound_op(op)?;
235 self.chunk.emit_u16(Op::SetVar, idx, self.line);
236 } else {
237 self.compile_node(value)?;
238 self.chunk.emit_u16(Op::SetVar, idx, self.line);
239 }
240 } else if let Node::PropertyAccess { object, property } = &target.node {
241 if let Some(var_name) = self.root_var_name(object) {
243 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
244 let prop_idx = self.chunk.add_constant(Constant::String(property.clone()));
245 if let Some(op) = op {
246 self.compile_node(target)?; self.compile_node(value)?;
249 self.emit_compound_op(op)?;
250 } else {
251 self.compile_node(value)?;
252 }
253 self.chunk.emit_u16(Op::SetProperty, prop_idx, self.line);
256 let hi = (var_idx >> 8) as u8;
258 let lo = var_idx as u8;
259 self.chunk.code.push(hi);
260 self.chunk.code.push(lo);
261 self.chunk.lines.push(self.line);
262 self.chunk.columns.push(self.column);
263 self.chunk.lines.push(self.line);
264 self.chunk.columns.push(self.column);
265 }
266 } else if let Node::SubscriptAccess { object, index } = &target.node {
267 if let Some(var_name) = self.root_var_name(object) {
269 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
270 if let Some(op) = op {
271 self.compile_node(target)?;
272 self.compile_node(value)?;
273 self.emit_compound_op(op)?;
274 } else {
275 self.compile_node(value)?;
276 }
277 self.compile_node(index)?;
278 self.chunk.emit_u16(Op::SetSubscript, var_idx, self.line);
279 }
280 }
281 }
282
283 Node::BinaryOp { op, left, right } => {
284 match op.as_str() {
286 "&&" => {
287 self.compile_node(left)?;
288 let jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
289 self.chunk.emit(Op::Pop, self.line);
290 self.compile_node(right)?;
291 self.chunk.patch_jump(jump);
292 self.chunk.emit(Op::Not, self.line);
294 self.chunk.emit(Op::Not, self.line);
295 return Ok(());
296 }
297 "||" => {
298 self.compile_node(left)?;
299 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
300 self.chunk.emit(Op::Pop, self.line);
301 self.compile_node(right)?;
302 self.chunk.patch_jump(jump);
303 self.chunk.emit(Op::Not, self.line);
304 self.chunk.emit(Op::Not, self.line);
305 return Ok(());
306 }
307 "??" => {
308 self.compile_node(left)?;
309 self.chunk.emit(Op::Dup, self.line);
310 self.chunk.emit(Op::Nil, self.line);
312 self.chunk.emit(Op::NotEqual, self.line);
313 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
314 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_node(right)?;
317 let end = self.chunk.emit_jump(Op::Jump, self.line);
318 self.chunk.patch_jump(jump);
319 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end);
321 return Ok(());
322 }
323 "|>" => {
324 self.compile_node(left)?;
325 if contains_pipe_placeholder(right) {
328 let replaced = replace_pipe_placeholder(right);
329 let closure_node = SNode::dummy(Node::Closure {
330 params: vec![TypedParam {
331 name: "__pipe".into(),
332 type_expr: None,
333 }],
334 body: vec![replaced],
335 });
336 self.compile_node(&closure_node)?;
337 } else {
338 self.compile_node(right)?;
339 }
340 self.chunk.emit(Op::Pipe, self.line);
341 return Ok(());
342 }
343 _ => {}
344 }
345
346 self.compile_node(left)?;
347 self.compile_node(right)?;
348 match op.as_str() {
349 "+" => self.chunk.emit(Op::Add, self.line),
350 "-" => self.chunk.emit(Op::Sub, self.line),
351 "*" => self.chunk.emit(Op::Mul, self.line),
352 "/" => self.chunk.emit(Op::Div, self.line),
353 "%" => self.chunk.emit(Op::Mod, self.line),
354 "==" => self.chunk.emit(Op::Equal, self.line),
355 "!=" => self.chunk.emit(Op::NotEqual, self.line),
356 "<" => self.chunk.emit(Op::Less, self.line),
357 ">" => self.chunk.emit(Op::Greater, self.line),
358 "<=" => self.chunk.emit(Op::LessEqual, self.line),
359 ">=" => self.chunk.emit(Op::GreaterEqual, self.line),
360 _ => {
361 return Err(CompileError {
362 message: format!("Unknown operator: {op}"),
363 line: self.line,
364 })
365 }
366 }
367 }
368
369 Node::UnaryOp { op, operand } => {
370 self.compile_node(operand)?;
371 match op.as_str() {
372 "-" => self.chunk.emit(Op::Negate, self.line),
373 "!" => self.chunk.emit(Op::Not, self.line),
374 _ => {}
375 }
376 }
377
378 Node::Ternary {
379 condition,
380 true_expr,
381 false_expr,
382 } => {
383 self.compile_node(condition)?;
384 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
385 self.chunk.emit(Op::Pop, self.line);
386 self.compile_node(true_expr)?;
387 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
388 self.chunk.patch_jump(else_jump);
389 self.chunk.emit(Op::Pop, self.line);
390 self.compile_node(false_expr)?;
391 self.chunk.patch_jump(end_jump);
392 }
393
394 Node::FunctionCall { name, args } => {
395 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
397 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
398 for arg in args {
400 self.compile_node(arg)?;
401 }
402 self.chunk.emit_u8(Op::Call, args.len() as u8, self.line);
403 }
404
405 Node::MethodCall {
406 object,
407 method,
408 args,
409 } => {
410 if let Node::Identifier(name) = &object.node {
412 if self.enum_names.contains(name) {
413 for arg in args {
415 self.compile_node(arg)?;
416 }
417 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
418 let var_idx = self.chunk.add_constant(Constant::String(method.clone()));
419 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
420 let hi = (var_idx >> 8) as u8;
421 let lo = var_idx as u8;
422 self.chunk.code.push(hi);
423 self.chunk.code.push(lo);
424 self.chunk.lines.push(self.line);
425 self.chunk.columns.push(self.column);
426 self.chunk.lines.push(self.line);
427 self.chunk.columns.push(self.column);
428 let fc = args.len() as u16;
429 let fhi = (fc >> 8) as u8;
430 let flo = fc as u8;
431 self.chunk.code.push(fhi);
432 self.chunk.code.push(flo);
433 self.chunk.lines.push(self.line);
434 self.chunk.columns.push(self.column);
435 self.chunk.lines.push(self.line);
436 self.chunk.columns.push(self.column);
437 return Ok(());
438 }
439 }
440 self.compile_node(object)?;
441 for arg in args {
442 self.compile_node(arg)?;
443 }
444 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
445 self.chunk
446 .emit_method_call(name_idx, args.len() as u8, self.line);
447 }
448
449 Node::OptionalMethodCall {
450 object,
451 method,
452 args,
453 } => {
454 self.compile_node(object)?;
455 for arg in args {
456 self.compile_node(arg)?;
457 }
458 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
459 self.chunk
460 .emit_method_call_opt(name_idx, args.len() as u8, self.line);
461 }
462
463 Node::PropertyAccess { object, property } => {
464 if let Node::Identifier(name) = &object.node {
466 if self.enum_names.contains(name) {
467 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
469 let var_idx = self.chunk.add_constant(Constant::String(property.clone()));
470 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
471 let hi = (var_idx >> 8) as u8;
472 let lo = var_idx as u8;
473 self.chunk.code.push(hi);
474 self.chunk.code.push(lo);
475 self.chunk.lines.push(self.line);
476 self.chunk.columns.push(self.column);
477 self.chunk.lines.push(self.line);
478 self.chunk.columns.push(self.column);
479 self.chunk.code.push(0);
481 self.chunk.code.push(0);
482 self.chunk.lines.push(self.line);
483 self.chunk.columns.push(self.column);
484 self.chunk.lines.push(self.line);
485 self.chunk.columns.push(self.column);
486 return Ok(());
487 }
488 }
489 self.compile_node(object)?;
490 let idx = self.chunk.add_constant(Constant::String(property.clone()));
491 self.chunk.emit_u16(Op::GetProperty, idx, self.line);
492 }
493
494 Node::OptionalPropertyAccess { object, property } => {
495 self.compile_node(object)?;
496 let idx = self.chunk.add_constant(Constant::String(property.clone()));
497 self.chunk.emit_u16(Op::GetPropertyOpt, idx, self.line);
498 }
499
500 Node::SubscriptAccess { object, index } => {
501 self.compile_node(object)?;
502 self.compile_node(index)?;
503 self.chunk.emit(Op::Subscript, self.line);
504 }
505
506 Node::SliceAccess { object, start, end } => {
507 self.compile_node(object)?;
508 if let Some(s) = start {
509 self.compile_node(s)?;
510 } else {
511 self.chunk.emit(Op::Nil, self.line);
512 }
513 if let Some(e) = end {
514 self.compile_node(e)?;
515 } else {
516 self.chunk.emit(Op::Nil, self.line);
517 }
518 self.chunk.emit(Op::Slice, self.line);
519 }
520
521 Node::IfElse {
522 condition,
523 then_body,
524 else_body,
525 } => {
526 self.compile_node(condition)?;
527 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
528 self.chunk.emit(Op::Pop, self.line);
529 self.compile_block(then_body)?;
530 if let Some(else_body) = else_body {
531 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
532 self.chunk.patch_jump(else_jump);
533 self.chunk.emit(Op::Pop, self.line);
534 self.compile_block(else_body)?;
535 self.chunk.patch_jump(end_jump);
536 } else {
537 self.chunk.patch_jump(else_jump);
538 self.chunk.emit(Op::Pop, self.line);
539 self.chunk.emit(Op::Nil, self.line);
540 }
541 }
542
543 Node::WhileLoop { condition, body } => {
544 let loop_start = self.chunk.current_offset();
545 self.loop_stack.push(LoopContext {
546 start_offset: loop_start,
547 break_patches: Vec::new(),
548 has_iterator: false,
549 handler_depth: self.handler_depth,
550 });
551 self.compile_node(condition)?;
552 let exit_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
553 self.chunk.emit(Op::Pop, self.line); for sn in body {
556 self.compile_node(sn)?;
557 if Self::produces_value(&sn.node) {
558 self.chunk.emit(Op::Pop, self.line);
559 }
560 }
561 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
563 self.chunk.patch_jump(exit_jump);
564 self.chunk.emit(Op::Pop, self.line); let ctx = self.loop_stack.pop().unwrap();
567 for patch_pos in ctx.break_patches {
568 self.chunk.patch_jump(patch_pos);
569 }
570 self.chunk.emit(Op::Nil, self.line);
571 }
572
573 Node::ForIn {
574 pattern,
575 iterable,
576 body,
577 } => {
578 self.compile_node(iterable)?;
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.compile_destructuring(pattern, true)?;
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 compile_destructuring(
1368 &mut self,
1369 pattern: &BindingPattern,
1370 is_mutable: bool,
1371 ) -> Result<(), CompileError> {
1372 let def_op = if is_mutable { Op::DefVar } else { Op::DefLet };
1373 match pattern {
1374 BindingPattern::Identifier(name) => {
1375 let idx = self.chunk.add_constant(Constant::String(name.clone()));
1377 self.chunk.emit_u16(def_op, idx, self.line);
1378 }
1379 BindingPattern::Dict(fields) => {
1380 let non_rest: Vec<_> = fields.iter().filter(|f| !f.is_rest).collect();
1384 let rest_field = fields.iter().find(|f| f.is_rest);
1385
1386 for field in &non_rest {
1387 self.chunk.emit(Op::Dup, self.line);
1388 let key_idx = self
1389 .chunk
1390 .add_constant(Constant::String(field.key.clone()));
1391 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1392 self.chunk.emit(Op::Subscript, self.line);
1393 let binding_name = field.alias.as_deref().unwrap_or(&field.key);
1394 let name_idx = self
1395 .chunk
1396 .add_constant(Constant::String(binding_name.to_string()));
1397 self.chunk.emit_u16(def_op, name_idx, self.line);
1398 }
1399
1400 if let Some(rest) = rest_field {
1401 let fn_idx = self
1404 .chunk
1405 .add_constant(Constant::String("__dict_rest".into()));
1406 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
1407 self.chunk.emit(Op::Swap, self.line);
1409 for field in &non_rest {
1411 let key_idx = self
1412 .chunk
1413 .add_constant(Constant::String(field.key.clone()));
1414 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1415 }
1416 self.chunk
1417 .emit_u16(Op::BuildList, non_rest.len() as u16, self.line);
1418 self.chunk.emit_u8(Op::Call, 2, self.line);
1420 let rest_name = &rest.key;
1421 let rest_idx = self
1422 .chunk
1423 .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 let non_rest: Vec<_> = elements.iter().filter(|e| !e.is_rest).collect();
1433 let rest_elem = elements.iter().find(|e| e.is_rest);
1434
1435 for (i, elem) in non_rest.iter().enumerate() {
1436 self.chunk.emit(Op::Dup, self.line);
1437 let idx_const = self.chunk.add_constant(Constant::Int(i as i64));
1438 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1439 self.chunk.emit(Op::Subscript, self.line);
1440 let name_idx = self
1441 .chunk
1442 .add_constant(Constant::String(elem.name.clone()));
1443 self.chunk.emit_u16(def_op, name_idx, self.line);
1444 }
1445
1446 if let Some(rest) = rest_elem {
1447 let start_idx = self
1451 .chunk
1452 .add_constant(Constant::Int(non_rest.len() as i64));
1453 self.chunk.emit_u16(Op::Constant, start_idx, self.line);
1454 self.chunk.emit(Op::Nil, self.line); self.chunk.emit(Op::Slice, self.line);
1456 let rest_name_idx = self
1457 .chunk
1458 .add_constant(Constant::String(rest.name.clone()));
1459 self.chunk.emit_u16(def_op, rest_name_idx, self.line);
1460 } else {
1461 self.chunk.emit(Op::Pop, self.line);
1463 }
1464 }
1465 }
1466 Ok(())
1467 }
1468
1469 fn produces_value(node: &Node) -> bool {
1471 match node {
1472 Node::LetBinding { .. }
1474 | Node::VarBinding { .. }
1475 | Node::Assignment { .. }
1476 | Node::ReturnStmt { .. }
1477 | Node::FnDecl { .. }
1478 | Node::ThrowStmt { .. }
1479 | Node::BreakStmt
1480 | Node::ContinueStmt => false,
1481 Node::TryCatch { .. }
1483 | Node::Retry { .. }
1484 | Node::GuardStmt { .. }
1485 | Node::DeadlineBlock { .. }
1486 | Node::MutexBlock { .. } => true,
1487 _ => true,
1489 }
1490 }
1491}
1492
1493impl Compiler {
1494 pub fn compile_fn_body(
1496 &mut self,
1497 params: &[TypedParam],
1498 body: &[SNode],
1499 ) -> Result<CompiledFunction, CompileError> {
1500 let mut fn_compiler = Compiler::new();
1501 fn_compiler.compile_block(body)?;
1502 fn_compiler.chunk.emit(Op::Nil, 0);
1503 fn_compiler.chunk.emit(Op::Return, 0);
1504 Ok(CompiledFunction {
1505 name: String::new(),
1506 params: TypedParam::names(params),
1507 chunk: fn_compiler.chunk,
1508 })
1509 }
1510
1511 fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1513 if body.is_empty() {
1514 self.chunk.emit(Op::Nil, self.line);
1515 } else {
1516 self.compile_block(body)?;
1517 if !Self::produces_value(&body.last().unwrap().node) {
1519 self.chunk.emit(Op::Nil, self.line);
1520 }
1521 }
1522 Ok(())
1523 }
1524
1525 fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
1527 match op {
1528 "+" => self.chunk.emit(Op::Add, self.line),
1529 "-" => self.chunk.emit(Op::Sub, self.line),
1530 "*" => self.chunk.emit(Op::Mul, self.line),
1531 "/" => self.chunk.emit(Op::Div, self.line),
1532 "%" => self.chunk.emit(Op::Mod, self.line),
1533 _ => {
1534 return Err(CompileError {
1535 message: format!("Unknown compound operator: {op}"),
1536 line: self.line,
1537 })
1538 }
1539 }
1540 Ok(())
1541 }
1542
1543 fn root_var_name(&self, node: &SNode) -> Option<String> {
1545 match &node.node {
1546 Node::Identifier(name) => Some(name.clone()),
1547 Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
1548 self.root_var_name(object)
1549 }
1550 Node::SubscriptAccess { object, .. } => self.root_var_name(object),
1551 _ => None,
1552 }
1553 }
1554}
1555
1556impl Compiler {
1557 fn collect_enum_names(nodes: &[SNode], names: &mut std::collections::HashSet<String>) {
1559 for sn in nodes {
1560 match &sn.node {
1561 Node::EnumDecl { name, .. } => {
1562 names.insert(name.clone());
1563 }
1564 Node::Pipeline { body, .. } => {
1565 Self::collect_enum_names(body, names);
1566 }
1567 Node::FnDecl { body, .. } => {
1568 Self::collect_enum_names(body, names);
1569 }
1570 Node::Block(stmts) => {
1571 Self::collect_enum_names(stmts, names);
1572 }
1573 _ => {}
1574 }
1575 }
1576 }
1577}
1578
1579impl Default for Compiler {
1580 fn default() -> Self {
1581 Self::new()
1582 }
1583}
1584
1585fn contains_pipe_placeholder(node: &SNode) -> bool {
1587 match &node.node {
1588 Node::Identifier(name) if name == "_" => true,
1589 Node::FunctionCall { args, .. } => args.iter().any(contains_pipe_placeholder),
1590 Node::MethodCall { object, args, .. } => {
1591 contains_pipe_placeholder(object) || args.iter().any(contains_pipe_placeholder)
1592 }
1593 Node::BinaryOp { left, right, .. } => {
1594 contains_pipe_placeholder(left) || contains_pipe_placeholder(right)
1595 }
1596 Node::UnaryOp { operand, .. } => contains_pipe_placeholder(operand),
1597 Node::ListLiteral(items) => items.iter().any(contains_pipe_placeholder),
1598 Node::PropertyAccess { object, .. } => contains_pipe_placeholder(object),
1599 Node::SubscriptAccess { object, index } => {
1600 contains_pipe_placeholder(object) || contains_pipe_placeholder(index)
1601 }
1602 _ => false,
1603 }
1604}
1605
1606fn replace_pipe_placeholder(node: &SNode) -> SNode {
1608 let new_node = match &node.node {
1609 Node::Identifier(name) if name == "_" => Node::Identifier("__pipe".into()),
1610 Node::FunctionCall { name, args } => Node::FunctionCall {
1611 name: name.clone(),
1612 args: args.iter().map(replace_pipe_placeholder).collect(),
1613 },
1614 Node::MethodCall {
1615 object,
1616 method,
1617 args,
1618 } => Node::MethodCall {
1619 object: Box::new(replace_pipe_placeholder(object)),
1620 method: method.clone(),
1621 args: args.iter().map(replace_pipe_placeholder).collect(),
1622 },
1623 Node::BinaryOp { op, left, right } => Node::BinaryOp {
1624 op: op.clone(),
1625 left: Box::new(replace_pipe_placeholder(left)),
1626 right: Box::new(replace_pipe_placeholder(right)),
1627 },
1628 Node::UnaryOp { op, operand } => Node::UnaryOp {
1629 op: op.clone(),
1630 operand: Box::new(replace_pipe_placeholder(operand)),
1631 },
1632 Node::ListLiteral(items) => {
1633 Node::ListLiteral(items.iter().map(replace_pipe_placeholder).collect())
1634 }
1635 Node::PropertyAccess { object, property } => Node::PropertyAccess {
1636 object: Box::new(replace_pipe_placeholder(object)),
1637 property: property.clone(),
1638 },
1639 Node::SubscriptAccess { object, index } => Node::SubscriptAccess {
1640 object: Box::new(replace_pipe_placeholder(object)),
1641 index: Box::new(replace_pipe_placeholder(index)),
1642 },
1643 _ => return node.clone(),
1644 };
1645 SNode::new(new_node, node.span)
1646}
1647
1648#[cfg(test)]
1649mod tests {
1650 use super::*;
1651 use harn_lexer::Lexer;
1652 use harn_parser::Parser;
1653
1654 fn compile_source(source: &str) -> Chunk {
1655 let mut lexer = Lexer::new(source);
1656 let tokens = lexer.tokenize().unwrap();
1657 let mut parser = Parser::new(tokens);
1658 let program = parser.parse().unwrap();
1659 Compiler::new().compile(&program).unwrap()
1660 }
1661
1662 #[test]
1663 fn test_compile_arithmetic() {
1664 let chunk = compile_source("pipeline test(task) { let x = 2 + 3 }");
1665 assert!(!chunk.code.is_empty());
1666 assert!(chunk.constants.contains(&Constant::Int(2)));
1668 assert!(chunk.constants.contains(&Constant::Int(3)));
1669 }
1670
1671 #[test]
1672 fn test_compile_function_call() {
1673 let chunk = compile_source("pipeline test(task) { log(42) }");
1674 let disasm = chunk.disassemble("test");
1675 assert!(disasm.contains("CALL"));
1676 }
1677
1678 #[test]
1679 fn test_compile_if_else() {
1680 let chunk =
1681 compile_source(r#"pipeline test(task) { if true { log("yes") } else { log("no") } }"#);
1682 let disasm = chunk.disassemble("test");
1683 assert!(disasm.contains("JUMP_IF_FALSE"));
1684 assert!(disasm.contains("JUMP"));
1685 }
1686
1687 #[test]
1688 fn test_compile_while() {
1689 let chunk = compile_source("pipeline test(task) { var i = 0\n while i < 5 { i = i + 1 } }");
1690 let disasm = chunk.disassemble("test");
1691 assert!(disasm.contains("JUMP_IF_FALSE"));
1692 assert!(disasm.contains("JUMP"));
1694 }
1695
1696 #[test]
1697 fn test_compile_closure() {
1698 let chunk = compile_source("pipeline test(task) { let f = { x -> x * 2 } }");
1699 assert!(!chunk.functions.is_empty());
1700 assert_eq!(chunk.functions[0].params, vec!["x"]);
1701 }
1702
1703 #[test]
1704 fn test_compile_list() {
1705 let chunk = compile_source("pipeline test(task) { let a = [1, 2, 3] }");
1706 let disasm = chunk.disassemble("test");
1707 assert!(disasm.contains("BUILD_LIST"));
1708 }
1709
1710 #[test]
1711 fn test_compile_dict() {
1712 let chunk = compile_source(r#"pipeline test(task) { let d = {name: "test"} }"#);
1713 let disasm = chunk.disassemble("test");
1714 assert!(disasm.contains("BUILD_DICT"));
1715 }
1716
1717 #[test]
1718 fn test_disassemble() {
1719 let chunk = compile_source("pipeline test(task) { log(2 + 3) }");
1720 let disasm = chunk.disassemble("test");
1721 assert!(disasm.contains("CONSTANT"));
1723 assert!(disasm.contains("ADD"));
1724 assert!(disasm.contains("CALL"));
1725 }
1726}