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 let has_spread = elements.iter().any(|e| matches!(&e.node, Node::Spread(_)));
685 if !has_spread {
686 for el in elements {
687 self.compile_node(el)?;
688 }
689 self.chunk
690 .emit_u16(Op::BuildList, elements.len() as u16, self.line);
691 } else {
692 self.chunk.emit_u16(Op::BuildList, 0, self.line);
695 let mut pending = 0u16;
696 for el in elements {
697 if let Node::Spread(inner) = &el.node {
698 if pending > 0 {
700 self.chunk.emit_u16(Op::BuildList, pending, self.line);
701 self.chunk.emit(Op::Add, self.line);
703 pending = 0;
704 }
705 self.compile_node(inner)?;
707 self.chunk.emit(Op::Dup, self.line);
708 let assert_idx = self
709 .chunk
710 .add_constant(Constant::String("__assert_list".into()));
711 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
712 self.chunk.emit(Op::Swap, self.line);
713 self.chunk.emit_u8(Op::Call, 1, self.line);
714 self.chunk.emit(Op::Pop, self.line);
715 self.chunk.emit(Op::Add, self.line);
716 } else {
717 self.compile_node(el)?;
718 pending += 1;
719 }
720 }
721 if pending > 0 {
722 self.chunk.emit_u16(Op::BuildList, pending, self.line);
723 self.chunk.emit(Op::Add, self.line);
724 }
725 }
726 }
727
728 Node::DictLiteral(entries) => {
729 let has_spread = entries
730 .iter()
731 .any(|e| matches!(&e.value.node, Node::Spread(_)));
732 if !has_spread {
733 for entry in entries {
734 self.compile_node(&entry.key)?;
735 self.compile_node(&entry.value)?;
736 }
737 self.chunk
738 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
739 } else {
740 self.chunk.emit_u16(Op::BuildDict, 0, self.line);
742 let mut pending = 0u16;
743 for entry in entries {
744 if let Node::Spread(inner) = &entry.value.node {
745 if pending > 0 {
747 self.chunk.emit_u16(Op::BuildDict, pending, self.line);
748 self.chunk.emit(Op::Add, self.line);
749 pending = 0;
750 }
751 self.compile_node(inner)?;
753 self.chunk.emit(Op::Dup, self.line);
754 let assert_idx = self
755 .chunk
756 .add_constant(Constant::String("__assert_dict".into()));
757 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
758 self.chunk.emit(Op::Swap, self.line);
759 self.chunk.emit_u8(Op::Call, 1, self.line);
760 self.chunk.emit(Op::Pop, self.line);
761 self.chunk.emit(Op::Add, self.line);
762 } else {
763 self.compile_node(&entry.key)?;
764 self.compile_node(&entry.value)?;
765 pending += 1;
766 }
767 }
768 if pending > 0 {
769 self.chunk.emit_u16(Op::BuildDict, pending, self.line);
770 self.chunk.emit(Op::Add, self.line);
771 }
772 }
773 }
774
775 Node::InterpolatedString(segments) => {
776 let mut part_count = 0u16;
777 for seg in segments {
778 match seg {
779 StringSegment::Literal(s) => {
780 let idx = self.chunk.add_constant(Constant::String(s.clone()));
781 self.chunk.emit_u16(Op::Constant, idx, self.line);
782 part_count += 1;
783 }
784 StringSegment::Expression(expr_str) => {
785 let mut lexer = harn_lexer::Lexer::new(expr_str);
787 if let Ok(tokens) = lexer.tokenize() {
788 let mut parser = harn_parser::Parser::new(tokens);
789 if let Ok(snode) = parser.parse_single_expression() {
790 self.compile_node(&snode)?;
791 let to_str = self
793 .chunk
794 .add_constant(Constant::String("to_string".into()));
795 self.chunk.emit_u16(Op::Constant, to_str, self.line);
796 self.chunk.emit(Op::Swap, self.line);
797 self.chunk.emit_u8(Op::Call, 1, self.line);
798 part_count += 1;
799 } else {
800 let idx =
802 self.chunk.add_constant(Constant::String(expr_str.clone()));
803 self.chunk.emit_u16(Op::Constant, idx, self.line);
804 part_count += 1;
805 }
806 }
807 }
808 }
809 }
810 if part_count > 1 {
811 self.chunk.emit_u16(Op::Concat, part_count, self.line);
812 }
813 }
814
815 Node::FnDecl {
816 name, params, body, ..
817 } => {
818 let mut fn_compiler = Compiler::new();
820 fn_compiler.enum_names = self.enum_names.clone();
821 fn_compiler.compile_block(body)?;
822 fn_compiler.chunk.emit(Op::Nil, self.line);
823 fn_compiler.chunk.emit(Op::Return, self.line);
824
825 let func = CompiledFunction {
826 name: name.clone(),
827 params: TypedParam::names(params),
828 chunk: fn_compiler.chunk,
829 };
830 let fn_idx = self.chunk.functions.len();
831 self.chunk.functions.push(func);
832
833 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
834 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
835 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
836 }
837
838 Node::Closure { params, body } => {
839 let mut fn_compiler = Compiler::new();
840 fn_compiler.enum_names = self.enum_names.clone();
841 fn_compiler.compile_block(body)?;
842 fn_compiler.chunk.emit(Op::Return, self.line);
844
845 let func = CompiledFunction {
846 name: "<closure>".to_string(),
847 params: TypedParam::names(params),
848 chunk: fn_compiler.chunk,
849 };
850 let fn_idx = self.chunk.functions.len();
851 self.chunk.functions.push(func);
852
853 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
854 }
855
856 Node::ThrowStmt { value } => {
857 self.compile_node(value)?;
858 self.chunk.emit(Op::Throw, self.line);
859 }
860
861 Node::MatchExpr { value, arms } => {
862 self.compile_node(value)?;
863 let mut end_jumps = Vec::new();
864 for arm in arms {
865 match &arm.pattern.node {
866 Node::Identifier(name) if name == "_" => {
868 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
870 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
871 }
872 Node::EnumConstruct {
874 enum_name,
875 variant,
876 args: pat_args,
877 } => {
878 self.chunk.emit(Op::Dup, self.line);
880 let en_idx =
881 self.chunk.add_constant(Constant::String(enum_name.clone()));
882 let vn_idx = self.chunk.add_constant(Constant::String(variant.clone()));
883 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
884 let hi = (vn_idx >> 8) as u8;
885 let lo = vn_idx as u8;
886 self.chunk.code.push(hi);
887 self.chunk.code.push(lo);
888 self.chunk.lines.push(self.line);
889 self.chunk.columns.push(self.column);
890 self.chunk.lines.push(self.line);
891 self.chunk.columns.push(self.column);
892 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
894 self.chunk.emit(Op::Pop, self.line); for (i, pat_arg) in pat_args.iter().enumerate() {
899 if let Node::Identifier(binding_name) = &pat_arg.node {
900 self.chunk.emit(Op::Dup, self.line);
902 let fields_idx = self
903 .chunk
904 .add_constant(Constant::String("fields".to_string()));
905 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
906 let idx_const =
907 self.chunk.add_constant(Constant::Int(i as i64));
908 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
909 self.chunk.emit(Op::Subscript, self.line);
910 let name_idx = self
911 .chunk
912 .add_constant(Constant::String(binding_name.clone()));
913 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
914 }
915 }
916
917 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
919 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
920 self.chunk.patch_jump(skip);
921 self.chunk.emit(Op::Pop, self.line); }
923 Node::PropertyAccess { object, property } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
925 {
926 let enum_name = if let Node::Identifier(n) = &object.node {
927 n.clone()
928 } else {
929 unreachable!()
930 };
931 self.chunk.emit(Op::Dup, self.line);
932 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
933 let vn_idx =
934 self.chunk.add_constant(Constant::String(property.clone()));
935 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
936 let hi = (vn_idx >> 8) as u8;
937 let lo = vn_idx as u8;
938 self.chunk.code.push(hi);
939 self.chunk.code.push(lo);
940 self.chunk.lines.push(self.line);
941 self.chunk.columns.push(self.column);
942 self.chunk.lines.push(self.line);
943 self.chunk.columns.push(self.column);
944 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
945 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
948 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
949 self.chunk.patch_jump(skip);
950 self.chunk.emit(Op::Pop, self.line); }
952 Node::MethodCall {
955 object,
956 method,
957 args: pat_args,
958 } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
959 {
960 let enum_name = if let Node::Identifier(n) = &object.node {
961 n.clone()
962 } else {
963 unreachable!()
964 };
965 self.chunk.emit(Op::Dup, self.line);
967 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
968 let vn_idx = self.chunk.add_constant(Constant::String(method.clone()));
969 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
970 let hi = (vn_idx >> 8) as u8;
971 let lo = vn_idx as u8;
972 self.chunk.code.push(hi);
973 self.chunk.code.push(lo);
974 self.chunk.lines.push(self.line);
975 self.chunk.columns.push(self.column);
976 self.chunk.lines.push(self.line);
977 self.chunk.columns.push(self.column);
978 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
979 self.chunk.emit(Op::Pop, self.line); for (i, pat_arg) in pat_args.iter().enumerate() {
983 if let Node::Identifier(binding_name) = &pat_arg.node {
984 self.chunk.emit(Op::Dup, self.line);
985 let fields_idx = self
986 .chunk
987 .add_constant(Constant::String("fields".to_string()));
988 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
989 let idx_const =
990 self.chunk.add_constant(Constant::Int(i as i64));
991 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
992 self.chunk.emit(Op::Subscript, self.line);
993 let name_idx = self
994 .chunk
995 .add_constant(Constant::String(binding_name.clone()));
996 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
997 }
998 }
999
1000 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1002 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1003 self.chunk.patch_jump(skip);
1004 self.chunk.emit(Op::Pop, self.line); }
1006 Node::Identifier(name) => {
1008 self.chunk.emit(Op::Dup, self.line); let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1011 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1012 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1014 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1015 }
1016 Node::DictLiteral(entries)
1018 if entries
1019 .iter()
1020 .all(|e| matches!(&e.key.node, Node::StringLiteral(_))) =>
1021 {
1022 self.chunk.emit(Op::Dup, self.line);
1024 let typeof_idx =
1025 self.chunk.add_constant(Constant::String("type_of".into()));
1026 self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
1027 self.chunk.emit(Op::Swap, self.line);
1028 self.chunk.emit_u8(Op::Call, 1, self.line);
1029 let dict_str = self.chunk.add_constant(Constant::String("dict".into()));
1030 self.chunk.emit_u16(Op::Constant, dict_str, self.line);
1031 self.chunk.emit(Op::Equal, self.line);
1032 let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1033 self.chunk.emit(Op::Pop, self.line); let mut constraint_skips = Vec::new();
1037 let mut bindings = Vec::new();
1038 for entry in entries {
1039 if let Node::StringLiteral(key) = &entry.key.node {
1040 match &entry.value.node {
1041 Node::StringLiteral(_)
1043 | Node::IntLiteral(_)
1044 | Node::FloatLiteral(_)
1045 | Node::BoolLiteral(_)
1046 | Node::NilLiteral => {
1047 self.chunk.emit(Op::Dup, self.line);
1048 let key_idx = self
1049 .chunk
1050 .add_constant(Constant::String(key.clone()));
1051 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1052 self.chunk.emit(Op::Subscript, self.line);
1053 self.compile_node(&entry.value)?;
1054 self.chunk.emit(Op::Equal, self.line);
1055 let skip =
1056 self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1057 self.chunk.emit(Op::Pop, self.line); constraint_skips.push(skip);
1059 }
1060 Node::Identifier(binding) => {
1062 bindings.push((key.clone(), binding.clone()));
1063 }
1064 _ => {
1065 self.chunk.emit(Op::Dup, self.line);
1067 let key_idx = self
1068 .chunk
1069 .add_constant(Constant::String(key.clone()));
1070 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1071 self.chunk.emit(Op::Subscript, self.line);
1072 self.compile_node(&entry.value)?;
1073 self.chunk.emit(Op::Equal, self.line);
1074 let skip =
1075 self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1076 self.chunk.emit(Op::Pop, self.line);
1077 constraint_skips.push(skip);
1078 }
1079 }
1080 }
1081 }
1082
1083 for (key, binding) in &bindings {
1085 self.chunk.emit(Op::Dup, self.line);
1086 let key_idx =
1087 self.chunk.add_constant(Constant::String(key.clone()));
1088 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1089 self.chunk.emit(Op::Subscript, self.line);
1090 let name_idx =
1091 self.chunk.add_constant(Constant::String(binding.clone()));
1092 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1093 }
1094
1095 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1097 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1098
1099 let fail_target = self.chunk.code.len();
1101 self.chunk.emit(Op::Pop, self.line); for skip in constraint_skips {
1104 self.chunk.patch_jump_to(skip, fail_target);
1105 }
1106 self.chunk.patch_jump_to(skip_type, fail_target);
1107 }
1108 Node::ListLiteral(elements) => {
1110 self.chunk.emit(Op::Dup, self.line);
1112 let typeof_idx =
1113 self.chunk.add_constant(Constant::String("type_of".into()));
1114 self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
1115 self.chunk.emit(Op::Swap, self.line);
1116 self.chunk.emit_u8(Op::Call, 1, self.line);
1117 let list_str = self.chunk.add_constant(Constant::String("list".into()));
1118 self.chunk.emit_u16(Op::Constant, list_str, self.line);
1119 self.chunk.emit(Op::Equal, self.line);
1120 let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1121 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Dup, self.line);
1125 let len_idx = self.chunk.add_constant(Constant::String("len".into()));
1126 self.chunk.emit_u16(Op::Constant, len_idx, self.line);
1127 self.chunk.emit(Op::Swap, self.line);
1128 self.chunk.emit_u8(Op::Call, 1, self.line);
1129 let count = self
1130 .chunk
1131 .add_constant(Constant::Int(elements.len() as i64));
1132 self.chunk.emit_u16(Op::Constant, count, self.line);
1133 self.chunk.emit(Op::GreaterEqual, self.line);
1134 let skip_len = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1135 self.chunk.emit(Op::Pop, self.line); let mut constraint_skips = Vec::new();
1139 let mut bindings = Vec::new();
1140 for (i, elem) in elements.iter().enumerate() {
1141 match &elem.node {
1142 Node::Identifier(name) if name != "_" => {
1143 bindings.push((i, name.clone()));
1144 }
1145 Node::Identifier(_) => {} _ => {
1148 self.chunk.emit(Op::Dup, self.line);
1149 let idx_const =
1150 self.chunk.add_constant(Constant::Int(i as i64));
1151 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1152 self.chunk.emit(Op::Subscript, self.line);
1153 self.compile_node(elem)?;
1154 self.chunk.emit(Op::Equal, self.line);
1155 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1156 self.chunk.emit(Op::Pop, self.line);
1157 constraint_skips.push(skip);
1158 }
1159 }
1160 }
1161
1162 for (i, name) in &bindings {
1164 self.chunk.emit(Op::Dup, self.line);
1165 let idx_const = self.chunk.add_constant(Constant::Int(*i as i64));
1166 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1167 self.chunk.emit(Op::Subscript, self.line);
1168 let name_idx =
1169 self.chunk.add_constant(Constant::String(name.clone()));
1170 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1171 }
1172
1173 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1175 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1176
1177 let fail_target = self.chunk.code.len();
1179 self.chunk.emit(Op::Pop, self.line); for skip in constraint_skips {
1181 self.chunk.patch_jump_to(skip, fail_target);
1182 }
1183 self.chunk.patch_jump_to(skip_len, fail_target);
1184 self.chunk.patch_jump_to(skip_type, fail_target);
1185 }
1186 _ => {
1188 self.chunk.emit(Op::Dup, self.line);
1189 self.compile_node(&arm.pattern)?;
1190 self.chunk.emit(Op::Equal, self.line);
1191 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1192 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1195 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1196 self.chunk.patch_jump(skip);
1197 self.chunk.emit(Op::Pop, self.line); }
1199 }
1200 }
1201 self.chunk.emit(Op::Pop, self.line);
1203 self.chunk.emit(Op::Nil, self.line);
1204 for j in end_jumps {
1205 self.chunk.patch_jump(j);
1206 }
1207 }
1208
1209 Node::RangeExpr {
1210 start,
1211 end,
1212 inclusive,
1213 } => {
1214 let name_idx = self
1216 .chunk
1217 .add_constant(Constant::String("__range__".to_string()));
1218 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
1219 self.compile_node(start)?;
1220 self.compile_node(end)?;
1221 if *inclusive {
1222 self.chunk.emit(Op::True, self.line);
1223 } else {
1224 self.chunk.emit(Op::False, self.line);
1225 }
1226 self.chunk.emit_u8(Op::Call, 3, self.line);
1227 }
1228
1229 Node::GuardStmt {
1230 condition,
1231 else_body,
1232 } => {
1233 self.compile_node(condition)?;
1236 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
1237 self.chunk.emit(Op::Pop, self.line); self.compile_block(else_body)?;
1240 if !else_body.is_empty() && Self::produces_value(&else_body.last().unwrap().node) {
1242 self.chunk.emit(Op::Pop, self.line);
1243 }
1244 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1245 self.chunk.patch_jump(skip_jump);
1246 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end_jump);
1248 self.chunk.emit(Op::Nil, self.line);
1249 }
1250
1251 Node::Block(stmts) => {
1252 if stmts.is_empty() {
1253 self.chunk.emit(Op::Nil, self.line);
1254 } else {
1255 self.compile_block(stmts)?;
1256 }
1257 }
1258
1259 Node::DeadlineBlock { duration, body } => {
1260 self.compile_node(duration)?;
1261 self.chunk.emit(Op::DeadlineSetup, self.line);
1262 if body.is_empty() {
1263 self.chunk.emit(Op::Nil, self.line);
1264 } else {
1265 self.compile_block(body)?;
1266 }
1267 self.chunk.emit(Op::DeadlineEnd, self.line);
1268 }
1269
1270 Node::MutexBlock { body } => {
1271 if body.is_empty() {
1273 self.chunk.emit(Op::Nil, self.line);
1274 } else {
1275 for sn in body {
1278 self.compile_node(sn)?;
1279 if Self::produces_value(&sn.node) {
1280 self.chunk.emit(Op::Pop, self.line);
1281 }
1282 }
1283 self.chunk.emit(Op::Nil, self.line);
1284 }
1285 }
1286
1287 Node::YieldExpr { .. } => {
1288 self.chunk.emit(Op::Nil, self.line);
1290 }
1291
1292 Node::AskExpr { fields } => {
1293 for entry in fields {
1296 self.compile_node(&entry.key)?;
1297 self.compile_node(&entry.value)?;
1298 }
1299 self.chunk
1300 .emit_u16(Op::BuildDict, fields.len() as u16, self.line);
1301 }
1302
1303 Node::EnumConstruct {
1304 enum_name,
1305 variant,
1306 args,
1307 } => {
1308 for arg in args {
1310 self.compile_node(arg)?;
1311 }
1312 let enum_idx = self.chunk.add_constant(Constant::String(enum_name.clone()));
1313 let var_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1314 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
1316 let hi = (var_idx >> 8) as u8;
1317 let lo = var_idx as u8;
1318 self.chunk.code.push(hi);
1319 self.chunk.code.push(lo);
1320 self.chunk.lines.push(self.line);
1321 self.chunk.columns.push(self.column);
1322 self.chunk.lines.push(self.line);
1323 self.chunk.columns.push(self.column);
1324 let fc = args.len() as u16;
1325 let fhi = (fc >> 8) as u8;
1326 let flo = fc as u8;
1327 self.chunk.code.push(fhi);
1328 self.chunk.code.push(flo);
1329 self.chunk.lines.push(self.line);
1330 self.chunk.columns.push(self.column);
1331 self.chunk.lines.push(self.line);
1332 self.chunk.columns.push(self.column);
1333 }
1334
1335 Node::StructConstruct {
1336 struct_name,
1337 fields,
1338 } => {
1339 let struct_key = self
1341 .chunk
1342 .add_constant(Constant::String("__struct__".to_string()));
1343 let struct_val = self
1344 .chunk
1345 .add_constant(Constant::String(struct_name.clone()));
1346 self.chunk.emit_u16(Op::Constant, struct_key, self.line);
1347 self.chunk.emit_u16(Op::Constant, struct_val, self.line);
1348
1349 for entry in fields {
1350 self.compile_node(&entry.key)?;
1351 self.compile_node(&entry.value)?;
1352 }
1353 self.chunk
1354 .emit_u16(Op::BuildDict, (fields.len() + 1) as u16, self.line);
1355 }
1356
1357 Node::ImportDecl { path } => {
1358 let idx = self.chunk.add_constant(Constant::String(path.clone()));
1359 self.chunk.emit_u16(Op::Import, idx, self.line);
1360 }
1361
1362 Node::SelectiveImport { names, path } => {
1363 let path_idx = self.chunk.add_constant(Constant::String(path.clone()));
1364 let names_str = names.join(",");
1365 let names_idx = self.chunk.add_constant(Constant::String(names_str));
1366 self.chunk
1367 .emit_u16(Op::SelectiveImport, path_idx, self.line);
1368 let hi = (names_idx >> 8) as u8;
1369 let lo = names_idx as u8;
1370 self.chunk.code.push(hi);
1371 self.chunk.code.push(lo);
1372 self.chunk.lines.push(self.line);
1373 self.chunk.columns.push(self.column);
1374 self.chunk.lines.push(self.line);
1375 self.chunk.columns.push(self.column);
1376 }
1377
1378 Node::Pipeline { .. }
1380 | Node::OverrideDecl { .. }
1381 | Node::TypeDecl { .. }
1382 | Node::EnumDecl { .. }
1383 | Node::StructDecl { .. }
1384 | Node::InterfaceDecl { .. } => {
1385 self.chunk.emit(Op::Nil, self.line);
1386 }
1387
1388 Node::TryCatch {
1389 body,
1390 error_var,
1391 error_type,
1392 catch_body,
1393 } => {
1394 let type_name = error_type.as_ref().and_then(|te| {
1396 if let harn_parser::TypeExpr::Named(name) = te {
1398 Some(name.clone())
1399 } else {
1400 None
1401 }
1402 });
1403
1404 let type_name_idx = if let Some(ref tn) = type_name {
1406 self.chunk.add_constant(Constant::String(tn.clone()))
1407 } else {
1408 self.chunk.add_constant(Constant::String(String::new()))
1409 };
1410
1411 self.handler_depth += 1;
1413 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1414 let hi = (type_name_idx >> 8) as u8;
1416 let lo = type_name_idx as u8;
1417 self.chunk.code.push(hi);
1418 self.chunk.code.push(lo);
1419 self.chunk.lines.push(self.line);
1420 self.chunk.columns.push(self.column);
1421 self.chunk.lines.push(self.line);
1422 self.chunk.columns.push(self.column);
1423
1424 if body.is_empty() {
1426 self.chunk.emit(Op::Nil, self.line);
1427 } else {
1428 self.compile_block(body)?;
1429 if !Self::produces_value(&body.last().unwrap().node) {
1431 self.chunk.emit(Op::Nil, self.line);
1432 }
1433 }
1434
1435 self.handler_depth -= 1;
1437 self.chunk.emit(Op::PopHandler, self.line);
1438
1439 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1441
1442 self.chunk.patch_jump(catch_jump);
1444
1445 if let Some(var_name) = error_var {
1448 let idx = self.chunk.add_constant(Constant::String(var_name.clone()));
1449 self.chunk.emit_u16(Op::DefLet, idx, self.line);
1450 } else {
1451 self.chunk.emit(Op::Pop, self.line);
1452 }
1453
1454 if catch_body.is_empty() {
1456 self.chunk.emit(Op::Nil, self.line);
1457 } else {
1458 self.compile_block(catch_body)?;
1459 if !Self::produces_value(&catch_body.last().unwrap().node) {
1460 self.chunk.emit(Op::Nil, self.line);
1461 }
1462 }
1463
1464 self.chunk.patch_jump(end_jump);
1466 }
1467
1468 Node::Retry { count, body } => {
1469 self.compile_node(count)?;
1471 let counter_name = "__retry_counter__";
1472 let counter_idx = self
1473 .chunk
1474 .add_constant(Constant::String(counter_name.to_string()));
1475 self.chunk.emit_u16(Op::DefVar, counter_idx, self.line);
1476
1477 self.chunk.emit(Op::Nil, self.line);
1479 let err_name = "__retry_last_error__";
1480 let err_idx = self
1481 .chunk
1482 .add_constant(Constant::String(err_name.to_string()));
1483 self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
1484
1485 let loop_start = self.chunk.current_offset();
1487
1488 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1490 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1492 let hi = (empty_type >> 8) as u8;
1493 let lo = empty_type as u8;
1494 self.chunk.code.push(hi);
1495 self.chunk.code.push(lo);
1496 self.chunk.lines.push(self.line);
1497 self.chunk.columns.push(self.column);
1498 self.chunk.lines.push(self.line);
1499 self.chunk.columns.push(self.column);
1500
1501 self.compile_block(body)?;
1503
1504 self.chunk.emit(Op::PopHandler, self.line);
1506 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1507
1508 self.chunk.patch_jump(catch_jump);
1510 self.chunk.emit(Op::Dup, self.line);
1512 self.chunk.emit_u16(Op::SetVar, err_idx, self.line);
1513 self.chunk.emit(Op::Pop, self.line);
1515
1516 self.chunk.emit_u16(Op::GetVar, counter_idx, self.line);
1518 let one_idx = self.chunk.add_constant(Constant::Int(1));
1519 self.chunk.emit_u16(Op::Constant, one_idx, self.line);
1520 self.chunk.emit(Op::Sub, self.line);
1521 self.chunk.emit(Op::Dup, self.line);
1522 self.chunk.emit_u16(Op::SetVar, counter_idx, self.line);
1523
1524 let zero_idx = self.chunk.add_constant(Constant::Int(0));
1526 self.chunk.emit_u16(Op::Constant, zero_idx, self.line);
1527 self.chunk.emit(Op::Greater, self.line);
1528 let retry_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1529 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
1531
1532 self.chunk.patch_jump(retry_jump);
1534 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::GetVar, err_idx, self.line);
1536 self.chunk.emit(Op::Throw, self.line);
1537
1538 self.chunk.patch_jump(end_jump);
1539 self.chunk.emit(Op::Nil, self.line);
1541 }
1542
1543 Node::Parallel {
1544 count,
1545 variable,
1546 body,
1547 } => {
1548 self.compile_node(count)?;
1549 let mut fn_compiler = Compiler::new();
1550 fn_compiler.enum_names = self.enum_names.clone();
1551 fn_compiler.compile_block(body)?;
1552 fn_compiler.chunk.emit(Op::Return, self.line);
1553 let params = vec![variable.clone().unwrap_or_else(|| "__i__".to_string())];
1554 let func = CompiledFunction {
1555 name: "<parallel>".to_string(),
1556 params,
1557 chunk: fn_compiler.chunk,
1558 };
1559 let fn_idx = self.chunk.functions.len();
1560 self.chunk.functions.push(func);
1561 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1562 self.chunk.emit(Op::Parallel, self.line);
1563 }
1564
1565 Node::ParallelMap {
1566 list,
1567 variable,
1568 body,
1569 } => {
1570 self.compile_node(list)?;
1571 let mut fn_compiler = Compiler::new();
1572 fn_compiler.enum_names = self.enum_names.clone();
1573 fn_compiler.compile_block(body)?;
1574 fn_compiler.chunk.emit(Op::Return, self.line);
1575 let func = CompiledFunction {
1576 name: "<parallel_map>".to_string(),
1577 params: vec![variable.clone()],
1578 chunk: fn_compiler.chunk,
1579 };
1580 let fn_idx = self.chunk.functions.len();
1581 self.chunk.functions.push(func);
1582 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1583 self.chunk.emit(Op::ParallelMap, self.line);
1584 }
1585
1586 Node::SpawnExpr { body } => {
1587 let mut fn_compiler = Compiler::new();
1588 fn_compiler.enum_names = self.enum_names.clone();
1589 fn_compiler.compile_block(body)?;
1590 fn_compiler.chunk.emit(Op::Return, self.line);
1591 let func = CompiledFunction {
1592 name: "<spawn>".to_string(),
1593 params: vec![],
1594 chunk: fn_compiler.chunk,
1595 };
1596 let fn_idx = self.chunk.functions.len();
1597 self.chunk.functions.push(func);
1598 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1599 self.chunk.emit(Op::Spawn, self.line);
1600 }
1601 Node::Spread(_) => {
1602 return Err(CompileError {
1603 message: "spread (...) can only be used inside list or dict literals".into(),
1604 line: self.line,
1605 });
1606 }
1607 }
1608 Ok(())
1609 }
1610
1611 fn compile_destructuring(
1615 &mut self,
1616 pattern: &BindingPattern,
1617 is_mutable: bool,
1618 ) -> Result<(), CompileError> {
1619 let def_op = if is_mutable { Op::DefVar } else { Op::DefLet };
1620 match pattern {
1621 BindingPattern::Identifier(name) => {
1622 let idx = self.chunk.add_constant(Constant::String(name.clone()));
1624 self.chunk.emit_u16(def_op, idx, self.line);
1625 }
1626 BindingPattern::Dict(fields) => {
1627 self.chunk.emit(Op::Dup, self.line);
1630 let assert_idx = self
1631 .chunk
1632 .add_constant(Constant::String("__assert_dict".into()));
1633 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1634 self.chunk.emit(Op::Swap, self.line);
1635 self.chunk.emit_u8(Op::Call, 1, self.line);
1636 self.chunk.emit(Op::Pop, self.line); let non_rest: Vec<_> = fields.iter().filter(|f| !f.is_rest).collect();
1641 let rest_field = fields.iter().find(|f| f.is_rest);
1642
1643 for field in &non_rest {
1644 self.chunk.emit(Op::Dup, self.line);
1645 let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
1646 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1647 self.chunk.emit(Op::Subscript, self.line);
1648 let binding_name = field.alias.as_deref().unwrap_or(&field.key);
1649 let name_idx = self
1650 .chunk
1651 .add_constant(Constant::String(binding_name.to_string()));
1652 self.chunk.emit_u16(def_op, name_idx, self.line);
1653 }
1654
1655 if let Some(rest) = rest_field {
1656 let fn_idx = self
1659 .chunk
1660 .add_constant(Constant::String("__dict_rest".into()));
1661 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
1662 self.chunk.emit(Op::Swap, self.line);
1664 for field in &non_rest {
1666 let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
1667 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1668 }
1669 self.chunk
1670 .emit_u16(Op::BuildList, non_rest.len() as u16, self.line);
1671 self.chunk.emit_u8(Op::Call, 2, self.line);
1673 let rest_name = &rest.key;
1674 let rest_idx = self.chunk.add_constant(Constant::String(rest_name.clone()));
1675 self.chunk.emit_u16(def_op, rest_idx, self.line);
1676 } else {
1677 self.chunk.emit(Op::Pop, self.line);
1679 }
1680 }
1681 BindingPattern::List(elements) => {
1682 self.chunk.emit(Op::Dup, self.line);
1685 let assert_idx = self
1686 .chunk
1687 .add_constant(Constant::String("__assert_list".into()));
1688 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1689 self.chunk.emit(Op::Swap, self.line);
1690 self.chunk.emit_u8(Op::Call, 1, self.line);
1691 self.chunk.emit(Op::Pop, self.line); let non_rest: Vec<_> = elements.iter().filter(|e| !e.is_rest).collect();
1694 let rest_elem = elements.iter().find(|e| e.is_rest);
1695
1696 for (i, elem) in non_rest.iter().enumerate() {
1697 self.chunk.emit(Op::Dup, self.line);
1698 let idx_const = self.chunk.add_constant(Constant::Int(i as i64));
1699 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1700 self.chunk.emit(Op::Subscript, self.line);
1701 let name_idx = self.chunk.add_constant(Constant::String(elem.name.clone()));
1702 self.chunk.emit_u16(def_op, name_idx, self.line);
1703 }
1704
1705 if let Some(rest) = rest_elem {
1706 let start_idx = self
1710 .chunk
1711 .add_constant(Constant::Int(non_rest.len() as i64));
1712 self.chunk.emit_u16(Op::Constant, start_idx, self.line);
1713 self.chunk.emit(Op::Nil, self.line); self.chunk.emit(Op::Slice, self.line);
1715 let rest_name_idx =
1716 self.chunk.add_constant(Constant::String(rest.name.clone()));
1717 self.chunk.emit_u16(def_op, rest_name_idx, self.line);
1718 } else {
1719 self.chunk.emit(Op::Pop, self.line);
1721 }
1722 }
1723 }
1724 Ok(())
1725 }
1726
1727 fn produces_value(node: &Node) -> bool {
1729 match node {
1730 Node::LetBinding { .. }
1732 | Node::VarBinding { .. }
1733 | Node::Assignment { .. }
1734 | Node::ReturnStmt { .. }
1735 | Node::FnDecl { .. }
1736 | Node::ThrowStmt { .. }
1737 | Node::BreakStmt
1738 | Node::ContinueStmt => false,
1739 Node::TryCatch { .. }
1741 | Node::Retry { .. }
1742 | Node::GuardStmt { .. }
1743 | Node::DeadlineBlock { .. }
1744 | Node::MutexBlock { .. }
1745 | Node::Spread(_) => true,
1746 _ => true,
1748 }
1749 }
1750}
1751
1752impl Compiler {
1753 pub fn compile_fn_body(
1755 &mut self,
1756 params: &[TypedParam],
1757 body: &[SNode],
1758 ) -> Result<CompiledFunction, CompileError> {
1759 let mut fn_compiler = Compiler::new();
1760 fn_compiler.compile_block(body)?;
1761 fn_compiler.chunk.emit(Op::Nil, 0);
1762 fn_compiler.chunk.emit(Op::Return, 0);
1763 Ok(CompiledFunction {
1764 name: String::new(),
1765 params: TypedParam::names(params),
1766 chunk: fn_compiler.chunk,
1767 })
1768 }
1769
1770 fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1772 if body.is_empty() {
1773 self.chunk.emit(Op::Nil, self.line);
1774 } else {
1775 self.compile_block(body)?;
1776 if !Self::produces_value(&body.last().unwrap().node) {
1778 self.chunk.emit(Op::Nil, self.line);
1779 }
1780 }
1781 Ok(())
1782 }
1783
1784 fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
1786 match op {
1787 "+" => self.chunk.emit(Op::Add, self.line),
1788 "-" => self.chunk.emit(Op::Sub, self.line),
1789 "*" => self.chunk.emit(Op::Mul, self.line),
1790 "/" => self.chunk.emit(Op::Div, self.line),
1791 "%" => self.chunk.emit(Op::Mod, self.line),
1792 _ => {
1793 return Err(CompileError {
1794 message: format!("Unknown compound operator: {op}"),
1795 line: self.line,
1796 })
1797 }
1798 }
1799 Ok(())
1800 }
1801
1802 fn root_var_name(&self, node: &SNode) -> Option<String> {
1804 match &node.node {
1805 Node::Identifier(name) => Some(name.clone()),
1806 Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
1807 self.root_var_name(object)
1808 }
1809 Node::SubscriptAccess { object, .. } => self.root_var_name(object),
1810 _ => None,
1811 }
1812 }
1813}
1814
1815impl Compiler {
1816 fn collect_enum_names(nodes: &[SNode], names: &mut std::collections::HashSet<String>) {
1818 for sn in nodes {
1819 match &sn.node {
1820 Node::EnumDecl { name, .. } => {
1821 names.insert(name.clone());
1822 }
1823 Node::Pipeline { body, .. } => {
1824 Self::collect_enum_names(body, names);
1825 }
1826 Node::FnDecl { body, .. } => {
1827 Self::collect_enum_names(body, names);
1828 }
1829 Node::Block(stmts) => {
1830 Self::collect_enum_names(stmts, names);
1831 }
1832 _ => {}
1833 }
1834 }
1835 }
1836}
1837
1838impl Default for Compiler {
1839 fn default() -> Self {
1840 Self::new()
1841 }
1842}
1843
1844fn contains_pipe_placeholder(node: &SNode) -> bool {
1846 match &node.node {
1847 Node::Identifier(name) if name == "_" => true,
1848 Node::FunctionCall { args, .. } => args.iter().any(contains_pipe_placeholder),
1849 Node::MethodCall { object, args, .. } => {
1850 contains_pipe_placeholder(object) || args.iter().any(contains_pipe_placeholder)
1851 }
1852 Node::BinaryOp { left, right, .. } => {
1853 contains_pipe_placeholder(left) || contains_pipe_placeholder(right)
1854 }
1855 Node::UnaryOp { operand, .. } => contains_pipe_placeholder(operand),
1856 Node::ListLiteral(items) => items.iter().any(contains_pipe_placeholder),
1857 Node::PropertyAccess { object, .. } => contains_pipe_placeholder(object),
1858 Node::SubscriptAccess { object, index } => {
1859 contains_pipe_placeholder(object) || contains_pipe_placeholder(index)
1860 }
1861 _ => false,
1862 }
1863}
1864
1865fn replace_pipe_placeholder(node: &SNode) -> SNode {
1867 let new_node = match &node.node {
1868 Node::Identifier(name) if name == "_" => Node::Identifier("__pipe".into()),
1869 Node::FunctionCall { name, args } => Node::FunctionCall {
1870 name: name.clone(),
1871 args: args.iter().map(replace_pipe_placeholder).collect(),
1872 },
1873 Node::MethodCall {
1874 object,
1875 method,
1876 args,
1877 } => Node::MethodCall {
1878 object: Box::new(replace_pipe_placeholder(object)),
1879 method: method.clone(),
1880 args: args.iter().map(replace_pipe_placeholder).collect(),
1881 },
1882 Node::BinaryOp { op, left, right } => Node::BinaryOp {
1883 op: op.clone(),
1884 left: Box::new(replace_pipe_placeholder(left)),
1885 right: Box::new(replace_pipe_placeholder(right)),
1886 },
1887 Node::UnaryOp { op, operand } => Node::UnaryOp {
1888 op: op.clone(),
1889 operand: Box::new(replace_pipe_placeholder(operand)),
1890 },
1891 Node::ListLiteral(items) => {
1892 Node::ListLiteral(items.iter().map(replace_pipe_placeholder).collect())
1893 }
1894 Node::PropertyAccess { object, property } => Node::PropertyAccess {
1895 object: Box::new(replace_pipe_placeholder(object)),
1896 property: property.clone(),
1897 },
1898 Node::SubscriptAccess { object, index } => Node::SubscriptAccess {
1899 object: Box::new(replace_pipe_placeholder(object)),
1900 index: Box::new(replace_pipe_placeholder(index)),
1901 },
1902 _ => return node.clone(),
1903 };
1904 SNode::new(new_node, node.span)
1905}
1906
1907#[cfg(test)]
1908mod tests {
1909 use super::*;
1910 use harn_lexer::Lexer;
1911 use harn_parser::Parser;
1912
1913 fn compile_source(source: &str) -> Chunk {
1914 let mut lexer = Lexer::new(source);
1915 let tokens = lexer.tokenize().unwrap();
1916 let mut parser = Parser::new(tokens);
1917 let program = parser.parse().unwrap();
1918 Compiler::new().compile(&program).unwrap()
1919 }
1920
1921 #[test]
1922 fn test_compile_arithmetic() {
1923 let chunk = compile_source("pipeline test(task) { let x = 2 + 3 }");
1924 assert!(!chunk.code.is_empty());
1925 assert!(chunk.constants.contains(&Constant::Int(2)));
1927 assert!(chunk.constants.contains(&Constant::Int(3)));
1928 }
1929
1930 #[test]
1931 fn test_compile_function_call() {
1932 let chunk = compile_source("pipeline test(task) { log(42) }");
1933 let disasm = chunk.disassemble("test");
1934 assert!(disasm.contains("CALL"));
1935 }
1936
1937 #[test]
1938 fn test_compile_if_else() {
1939 let chunk =
1940 compile_source(r#"pipeline test(task) { if true { log("yes") } else { log("no") } }"#);
1941 let disasm = chunk.disassemble("test");
1942 assert!(disasm.contains("JUMP_IF_FALSE"));
1943 assert!(disasm.contains("JUMP"));
1944 }
1945
1946 #[test]
1947 fn test_compile_while() {
1948 let chunk = compile_source("pipeline test(task) { var i = 0\n while i < 5 { i = i + 1 } }");
1949 let disasm = chunk.disassemble("test");
1950 assert!(disasm.contains("JUMP_IF_FALSE"));
1951 assert!(disasm.contains("JUMP"));
1953 }
1954
1955 #[test]
1956 fn test_compile_closure() {
1957 let chunk = compile_source("pipeline test(task) { let f = { x -> x * 2 } }");
1958 assert!(!chunk.functions.is_empty());
1959 assert_eq!(chunk.functions[0].params, vec!["x"]);
1960 }
1961
1962 #[test]
1963 fn test_compile_list() {
1964 let chunk = compile_source("pipeline test(task) { let a = [1, 2, 3] }");
1965 let disasm = chunk.disassemble("test");
1966 assert!(disasm.contains("BUILD_LIST"));
1967 }
1968
1969 #[test]
1970 fn test_compile_dict() {
1971 let chunk = compile_source(r#"pipeline test(task) { let d = {name: "test"} }"#);
1972 let disasm = chunk.disassemble("test");
1973 assert!(disasm.contains("BUILD_DICT"));
1974 }
1975
1976 #[test]
1977 fn test_disassemble() {
1978 let chunk = compile_source("pipeline test(task) { log(2 + 3) }");
1979 let disasm = chunk.disassemble("test");
1980 assert!(disasm.contains("CONSTANT"));
1982 assert!(disasm.contains("ADD"));
1983 assert!(disasm.contains("CALL"));
1984 }
1985}