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.chunk.add_constant(Constant::Int((i + 1) as i64));
179 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
180 self.chunk.emit(Op::GreaterEqual, self.line);
182 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
183 self.chunk.emit(Op::Pop, self.line);
185 self.compile_node(default_expr)?;
187 let name_idx = self
188 .chunk
189 .add_constant(Constant::String(param.name.clone()));
190 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
191 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
192 self.chunk.patch_jump(skip_jump);
193 self.chunk.emit(Op::Pop, self.line);
195 self.chunk.patch_jump(end_jump);
196 }
197 }
198 Ok(())
199 }
200
201 fn emit_type_name_extra(&mut self, type_name_idx: u16) {
203 let hi = (type_name_idx >> 8) as u8;
204 let lo = type_name_idx as u8;
205 self.chunk.code.push(hi);
206 self.chunk.code.push(lo);
207 self.chunk.lines.push(self.line);
208 self.chunk.columns.push(self.column);
209 self.chunk.lines.push(self.line);
210 self.chunk.columns.push(self.column);
211 }
212
213 fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
215 if body.is_empty() {
216 self.chunk.emit(Op::Nil, self.line);
217 } else {
218 self.compile_block(body)?;
219 if !Self::produces_value(&body.last().unwrap().node) {
220 self.chunk.emit(Op::Nil, self.line);
221 }
222 }
223 Ok(())
224 }
225
226 fn compile_catch_binding(&mut self, error_var: &Option<String>) -> Result<(), CompileError> {
228 if let Some(var_name) = error_var {
229 let idx = self.chunk.add_constant(Constant::String(var_name.clone()));
230 self.chunk.emit_u16(Op::DefLet, idx, self.line);
231 } else {
232 self.chunk.emit(Op::Pop, self.line);
233 }
234 Ok(())
235 }
236
237 fn compile_finally_inline(&mut self, finally_body: &[SNode]) -> Result<(), CompileError> {
239 if !finally_body.is_empty() {
240 self.compile_block(finally_body)?;
241 if Self::produces_value(&finally_body.last().unwrap().node) {
243 self.chunk.emit(Op::Pop, self.line);
244 }
245 }
246 Ok(())
247 }
248
249 fn compile_rethrow_with_finally(&mut self, finally_body: &[SNode]) -> Result<(), CompileError> {
251 self.temp_counter += 1;
253 let temp_name = format!("__finally_err_{}__", self.temp_counter);
254 let err_idx = self.chunk.add_constant(Constant::String(temp_name.clone()));
255 self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
256 self.compile_finally_inline(finally_body)?;
257 let get_idx = self.chunk.add_constant(Constant::String(temp_name));
258 self.chunk.emit_u16(Op::GetVar, get_idx, self.line);
259 self.chunk.emit(Op::Throw, self.line);
260 Ok(())
261 }
262
263 fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
264 for (i, snode) in stmts.iter().enumerate() {
265 self.compile_node(snode)?;
266 let is_last = i == stmts.len() - 1;
267 if is_last {
268 if !Self::produces_value(&snode.node) {
271 self.chunk.emit(Op::Nil, self.line);
272 }
273 } else {
274 if Self::produces_value(&snode.node) {
276 self.chunk.emit(Op::Pop, self.line);
277 }
278 }
279 }
280 Ok(())
281 }
282
283 fn compile_node(&mut self, snode: &SNode) -> Result<(), CompileError> {
284 self.line = snode.span.line as u32;
285 self.column = snode.span.column as u32;
286 self.chunk.set_column(self.column);
287 match &snode.node {
288 Node::IntLiteral(n) => {
289 let idx = self.chunk.add_constant(Constant::Int(*n));
290 self.chunk.emit_u16(Op::Constant, idx, self.line);
291 }
292 Node::FloatLiteral(n) => {
293 let idx = self.chunk.add_constant(Constant::Float(*n));
294 self.chunk.emit_u16(Op::Constant, idx, self.line);
295 }
296 Node::StringLiteral(s) => {
297 let idx = self.chunk.add_constant(Constant::String(s.clone()));
298 self.chunk.emit_u16(Op::Constant, idx, self.line);
299 }
300 Node::BoolLiteral(true) => self.chunk.emit(Op::True, self.line),
301 Node::BoolLiteral(false) => self.chunk.emit(Op::False, self.line),
302 Node::NilLiteral => self.chunk.emit(Op::Nil, self.line),
303 Node::DurationLiteral(ms) => {
304 let idx = self.chunk.add_constant(Constant::Duration(*ms));
305 self.chunk.emit_u16(Op::Constant, idx, self.line);
306 }
307
308 Node::Identifier(name) => {
309 let idx = self.chunk.add_constant(Constant::String(name.clone()));
310 self.chunk.emit_u16(Op::GetVar, idx, self.line);
311 }
312
313 Node::LetBinding { pattern, value, .. } => {
314 self.compile_node(value)?;
315 self.compile_destructuring(pattern, false)?;
316 }
317
318 Node::VarBinding { pattern, value, .. } => {
319 self.compile_node(value)?;
320 self.compile_destructuring(pattern, true)?;
321 }
322
323 Node::Assignment {
324 target, value, op, ..
325 } => {
326 if let Node::Identifier(name) = &target.node {
327 let idx = self.chunk.add_constant(Constant::String(name.clone()));
328 if let Some(op) = op {
329 self.chunk.emit_u16(Op::GetVar, idx, self.line);
330 self.compile_node(value)?;
331 self.emit_compound_op(op)?;
332 self.chunk.emit_u16(Op::SetVar, idx, self.line);
333 } else {
334 self.compile_node(value)?;
335 self.chunk.emit_u16(Op::SetVar, idx, self.line);
336 }
337 } else if let Node::PropertyAccess { object, property } = &target.node {
338 if let Some(var_name) = self.root_var_name(object) {
340 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
341 let prop_idx = self.chunk.add_constant(Constant::String(property.clone()));
342 if let Some(op) = op {
343 self.compile_node(target)?; self.compile_node(value)?;
346 self.emit_compound_op(op)?;
347 } else {
348 self.compile_node(value)?;
349 }
350 self.chunk.emit_u16(Op::SetProperty, prop_idx, self.line);
353 let hi = (var_idx >> 8) as u8;
355 let lo = var_idx as u8;
356 self.chunk.code.push(hi);
357 self.chunk.code.push(lo);
358 self.chunk.lines.push(self.line);
359 self.chunk.columns.push(self.column);
360 self.chunk.lines.push(self.line);
361 self.chunk.columns.push(self.column);
362 }
363 } else if let Node::SubscriptAccess { object, index } = &target.node {
364 if let Some(var_name) = self.root_var_name(object) {
366 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
367 if let Some(op) = op {
368 self.compile_node(target)?;
369 self.compile_node(value)?;
370 self.emit_compound_op(op)?;
371 } else {
372 self.compile_node(value)?;
373 }
374 self.compile_node(index)?;
375 self.chunk.emit_u16(Op::SetSubscript, var_idx, self.line);
376 }
377 }
378 }
379
380 Node::BinaryOp { op, left, right } => {
381 match op.as_str() {
383 "&&" => {
384 self.compile_node(left)?;
385 let jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
386 self.chunk.emit(Op::Pop, self.line);
387 self.compile_node(right)?;
388 self.chunk.patch_jump(jump);
389 self.chunk.emit(Op::Not, self.line);
391 self.chunk.emit(Op::Not, self.line);
392 return Ok(());
393 }
394 "||" => {
395 self.compile_node(left)?;
396 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
397 self.chunk.emit(Op::Pop, self.line);
398 self.compile_node(right)?;
399 self.chunk.patch_jump(jump);
400 self.chunk.emit(Op::Not, self.line);
401 self.chunk.emit(Op::Not, self.line);
402 return Ok(());
403 }
404 "??" => {
405 self.compile_node(left)?;
406 self.chunk.emit(Op::Dup, self.line);
407 self.chunk.emit(Op::Nil, self.line);
409 self.chunk.emit(Op::NotEqual, self.line);
410 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
411 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_node(right)?;
414 let end = self.chunk.emit_jump(Op::Jump, self.line);
415 self.chunk.patch_jump(jump);
416 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end);
418 return Ok(());
419 }
420 "|>" => {
421 self.compile_node(left)?;
422 if contains_pipe_placeholder(right) {
425 let replaced = replace_pipe_placeholder(right);
426 let closure_node = SNode::dummy(Node::Closure {
427 params: vec![TypedParam {
428 name: "__pipe".into(),
429 type_expr: None,
430 default_value: None,
431 }],
432 body: vec![replaced],
433 });
434 self.compile_node(&closure_node)?;
435 } else {
436 self.compile_node(right)?;
437 }
438 self.chunk.emit(Op::Pipe, self.line);
439 return Ok(());
440 }
441 _ => {}
442 }
443
444 self.compile_node(left)?;
445 self.compile_node(right)?;
446 match op.as_str() {
447 "+" => self.chunk.emit(Op::Add, self.line),
448 "-" => self.chunk.emit(Op::Sub, self.line),
449 "*" => self.chunk.emit(Op::Mul, self.line),
450 "/" => self.chunk.emit(Op::Div, self.line),
451 "%" => self.chunk.emit(Op::Mod, self.line),
452 "==" => self.chunk.emit(Op::Equal, self.line),
453 "!=" => self.chunk.emit(Op::NotEqual, self.line),
454 "<" => self.chunk.emit(Op::Less, self.line),
455 ">" => self.chunk.emit(Op::Greater, self.line),
456 "<=" => self.chunk.emit(Op::LessEqual, self.line),
457 ">=" => self.chunk.emit(Op::GreaterEqual, self.line),
458 _ => {
459 return Err(CompileError {
460 message: format!("Unknown operator: {op}"),
461 line: self.line,
462 })
463 }
464 }
465 }
466
467 Node::UnaryOp { op, operand } => {
468 self.compile_node(operand)?;
469 match op.as_str() {
470 "-" => self.chunk.emit(Op::Negate, self.line),
471 "!" => self.chunk.emit(Op::Not, self.line),
472 _ => {}
473 }
474 }
475
476 Node::Ternary {
477 condition,
478 true_expr,
479 false_expr,
480 } => {
481 self.compile_node(condition)?;
482 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
483 self.chunk.emit(Op::Pop, self.line);
484 self.compile_node(true_expr)?;
485 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
486 self.chunk.patch_jump(else_jump);
487 self.chunk.emit(Op::Pop, self.line);
488 self.compile_node(false_expr)?;
489 self.chunk.patch_jump(end_jump);
490 }
491
492 Node::FunctionCall { name, args } => {
493 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
495 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
496 for arg in args {
498 self.compile_node(arg)?;
499 }
500 self.chunk.emit_u8(Op::Call, args.len() as u8, self.line);
501 }
502
503 Node::MethodCall {
504 object,
505 method,
506 args,
507 } => {
508 if let Node::Identifier(name) = &object.node {
510 if self.enum_names.contains(name) {
511 for arg in args {
513 self.compile_node(arg)?;
514 }
515 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
516 let var_idx = self.chunk.add_constant(Constant::String(method.clone()));
517 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
518 let hi = (var_idx >> 8) as u8;
519 let lo = var_idx as u8;
520 self.chunk.code.push(hi);
521 self.chunk.code.push(lo);
522 self.chunk.lines.push(self.line);
523 self.chunk.columns.push(self.column);
524 self.chunk.lines.push(self.line);
525 self.chunk.columns.push(self.column);
526 let fc = args.len() as u16;
527 let fhi = (fc >> 8) as u8;
528 let flo = fc as u8;
529 self.chunk.code.push(fhi);
530 self.chunk.code.push(flo);
531 self.chunk.lines.push(self.line);
532 self.chunk.columns.push(self.column);
533 self.chunk.lines.push(self.line);
534 self.chunk.columns.push(self.column);
535 return Ok(());
536 }
537 }
538 self.compile_node(object)?;
539 for arg in args {
540 self.compile_node(arg)?;
541 }
542 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
543 self.chunk
544 .emit_method_call(name_idx, args.len() as u8, self.line);
545 }
546
547 Node::OptionalMethodCall {
548 object,
549 method,
550 args,
551 } => {
552 self.compile_node(object)?;
553 for arg in args {
554 self.compile_node(arg)?;
555 }
556 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
557 self.chunk
558 .emit_method_call_opt(name_idx, args.len() as u8, self.line);
559 }
560
561 Node::PropertyAccess { object, property } => {
562 if let Node::Identifier(name) = &object.node {
564 if self.enum_names.contains(name) {
565 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
567 let var_idx = self.chunk.add_constant(Constant::String(property.clone()));
568 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
569 let hi = (var_idx >> 8) as u8;
570 let lo = var_idx as u8;
571 self.chunk.code.push(hi);
572 self.chunk.code.push(lo);
573 self.chunk.lines.push(self.line);
574 self.chunk.columns.push(self.column);
575 self.chunk.lines.push(self.line);
576 self.chunk.columns.push(self.column);
577 self.chunk.code.push(0);
579 self.chunk.code.push(0);
580 self.chunk.lines.push(self.line);
581 self.chunk.columns.push(self.column);
582 self.chunk.lines.push(self.line);
583 self.chunk.columns.push(self.column);
584 return Ok(());
585 }
586 }
587 self.compile_node(object)?;
588 let idx = self.chunk.add_constant(Constant::String(property.clone()));
589 self.chunk.emit_u16(Op::GetProperty, idx, self.line);
590 }
591
592 Node::OptionalPropertyAccess { object, property } => {
593 self.compile_node(object)?;
594 let idx = self.chunk.add_constant(Constant::String(property.clone()));
595 self.chunk.emit_u16(Op::GetPropertyOpt, idx, self.line);
596 }
597
598 Node::SubscriptAccess { object, index } => {
599 self.compile_node(object)?;
600 self.compile_node(index)?;
601 self.chunk.emit(Op::Subscript, self.line);
602 }
603
604 Node::SliceAccess { object, start, end } => {
605 self.compile_node(object)?;
606 if let Some(s) = start {
607 self.compile_node(s)?;
608 } else {
609 self.chunk.emit(Op::Nil, self.line);
610 }
611 if let Some(e) = end {
612 self.compile_node(e)?;
613 } else {
614 self.chunk.emit(Op::Nil, self.line);
615 }
616 self.chunk.emit(Op::Slice, self.line);
617 }
618
619 Node::IfElse {
620 condition,
621 then_body,
622 else_body,
623 } => {
624 self.compile_node(condition)?;
625 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
626 self.chunk.emit(Op::Pop, self.line);
627 self.compile_block(then_body)?;
628 if let Some(else_body) = else_body {
629 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
630 self.chunk.patch_jump(else_jump);
631 self.chunk.emit(Op::Pop, self.line);
632 self.compile_block(else_body)?;
633 self.chunk.patch_jump(end_jump);
634 } else {
635 self.chunk.patch_jump(else_jump);
636 self.chunk.emit(Op::Pop, self.line);
637 self.chunk.emit(Op::Nil, self.line);
638 }
639 }
640
641 Node::WhileLoop { condition, body } => {
642 let loop_start = self.chunk.current_offset();
643 self.loop_stack.push(LoopContext {
644 start_offset: loop_start,
645 break_patches: Vec::new(),
646 has_iterator: false,
647 handler_depth: self.handler_depth,
648 finally_depth: self.finally_bodies.len(),
649 });
650 self.compile_node(condition)?;
651 let exit_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
652 self.chunk.emit(Op::Pop, self.line); for sn in body {
655 self.compile_node(sn)?;
656 if Self::produces_value(&sn.node) {
657 self.chunk.emit(Op::Pop, self.line);
658 }
659 }
660 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
662 self.chunk.patch_jump(exit_jump);
663 self.chunk.emit(Op::Pop, self.line); let ctx = self.loop_stack.pop().unwrap();
666 for patch_pos in ctx.break_patches {
667 self.chunk.patch_jump(patch_pos);
668 }
669 self.chunk.emit(Op::Nil, self.line);
670 }
671
672 Node::ForIn {
673 pattern,
674 iterable,
675 body,
676 } => {
677 self.compile_node(iterable)?;
679 self.chunk.emit(Op::IterInit, self.line);
681 let loop_start = self.chunk.current_offset();
682 self.loop_stack.push(LoopContext {
683 start_offset: loop_start,
684 break_patches: Vec::new(),
685 has_iterator: true,
686 handler_depth: self.handler_depth,
687 finally_depth: self.finally_bodies.len(),
688 });
689 let exit_jump_pos = self.chunk.emit_jump(Op::IterNext, self.line);
691 self.compile_destructuring(pattern, true)?;
693 for sn in body {
695 self.compile_node(sn)?;
696 if Self::produces_value(&sn.node) {
697 self.chunk.emit(Op::Pop, self.line);
698 }
699 }
700 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
702 self.chunk.patch_jump(exit_jump_pos);
703 let ctx = self.loop_stack.pop().unwrap();
705 for patch_pos in ctx.break_patches {
706 self.chunk.patch_jump(patch_pos);
707 }
708 self.chunk.emit(Op::Nil, self.line);
710 }
711
712 Node::ReturnStmt { value } => {
713 let has_pending_finally = !self.finally_bodies.is_empty();
714
715 if has_pending_finally {
716 if let Some(val) = value {
719 self.compile_node(val)?;
720 } else {
721 self.chunk.emit(Op::Nil, self.line);
722 }
723 self.temp_counter += 1;
724 let temp_name = format!("__return_val_{}__", self.temp_counter);
725 let save_idx = self.chunk.add_constant(Constant::String(temp_name.clone()));
726 self.chunk.emit_u16(Op::DefVar, save_idx, self.line);
727 let finallys: Vec<_> = self.finally_bodies.iter().rev().cloned().collect();
729 for fb in &finallys {
730 self.compile_finally_inline(fb)?;
731 }
732 let restore_idx = self.chunk.add_constant(Constant::String(temp_name));
733 self.chunk.emit_u16(Op::GetVar, restore_idx, self.line);
734 self.chunk.emit(Op::Return, self.line);
735 } else {
736 if let Some(val) = value {
738 if let Node::FunctionCall { name, args } = &val.node {
739 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
740 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
741 for arg in args {
742 self.compile_node(arg)?;
743 }
744 self.chunk
745 .emit_u8(Op::TailCall, args.len() as u8, self.line);
746 } else if let Node::BinaryOp { op, left, right } = &val.node {
747 if op == "|>" {
748 self.compile_node(left)?;
749 self.compile_node(right)?;
750 self.chunk.emit(Op::Swap, self.line);
751 self.chunk.emit_u8(Op::TailCall, 1, self.line);
752 } else {
753 self.compile_node(val)?;
754 }
755 } else {
756 self.compile_node(val)?;
757 }
758 } else {
759 self.chunk.emit(Op::Nil, self.line);
760 }
761 self.chunk.emit(Op::Return, self.line);
762 }
763 }
764
765 Node::BreakStmt => {
766 if self.loop_stack.is_empty() {
767 return Err(CompileError {
768 message: "break outside of loop".to_string(),
769 line: self.line,
770 });
771 }
772 let ctx = self.loop_stack.last().unwrap();
774 let finally_depth = ctx.finally_depth;
775 let handler_depth = ctx.handler_depth;
776 let has_iterator = ctx.has_iterator;
777 for _ in handler_depth..self.handler_depth {
779 self.chunk.emit(Op::PopHandler, self.line);
780 }
781 if self.finally_bodies.len() > finally_depth {
783 let finallys: Vec<_> = self.finally_bodies[finally_depth..]
784 .iter()
785 .rev()
786 .cloned()
787 .collect();
788 for fb in &finallys {
789 self.compile_finally_inline(fb)?;
790 }
791 }
792 if has_iterator {
793 self.chunk.emit(Op::PopIterator, self.line);
794 }
795 let patch = self.chunk.emit_jump(Op::Jump, self.line);
796 self.loop_stack
797 .last_mut()
798 .unwrap()
799 .break_patches
800 .push(patch);
801 }
802
803 Node::ContinueStmt => {
804 if self.loop_stack.is_empty() {
805 return Err(CompileError {
806 message: "continue outside of loop".to_string(),
807 line: self.line,
808 });
809 }
810 let ctx = self.loop_stack.last().unwrap();
811 let finally_depth = ctx.finally_depth;
812 let handler_depth = ctx.handler_depth;
813 let loop_start = ctx.start_offset;
814 for _ in handler_depth..self.handler_depth {
815 self.chunk.emit(Op::PopHandler, self.line);
816 }
817 if self.finally_bodies.len() > finally_depth {
818 let finallys: Vec<_> = self.finally_bodies[finally_depth..]
819 .iter()
820 .rev()
821 .cloned()
822 .collect();
823 for fb in &finallys {
824 self.compile_finally_inline(fb)?;
825 }
826 }
827 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
828 }
829
830 Node::ListLiteral(elements) => {
831 let has_spread = elements.iter().any(|e| matches!(&e.node, Node::Spread(_)));
832 if !has_spread {
833 for el in elements {
834 self.compile_node(el)?;
835 }
836 self.chunk
837 .emit_u16(Op::BuildList, elements.len() as u16, self.line);
838 } else {
839 self.chunk.emit_u16(Op::BuildList, 0, self.line);
842 let mut pending = 0u16;
843 for el in elements {
844 if let Node::Spread(inner) = &el.node {
845 if pending > 0 {
847 self.chunk.emit_u16(Op::BuildList, pending, self.line);
848 self.chunk.emit(Op::Add, self.line);
850 pending = 0;
851 }
852 self.compile_node(inner)?;
854 self.chunk.emit(Op::Dup, self.line);
855 let assert_idx = self
856 .chunk
857 .add_constant(Constant::String("__assert_list".into()));
858 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
859 self.chunk.emit(Op::Swap, self.line);
860 self.chunk.emit_u8(Op::Call, 1, self.line);
861 self.chunk.emit(Op::Pop, self.line);
862 self.chunk.emit(Op::Add, self.line);
863 } else {
864 self.compile_node(el)?;
865 pending += 1;
866 }
867 }
868 if pending > 0 {
869 self.chunk.emit_u16(Op::BuildList, pending, self.line);
870 self.chunk.emit(Op::Add, self.line);
871 }
872 }
873 }
874
875 Node::DictLiteral(entries) => {
876 let has_spread = entries
877 .iter()
878 .any(|e| matches!(&e.value.node, Node::Spread(_)));
879 if !has_spread {
880 for entry in entries {
881 self.compile_node(&entry.key)?;
882 self.compile_node(&entry.value)?;
883 }
884 self.chunk
885 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
886 } else {
887 self.chunk.emit_u16(Op::BuildDict, 0, self.line);
889 let mut pending = 0u16;
890 for entry in entries {
891 if let Node::Spread(inner) = &entry.value.node {
892 if pending > 0 {
894 self.chunk.emit_u16(Op::BuildDict, pending, self.line);
895 self.chunk.emit(Op::Add, self.line);
896 pending = 0;
897 }
898 self.compile_node(inner)?;
900 self.chunk.emit(Op::Dup, self.line);
901 let assert_idx = self
902 .chunk
903 .add_constant(Constant::String("__assert_dict".into()));
904 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
905 self.chunk.emit(Op::Swap, self.line);
906 self.chunk.emit_u8(Op::Call, 1, self.line);
907 self.chunk.emit(Op::Pop, self.line);
908 self.chunk.emit(Op::Add, self.line);
909 } else {
910 self.compile_node(&entry.key)?;
911 self.compile_node(&entry.value)?;
912 pending += 1;
913 }
914 }
915 if pending > 0 {
916 self.chunk.emit_u16(Op::BuildDict, pending, self.line);
917 self.chunk.emit(Op::Add, self.line);
918 }
919 }
920 }
921
922 Node::InterpolatedString(segments) => {
923 let mut part_count = 0u16;
924 for seg in segments {
925 match seg {
926 StringSegment::Literal(s) => {
927 let idx = self.chunk.add_constant(Constant::String(s.clone()));
928 self.chunk.emit_u16(Op::Constant, idx, self.line);
929 part_count += 1;
930 }
931 StringSegment::Expression(expr_str) => {
932 let mut lexer = harn_lexer::Lexer::new(expr_str);
934 if let Ok(tokens) = lexer.tokenize() {
935 let mut parser = harn_parser::Parser::new(tokens);
936 if let Ok(snode) = parser.parse_single_expression() {
937 self.compile_node(&snode)?;
938 let to_str = self
940 .chunk
941 .add_constant(Constant::String("to_string".into()));
942 self.chunk.emit_u16(Op::Constant, to_str, self.line);
943 self.chunk.emit(Op::Swap, self.line);
944 self.chunk.emit_u8(Op::Call, 1, self.line);
945 part_count += 1;
946 } else {
947 let idx =
949 self.chunk.add_constant(Constant::String(expr_str.clone()));
950 self.chunk.emit_u16(Op::Constant, idx, self.line);
951 part_count += 1;
952 }
953 }
954 }
955 }
956 }
957 if part_count > 1 {
958 self.chunk.emit_u16(Op::Concat, part_count, self.line);
959 }
960 }
961
962 Node::FnDecl {
963 name, params, body, ..
964 } => {
965 let mut fn_compiler = Compiler::new();
967 fn_compiler.enum_names = self.enum_names.clone();
968 fn_compiler.emit_default_preamble(params)?;
969 fn_compiler.compile_block(body)?;
970 fn_compiler.chunk.emit(Op::Nil, self.line);
971 fn_compiler.chunk.emit(Op::Return, self.line);
972
973 let func = CompiledFunction {
974 name: name.clone(),
975 params: TypedParam::names(params),
976 default_start: TypedParam::default_start(params),
977 chunk: fn_compiler.chunk,
978 };
979 let fn_idx = self.chunk.functions.len();
980 self.chunk.functions.push(func);
981
982 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
983 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
984 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
985 }
986
987 Node::Closure { params, body } => {
988 let mut fn_compiler = Compiler::new();
989 fn_compiler.enum_names = self.enum_names.clone();
990 fn_compiler.emit_default_preamble(params)?;
991 fn_compiler.compile_block(body)?;
992 fn_compiler.chunk.emit(Op::Return, self.line);
994
995 let func = CompiledFunction {
996 name: "<closure>".to_string(),
997 params: TypedParam::names(params),
998 default_start: TypedParam::default_start(params),
999 chunk: fn_compiler.chunk,
1000 };
1001 let fn_idx = self.chunk.functions.len();
1002 self.chunk.functions.push(func);
1003
1004 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1005 }
1006
1007 Node::ThrowStmt { value } => {
1008 self.compile_node(value)?;
1009 self.chunk.emit(Op::Throw, self.line);
1010 }
1011
1012 Node::MatchExpr { value, arms } => {
1013 self.compile_node(value)?;
1014 let mut end_jumps = Vec::new();
1015 for arm in arms {
1016 match &arm.pattern.node {
1017 Node::Identifier(name) if name == "_" => {
1019 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1021 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1022 }
1023 Node::EnumConstruct {
1025 enum_name,
1026 variant,
1027 args: pat_args,
1028 } => {
1029 self.chunk.emit(Op::Dup, self.line);
1031 let en_idx =
1032 self.chunk.add_constant(Constant::String(enum_name.clone()));
1033 let vn_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1034 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1035 let hi = (vn_idx >> 8) as u8;
1036 let lo = vn_idx as u8;
1037 self.chunk.code.push(hi);
1038 self.chunk.code.push(lo);
1039 self.chunk.lines.push(self.line);
1040 self.chunk.columns.push(self.column);
1041 self.chunk.lines.push(self.line);
1042 self.chunk.columns.push(self.column);
1043 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1045 self.chunk.emit(Op::Pop, self.line); for (i, pat_arg) in pat_args.iter().enumerate() {
1050 if let Node::Identifier(binding_name) = &pat_arg.node {
1051 self.chunk.emit(Op::Dup, self.line);
1053 let fields_idx = self
1054 .chunk
1055 .add_constant(Constant::String("fields".to_string()));
1056 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
1057 let idx_const =
1058 self.chunk.add_constant(Constant::Int(i as i64));
1059 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1060 self.chunk.emit(Op::Subscript, self.line);
1061 let name_idx = self
1062 .chunk
1063 .add_constant(Constant::String(binding_name.clone()));
1064 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1065 }
1066 }
1067
1068 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1070 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1071 self.chunk.patch_jump(skip);
1072 self.chunk.emit(Op::Pop, self.line); }
1074 Node::PropertyAccess { object, property } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
1076 {
1077 let enum_name = if let Node::Identifier(n) = &object.node {
1078 n.clone()
1079 } else {
1080 unreachable!()
1081 };
1082 self.chunk.emit(Op::Dup, self.line);
1083 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
1084 let vn_idx =
1085 self.chunk.add_constant(Constant::String(property.clone()));
1086 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1087 let hi = (vn_idx >> 8) as u8;
1088 let lo = vn_idx as u8;
1089 self.chunk.code.push(hi);
1090 self.chunk.code.push(lo);
1091 self.chunk.lines.push(self.line);
1092 self.chunk.columns.push(self.column);
1093 self.chunk.lines.push(self.line);
1094 self.chunk.columns.push(self.column);
1095 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1096 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1099 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1100 self.chunk.patch_jump(skip);
1101 self.chunk.emit(Op::Pop, self.line); }
1103 Node::MethodCall {
1106 object,
1107 method,
1108 args: pat_args,
1109 } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
1110 {
1111 let enum_name = if let Node::Identifier(n) = &object.node {
1112 n.clone()
1113 } else {
1114 unreachable!()
1115 };
1116 self.chunk.emit(Op::Dup, self.line);
1118 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
1119 let vn_idx = self.chunk.add_constant(Constant::String(method.clone()));
1120 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1121 let hi = (vn_idx >> 8) as u8;
1122 let lo = vn_idx as u8;
1123 self.chunk.code.push(hi);
1124 self.chunk.code.push(lo);
1125 self.chunk.lines.push(self.line);
1126 self.chunk.columns.push(self.column);
1127 self.chunk.lines.push(self.line);
1128 self.chunk.columns.push(self.column);
1129 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1130 self.chunk.emit(Op::Pop, self.line); for (i, pat_arg) in pat_args.iter().enumerate() {
1134 if let Node::Identifier(binding_name) = &pat_arg.node {
1135 self.chunk.emit(Op::Dup, self.line);
1136 let fields_idx = self
1137 .chunk
1138 .add_constant(Constant::String("fields".to_string()));
1139 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
1140 let idx_const =
1141 self.chunk.add_constant(Constant::Int(i as i64));
1142 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1143 self.chunk.emit(Op::Subscript, self.line);
1144 let name_idx = self
1145 .chunk
1146 .add_constant(Constant::String(binding_name.clone()));
1147 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1148 }
1149 }
1150
1151 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1153 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1154 self.chunk.patch_jump(skip);
1155 self.chunk.emit(Op::Pop, self.line); }
1157 Node::Identifier(name) => {
1159 self.chunk.emit(Op::Dup, self.line); let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1162 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1163 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1165 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1166 }
1167 Node::DictLiteral(entries)
1169 if entries
1170 .iter()
1171 .all(|e| matches!(&e.key.node, Node::StringLiteral(_))) =>
1172 {
1173 self.chunk.emit(Op::Dup, self.line);
1175 let typeof_idx =
1176 self.chunk.add_constant(Constant::String("type_of".into()));
1177 self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
1178 self.chunk.emit(Op::Swap, self.line);
1179 self.chunk.emit_u8(Op::Call, 1, self.line);
1180 let dict_str = self.chunk.add_constant(Constant::String("dict".into()));
1181 self.chunk.emit_u16(Op::Constant, dict_str, self.line);
1182 self.chunk.emit(Op::Equal, self.line);
1183 let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1184 self.chunk.emit(Op::Pop, self.line); let mut constraint_skips = Vec::new();
1188 let mut bindings = Vec::new();
1189 for entry in entries {
1190 if let Node::StringLiteral(key) = &entry.key.node {
1191 match &entry.value.node {
1192 Node::StringLiteral(_)
1194 | Node::IntLiteral(_)
1195 | Node::FloatLiteral(_)
1196 | Node::BoolLiteral(_)
1197 | Node::NilLiteral => {
1198 self.chunk.emit(Op::Dup, self.line);
1199 let key_idx = self
1200 .chunk
1201 .add_constant(Constant::String(key.clone()));
1202 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1203 self.chunk.emit(Op::Subscript, self.line);
1204 self.compile_node(&entry.value)?;
1205 self.chunk.emit(Op::Equal, self.line);
1206 let skip =
1207 self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1208 self.chunk.emit(Op::Pop, self.line); constraint_skips.push(skip);
1210 }
1211 Node::Identifier(binding) => {
1213 bindings.push((key.clone(), binding.clone()));
1214 }
1215 _ => {
1216 self.chunk.emit(Op::Dup, self.line);
1218 let key_idx = self
1219 .chunk
1220 .add_constant(Constant::String(key.clone()));
1221 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1222 self.chunk.emit(Op::Subscript, self.line);
1223 self.compile_node(&entry.value)?;
1224 self.chunk.emit(Op::Equal, self.line);
1225 let skip =
1226 self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1227 self.chunk.emit(Op::Pop, self.line);
1228 constraint_skips.push(skip);
1229 }
1230 }
1231 }
1232 }
1233
1234 for (key, binding) in &bindings {
1236 self.chunk.emit(Op::Dup, self.line);
1237 let key_idx =
1238 self.chunk.add_constant(Constant::String(key.clone()));
1239 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1240 self.chunk.emit(Op::Subscript, self.line);
1241 let name_idx =
1242 self.chunk.add_constant(Constant::String(binding.clone()));
1243 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1244 }
1245
1246 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1248 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1249
1250 let fail_target = self.chunk.code.len();
1252 self.chunk.emit(Op::Pop, self.line); for skip in constraint_skips {
1255 self.chunk.patch_jump_to(skip, fail_target);
1256 }
1257 self.chunk.patch_jump_to(skip_type, fail_target);
1258 }
1259 Node::ListLiteral(elements) => {
1261 self.chunk.emit(Op::Dup, self.line);
1263 let typeof_idx =
1264 self.chunk.add_constant(Constant::String("type_of".into()));
1265 self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
1266 self.chunk.emit(Op::Swap, self.line);
1267 self.chunk.emit_u8(Op::Call, 1, self.line);
1268 let list_str = self.chunk.add_constant(Constant::String("list".into()));
1269 self.chunk.emit_u16(Op::Constant, list_str, self.line);
1270 self.chunk.emit(Op::Equal, self.line);
1271 let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1272 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Dup, self.line);
1276 let len_idx = self.chunk.add_constant(Constant::String("len".into()));
1277 self.chunk.emit_u16(Op::Constant, len_idx, self.line);
1278 self.chunk.emit(Op::Swap, self.line);
1279 self.chunk.emit_u8(Op::Call, 1, self.line);
1280 let count = self
1281 .chunk
1282 .add_constant(Constant::Int(elements.len() as i64));
1283 self.chunk.emit_u16(Op::Constant, count, self.line);
1284 self.chunk.emit(Op::GreaterEqual, self.line);
1285 let skip_len = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1286 self.chunk.emit(Op::Pop, self.line); let mut constraint_skips = Vec::new();
1290 let mut bindings = Vec::new();
1291 for (i, elem) in elements.iter().enumerate() {
1292 match &elem.node {
1293 Node::Identifier(name) if name != "_" => {
1294 bindings.push((i, name.clone()));
1295 }
1296 Node::Identifier(_) => {} _ => {
1299 self.chunk.emit(Op::Dup, self.line);
1300 let idx_const =
1301 self.chunk.add_constant(Constant::Int(i as i64));
1302 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1303 self.chunk.emit(Op::Subscript, self.line);
1304 self.compile_node(elem)?;
1305 self.chunk.emit(Op::Equal, self.line);
1306 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1307 self.chunk.emit(Op::Pop, self.line);
1308 constraint_skips.push(skip);
1309 }
1310 }
1311 }
1312
1313 for (i, name) in &bindings {
1315 self.chunk.emit(Op::Dup, self.line);
1316 let idx_const = self.chunk.add_constant(Constant::Int(*i as i64));
1317 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1318 self.chunk.emit(Op::Subscript, self.line);
1319 let name_idx =
1320 self.chunk.add_constant(Constant::String(name.clone()));
1321 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1322 }
1323
1324 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1326 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1327
1328 let fail_target = self.chunk.code.len();
1330 self.chunk.emit(Op::Pop, self.line); for skip in constraint_skips {
1332 self.chunk.patch_jump_to(skip, fail_target);
1333 }
1334 self.chunk.patch_jump_to(skip_len, fail_target);
1335 self.chunk.patch_jump_to(skip_type, fail_target);
1336 }
1337 _ => {
1339 self.chunk.emit(Op::Dup, self.line);
1340 self.compile_node(&arm.pattern)?;
1341 self.chunk.emit(Op::Equal, self.line);
1342 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1343 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1346 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1347 self.chunk.patch_jump(skip);
1348 self.chunk.emit(Op::Pop, self.line); }
1350 }
1351 }
1352 self.chunk.emit(Op::Pop, self.line);
1354 self.chunk.emit(Op::Nil, self.line);
1355 for j in end_jumps {
1356 self.chunk.patch_jump(j);
1357 }
1358 }
1359
1360 Node::RangeExpr {
1361 start,
1362 end,
1363 inclusive,
1364 } => {
1365 let name_idx = self
1367 .chunk
1368 .add_constant(Constant::String("__range__".to_string()));
1369 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
1370 self.compile_node(start)?;
1371 self.compile_node(end)?;
1372 if *inclusive {
1373 self.chunk.emit(Op::True, self.line);
1374 } else {
1375 self.chunk.emit(Op::False, self.line);
1376 }
1377 self.chunk.emit_u8(Op::Call, 3, self.line);
1378 }
1379
1380 Node::GuardStmt {
1381 condition,
1382 else_body,
1383 } => {
1384 self.compile_node(condition)?;
1387 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
1388 self.chunk.emit(Op::Pop, self.line); self.compile_block(else_body)?;
1391 if !else_body.is_empty() && Self::produces_value(&else_body.last().unwrap().node) {
1393 self.chunk.emit(Op::Pop, self.line);
1394 }
1395 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1396 self.chunk.patch_jump(skip_jump);
1397 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end_jump);
1399 self.chunk.emit(Op::Nil, self.line);
1400 }
1401
1402 Node::Block(stmts) => {
1403 if stmts.is_empty() {
1404 self.chunk.emit(Op::Nil, self.line);
1405 } else {
1406 self.compile_block(stmts)?;
1407 }
1408 }
1409
1410 Node::DeadlineBlock { duration, body } => {
1411 self.compile_node(duration)?;
1412 self.chunk.emit(Op::DeadlineSetup, self.line);
1413 if body.is_empty() {
1414 self.chunk.emit(Op::Nil, self.line);
1415 } else {
1416 self.compile_block(body)?;
1417 }
1418 self.chunk.emit(Op::DeadlineEnd, self.line);
1419 }
1420
1421 Node::MutexBlock { body } => {
1422 if body.is_empty() {
1424 self.chunk.emit(Op::Nil, self.line);
1425 } else {
1426 for sn in body {
1429 self.compile_node(sn)?;
1430 if Self::produces_value(&sn.node) {
1431 self.chunk.emit(Op::Pop, self.line);
1432 }
1433 }
1434 self.chunk.emit(Op::Nil, self.line);
1435 }
1436 }
1437
1438 Node::YieldExpr { .. } => {
1439 self.chunk.emit(Op::Nil, self.line);
1441 }
1442
1443 Node::AskExpr { fields } => {
1444 for entry in fields {
1447 self.compile_node(&entry.key)?;
1448 self.compile_node(&entry.value)?;
1449 }
1450 self.chunk
1451 .emit_u16(Op::BuildDict, fields.len() as u16, self.line);
1452 }
1453
1454 Node::EnumConstruct {
1455 enum_name,
1456 variant,
1457 args,
1458 } => {
1459 for arg in args {
1461 self.compile_node(arg)?;
1462 }
1463 let enum_idx = self.chunk.add_constant(Constant::String(enum_name.clone()));
1464 let var_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1465 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
1467 let hi = (var_idx >> 8) as u8;
1468 let lo = var_idx as u8;
1469 self.chunk.code.push(hi);
1470 self.chunk.code.push(lo);
1471 self.chunk.lines.push(self.line);
1472 self.chunk.columns.push(self.column);
1473 self.chunk.lines.push(self.line);
1474 self.chunk.columns.push(self.column);
1475 let fc = args.len() as u16;
1476 let fhi = (fc >> 8) as u8;
1477 let flo = fc as u8;
1478 self.chunk.code.push(fhi);
1479 self.chunk.code.push(flo);
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
1486 Node::StructConstruct {
1487 struct_name,
1488 fields,
1489 } => {
1490 let struct_key = self
1492 .chunk
1493 .add_constant(Constant::String("__struct__".to_string()));
1494 let struct_val = self
1495 .chunk
1496 .add_constant(Constant::String(struct_name.clone()));
1497 self.chunk.emit_u16(Op::Constant, struct_key, self.line);
1498 self.chunk.emit_u16(Op::Constant, struct_val, self.line);
1499
1500 for entry in fields {
1501 self.compile_node(&entry.key)?;
1502 self.compile_node(&entry.value)?;
1503 }
1504 self.chunk
1505 .emit_u16(Op::BuildDict, (fields.len() + 1) as u16, self.line);
1506 }
1507
1508 Node::ImportDecl { path } => {
1509 let idx = self.chunk.add_constant(Constant::String(path.clone()));
1510 self.chunk.emit_u16(Op::Import, idx, self.line);
1511 }
1512
1513 Node::SelectiveImport { names, path } => {
1514 let path_idx = self.chunk.add_constant(Constant::String(path.clone()));
1515 let names_str = names.join(",");
1516 let names_idx = self.chunk.add_constant(Constant::String(names_str));
1517 self.chunk
1518 .emit_u16(Op::SelectiveImport, path_idx, self.line);
1519 let hi = (names_idx >> 8) as u8;
1520 let lo = names_idx as u8;
1521 self.chunk.code.push(hi);
1522 self.chunk.code.push(lo);
1523 self.chunk.lines.push(self.line);
1524 self.chunk.columns.push(self.column);
1525 self.chunk.lines.push(self.line);
1526 self.chunk.columns.push(self.column);
1527 }
1528
1529 Node::Pipeline { .. }
1531 | Node::OverrideDecl { .. }
1532 | Node::TypeDecl { .. }
1533 | Node::EnumDecl { .. }
1534 | Node::StructDecl { .. }
1535 | Node::InterfaceDecl { .. } => {
1536 self.chunk.emit(Op::Nil, self.line);
1537 }
1538
1539 Node::TryCatch {
1540 body,
1541 error_var,
1542 error_type,
1543 catch_body,
1544 finally_body,
1545 } => {
1546 let type_name = error_type.as_ref().and_then(|te| {
1548 if let harn_parser::TypeExpr::Named(name) = te {
1549 Some(name.clone())
1550 } else {
1551 None
1552 }
1553 });
1554
1555 let type_name_idx = if let Some(ref tn) = type_name {
1556 self.chunk.add_constant(Constant::String(tn.clone()))
1557 } else {
1558 self.chunk.add_constant(Constant::String(String::new()))
1559 };
1560
1561 let has_catch = !catch_body.is_empty() || error_var.is_some();
1562 let has_finally = finally_body.is_some();
1563
1564 if has_catch && has_finally {
1565 let finally_body = finally_body.as_ref().unwrap();
1567
1568 self.finally_bodies.push(finally_body.clone());
1570
1571 self.handler_depth += 1;
1573 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1574 self.emit_type_name_extra(type_name_idx);
1575
1576 self.compile_try_body(body)?;
1578
1579 self.handler_depth -= 1;
1581 self.chunk.emit(Op::PopHandler, self.line);
1582 self.compile_finally_inline(finally_body)?;
1583 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1584
1585 self.chunk.patch_jump(catch_jump);
1587 self.compile_catch_binding(error_var)?;
1588
1589 self.handler_depth += 1;
1591 let rethrow_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1592 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1593 self.emit_type_name_extra(empty_type);
1594
1595 self.compile_try_body(catch_body)?;
1597
1598 self.handler_depth -= 1;
1600 self.chunk.emit(Op::PopHandler, self.line);
1601 self.compile_finally_inline(finally_body)?;
1602 let end_jump2 = self.chunk.emit_jump(Op::Jump, self.line);
1603
1604 self.chunk.patch_jump(rethrow_jump);
1606 self.compile_rethrow_with_finally(finally_body)?;
1607
1608 self.chunk.patch_jump(end_jump);
1609 self.chunk.patch_jump(end_jump2);
1610
1611 self.finally_bodies.pop();
1612 } else if has_finally {
1613 let finally_body = finally_body.as_ref().unwrap();
1615
1616 self.finally_bodies.push(finally_body.clone());
1617
1618 self.handler_depth += 1;
1620 let error_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1621 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1622 self.emit_type_name_extra(empty_type);
1623
1624 self.compile_try_body(body)?;
1626
1627 self.handler_depth -= 1;
1629 self.chunk.emit(Op::PopHandler, self.line);
1630 self.compile_finally_inline(finally_body)?;
1631 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1632
1633 self.chunk.patch_jump(error_jump);
1635 self.compile_rethrow_with_finally(finally_body)?;
1636
1637 self.chunk.patch_jump(end_jump);
1638
1639 self.finally_bodies.pop();
1640 } else {
1641 self.handler_depth += 1;
1645 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1646 self.emit_type_name_extra(type_name_idx);
1647
1648 self.compile_try_body(body)?;
1650
1651 self.handler_depth -= 1;
1653 self.chunk.emit(Op::PopHandler, self.line);
1654 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1655
1656 self.chunk.patch_jump(catch_jump);
1658 self.compile_catch_binding(error_var)?;
1659
1660 self.compile_try_body(catch_body)?;
1662
1663 self.chunk.patch_jump(end_jump);
1665 }
1666 }
1667
1668 Node::Retry { count, body } => {
1669 self.compile_node(count)?;
1671 let counter_name = "__retry_counter__";
1672 let counter_idx = self
1673 .chunk
1674 .add_constant(Constant::String(counter_name.to_string()));
1675 self.chunk.emit_u16(Op::DefVar, counter_idx, self.line);
1676
1677 self.chunk.emit(Op::Nil, self.line);
1679 let err_name = "__retry_last_error__";
1680 let err_idx = self
1681 .chunk
1682 .add_constant(Constant::String(err_name.to_string()));
1683 self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
1684
1685 let loop_start = self.chunk.current_offset();
1687
1688 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1690 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1692 let hi = (empty_type >> 8) as u8;
1693 let lo = empty_type as u8;
1694 self.chunk.code.push(hi);
1695 self.chunk.code.push(lo);
1696 self.chunk.lines.push(self.line);
1697 self.chunk.columns.push(self.column);
1698 self.chunk.lines.push(self.line);
1699 self.chunk.columns.push(self.column);
1700
1701 self.compile_block(body)?;
1703
1704 self.chunk.emit(Op::PopHandler, self.line);
1706 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1707
1708 self.chunk.patch_jump(catch_jump);
1710 self.chunk.emit(Op::Dup, self.line);
1712 self.chunk.emit_u16(Op::SetVar, err_idx, self.line);
1713 self.chunk.emit(Op::Pop, self.line);
1715
1716 self.chunk.emit_u16(Op::GetVar, counter_idx, self.line);
1718 let one_idx = self.chunk.add_constant(Constant::Int(1));
1719 self.chunk.emit_u16(Op::Constant, one_idx, self.line);
1720 self.chunk.emit(Op::Sub, self.line);
1721 self.chunk.emit(Op::Dup, self.line);
1722 self.chunk.emit_u16(Op::SetVar, counter_idx, self.line);
1723
1724 let zero_idx = self.chunk.add_constant(Constant::Int(0));
1726 self.chunk.emit_u16(Op::Constant, zero_idx, self.line);
1727 self.chunk.emit(Op::Greater, self.line);
1728 let retry_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1729 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
1731
1732 self.chunk.patch_jump(retry_jump);
1734 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::GetVar, err_idx, self.line);
1736 self.chunk.emit(Op::Throw, self.line);
1737
1738 self.chunk.patch_jump(end_jump);
1739 self.chunk.emit(Op::Nil, self.line);
1741 }
1742
1743 Node::Parallel {
1744 count,
1745 variable,
1746 body,
1747 } => {
1748 self.compile_node(count)?;
1749 let mut fn_compiler = Compiler::new();
1750 fn_compiler.enum_names = self.enum_names.clone();
1751 fn_compiler.compile_block(body)?;
1752 fn_compiler.chunk.emit(Op::Return, self.line);
1753 let params = vec![variable.clone().unwrap_or_else(|| "__i__".to_string())];
1754 let func = CompiledFunction {
1755 name: "<parallel>".to_string(),
1756 params,
1757 default_start: None,
1758 chunk: fn_compiler.chunk,
1759 };
1760 let fn_idx = self.chunk.functions.len();
1761 self.chunk.functions.push(func);
1762 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1763 self.chunk.emit(Op::Parallel, self.line);
1764 }
1765
1766 Node::ParallelMap {
1767 list,
1768 variable,
1769 body,
1770 } => {
1771 self.compile_node(list)?;
1772 let mut fn_compiler = Compiler::new();
1773 fn_compiler.enum_names = self.enum_names.clone();
1774 fn_compiler.compile_block(body)?;
1775 fn_compiler.chunk.emit(Op::Return, self.line);
1776 let func = CompiledFunction {
1777 name: "<parallel_map>".to_string(),
1778 params: vec![variable.clone()],
1779 default_start: None,
1780 chunk: fn_compiler.chunk,
1781 };
1782 let fn_idx = self.chunk.functions.len();
1783 self.chunk.functions.push(func);
1784 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1785 self.chunk.emit(Op::ParallelMap, self.line);
1786 }
1787
1788 Node::SpawnExpr { body } => {
1789 let mut fn_compiler = Compiler::new();
1790 fn_compiler.enum_names = self.enum_names.clone();
1791 fn_compiler.compile_block(body)?;
1792 fn_compiler.chunk.emit(Op::Return, self.line);
1793 let func = CompiledFunction {
1794 name: "<spawn>".to_string(),
1795 params: vec![],
1796 default_start: None,
1797 chunk: fn_compiler.chunk,
1798 };
1799 let fn_idx = self.chunk.functions.len();
1800 self.chunk.functions.push(func);
1801 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1802 self.chunk.emit(Op::Spawn, self.line);
1803 }
1804 Node::SelectExpr {
1805 cases,
1806 timeout,
1807 default_body,
1808 } => {
1809 let builtin_name = if timeout.is_some() {
1816 "__select_timeout"
1817 } else if default_body.is_some() {
1818 "__select_try"
1819 } else {
1820 "__select_list"
1821 };
1822
1823 let name_idx = self
1825 .chunk
1826 .add_constant(Constant::String(builtin_name.into()));
1827 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
1828
1829 for case in cases {
1831 self.compile_node(&case.channel)?;
1832 }
1833 self.chunk
1834 .emit_u16(Op::BuildList, cases.len() as u16, self.line);
1835
1836 if let Some((duration_expr, _)) = timeout {
1838 self.compile_node(duration_expr)?;
1839 self.chunk.emit_u8(Op::Call, 2, self.line);
1840 } else {
1841 self.chunk.emit_u8(Op::Call, 1, self.line);
1842 }
1843
1844 self.temp_counter += 1;
1846 let result_name = format!("__sel_result_{}__", self.temp_counter);
1847 let result_idx = self
1848 .chunk
1849 .add_constant(Constant::String(result_name.clone()));
1850 self.chunk.emit_u16(Op::DefVar, result_idx, self.line);
1851
1852 let mut end_jumps = Vec::new();
1854
1855 for (i, case) in cases.iter().enumerate() {
1856 let get_r = self
1857 .chunk
1858 .add_constant(Constant::String(result_name.clone()));
1859 self.chunk.emit_u16(Op::GetVar, get_r, self.line);
1860 let idx_prop = self.chunk.add_constant(Constant::String("index".into()));
1861 self.chunk.emit_u16(Op::GetProperty, idx_prop, self.line);
1862 let case_i = self.chunk.add_constant(Constant::Int(i as i64));
1863 self.chunk.emit_u16(Op::Constant, case_i, self.line);
1864 self.chunk.emit(Op::Equal, self.line);
1865 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1866 self.chunk.emit(Op::Pop, self.line);
1867
1868 let get_r2 = self
1870 .chunk
1871 .add_constant(Constant::String(result_name.clone()));
1872 self.chunk.emit_u16(Op::GetVar, get_r2, self.line);
1873 let val_prop = self.chunk.add_constant(Constant::String("value".into()));
1874 self.chunk.emit_u16(Op::GetProperty, val_prop, self.line);
1875 let var_idx = self
1876 .chunk
1877 .add_constant(Constant::String(case.variable.clone()));
1878 self.chunk.emit_u16(Op::DefLet, var_idx, self.line);
1879
1880 self.compile_try_body(&case.body)?;
1881 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1882 self.chunk.patch_jump(skip);
1883 self.chunk.emit(Op::Pop, self.line);
1884 }
1885
1886 if let Some((_, ref timeout_body)) = timeout {
1888 self.compile_try_body(timeout_body)?;
1889 } else if let Some(ref def_body) = default_body {
1890 self.compile_try_body(def_body)?;
1891 } else {
1892 self.chunk.emit(Op::Nil, self.line);
1893 }
1894
1895 for ej in end_jumps {
1896 self.chunk.patch_jump(ej);
1897 }
1898 }
1899 Node::Spread(_) => {
1900 return Err(CompileError {
1901 message: "spread (...) can only be used inside list or dict literals".into(),
1902 line: self.line,
1903 });
1904 }
1905 }
1906 Ok(())
1907 }
1908
1909 fn compile_destructuring(
1913 &mut self,
1914 pattern: &BindingPattern,
1915 is_mutable: bool,
1916 ) -> Result<(), CompileError> {
1917 let def_op = if is_mutable { Op::DefVar } else { Op::DefLet };
1918 match pattern {
1919 BindingPattern::Identifier(name) => {
1920 let idx = self.chunk.add_constant(Constant::String(name.clone()));
1922 self.chunk.emit_u16(def_op, idx, self.line);
1923 }
1924 BindingPattern::Dict(fields) => {
1925 self.chunk.emit(Op::Dup, self.line);
1928 let assert_idx = self
1929 .chunk
1930 .add_constant(Constant::String("__assert_dict".into()));
1931 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1932 self.chunk.emit(Op::Swap, self.line);
1933 self.chunk.emit_u8(Op::Call, 1, self.line);
1934 self.chunk.emit(Op::Pop, self.line); let non_rest: Vec<_> = fields.iter().filter(|f| !f.is_rest).collect();
1939 let rest_field = fields.iter().find(|f| f.is_rest);
1940
1941 for field in &non_rest {
1942 self.chunk.emit(Op::Dup, self.line);
1943 let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
1944 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1945 self.chunk.emit(Op::Subscript, self.line);
1946 let binding_name = field.alias.as_deref().unwrap_or(&field.key);
1947 let name_idx = self
1948 .chunk
1949 .add_constant(Constant::String(binding_name.to_string()));
1950 self.chunk.emit_u16(def_op, name_idx, self.line);
1951 }
1952
1953 if let Some(rest) = rest_field {
1954 let fn_idx = self
1957 .chunk
1958 .add_constant(Constant::String("__dict_rest".into()));
1959 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
1960 self.chunk.emit(Op::Swap, self.line);
1962 for field in &non_rest {
1964 let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
1965 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1966 }
1967 self.chunk
1968 .emit_u16(Op::BuildList, non_rest.len() as u16, self.line);
1969 self.chunk.emit_u8(Op::Call, 2, self.line);
1971 let rest_name = &rest.key;
1972 let rest_idx = self.chunk.add_constant(Constant::String(rest_name.clone()));
1973 self.chunk.emit_u16(def_op, rest_idx, self.line);
1974 } else {
1975 self.chunk.emit(Op::Pop, self.line);
1977 }
1978 }
1979 BindingPattern::List(elements) => {
1980 self.chunk.emit(Op::Dup, self.line);
1983 let assert_idx = self
1984 .chunk
1985 .add_constant(Constant::String("__assert_list".into()));
1986 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1987 self.chunk.emit(Op::Swap, self.line);
1988 self.chunk.emit_u8(Op::Call, 1, self.line);
1989 self.chunk.emit(Op::Pop, self.line); let non_rest: Vec<_> = elements.iter().filter(|e| !e.is_rest).collect();
1992 let rest_elem = elements.iter().find(|e| e.is_rest);
1993
1994 for (i, elem) in non_rest.iter().enumerate() {
1995 self.chunk.emit(Op::Dup, self.line);
1996 let idx_const = self.chunk.add_constant(Constant::Int(i as i64));
1997 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1998 self.chunk.emit(Op::Subscript, self.line);
1999 let name_idx = self.chunk.add_constant(Constant::String(elem.name.clone()));
2000 self.chunk.emit_u16(def_op, name_idx, self.line);
2001 }
2002
2003 if let Some(rest) = rest_elem {
2004 let start_idx = self
2008 .chunk
2009 .add_constant(Constant::Int(non_rest.len() as i64));
2010 self.chunk.emit_u16(Op::Constant, start_idx, self.line);
2011 self.chunk.emit(Op::Nil, self.line); self.chunk.emit(Op::Slice, self.line);
2013 let rest_name_idx =
2014 self.chunk.add_constant(Constant::String(rest.name.clone()));
2015 self.chunk.emit_u16(def_op, rest_name_idx, self.line);
2016 } else {
2017 self.chunk.emit(Op::Pop, self.line);
2019 }
2020 }
2021 }
2022 Ok(())
2023 }
2024
2025 fn produces_value(node: &Node) -> bool {
2027 match node {
2028 Node::LetBinding { .. }
2030 | Node::VarBinding { .. }
2031 | Node::Assignment { .. }
2032 | Node::ReturnStmt { .. }
2033 | Node::FnDecl { .. }
2034 | Node::ThrowStmt { .. }
2035 | Node::BreakStmt
2036 | Node::ContinueStmt => false,
2037 Node::TryCatch { .. }
2039 | Node::Retry { .. }
2040 | Node::GuardStmt { .. }
2041 | Node::DeadlineBlock { .. }
2042 | Node::MutexBlock { .. }
2043 | Node::Spread(_) => true,
2044 _ => true,
2046 }
2047 }
2048}
2049
2050impl Compiler {
2051 pub fn compile_fn_body(
2053 &mut self,
2054 params: &[TypedParam],
2055 body: &[SNode],
2056 ) -> Result<CompiledFunction, CompileError> {
2057 let mut fn_compiler = Compiler::new();
2058 fn_compiler.compile_block(body)?;
2059 fn_compiler.chunk.emit(Op::Nil, 0);
2060 fn_compiler.chunk.emit(Op::Return, 0);
2061 Ok(CompiledFunction {
2062 name: String::new(),
2063 params: TypedParam::names(params),
2064 default_start: TypedParam::default_start(params),
2065 chunk: fn_compiler.chunk,
2066 })
2067 }
2068
2069 fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
2071 if body.is_empty() {
2072 self.chunk.emit(Op::Nil, self.line);
2073 } else {
2074 self.compile_block(body)?;
2075 if !Self::produces_value(&body.last().unwrap().node) {
2077 self.chunk.emit(Op::Nil, self.line);
2078 }
2079 }
2080 Ok(())
2081 }
2082
2083 fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
2085 match op {
2086 "+" => self.chunk.emit(Op::Add, self.line),
2087 "-" => self.chunk.emit(Op::Sub, self.line),
2088 "*" => self.chunk.emit(Op::Mul, self.line),
2089 "/" => self.chunk.emit(Op::Div, self.line),
2090 "%" => self.chunk.emit(Op::Mod, self.line),
2091 _ => {
2092 return Err(CompileError {
2093 message: format!("Unknown compound operator: {op}"),
2094 line: self.line,
2095 })
2096 }
2097 }
2098 Ok(())
2099 }
2100
2101 fn root_var_name(&self, node: &SNode) -> Option<String> {
2103 match &node.node {
2104 Node::Identifier(name) => Some(name.clone()),
2105 Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
2106 self.root_var_name(object)
2107 }
2108 Node::SubscriptAccess { object, .. } => self.root_var_name(object),
2109 _ => None,
2110 }
2111 }
2112}
2113
2114impl Compiler {
2115 fn collect_enum_names(nodes: &[SNode], names: &mut std::collections::HashSet<String>) {
2117 for sn in nodes {
2118 match &sn.node {
2119 Node::EnumDecl { name, .. } => {
2120 names.insert(name.clone());
2121 }
2122 Node::Pipeline { body, .. } => {
2123 Self::collect_enum_names(body, names);
2124 }
2125 Node::FnDecl { body, .. } => {
2126 Self::collect_enum_names(body, names);
2127 }
2128 Node::Block(stmts) => {
2129 Self::collect_enum_names(stmts, names);
2130 }
2131 _ => {}
2132 }
2133 }
2134 }
2135}
2136
2137impl Default for Compiler {
2138 fn default() -> Self {
2139 Self::new()
2140 }
2141}
2142
2143fn contains_pipe_placeholder(node: &SNode) -> bool {
2145 match &node.node {
2146 Node::Identifier(name) if name == "_" => true,
2147 Node::FunctionCall { args, .. } => args.iter().any(contains_pipe_placeholder),
2148 Node::MethodCall { object, args, .. } => {
2149 contains_pipe_placeholder(object) || args.iter().any(contains_pipe_placeholder)
2150 }
2151 Node::BinaryOp { left, right, .. } => {
2152 contains_pipe_placeholder(left) || contains_pipe_placeholder(right)
2153 }
2154 Node::UnaryOp { operand, .. } => contains_pipe_placeholder(operand),
2155 Node::ListLiteral(items) => items.iter().any(contains_pipe_placeholder),
2156 Node::PropertyAccess { object, .. } => contains_pipe_placeholder(object),
2157 Node::SubscriptAccess { object, index } => {
2158 contains_pipe_placeholder(object) || contains_pipe_placeholder(index)
2159 }
2160 _ => false,
2161 }
2162}
2163
2164fn replace_pipe_placeholder(node: &SNode) -> SNode {
2166 let new_node = match &node.node {
2167 Node::Identifier(name) if name == "_" => Node::Identifier("__pipe".into()),
2168 Node::FunctionCall { name, args } => Node::FunctionCall {
2169 name: name.clone(),
2170 args: args.iter().map(replace_pipe_placeholder).collect(),
2171 },
2172 Node::MethodCall {
2173 object,
2174 method,
2175 args,
2176 } => Node::MethodCall {
2177 object: Box::new(replace_pipe_placeholder(object)),
2178 method: method.clone(),
2179 args: args.iter().map(replace_pipe_placeholder).collect(),
2180 },
2181 Node::BinaryOp { op, left, right } => Node::BinaryOp {
2182 op: op.clone(),
2183 left: Box::new(replace_pipe_placeholder(left)),
2184 right: Box::new(replace_pipe_placeholder(right)),
2185 },
2186 Node::UnaryOp { op, operand } => Node::UnaryOp {
2187 op: op.clone(),
2188 operand: Box::new(replace_pipe_placeholder(operand)),
2189 },
2190 Node::ListLiteral(items) => {
2191 Node::ListLiteral(items.iter().map(replace_pipe_placeholder).collect())
2192 }
2193 Node::PropertyAccess { object, property } => Node::PropertyAccess {
2194 object: Box::new(replace_pipe_placeholder(object)),
2195 property: property.clone(),
2196 },
2197 Node::SubscriptAccess { object, index } => Node::SubscriptAccess {
2198 object: Box::new(replace_pipe_placeholder(object)),
2199 index: Box::new(replace_pipe_placeholder(index)),
2200 },
2201 _ => return node.clone(),
2202 };
2203 SNode::new(new_node, node.span)
2204}
2205
2206#[cfg(test)]
2207mod tests {
2208 use super::*;
2209 use harn_lexer::Lexer;
2210 use harn_parser::Parser;
2211
2212 fn compile_source(source: &str) -> Chunk {
2213 let mut lexer = Lexer::new(source);
2214 let tokens = lexer.tokenize().unwrap();
2215 let mut parser = Parser::new(tokens);
2216 let program = parser.parse().unwrap();
2217 Compiler::new().compile(&program).unwrap()
2218 }
2219
2220 #[test]
2221 fn test_compile_arithmetic() {
2222 let chunk = compile_source("pipeline test(task) { let x = 2 + 3 }");
2223 assert!(!chunk.code.is_empty());
2224 assert!(chunk.constants.contains(&Constant::Int(2)));
2226 assert!(chunk.constants.contains(&Constant::Int(3)));
2227 }
2228
2229 #[test]
2230 fn test_compile_function_call() {
2231 let chunk = compile_source("pipeline test(task) { log(42) }");
2232 let disasm = chunk.disassemble("test");
2233 assert!(disasm.contains("CALL"));
2234 }
2235
2236 #[test]
2237 fn test_compile_if_else() {
2238 let chunk =
2239 compile_source(r#"pipeline test(task) { if true { log("yes") } else { log("no") } }"#);
2240 let disasm = chunk.disassemble("test");
2241 assert!(disasm.contains("JUMP_IF_FALSE"));
2242 assert!(disasm.contains("JUMP"));
2243 }
2244
2245 #[test]
2246 fn test_compile_while() {
2247 let chunk = compile_source("pipeline test(task) { var i = 0\n while i < 5 { i = i + 1 } }");
2248 let disasm = chunk.disassemble("test");
2249 assert!(disasm.contains("JUMP_IF_FALSE"));
2250 assert!(disasm.contains("JUMP"));
2252 }
2253
2254 #[test]
2255 fn test_compile_closure() {
2256 let chunk = compile_source("pipeline test(task) { let f = { x -> x * 2 } }");
2257 assert!(!chunk.functions.is_empty());
2258 assert_eq!(chunk.functions[0].params, vec!["x"]);
2259 }
2260
2261 #[test]
2262 fn test_compile_list() {
2263 let chunk = compile_source("pipeline test(task) { let a = [1, 2, 3] }");
2264 let disasm = chunk.disassemble("test");
2265 assert!(disasm.contains("BUILD_LIST"));
2266 }
2267
2268 #[test]
2269 fn test_compile_dict() {
2270 let chunk = compile_source(r#"pipeline test(task) { let d = {name: "test"} }"#);
2271 let disasm = chunk.disassemble("test");
2272 assert!(disasm.contains("BUILD_DICT"));
2273 }
2274
2275 #[test]
2276 fn test_disassemble() {
2277 let chunk = compile_source("pipeline test(task) { log(2 + 3) }");
2278 let disasm = chunk.disassemble("test");
2279 assert!(disasm.contains("CONSTANT"));
2281 assert!(disasm.contains("ADD"));
2282 assert!(disasm.contains("CALL"));
2283 }
2284}