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 finally_depth: usize,
33}
34
35pub struct Compiler {
37 chunk: Chunk,
38 line: u32,
39 column: u32,
40 enum_names: std::collections::HashSet<String>,
42 loop_stack: Vec<LoopContext>,
44 handler_depth: usize,
46 finally_bodies: Vec<Vec<SNode>>,
48 temp_counter: usize,
50}
51
52impl Compiler {
53 pub fn new() -> Self {
54 Self {
55 chunk: Chunk::new(),
56 line: 1,
57 column: 1,
58 enum_names: std::collections::HashSet::new(),
59 loop_stack: Vec::new(),
60 handler_depth: 0,
61 finally_bodies: Vec::new(),
62 temp_counter: 0,
63 }
64 }
65
66 pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
69 Self::collect_enum_names(program, &mut self.enum_names);
72
73 for sn in program {
75 match &sn.node {
76 Node::ImportDecl { .. } | Node::SelectiveImport { .. } => {
77 self.compile_node(sn)?;
78 }
79 _ => {}
80 }
81 }
82
83 let main = program
85 .iter()
86 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == "default"))
87 .or_else(|| {
88 program
89 .iter()
90 .find(|sn| matches!(&sn.node, Node::Pipeline { .. }))
91 });
92
93 if let Some(sn) = main {
94 if let Node::Pipeline { body, extends, .. } = &sn.node {
95 if let Some(parent_name) = extends {
97 self.compile_parent_pipeline(program, parent_name)?;
98 }
99 self.compile_block(body)?;
100 }
101 }
102
103 self.chunk.emit(Op::Nil, self.line);
104 self.chunk.emit(Op::Return, self.line);
105 Ok(self.chunk)
106 }
107
108 pub fn compile_named(
110 mut self,
111 program: &[SNode],
112 pipeline_name: &str,
113 ) -> Result<Chunk, CompileError> {
114 Self::collect_enum_names(program, &mut self.enum_names);
115
116 for sn in program {
117 if matches!(
118 &sn.node,
119 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
120 ) {
121 self.compile_node(sn)?;
122 }
123 }
124
125 let target = program
126 .iter()
127 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == pipeline_name));
128
129 if let Some(sn) = target {
130 if let Node::Pipeline { body, extends, .. } = &sn.node {
131 if let Some(parent_name) = extends {
132 self.compile_parent_pipeline(program, parent_name)?;
133 }
134 self.compile_block(body)?;
135 }
136 }
137
138 self.chunk.emit(Op::Nil, self.line);
139 self.chunk.emit(Op::Return, self.line);
140 Ok(self.chunk)
141 }
142
143 fn compile_parent_pipeline(
145 &mut self,
146 program: &[SNode],
147 parent_name: &str,
148 ) -> Result<(), CompileError> {
149 let parent = program
150 .iter()
151 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == parent_name));
152 if let Some(sn) = parent {
153 if let Node::Pipeline { body, extends, .. } = &sn.node {
154 if let Some(grandparent) = extends {
156 self.compile_parent_pipeline(program, grandparent)?;
157 }
158 for stmt in body {
160 self.compile_node(stmt)?;
161 if Self::produces_value(&stmt.node) {
162 self.chunk.emit(Op::Pop, self.line);
163 }
164 }
165 }
166 }
167 Ok(())
168 }
169
170 fn emit_default_preamble(&mut self, params: &[TypedParam]) -> Result<(), CompileError> {
175 for (i, param) in params.iter().enumerate() {
176 if let Some(default_expr) = ¶m.default_value {
177 self.chunk.emit(Op::GetArgc, self.line);
178 let threshold_idx = self
179 .chunk
180 .add_constant(Constant::Int((i + 1) as i64));
181 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
182 self.chunk.emit(Op::GreaterEqual, self.line);
184 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
185 self.chunk.emit(Op::Pop, self.line);
187 self.compile_node(default_expr)?;
189 let name_idx = self
190 .chunk
191 .add_constant(Constant::String(param.name.clone()));
192 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
193 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
194 self.chunk.patch_jump(skip_jump);
195 self.chunk.emit(Op::Pop, self.line);
197 self.chunk.patch_jump(end_jump);
198 }
199 }
200 Ok(())
201 }
202
203 fn emit_type_name_extra(&mut self, type_name_idx: u16) {
205 let hi = (type_name_idx >> 8) as u8;
206 let lo = type_name_idx as u8;
207 self.chunk.code.push(hi);
208 self.chunk.code.push(lo);
209 self.chunk.lines.push(self.line);
210 self.chunk.columns.push(self.column);
211 self.chunk.lines.push(self.line);
212 self.chunk.columns.push(self.column);
213 }
214
215 fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
217 if body.is_empty() {
218 self.chunk.emit(Op::Nil, self.line);
219 } else {
220 self.compile_block(body)?;
221 if !Self::produces_value(&body.last().unwrap().node) {
222 self.chunk.emit(Op::Nil, self.line);
223 }
224 }
225 Ok(())
226 }
227
228 fn compile_catch_binding(&mut self, error_var: &Option<String>) -> Result<(), CompileError> {
230 if let Some(var_name) = error_var {
231 let idx = self.chunk.add_constant(Constant::String(var_name.clone()));
232 self.chunk.emit_u16(Op::DefLet, idx, self.line);
233 } else {
234 self.chunk.emit(Op::Pop, self.line);
235 }
236 Ok(())
237 }
238
239 fn compile_finally_inline(&mut self, finally_body: &[SNode]) -> Result<(), CompileError> {
241 if !finally_body.is_empty() {
242 self.compile_block(finally_body)?;
243 if Self::produces_value(&finally_body.last().unwrap().node) {
245 self.chunk.emit(Op::Pop, self.line);
246 }
247 }
248 Ok(())
249 }
250
251 fn compile_rethrow_with_finally(
253 &mut self,
254 finally_body: &[SNode],
255 ) -> Result<(), CompileError> {
256 self.temp_counter += 1;
258 let temp_name = format!("__finally_err_{}__", self.temp_counter);
259 let err_idx = self.chunk.add_constant(Constant::String(temp_name.clone()));
260 self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
261 self.compile_finally_inline(finally_body)?;
262 let get_idx = self.chunk.add_constant(Constant::String(temp_name));
263 self.chunk.emit_u16(Op::GetVar, get_idx, self.line);
264 self.chunk.emit(Op::Throw, self.line);
265 Ok(())
266 }
267
268 fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
269 for (i, snode) in stmts.iter().enumerate() {
270 self.compile_node(snode)?;
271 let is_last = i == stmts.len() - 1;
272 if is_last {
273 if !Self::produces_value(&snode.node) {
276 self.chunk.emit(Op::Nil, self.line);
277 }
278 } else {
279 if Self::produces_value(&snode.node) {
281 self.chunk.emit(Op::Pop, self.line);
282 }
283 }
284 }
285 Ok(())
286 }
287
288 fn compile_node(&mut self, snode: &SNode) -> Result<(), CompileError> {
289 self.line = snode.span.line as u32;
290 self.column = snode.span.column as u32;
291 self.chunk.set_column(self.column);
292 match &snode.node {
293 Node::IntLiteral(n) => {
294 let idx = self.chunk.add_constant(Constant::Int(*n));
295 self.chunk.emit_u16(Op::Constant, idx, self.line);
296 }
297 Node::FloatLiteral(n) => {
298 let idx = self.chunk.add_constant(Constant::Float(*n));
299 self.chunk.emit_u16(Op::Constant, idx, self.line);
300 }
301 Node::StringLiteral(s) => {
302 let idx = self.chunk.add_constant(Constant::String(s.clone()));
303 self.chunk.emit_u16(Op::Constant, idx, self.line);
304 }
305 Node::BoolLiteral(true) => self.chunk.emit(Op::True, self.line),
306 Node::BoolLiteral(false) => self.chunk.emit(Op::False, self.line),
307 Node::NilLiteral => self.chunk.emit(Op::Nil, self.line),
308 Node::DurationLiteral(ms) => {
309 let idx = self.chunk.add_constant(Constant::Duration(*ms));
310 self.chunk.emit_u16(Op::Constant, idx, self.line);
311 }
312
313 Node::Identifier(name) => {
314 let idx = self.chunk.add_constant(Constant::String(name.clone()));
315 self.chunk.emit_u16(Op::GetVar, idx, self.line);
316 }
317
318 Node::LetBinding { pattern, value, .. } => {
319 self.compile_node(value)?;
320 self.compile_destructuring(pattern, false)?;
321 }
322
323 Node::VarBinding { pattern, value, .. } => {
324 self.compile_node(value)?;
325 self.compile_destructuring(pattern, true)?;
326 }
327
328 Node::Assignment {
329 target, value, op, ..
330 } => {
331 if let Node::Identifier(name) = &target.node {
332 let idx = self.chunk.add_constant(Constant::String(name.clone()));
333 if let Some(op) = op {
334 self.chunk.emit_u16(Op::GetVar, idx, self.line);
335 self.compile_node(value)?;
336 self.emit_compound_op(op)?;
337 self.chunk.emit_u16(Op::SetVar, idx, self.line);
338 } else {
339 self.compile_node(value)?;
340 self.chunk.emit_u16(Op::SetVar, idx, self.line);
341 }
342 } else if let Node::PropertyAccess { object, property } = &target.node {
343 if let Some(var_name) = self.root_var_name(object) {
345 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
346 let prop_idx = self.chunk.add_constant(Constant::String(property.clone()));
347 if let Some(op) = op {
348 self.compile_node(target)?; self.compile_node(value)?;
351 self.emit_compound_op(op)?;
352 } else {
353 self.compile_node(value)?;
354 }
355 self.chunk.emit_u16(Op::SetProperty, prop_idx, self.line);
358 let hi = (var_idx >> 8) as u8;
360 let lo = var_idx as u8;
361 self.chunk.code.push(hi);
362 self.chunk.code.push(lo);
363 self.chunk.lines.push(self.line);
364 self.chunk.columns.push(self.column);
365 self.chunk.lines.push(self.line);
366 self.chunk.columns.push(self.column);
367 }
368 } else if let Node::SubscriptAccess { object, index } = &target.node {
369 if let Some(var_name) = self.root_var_name(object) {
371 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
372 if let Some(op) = op {
373 self.compile_node(target)?;
374 self.compile_node(value)?;
375 self.emit_compound_op(op)?;
376 } else {
377 self.compile_node(value)?;
378 }
379 self.compile_node(index)?;
380 self.chunk.emit_u16(Op::SetSubscript, var_idx, self.line);
381 }
382 }
383 }
384
385 Node::BinaryOp { op, left, right } => {
386 match op.as_str() {
388 "&&" => {
389 self.compile_node(left)?;
390 let jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
391 self.chunk.emit(Op::Pop, self.line);
392 self.compile_node(right)?;
393 self.chunk.patch_jump(jump);
394 self.chunk.emit(Op::Not, self.line);
396 self.chunk.emit(Op::Not, self.line);
397 return Ok(());
398 }
399 "||" => {
400 self.compile_node(left)?;
401 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
402 self.chunk.emit(Op::Pop, self.line);
403 self.compile_node(right)?;
404 self.chunk.patch_jump(jump);
405 self.chunk.emit(Op::Not, self.line);
406 self.chunk.emit(Op::Not, self.line);
407 return Ok(());
408 }
409 "??" => {
410 self.compile_node(left)?;
411 self.chunk.emit(Op::Dup, self.line);
412 self.chunk.emit(Op::Nil, self.line);
414 self.chunk.emit(Op::NotEqual, self.line);
415 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
416 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_node(right)?;
419 let end = self.chunk.emit_jump(Op::Jump, self.line);
420 self.chunk.patch_jump(jump);
421 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end);
423 return Ok(());
424 }
425 "|>" => {
426 self.compile_node(left)?;
427 if contains_pipe_placeholder(right) {
430 let replaced = replace_pipe_placeholder(right);
431 let closure_node = SNode::dummy(Node::Closure {
432 params: vec![TypedParam {
433 name: "__pipe".into(),
434 type_expr: None,
435 default_value: None,
436 }],
437 body: vec![replaced],
438 });
439 self.compile_node(&closure_node)?;
440 } else {
441 self.compile_node(right)?;
442 }
443 self.chunk.emit(Op::Pipe, self.line);
444 return Ok(());
445 }
446 _ => {}
447 }
448
449 self.compile_node(left)?;
450 self.compile_node(right)?;
451 match op.as_str() {
452 "+" => self.chunk.emit(Op::Add, self.line),
453 "-" => self.chunk.emit(Op::Sub, self.line),
454 "*" => self.chunk.emit(Op::Mul, self.line),
455 "/" => self.chunk.emit(Op::Div, self.line),
456 "%" => self.chunk.emit(Op::Mod, self.line),
457 "==" => self.chunk.emit(Op::Equal, self.line),
458 "!=" => self.chunk.emit(Op::NotEqual, self.line),
459 "<" => self.chunk.emit(Op::Less, self.line),
460 ">" => self.chunk.emit(Op::Greater, self.line),
461 "<=" => self.chunk.emit(Op::LessEqual, self.line),
462 ">=" => self.chunk.emit(Op::GreaterEqual, self.line),
463 _ => {
464 return Err(CompileError {
465 message: format!("Unknown operator: {op}"),
466 line: self.line,
467 })
468 }
469 }
470 }
471
472 Node::UnaryOp { op, operand } => {
473 self.compile_node(operand)?;
474 match op.as_str() {
475 "-" => self.chunk.emit(Op::Negate, self.line),
476 "!" => self.chunk.emit(Op::Not, self.line),
477 _ => {}
478 }
479 }
480
481 Node::Ternary {
482 condition,
483 true_expr,
484 false_expr,
485 } => {
486 self.compile_node(condition)?;
487 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
488 self.chunk.emit(Op::Pop, self.line);
489 self.compile_node(true_expr)?;
490 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
491 self.chunk.patch_jump(else_jump);
492 self.chunk.emit(Op::Pop, self.line);
493 self.compile_node(false_expr)?;
494 self.chunk.patch_jump(end_jump);
495 }
496
497 Node::FunctionCall { name, args } => {
498 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
500 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
501 for arg in args {
503 self.compile_node(arg)?;
504 }
505 self.chunk.emit_u8(Op::Call, args.len() as u8, self.line);
506 }
507
508 Node::MethodCall {
509 object,
510 method,
511 args,
512 } => {
513 if let Node::Identifier(name) = &object.node {
515 if self.enum_names.contains(name) {
516 for arg in args {
518 self.compile_node(arg)?;
519 }
520 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
521 let var_idx = self.chunk.add_constant(Constant::String(method.clone()));
522 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
523 let hi = (var_idx >> 8) as u8;
524 let lo = var_idx as u8;
525 self.chunk.code.push(hi);
526 self.chunk.code.push(lo);
527 self.chunk.lines.push(self.line);
528 self.chunk.columns.push(self.column);
529 self.chunk.lines.push(self.line);
530 self.chunk.columns.push(self.column);
531 let fc = args.len() as u16;
532 let fhi = (fc >> 8) as u8;
533 let flo = fc as u8;
534 self.chunk.code.push(fhi);
535 self.chunk.code.push(flo);
536 self.chunk.lines.push(self.line);
537 self.chunk.columns.push(self.column);
538 self.chunk.lines.push(self.line);
539 self.chunk.columns.push(self.column);
540 return Ok(());
541 }
542 }
543 self.compile_node(object)?;
544 for arg in args {
545 self.compile_node(arg)?;
546 }
547 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
548 self.chunk
549 .emit_method_call(name_idx, args.len() as u8, self.line);
550 }
551
552 Node::OptionalMethodCall {
553 object,
554 method,
555 args,
556 } => {
557 self.compile_node(object)?;
558 for arg in args {
559 self.compile_node(arg)?;
560 }
561 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
562 self.chunk
563 .emit_method_call_opt(name_idx, args.len() as u8, self.line);
564 }
565
566 Node::PropertyAccess { object, property } => {
567 if let Node::Identifier(name) = &object.node {
569 if self.enum_names.contains(name) {
570 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
572 let var_idx = self.chunk.add_constant(Constant::String(property.clone()));
573 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
574 let hi = (var_idx >> 8) as u8;
575 let lo = var_idx as u8;
576 self.chunk.code.push(hi);
577 self.chunk.code.push(lo);
578 self.chunk.lines.push(self.line);
579 self.chunk.columns.push(self.column);
580 self.chunk.lines.push(self.line);
581 self.chunk.columns.push(self.column);
582 self.chunk.code.push(0);
584 self.chunk.code.push(0);
585 self.chunk.lines.push(self.line);
586 self.chunk.columns.push(self.column);
587 self.chunk.lines.push(self.line);
588 self.chunk.columns.push(self.column);
589 return Ok(());
590 }
591 }
592 self.compile_node(object)?;
593 let idx = self.chunk.add_constant(Constant::String(property.clone()));
594 self.chunk.emit_u16(Op::GetProperty, idx, self.line);
595 }
596
597 Node::OptionalPropertyAccess { object, property } => {
598 self.compile_node(object)?;
599 let idx = self.chunk.add_constant(Constant::String(property.clone()));
600 self.chunk.emit_u16(Op::GetPropertyOpt, idx, self.line);
601 }
602
603 Node::SubscriptAccess { object, index } => {
604 self.compile_node(object)?;
605 self.compile_node(index)?;
606 self.chunk.emit(Op::Subscript, self.line);
607 }
608
609 Node::SliceAccess { object, start, end } => {
610 self.compile_node(object)?;
611 if let Some(s) = start {
612 self.compile_node(s)?;
613 } else {
614 self.chunk.emit(Op::Nil, self.line);
615 }
616 if let Some(e) = end {
617 self.compile_node(e)?;
618 } else {
619 self.chunk.emit(Op::Nil, self.line);
620 }
621 self.chunk.emit(Op::Slice, self.line);
622 }
623
624 Node::IfElse {
625 condition,
626 then_body,
627 else_body,
628 } => {
629 self.compile_node(condition)?;
630 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
631 self.chunk.emit(Op::Pop, self.line);
632 self.compile_block(then_body)?;
633 if let Some(else_body) = else_body {
634 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
635 self.chunk.patch_jump(else_jump);
636 self.chunk.emit(Op::Pop, self.line);
637 self.compile_block(else_body)?;
638 self.chunk.patch_jump(end_jump);
639 } else {
640 self.chunk.patch_jump(else_jump);
641 self.chunk.emit(Op::Pop, self.line);
642 self.chunk.emit(Op::Nil, self.line);
643 }
644 }
645
646 Node::WhileLoop { condition, body } => {
647 let loop_start = self.chunk.current_offset();
648 self.loop_stack.push(LoopContext {
649 start_offset: loop_start,
650 break_patches: Vec::new(),
651 has_iterator: false,
652 handler_depth: self.handler_depth,
653 finally_depth: self.finally_bodies.len(),
654 });
655 self.compile_node(condition)?;
656 let exit_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
657 self.chunk.emit(Op::Pop, self.line); for sn in body {
660 self.compile_node(sn)?;
661 if Self::produces_value(&sn.node) {
662 self.chunk.emit(Op::Pop, self.line);
663 }
664 }
665 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
667 self.chunk.patch_jump(exit_jump);
668 self.chunk.emit(Op::Pop, self.line); let ctx = self.loop_stack.pop().unwrap();
671 for patch_pos in ctx.break_patches {
672 self.chunk.patch_jump(patch_pos);
673 }
674 self.chunk.emit(Op::Nil, self.line);
675 }
676
677 Node::ForIn {
678 pattern,
679 iterable,
680 body,
681 } => {
682 self.compile_node(iterable)?;
684 self.chunk.emit(Op::IterInit, self.line);
686 let loop_start = self.chunk.current_offset();
687 self.loop_stack.push(LoopContext {
688 start_offset: loop_start,
689 break_patches: Vec::new(),
690 has_iterator: true,
691 handler_depth: self.handler_depth,
692 finally_depth: self.finally_bodies.len(),
693 });
694 let exit_jump_pos = self.chunk.emit_jump(Op::IterNext, self.line);
696 self.compile_destructuring(pattern, true)?;
698 for sn in body {
700 self.compile_node(sn)?;
701 if Self::produces_value(&sn.node) {
702 self.chunk.emit(Op::Pop, self.line);
703 }
704 }
705 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
707 self.chunk.patch_jump(exit_jump_pos);
708 let ctx = self.loop_stack.pop().unwrap();
710 for patch_pos in ctx.break_patches {
711 self.chunk.patch_jump(patch_pos);
712 }
713 self.chunk.emit(Op::Nil, self.line);
715 }
716
717 Node::ReturnStmt { value } => {
718 let has_pending_finally = !self.finally_bodies.is_empty();
719
720 if has_pending_finally {
721 if let Some(val) = value {
724 self.compile_node(val)?;
725 } else {
726 self.chunk.emit(Op::Nil, self.line);
727 }
728 self.temp_counter += 1;
729 let temp_name = format!("__return_val_{}__", self.temp_counter);
730 let save_idx =
731 self.chunk.add_constant(Constant::String(temp_name.clone()));
732 self.chunk.emit_u16(Op::DefVar, save_idx, self.line);
733 let finallys: Vec<_> = self.finally_bodies.iter().rev().cloned().collect();
735 for fb in &finallys {
736 self.compile_finally_inline(fb)?;
737 }
738 let restore_idx =
739 self.chunk.add_constant(Constant::String(temp_name));
740 self.chunk.emit_u16(Op::GetVar, restore_idx, self.line);
741 self.chunk.emit(Op::Return, self.line);
742 } else {
743 if let Some(val) = value {
745 if let Node::FunctionCall { name, args } = &val.node {
746 let name_idx =
747 self.chunk.add_constant(Constant::String(name.clone()));
748 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
749 for arg in args {
750 self.compile_node(arg)?;
751 }
752 self.chunk
753 .emit_u8(Op::TailCall, args.len() as u8, self.line);
754 } else if let Node::BinaryOp { op, left, right } = &val.node {
755 if op == "|>" {
756 self.compile_node(left)?;
757 self.compile_node(right)?;
758 self.chunk.emit(Op::Swap, self.line);
759 self.chunk.emit_u8(Op::TailCall, 1, self.line);
760 } else {
761 self.compile_node(val)?;
762 }
763 } else {
764 self.compile_node(val)?;
765 }
766 } else {
767 self.chunk.emit(Op::Nil, self.line);
768 }
769 self.chunk.emit(Op::Return, self.line);
770 }
771 }
772
773 Node::BreakStmt => {
774 if self.loop_stack.is_empty() {
775 return Err(CompileError {
776 message: "break outside of loop".to_string(),
777 line: self.line,
778 });
779 }
780 let ctx = self.loop_stack.last().unwrap();
782 let finally_depth = ctx.finally_depth;
783 let handler_depth = ctx.handler_depth;
784 let has_iterator = ctx.has_iterator;
785 for _ in handler_depth..self.handler_depth {
787 self.chunk.emit(Op::PopHandler, self.line);
788 }
789 if self.finally_bodies.len() > finally_depth {
791 let finallys: Vec<_> = self.finally_bodies[finally_depth..]
792 .iter()
793 .rev()
794 .cloned()
795 .collect();
796 for fb in &finallys {
797 self.compile_finally_inline(fb)?;
798 }
799 }
800 if has_iterator {
801 self.chunk.emit(Op::PopIterator, self.line);
802 }
803 let patch = self.chunk.emit_jump(Op::Jump, self.line);
804 self.loop_stack
805 .last_mut()
806 .unwrap()
807 .break_patches
808 .push(patch);
809 }
810
811 Node::ContinueStmt => {
812 if self.loop_stack.is_empty() {
813 return Err(CompileError {
814 message: "continue outside of loop".to_string(),
815 line: self.line,
816 });
817 }
818 let ctx = self.loop_stack.last().unwrap();
819 let finally_depth = ctx.finally_depth;
820 let handler_depth = ctx.handler_depth;
821 let loop_start = ctx.start_offset;
822 for _ in handler_depth..self.handler_depth {
823 self.chunk.emit(Op::PopHandler, self.line);
824 }
825 if self.finally_bodies.len() > finally_depth {
826 let finallys: Vec<_> = self.finally_bodies[finally_depth..]
827 .iter()
828 .rev()
829 .cloned()
830 .collect();
831 for fb in &finallys {
832 self.compile_finally_inline(fb)?;
833 }
834 }
835 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
836 }
837
838 Node::ListLiteral(elements) => {
839 let has_spread = elements.iter().any(|e| matches!(&e.node, Node::Spread(_)));
840 if !has_spread {
841 for el in elements {
842 self.compile_node(el)?;
843 }
844 self.chunk
845 .emit_u16(Op::BuildList, elements.len() as u16, self.line);
846 } else {
847 self.chunk.emit_u16(Op::BuildList, 0, self.line);
850 let mut pending = 0u16;
851 for el in elements {
852 if let Node::Spread(inner) = &el.node {
853 if pending > 0 {
855 self.chunk.emit_u16(Op::BuildList, pending, self.line);
856 self.chunk.emit(Op::Add, self.line);
858 pending = 0;
859 }
860 self.compile_node(inner)?;
862 self.chunk.emit(Op::Dup, self.line);
863 let assert_idx = self
864 .chunk
865 .add_constant(Constant::String("__assert_list".into()));
866 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
867 self.chunk.emit(Op::Swap, self.line);
868 self.chunk.emit_u8(Op::Call, 1, self.line);
869 self.chunk.emit(Op::Pop, self.line);
870 self.chunk.emit(Op::Add, self.line);
871 } else {
872 self.compile_node(el)?;
873 pending += 1;
874 }
875 }
876 if pending > 0 {
877 self.chunk.emit_u16(Op::BuildList, pending, self.line);
878 self.chunk.emit(Op::Add, self.line);
879 }
880 }
881 }
882
883 Node::DictLiteral(entries) => {
884 let has_spread = entries
885 .iter()
886 .any(|e| matches!(&e.value.node, Node::Spread(_)));
887 if !has_spread {
888 for entry in entries {
889 self.compile_node(&entry.key)?;
890 self.compile_node(&entry.value)?;
891 }
892 self.chunk
893 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
894 } else {
895 self.chunk.emit_u16(Op::BuildDict, 0, self.line);
897 let mut pending = 0u16;
898 for entry in entries {
899 if let Node::Spread(inner) = &entry.value.node {
900 if pending > 0 {
902 self.chunk.emit_u16(Op::BuildDict, pending, self.line);
903 self.chunk.emit(Op::Add, self.line);
904 pending = 0;
905 }
906 self.compile_node(inner)?;
908 self.chunk.emit(Op::Dup, self.line);
909 let assert_idx = self
910 .chunk
911 .add_constant(Constant::String("__assert_dict".into()));
912 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
913 self.chunk.emit(Op::Swap, self.line);
914 self.chunk.emit_u8(Op::Call, 1, self.line);
915 self.chunk.emit(Op::Pop, self.line);
916 self.chunk.emit(Op::Add, self.line);
917 } else {
918 self.compile_node(&entry.key)?;
919 self.compile_node(&entry.value)?;
920 pending += 1;
921 }
922 }
923 if pending > 0 {
924 self.chunk.emit_u16(Op::BuildDict, pending, self.line);
925 self.chunk.emit(Op::Add, self.line);
926 }
927 }
928 }
929
930 Node::InterpolatedString(segments) => {
931 let mut part_count = 0u16;
932 for seg in segments {
933 match seg {
934 StringSegment::Literal(s) => {
935 let idx = self.chunk.add_constant(Constant::String(s.clone()));
936 self.chunk.emit_u16(Op::Constant, idx, self.line);
937 part_count += 1;
938 }
939 StringSegment::Expression(expr_str) => {
940 let mut lexer = harn_lexer::Lexer::new(expr_str);
942 if let Ok(tokens) = lexer.tokenize() {
943 let mut parser = harn_parser::Parser::new(tokens);
944 if let Ok(snode) = parser.parse_single_expression() {
945 self.compile_node(&snode)?;
946 let to_str = self
948 .chunk
949 .add_constant(Constant::String("to_string".into()));
950 self.chunk.emit_u16(Op::Constant, to_str, self.line);
951 self.chunk.emit(Op::Swap, self.line);
952 self.chunk.emit_u8(Op::Call, 1, self.line);
953 part_count += 1;
954 } else {
955 let idx =
957 self.chunk.add_constant(Constant::String(expr_str.clone()));
958 self.chunk.emit_u16(Op::Constant, idx, self.line);
959 part_count += 1;
960 }
961 }
962 }
963 }
964 }
965 if part_count > 1 {
966 self.chunk.emit_u16(Op::Concat, part_count, self.line);
967 }
968 }
969
970 Node::FnDecl {
971 name, params, body, ..
972 } => {
973 let mut fn_compiler = Compiler::new();
975 fn_compiler.enum_names = self.enum_names.clone();
976 fn_compiler.emit_default_preamble(params)?;
977 fn_compiler.compile_block(body)?;
978 fn_compiler.chunk.emit(Op::Nil, self.line);
979 fn_compiler.chunk.emit(Op::Return, self.line);
980
981 let func = CompiledFunction {
982 name: name.clone(),
983 params: TypedParam::names(params),
984 default_start: TypedParam::default_start(params),
985 chunk: fn_compiler.chunk,
986 };
987 let fn_idx = self.chunk.functions.len();
988 self.chunk.functions.push(func);
989
990 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
991 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
992 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
993 }
994
995 Node::Closure { params, body } => {
996 let mut fn_compiler = Compiler::new();
997 fn_compiler.enum_names = self.enum_names.clone();
998 fn_compiler.emit_default_preamble(params)?;
999 fn_compiler.compile_block(body)?;
1000 fn_compiler.chunk.emit(Op::Return, self.line);
1002
1003 let func = CompiledFunction {
1004 name: "<closure>".to_string(),
1005 params: TypedParam::names(params),
1006 default_start: TypedParam::default_start(params),
1007 chunk: fn_compiler.chunk,
1008 };
1009 let fn_idx = self.chunk.functions.len();
1010 self.chunk.functions.push(func);
1011
1012 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1013 }
1014
1015 Node::ThrowStmt { value } => {
1016 self.compile_node(value)?;
1017 self.chunk.emit(Op::Throw, self.line);
1018 }
1019
1020 Node::MatchExpr { value, arms } => {
1021 self.compile_node(value)?;
1022 let mut end_jumps = Vec::new();
1023 for arm in arms {
1024 match &arm.pattern.node {
1025 Node::Identifier(name) if name == "_" => {
1027 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1029 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1030 }
1031 Node::EnumConstruct {
1033 enum_name,
1034 variant,
1035 args: pat_args,
1036 } => {
1037 self.chunk.emit(Op::Dup, self.line);
1039 let en_idx =
1040 self.chunk.add_constant(Constant::String(enum_name.clone()));
1041 let vn_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1042 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1043 let hi = (vn_idx >> 8) as u8;
1044 let lo = vn_idx as u8;
1045 self.chunk.code.push(hi);
1046 self.chunk.code.push(lo);
1047 self.chunk.lines.push(self.line);
1048 self.chunk.columns.push(self.column);
1049 self.chunk.lines.push(self.line);
1050 self.chunk.columns.push(self.column);
1051 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1053 self.chunk.emit(Op::Pop, self.line); for (i, pat_arg) in pat_args.iter().enumerate() {
1058 if let Node::Identifier(binding_name) = &pat_arg.node {
1059 self.chunk.emit(Op::Dup, self.line);
1061 let fields_idx = self
1062 .chunk
1063 .add_constant(Constant::String("fields".to_string()));
1064 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
1065 let idx_const =
1066 self.chunk.add_constant(Constant::Int(i as i64));
1067 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1068 self.chunk.emit(Op::Subscript, self.line);
1069 let name_idx = self
1070 .chunk
1071 .add_constant(Constant::String(binding_name.clone()));
1072 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1073 }
1074 }
1075
1076 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1078 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1079 self.chunk.patch_jump(skip);
1080 self.chunk.emit(Op::Pop, self.line); }
1082 Node::PropertyAccess { object, property } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
1084 {
1085 let enum_name = if let Node::Identifier(n) = &object.node {
1086 n.clone()
1087 } else {
1088 unreachable!()
1089 };
1090 self.chunk.emit(Op::Dup, self.line);
1091 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
1092 let vn_idx =
1093 self.chunk.add_constant(Constant::String(property.clone()));
1094 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1095 let hi = (vn_idx >> 8) as u8;
1096 let lo = vn_idx as u8;
1097 self.chunk.code.push(hi);
1098 self.chunk.code.push(lo);
1099 self.chunk.lines.push(self.line);
1100 self.chunk.columns.push(self.column);
1101 self.chunk.lines.push(self.line);
1102 self.chunk.columns.push(self.column);
1103 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1104 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1107 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1108 self.chunk.patch_jump(skip);
1109 self.chunk.emit(Op::Pop, self.line); }
1111 Node::MethodCall {
1114 object,
1115 method,
1116 args: pat_args,
1117 } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
1118 {
1119 let enum_name = if let Node::Identifier(n) = &object.node {
1120 n.clone()
1121 } else {
1122 unreachable!()
1123 };
1124 self.chunk.emit(Op::Dup, self.line);
1126 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
1127 let vn_idx = self.chunk.add_constant(Constant::String(method.clone()));
1128 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1129 let hi = (vn_idx >> 8) as u8;
1130 let lo = vn_idx as u8;
1131 self.chunk.code.push(hi);
1132 self.chunk.code.push(lo);
1133 self.chunk.lines.push(self.line);
1134 self.chunk.columns.push(self.column);
1135 self.chunk.lines.push(self.line);
1136 self.chunk.columns.push(self.column);
1137 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1138 self.chunk.emit(Op::Pop, self.line); for (i, pat_arg) in pat_args.iter().enumerate() {
1142 if let Node::Identifier(binding_name) = &pat_arg.node {
1143 self.chunk.emit(Op::Dup, self.line);
1144 let fields_idx = self
1145 .chunk
1146 .add_constant(Constant::String("fields".to_string()));
1147 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
1148 let idx_const =
1149 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 = self
1153 .chunk
1154 .add_constant(Constant::String(binding_name.clone()));
1155 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1156 }
1157 }
1158
1159 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1161 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1162 self.chunk.patch_jump(skip);
1163 self.chunk.emit(Op::Pop, self.line); }
1165 Node::Identifier(name) => {
1167 self.chunk.emit(Op::Dup, self.line); let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1170 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1171 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1173 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1174 }
1175 Node::DictLiteral(entries)
1177 if entries
1178 .iter()
1179 .all(|e| matches!(&e.key.node, Node::StringLiteral(_))) =>
1180 {
1181 self.chunk.emit(Op::Dup, self.line);
1183 let typeof_idx =
1184 self.chunk.add_constant(Constant::String("type_of".into()));
1185 self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
1186 self.chunk.emit(Op::Swap, self.line);
1187 self.chunk.emit_u8(Op::Call, 1, self.line);
1188 let dict_str = self.chunk.add_constant(Constant::String("dict".into()));
1189 self.chunk.emit_u16(Op::Constant, dict_str, self.line);
1190 self.chunk.emit(Op::Equal, self.line);
1191 let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1192 self.chunk.emit(Op::Pop, self.line); let mut constraint_skips = Vec::new();
1196 let mut bindings = Vec::new();
1197 for entry in entries {
1198 if let Node::StringLiteral(key) = &entry.key.node {
1199 match &entry.value.node {
1200 Node::StringLiteral(_)
1202 | Node::IntLiteral(_)
1203 | Node::FloatLiteral(_)
1204 | Node::BoolLiteral(_)
1205 | Node::NilLiteral => {
1206 self.chunk.emit(Op::Dup, self.line);
1207 let key_idx = self
1208 .chunk
1209 .add_constant(Constant::String(key.clone()));
1210 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1211 self.chunk.emit(Op::Subscript, self.line);
1212 self.compile_node(&entry.value)?;
1213 self.chunk.emit(Op::Equal, self.line);
1214 let skip =
1215 self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1216 self.chunk.emit(Op::Pop, self.line); constraint_skips.push(skip);
1218 }
1219 Node::Identifier(binding) => {
1221 bindings.push((key.clone(), binding.clone()));
1222 }
1223 _ => {
1224 self.chunk.emit(Op::Dup, self.line);
1226 let key_idx = self
1227 .chunk
1228 .add_constant(Constant::String(key.clone()));
1229 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1230 self.chunk.emit(Op::Subscript, self.line);
1231 self.compile_node(&entry.value)?;
1232 self.chunk.emit(Op::Equal, self.line);
1233 let skip =
1234 self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1235 self.chunk.emit(Op::Pop, self.line);
1236 constraint_skips.push(skip);
1237 }
1238 }
1239 }
1240 }
1241
1242 for (key, binding) in &bindings {
1244 self.chunk.emit(Op::Dup, self.line);
1245 let key_idx =
1246 self.chunk.add_constant(Constant::String(key.clone()));
1247 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1248 self.chunk.emit(Op::Subscript, self.line);
1249 let name_idx =
1250 self.chunk.add_constant(Constant::String(binding.clone()));
1251 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1252 }
1253
1254 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1256 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1257
1258 let fail_target = self.chunk.code.len();
1260 self.chunk.emit(Op::Pop, self.line); for skip in constraint_skips {
1263 self.chunk.patch_jump_to(skip, fail_target);
1264 }
1265 self.chunk.patch_jump_to(skip_type, fail_target);
1266 }
1267 Node::ListLiteral(elements) => {
1269 self.chunk.emit(Op::Dup, self.line);
1271 let typeof_idx =
1272 self.chunk.add_constant(Constant::String("type_of".into()));
1273 self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
1274 self.chunk.emit(Op::Swap, self.line);
1275 self.chunk.emit_u8(Op::Call, 1, self.line);
1276 let list_str = self.chunk.add_constant(Constant::String("list".into()));
1277 self.chunk.emit_u16(Op::Constant, list_str, self.line);
1278 self.chunk.emit(Op::Equal, self.line);
1279 let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1280 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Dup, self.line);
1284 let len_idx = self.chunk.add_constant(Constant::String("len".into()));
1285 self.chunk.emit_u16(Op::Constant, len_idx, self.line);
1286 self.chunk.emit(Op::Swap, self.line);
1287 self.chunk.emit_u8(Op::Call, 1, self.line);
1288 let count = self
1289 .chunk
1290 .add_constant(Constant::Int(elements.len() as i64));
1291 self.chunk.emit_u16(Op::Constant, count, self.line);
1292 self.chunk.emit(Op::GreaterEqual, self.line);
1293 let skip_len = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1294 self.chunk.emit(Op::Pop, self.line); let mut constraint_skips = Vec::new();
1298 let mut bindings = Vec::new();
1299 for (i, elem) in elements.iter().enumerate() {
1300 match &elem.node {
1301 Node::Identifier(name) if name != "_" => {
1302 bindings.push((i, name.clone()));
1303 }
1304 Node::Identifier(_) => {} _ => {
1307 self.chunk.emit(Op::Dup, self.line);
1308 let idx_const =
1309 self.chunk.add_constant(Constant::Int(i as i64));
1310 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1311 self.chunk.emit(Op::Subscript, self.line);
1312 self.compile_node(elem)?;
1313 self.chunk.emit(Op::Equal, self.line);
1314 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1315 self.chunk.emit(Op::Pop, self.line);
1316 constraint_skips.push(skip);
1317 }
1318 }
1319 }
1320
1321 for (i, name) in &bindings {
1323 self.chunk.emit(Op::Dup, self.line);
1324 let idx_const = self.chunk.add_constant(Constant::Int(*i as i64));
1325 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1326 self.chunk.emit(Op::Subscript, self.line);
1327 let name_idx =
1328 self.chunk.add_constant(Constant::String(name.clone()));
1329 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1330 }
1331
1332 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1334 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1335
1336 let fail_target = self.chunk.code.len();
1338 self.chunk.emit(Op::Pop, self.line); for skip in constraint_skips {
1340 self.chunk.patch_jump_to(skip, fail_target);
1341 }
1342 self.chunk.patch_jump_to(skip_len, fail_target);
1343 self.chunk.patch_jump_to(skip_type, fail_target);
1344 }
1345 _ => {
1347 self.chunk.emit(Op::Dup, self.line);
1348 self.compile_node(&arm.pattern)?;
1349 self.chunk.emit(Op::Equal, self.line);
1350 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1351 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1354 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1355 self.chunk.patch_jump(skip);
1356 self.chunk.emit(Op::Pop, self.line); }
1358 }
1359 }
1360 self.chunk.emit(Op::Pop, self.line);
1362 self.chunk.emit(Op::Nil, self.line);
1363 for j in end_jumps {
1364 self.chunk.patch_jump(j);
1365 }
1366 }
1367
1368 Node::RangeExpr {
1369 start,
1370 end,
1371 inclusive,
1372 } => {
1373 let name_idx = self
1375 .chunk
1376 .add_constant(Constant::String("__range__".to_string()));
1377 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
1378 self.compile_node(start)?;
1379 self.compile_node(end)?;
1380 if *inclusive {
1381 self.chunk.emit(Op::True, self.line);
1382 } else {
1383 self.chunk.emit(Op::False, self.line);
1384 }
1385 self.chunk.emit_u8(Op::Call, 3, self.line);
1386 }
1387
1388 Node::GuardStmt {
1389 condition,
1390 else_body,
1391 } => {
1392 self.compile_node(condition)?;
1395 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
1396 self.chunk.emit(Op::Pop, self.line); self.compile_block(else_body)?;
1399 if !else_body.is_empty() && Self::produces_value(&else_body.last().unwrap().node) {
1401 self.chunk.emit(Op::Pop, self.line);
1402 }
1403 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1404 self.chunk.patch_jump(skip_jump);
1405 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end_jump);
1407 self.chunk.emit(Op::Nil, self.line);
1408 }
1409
1410 Node::Block(stmts) => {
1411 if stmts.is_empty() {
1412 self.chunk.emit(Op::Nil, self.line);
1413 } else {
1414 self.compile_block(stmts)?;
1415 }
1416 }
1417
1418 Node::DeadlineBlock { duration, body } => {
1419 self.compile_node(duration)?;
1420 self.chunk.emit(Op::DeadlineSetup, self.line);
1421 if body.is_empty() {
1422 self.chunk.emit(Op::Nil, self.line);
1423 } else {
1424 self.compile_block(body)?;
1425 }
1426 self.chunk.emit(Op::DeadlineEnd, self.line);
1427 }
1428
1429 Node::MutexBlock { body } => {
1430 if body.is_empty() {
1432 self.chunk.emit(Op::Nil, self.line);
1433 } else {
1434 for sn in body {
1437 self.compile_node(sn)?;
1438 if Self::produces_value(&sn.node) {
1439 self.chunk.emit(Op::Pop, self.line);
1440 }
1441 }
1442 self.chunk.emit(Op::Nil, self.line);
1443 }
1444 }
1445
1446 Node::YieldExpr { .. } => {
1447 self.chunk.emit(Op::Nil, self.line);
1449 }
1450
1451 Node::AskExpr { fields } => {
1452 for entry in fields {
1455 self.compile_node(&entry.key)?;
1456 self.compile_node(&entry.value)?;
1457 }
1458 self.chunk
1459 .emit_u16(Op::BuildDict, fields.len() as u16, self.line);
1460 }
1461
1462 Node::EnumConstruct {
1463 enum_name,
1464 variant,
1465 args,
1466 } => {
1467 for arg in args {
1469 self.compile_node(arg)?;
1470 }
1471 let enum_idx = self.chunk.add_constant(Constant::String(enum_name.clone()));
1472 let var_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1473 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
1475 let hi = (var_idx >> 8) as u8;
1476 let lo = var_idx as u8;
1477 self.chunk.code.push(hi);
1478 self.chunk.code.push(lo);
1479 self.chunk.lines.push(self.line);
1480 self.chunk.columns.push(self.column);
1481 self.chunk.lines.push(self.line);
1482 self.chunk.columns.push(self.column);
1483 let fc = args.len() as u16;
1484 let fhi = (fc >> 8) as u8;
1485 let flo = fc as u8;
1486 self.chunk.code.push(fhi);
1487 self.chunk.code.push(flo);
1488 self.chunk.lines.push(self.line);
1489 self.chunk.columns.push(self.column);
1490 self.chunk.lines.push(self.line);
1491 self.chunk.columns.push(self.column);
1492 }
1493
1494 Node::StructConstruct {
1495 struct_name,
1496 fields,
1497 } => {
1498 let struct_key = self
1500 .chunk
1501 .add_constant(Constant::String("__struct__".to_string()));
1502 let struct_val = self
1503 .chunk
1504 .add_constant(Constant::String(struct_name.clone()));
1505 self.chunk.emit_u16(Op::Constant, struct_key, self.line);
1506 self.chunk.emit_u16(Op::Constant, struct_val, self.line);
1507
1508 for entry in fields {
1509 self.compile_node(&entry.key)?;
1510 self.compile_node(&entry.value)?;
1511 }
1512 self.chunk
1513 .emit_u16(Op::BuildDict, (fields.len() + 1) as u16, self.line);
1514 }
1515
1516 Node::ImportDecl { path } => {
1517 let idx = self.chunk.add_constant(Constant::String(path.clone()));
1518 self.chunk.emit_u16(Op::Import, idx, self.line);
1519 }
1520
1521 Node::SelectiveImport { names, path } => {
1522 let path_idx = self.chunk.add_constant(Constant::String(path.clone()));
1523 let names_str = names.join(",");
1524 let names_idx = self.chunk.add_constant(Constant::String(names_str));
1525 self.chunk
1526 .emit_u16(Op::SelectiveImport, path_idx, self.line);
1527 let hi = (names_idx >> 8) as u8;
1528 let lo = names_idx as u8;
1529 self.chunk.code.push(hi);
1530 self.chunk.code.push(lo);
1531 self.chunk.lines.push(self.line);
1532 self.chunk.columns.push(self.column);
1533 self.chunk.lines.push(self.line);
1534 self.chunk.columns.push(self.column);
1535 }
1536
1537 Node::Pipeline { .. }
1539 | Node::OverrideDecl { .. }
1540 | Node::TypeDecl { .. }
1541 | Node::EnumDecl { .. }
1542 | Node::StructDecl { .. }
1543 | Node::InterfaceDecl { .. } => {
1544 self.chunk.emit(Op::Nil, self.line);
1545 }
1546
1547 Node::TryCatch {
1548 body,
1549 error_var,
1550 error_type,
1551 catch_body,
1552 finally_body,
1553 } => {
1554 let type_name = error_type.as_ref().and_then(|te| {
1556 if let harn_parser::TypeExpr::Named(name) = te {
1557 Some(name.clone())
1558 } else {
1559 None
1560 }
1561 });
1562
1563 let type_name_idx = if let Some(ref tn) = type_name {
1564 self.chunk.add_constant(Constant::String(tn.clone()))
1565 } else {
1566 self.chunk.add_constant(Constant::String(String::new()))
1567 };
1568
1569 let has_catch = !catch_body.is_empty() || error_var.is_some();
1570 let has_finally = finally_body.is_some();
1571
1572 if has_catch && has_finally {
1573 let finally_body = finally_body.as_ref().unwrap();
1575
1576 self.finally_bodies.push(finally_body.clone());
1578
1579 self.handler_depth += 1;
1581 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1582 self.emit_type_name_extra(type_name_idx);
1583
1584 self.compile_try_body(body)?;
1586
1587 self.handler_depth -= 1;
1589 self.chunk.emit(Op::PopHandler, self.line);
1590 self.compile_finally_inline(finally_body)?;
1591 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1592
1593 self.chunk.patch_jump(catch_jump);
1595 self.compile_catch_binding(error_var)?;
1596
1597 self.handler_depth += 1;
1599 let rethrow_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1600 let empty_type =
1601 self.chunk.add_constant(Constant::String(String::new()));
1602 self.emit_type_name_extra(empty_type);
1603
1604 self.compile_try_body(catch_body)?;
1606
1607 self.handler_depth -= 1;
1609 self.chunk.emit(Op::PopHandler, self.line);
1610 self.compile_finally_inline(finally_body)?;
1611 let end_jump2 = self.chunk.emit_jump(Op::Jump, self.line);
1612
1613 self.chunk.patch_jump(rethrow_jump);
1615 self.compile_rethrow_with_finally(finally_body)?;
1616
1617 self.chunk.patch_jump(end_jump);
1618 self.chunk.patch_jump(end_jump2);
1619
1620 self.finally_bodies.pop();
1621 } else if has_finally {
1622 let finally_body = finally_body.as_ref().unwrap();
1624
1625 self.finally_bodies.push(finally_body.clone());
1626
1627 self.handler_depth += 1;
1629 let error_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1630 let empty_type =
1631 self.chunk.add_constant(Constant::String(String::new()));
1632 self.emit_type_name_extra(empty_type);
1633
1634 self.compile_try_body(body)?;
1636
1637 self.handler_depth -= 1;
1639 self.chunk.emit(Op::PopHandler, self.line);
1640 self.compile_finally_inline(finally_body)?;
1641 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1642
1643 self.chunk.patch_jump(error_jump);
1645 self.compile_rethrow_with_finally(finally_body)?;
1646
1647 self.chunk.patch_jump(end_jump);
1648
1649 self.finally_bodies.pop();
1650 } else {
1651 self.handler_depth += 1;
1655 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1656 self.emit_type_name_extra(type_name_idx);
1657
1658 self.compile_try_body(body)?;
1660
1661 self.handler_depth -= 1;
1663 self.chunk.emit(Op::PopHandler, self.line);
1664 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1665
1666 self.chunk.patch_jump(catch_jump);
1668 self.compile_catch_binding(error_var)?;
1669
1670 self.compile_try_body(catch_body)?;
1672
1673 self.chunk.patch_jump(end_jump);
1675 }
1676 }
1677
1678 Node::Retry { count, body } => {
1679 self.compile_node(count)?;
1681 let counter_name = "__retry_counter__";
1682 let counter_idx = self
1683 .chunk
1684 .add_constant(Constant::String(counter_name.to_string()));
1685 self.chunk.emit_u16(Op::DefVar, counter_idx, self.line);
1686
1687 self.chunk.emit(Op::Nil, self.line);
1689 let err_name = "__retry_last_error__";
1690 let err_idx = self
1691 .chunk
1692 .add_constant(Constant::String(err_name.to_string()));
1693 self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
1694
1695 let loop_start = self.chunk.current_offset();
1697
1698 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1700 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1702 let hi = (empty_type >> 8) as u8;
1703 let lo = empty_type as u8;
1704 self.chunk.code.push(hi);
1705 self.chunk.code.push(lo);
1706 self.chunk.lines.push(self.line);
1707 self.chunk.columns.push(self.column);
1708 self.chunk.lines.push(self.line);
1709 self.chunk.columns.push(self.column);
1710
1711 self.compile_block(body)?;
1713
1714 self.chunk.emit(Op::PopHandler, self.line);
1716 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1717
1718 self.chunk.patch_jump(catch_jump);
1720 self.chunk.emit(Op::Dup, self.line);
1722 self.chunk.emit_u16(Op::SetVar, err_idx, self.line);
1723 self.chunk.emit(Op::Pop, self.line);
1725
1726 self.chunk.emit_u16(Op::GetVar, counter_idx, self.line);
1728 let one_idx = self.chunk.add_constant(Constant::Int(1));
1729 self.chunk.emit_u16(Op::Constant, one_idx, self.line);
1730 self.chunk.emit(Op::Sub, self.line);
1731 self.chunk.emit(Op::Dup, self.line);
1732 self.chunk.emit_u16(Op::SetVar, counter_idx, self.line);
1733
1734 let zero_idx = self.chunk.add_constant(Constant::Int(0));
1736 self.chunk.emit_u16(Op::Constant, zero_idx, self.line);
1737 self.chunk.emit(Op::Greater, self.line);
1738 let retry_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1739 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
1741
1742 self.chunk.patch_jump(retry_jump);
1744 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::GetVar, err_idx, self.line);
1746 self.chunk.emit(Op::Throw, self.line);
1747
1748 self.chunk.patch_jump(end_jump);
1749 self.chunk.emit(Op::Nil, self.line);
1751 }
1752
1753 Node::Parallel {
1754 count,
1755 variable,
1756 body,
1757 } => {
1758 self.compile_node(count)?;
1759 let mut fn_compiler = Compiler::new();
1760 fn_compiler.enum_names = self.enum_names.clone();
1761 fn_compiler.compile_block(body)?;
1762 fn_compiler.chunk.emit(Op::Return, self.line);
1763 let params = vec![variable.clone().unwrap_or_else(|| "__i__".to_string())];
1764 let func = CompiledFunction {
1765 name: "<parallel>".to_string(),
1766 params,
1767 default_start: None,
1768 chunk: fn_compiler.chunk,
1769 };
1770 let fn_idx = self.chunk.functions.len();
1771 self.chunk.functions.push(func);
1772 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1773 self.chunk.emit(Op::Parallel, self.line);
1774 }
1775
1776 Node::ParallelMap {
1777 list,
1778 variable,
1779 body,
1780 } => {
1781 self.compile_node(list)?;
1782 let mut fn_compiler = Compiler::new();
1783 fn_compiler.enum_names = self.enum_names.clone();
1784 fn_compiler.compile_block(body)?;
1785 fn_compiler.chunk.emit(Op::Return, self.line);
1786 let func = CompiledFunction {
1787 name: "<parallel_map>".to_string(),
1788 params: vec![variable.clone()],
1789 default_start: None,
1790 chunk: fn_compiler.chunk,
1791 };
1792 let fn_idx = self.chunk.functions.len();
1793 self.chunk.functions.push(func);
1794 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1795 self.chunk.emit(Op::ParallelMap, self.line);
1796 }
1797
1798 Node::SpawnExpr { body } => {
1799 let mut fn_compiler = Compiler::new();
1800 fn_compiler.enum_names = self.enum_names.clone();
1801 fn_compiler.compile_block(body)?;
1802 fn_compiler.chunk.emit(Op::Return, self.line);
1803 let func = CompiledFunction {
1804 name: "<spawn>".to_string(),
1805 params: vec![],
1806 default_start: None,
1807 chunk: fn_compiler.chunk,
1808 };
1809 let fn_idx = self.chunk.functions.len();
1810 self.chunk.functions.push(func);
1811 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1812 self.chunk.emit(Op::Spawn, self.line);
1813 }
1814 Node::SelectExpr {
1815 cases,
1816 timeout,
1817 default_body,
1818 } => {
1819 let builtin_name = if timeout.is_some() {
1826 "__select_timeout"
1827 } else if default_body.is_some() {
1828 "__select_try"
1829 } else {
1830 "__select_list"
1831 };
1832
1833 let name_idx = self
1835 .chunk
1836 .add_constant(Constant::String(builtin_name.into()));
1837 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
1838
1839 for case in cases {
1841 self.compile_node(&case.channel)?;
1842 }
1843 self.chunk
1844 .emit_u16(Op::BuildList, cases.len() as u16, self.line);
1845
1846 if let Some((duration_expr, _)) = timeout {
1848 self.compile_node(duration_expr)?;
1849 self.chunk.emit_u8(Op::Call, 2, self.line);
1850 } else {
1851 self.chunk.emit_u8(Op::Call, 1, self.line);
1852 }
1853
1854 self.temp_counter += 1;
1856 let result_name = format!("__sel_result_{}__", self.temp_counter);
1857 let result_idx = self
1858 .chunk
1859 .add_constant(Constant::String(result_name.clone()));
1860 self.chunk.emit_u16(Op::DefVar, result_idx, self.line);
1861
1862 let mut end_jumps = Vec::new();
1864
1865 for (i, case) in cases.iter().enumerate() {
1866 let get_r = self
1867 .chunk
1868 .add_constant(Constant::String(result_name.clone()));
1869 self.chunk.emit_u16(Op::GetVar, get_r, self.line);
1870 let idx_prop = self
1871 .chunk
1872 .add_constant(Constant::String("index".into()));
1873 self.chunk.emit_u16(Op::GetProperty, idx_prop, self.line);
1874 let case_i = self.chunk.add_constant(Constant::Int(i as i64));
1875 self.chunk.emit_u16(Op::Constant, case_i, self.line);
1876 self.chunk.emit(Op::Equal, self.line);
1877 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1878 self.chunk.emit(Op::Pop, self.line);
1879
1880 let get_r2 = self
1882 .chunk
1883 .add_constant(Constant::String(result_name.clone()));
1884 self.chunk.emit_u16(Op::GetVar, get_r2, self.line);
1885 let val_prop = self
1886 .chunk
1887 .add_constant(Constant::String("value".into()));
1888 self.chunk.emit_u16(Op::GetProperty, val_prop, self.line);
1889 let var_idx = self
1890 .chunk
1891 .add_constant(Constant::String(case.variable.clone()));
1892 self.chunk.emit_u16(Op::DefLet, var_idx, self.line);
1893
1894 self.compile_try_body(&case.body)?;
1895 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1896 self.chunk.patch_jump(skip);
1897 self.chunk.emit(Op::Pop, self.line);
1898 }
1899
1900 if let Some((_, ref timeout_body)) = timeout {
1902 self.compile_try_body(timeout_body)?;
1903 } else if let Some(ref def_body) = default_body {
1904 self.compile_try_body(def_body)?;
1905 } else {
1906 self.chunk.emit(Op::Nil, self.line);
1907 }
1908
1909 for ej in end_jumps {
1910 self.chunk.patch_jump(ej);
1911 }
1912 }
1913 Node::Spread(_) => {
1914 return Err(CompileError {
1915 message: "spread (...) can only be used inside list or dict literals".into(),
1916 line: self.line,
1917 });
1918 }
1919 }
1920 Ok(())
1921 }
1922
1923 fn compile_destructuring(
1927 &mut self,
1928 pattern: &BindingPattern,
1929 is_mutable: bool,
1930 ) -> Result<(), CompileError> {
1931 let def_op = if is_mutable { Op::DefVar } else { Op::DefLet };
1932 match pattern {
1933 BindingPattern::Identifier(name) => {
1934 let idx = self.chunk.add_constant(Constant::String(name.clone()));
1936 self.chunk.emit_u16(def_op, idx, self.line);
1937 }
1938 BindingPattern::Dict(fields) => {
1939 self.chunk.emit(Op::Dup, self.line);
1942 let assert_idx = self
1943 .chunk
1944 .add_constant(Constant::String("__assert_dict".into()));
1945 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1946 self.chunk.emit(Op::Swap, self.line);
1947 self.chunk.emit_u8(Op::Call, 1, self.line);
1948 self.chunk.emit(Op::Pop, self.line); let non_rest: Vec<_> = fields.iter().filter(|f| !f.is_rest).collect();
1953 let rest_field = fields.iter().find(|f| f.is_rest);
1954
1955 for field in &non_rest {
1956 self.chunk.emit(Op::Dup, self.line);
1957 let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
1958 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1959 self.chunk.emit(Op::Subscript, self.line);
1960 let binding_name = field.alias.as_deref().unwrap_or(&field.key);
1961 let name_idx = self
1962 .chunk
1963 .add_constant(Constant::String(binding_name.to_string()));
1964 self.chunk.emit_u16(def_op, name_idx, self.line);
1965 }
1966
1967 if let Some(rest) = rest_field {
1968 let fn_idx = self
1971 .chunk
1972 .add_constant(Constant::String("__dict_rest".into()));
1973 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
1974 self.chunk.emit(Op::Swap, self.line);
1976 for field in &non_rest {
1978 let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
1979 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1980 }
1981 self.chunk
1982 .emit_u16(Op::BuildList, non_rest.len() as u16, self.line);
1983 self.chunk.emit_u8(Op::Call, 2, self.line);
1985 let rest_name = &rest.key;
1986 let rest_idx = self.chunk.add_constant(Constant::String(rest_name.clone()));
1987 self.chunk.emit_u16(def_op, rest_idx, self.line);
1988 } else {
1989 self.chunk.emit(Op::Pop, self.line);
1991 }
1992 }
1993 BindingPattern::List(elements) => {
1994 self.chunk.emit(Op::Dup, self.line);
1997 let assert_idx = self
1998 .chunk
1999 .add_constant(Constant::String("__assert_list".into()));
2000 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
2001 self.chunk.emit(Op::Swap, self.line);
2002 self.chunk.emit_u8(Op::Call, 1, self.line);
2003 self.chunk.emit(Op::Pop, self.line); let non_rest: Vec<_> = elements.iter().filter(|e| !e.is_rest).collect();
2006 let rest_elem = elements.iter().find(|e| e.is_rest);
2007
2008 for (i, elem) in non_rest.iter().enumerate() {
2009 self.chunk.emit(Op::Dup, self.line);
2010 let idx_const = self.chunk.add_constant(Constant::Int(i as i64));
2011 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
2012 self.chunk.emit(Op::Subscript, self.line);
2013 let name_idx = self.chunk.add_constant(Constant::String(elem.name.clone()));
2014 self.chunk.emit_u16(def_op, name_idx, self.line);
2015 }
2016
2017 if let Some(rest) = rest_elem {
2018 let start_idx = self
2022 .chunk
2023 .add_constant(Constant::Int(non_rest.len() as i64));
2024 self.chunk.emit_u16(Op::Constant, start_idx, self.line);
2025 self.chunk.emit(Op::Nil, self.line); self.chunk.emit(Op::Slice, self.line);
2027 let rest_name_idx =
2028 self.chunk.add_constant(Constant::String(rest.name.clone()));
2029 self.chunk.emit_u16(def_op, rest_name_idx, self.line);
2030 } else {
2031 self.chunk.emit(Op::Pop, self.line);
2033 }
2034 }
2035 }
2036 Ok(())
2037 }
2038
2039 fn produces_value(node: &Node) -> bool {
2041 match node {
2042 Node::LetBinding { .. }
2044 | Node::VarBinding { .. }
2045 | Node::Assignment { .. }
2046 | Node::ReturnStmt { .. }
2047 | Node::FnDecl { .. }
2048 | Node::ThrowStmt { .. }
2049 | Node::BreakStmt
2050 | Node::ContinueStmt => false,
2051 Node::TryCatch { .. }
2053 | Node::Retry { .. }
2054 | Node::GuardStmt { .. }
2055 | Node::DeadlineBlock { .. }
2056 | Node::MutexBlock { .. }
2057 | Node::Spread(_) => true,
2058 _ => true,
2060 }
2061 }
2062}
2063
2064impl Compiler {
2065 pub fn compile_fn_body(
2067 &mut self,
2068 params: &[TypedParam],
2069 body: &[SNode],
2070 ) -> Result<CompiledFunction, CompileError> {
2071 let mut fn_compiler = Compiler::new();
2072 fn_compiler.compile_block(body)?;
2073 fn_compiler.chunk.emit(Op::Nil, 0);
2074 fn_compiler.chunk.emit(Op::Return, 0);
2075 Ok(CompiledFunction {
2076 name: String::new(),
2077 params: TypedParam::names(params),
2078 default_start: TypedParam::default_start(params),
2079 chunk: fn_compiler.chunk,
2080 })
2081 }
2082
2083 fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
2085 if body.is_empty() {
2086 self.chunk.emit(Op::Nil, self.line);
2087 } else {
2088 self.compile_block(body)?;
2089 if !Self::produces_value(&body.last().unwrap().node) {
2091 self.chunk.emit(Op::Nil, self.line);
2092 }
2093 }
2094 Ok(())
2095 }
2096
2097 fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
2099 match op {
2100 "+" => self.chunk.emit(Op::Add, self.line),
2101 "-" => self.chunk.emit(Op::Sub, self.line),
2102 "*" => self.chunk.emit(Op::Mul, self.line),
2103 "/" => self.chunk.emit(Op::Div, self.line),
2104 "%" => self.chunk.emit(Op::Mod, self.line),
2105 _ => {
2106 return Err(CompileError {
2107 message: format!("Unknown compound operator: {op}"),
2108 line: self.line,
2109 })
2110 }
2111 }
2112 Ok(())
2113 }
2114
2115 fn root_var_name(&self, node: &SNode) -> Option<String> {
2117 match &node.node {
2118 Node::Identifier(name) => Some(name.clone()),
2119 Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
2120 self.root_var_name(object)
2121 }
2122 Node::SubscriptAccess { object, .. } => self.root_var_name(object),
2123 _ => None,
2124 }
2125 }
2126}
2127
2128impl Compiler {
2129 fn collect_enum_names(nodes: &[SNode], names: &mut std::collections::HashSet<String>) {
2131 for sn in nodes {
2132 match &sn.node {
2133 Node::EnumDecl { name, .. } => {
2134 names.insert(name.clone());
2135 }
2136 Node::Pipeline { body, .. } => {
2137 Self::collect_enum_names(body, names);
2138 }
2139 Node::FnDecl { body, .. } => {
2140 Self::collect_enum_names(body, names);
2141 }
2142 Node::Block(stmts) => {
2143 Self::collect_enum_names(stmts, names);
2144 }
2145 _ => {}
2146 }
2147 }
2148 }
2149}
2150
2151impl Default for Compiler {
2152 fn default() -> Self {
2153 Self::new()
2154 }
2155}
2156
2157fn contains_pipe_placeholder(node: &SNode) -> bool {
2159 match &node.node {
2160 Node::Identifier(name) if name == "_" => true,
2161 Node::FunctionCall { args, .. } => args.iter().any(contains_pipe_placeholder),
2162 Node::MethodCall { object, args, .. } => {
2163 contains_pipe_placeholder(object) || args.iter().any(contains_pipe_placeholder)
2164 }
2165 Node::BinaryOp { left, right, .. } => {
2166 contains_pipe_placeholder(left) || contains_pipe_placeholder(right)
2167 }
2168 Node::UnaryOp { operand, .. } => contains_pipe_placeholder(operand),
2169 Node::ListLiteral(items) => items.iter().any(contains_pipe_placeholder),
2170 Node::PropertyAccess { object, .. } => contains_pipe_placeholder(object),
2171 Node::SubscriptAccess { object, index } => {
2172 contains_pipe_placeholder(object) || contains_pipe_placeholder(index)
2173 }
2174 _ => false,
2175 }
2176}
2177
2178fn replace_pipe_placeholder(node: &SNode) -> SNode {
2180 let new_node = match &node.node {
2181 Node::Identifier(name) if name == "_" => Node::Identifier("__pipe".into()),
2182 Node::FunctionCall { name, args } => Node::FunctionCall {
2183 name: name.clone(),
2184 args: args.iter().map(replace_pipe_placeholder).collect(),
2185 },
2186 Node::MethodCall {
2187 object,
2188 method,
2189 args,
2190 } => Node::MethodCall {
2191 object: Box::new(replace_pipe_placeholder(object)),
2192 method: method.clone(),
2193 args: args.iter().map(replace_pipe_placeholder).collect(),
2194 },
2195 Node::BinaryOp { op, left, right } => Node::BinaryOp {
2196 op: op.clone(),
2197 left: Box::new(replace_pipe_placeholder(left)),
2198 right: Box::new(replace_pipe_placeholder(right)),
2199 },
2200 Node::UnaryOp { op, operand } => Node::UnaryOp {
2201 op: op.clone(),
2202 operand: Box::new(replace_pipe_placeholder(operand)),
2203 },
2204 Node::ListLiteral(items) => {
2205 Node::ListLiteral(items.iter().map(replace_pipe_placeholder).collect())
2206 }
2207 Node::PropertyAccess { object, property } => Node::PropertyAccess {
2208 object: Box::new(replace_pipe_placeholder(object)),
2209 property: property.clone(),
2210 },
2211 Node::SubscriptAccess { object, index } => Node::SubscriptAccess {
2212 object: Box::new(replace_pipe_placeholder(object)),
2213 index: Box::new(replace_pipe_placeholder(index)),
2214 },
2215 _ => return node.clone(),
2216 };
2217 SNode::new(new_node, node.span)
2218}
2219
2220#[cfg(test)]
2221mod tests {
2222 use super::*;
2223 use harn_lexer::Lexer;
2224 use harn_parser::Parser;
2225
2226 fn compile_source(source: &str) -> Chunk {
2227 let mut lexer = Lexer::new(source);
2228 let tokens = lexer.tokenize().unwrap();
2229 let mut parser = Parser::new(tokens);
2230 let program = parser.parse().unwrap();
2231 Compiler::new().compile(&program).unwrap()
2232 }
2233
2234 #[test]
2235 fn test_compile_arithmetic() {
2236 let chunk = compile_source("pipeline test(task) { let x = 2 + 3 }");
2237 assert!(!chunk.code.is_empty());
2238 assert!(chunk.constants.contains(&Constant::Int(2)));
2240 assert!(chunk.constants.contains(&Constant::Int(3)));
2241 }
2242
2243 #[test]
2244 fn test_compile_function_call() {
2245 let chunk = compile_source("pipeline test(task) { log(42) }");
2246 let disasm = chunk.disassemble("test");
2247 assert!(disasm.contains("CALL"));
2248 }
2249
2250 #[test]
2251 fn test_compile_if_else() {
2252 let chunk =
2253 compile_source(r#"pipeline test(task) { if true { log("yes") } else { log("no") } }"#);
2254 let disasm = chunk.disassemble("test");
2255 assert!(disasm.contains("JUMP_IF_FALSE"));
2256 assert!(disasm.contains("JUMP"));
2257 }
2258
2259 #[test]
2260 fn test_compile_while() {
2261 let chunk = compile_source("pipeline test(task) { var i = 0\n while i < 5 { i = i + 1 } }");
2262 let disasm = chunk.disassemble("test");
2263 assert!(disasm.contains("JUMP_IF_FALSE"));
2264 assert!(disasm.contains("JUMP"));
2266 }
2267
2268 #[test]
2269 fn test_compile_closure() {
2270 let chunk = compile_source("pipeline test(task) { let f = { x -> x * 2 } }");
2271 assert!(!chunk.functions.is_empty());
2272 assert_eq!(chunk.functions[0].params, vec!["x"]);
2273 }
2274
2275 #[test]
2276 fn test_compile_list() {
2277 let chunk = compile_source("pipeline test(task) { let a = [1, 2, 3] }");
2278 let disasm = chunk.disassemble("test");
2279 assert!(disasm.contains("BUILD_LIST"));
2280 }
2281
2282 #[test]
2283 fn test_compile_dict() {
2284 let chunk = compile_source(r#"pipeline test(task) { let d = {name: "test"} }"#);
2285 let disasm = chunk.disassemble("test");
2286 assert!(disasm.contains("BUILD_DICT"));
2287 }
2288
2289 #[test]
2290 fn test_disassemble() {
2291 let chunk = compile_source("pipeline test(task) { log(2 + 3) }");
2292 let disasm = chunk.disassemble("test");
2293 assert!(disasm.contains("CONSTANT"));
2295 assert!(disasm.contains("ADD"));
2296 assert!(disasm.contains("CALL"));
2297 }
2298}