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