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 scope_depth: usize,
35}
36
37pub struct Compiler {
39 chunk: Chunk,
40 line: u32,
41 column: u32,
42 enum_names: std::collections::HashSet<String>,
44 interface_methods: std::collections::HashMap<String, Vec<String>>,
46 loop_stack: Vec<LoopContext>,
48 handler_depth: usize,
50 finally_bodies: Vec<Vec<SNode>>,
52 temp_counter: usize,
54 scope_depth: usize,
56}
57
58impl Compiler {
59 pub fn new() -> Self {
60 Self {
61 chunk: Chunk::new(),
62 line: 1,
63 column: 1,
64 enum_names: std::collections::HashSet::new(),
65 interface_methods: std::collections::HashMap::new(),
66 loop_stack: Vec::new(),
67 handler_depth: 0,
68 finally_bodies: Vec::new(),
69 temp_counter: 0,
70 scope_depth: 0,
71 }
72 }
73
74 pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
77 Self::collect_enum_names(program, &mut self.enum_names);
80 self.enum_names.insert("Result".to_string());
82 Self::collect_interface_methods(program, &mut self.interface_methods);
83
84 for sn in program {
86 match &sn.node {
87 Node::ImportDecl { .. } | Node::SelectiveImport { .. } => {
88 self.compile_node(sn)?;
89 }
90 _ => {}
91 }
92 }
93 let main = program
95 .iter()
96 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == "default"))
97 .or_else(|| {
98 program
99 .iter()
100 .find(|sn| matches!(&sn.node, Node::Pipeline { .. }))
101 });
102
103 if let Some(sn) = main {
104 self.compile_top_level_declarations(program)?;
105 if let Node::Pipeline { body, extends, .. } = &sn.node {
106 if let Some(parent_name) = extends {
108 self.compile_parent_pipeline(program, parent_name)?;
109 }
110 self.compile_block(body)?;
111 }
112 } else {
113 let top_level: Vec<&SNode> = program
116 .iter()
117 .filter(|sn| {
118 !matches!(
119 &sn.node,
120 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
121 )
122 })
123 .collect();
124 for sn in &top_level {
125 self.compile_node(sn)?;
126 if Self::produces_value(&sn.node) {
127 self.chunk.emit(Op::Pop, self.line);
128 }
129 }
130 }
131
132 self.chunk.emit(Op::Nil, self.line);
133 self.chunk.emit(Op::Return, self.line);
134 Ok(self.chunk)
135 }
136
137 pub fn compile_named(
139 mut self,
140 program: &[SNode],
141 pipeline_name: &str,
142 ) -> Result<Chunk, CompileError> {
143 Self::collect_enum_names(program, &mut self.enum_names);
144 Self::collect_interface_methods(program, &mut self.interface_methods);
145
146 for sn in program {
147 if matches!(
148 &sn.node,
149 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
150 ) {
151 self.compile_node(sn)?;
152 }
153 }
154 let target = program
155 .iter()
156 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == pipeline_name));
157
158 if let Some(sn) = target {
159 self.compile_top_level_declarations(program)?;
160 if let Node::Pipeline { body, extends, .. } = &sn.node {
161 if let Some(parent_name) = extends {
162 self.compile_parent_pipeline(program, parent_name)?;
163 }
164 self.compile_block(body)?;
165 }
166 }
167
168 self.chunk.emit(Op::Nil, self.line);
169 self.chunk.emit(Op::Return, self.line);
170 Ok(self.chunk)
171 }
172
173 fn compile_parent_pipeline(
175 &mut self,
176 program: &[SNode],
177 parent_name: &str,
178 ) -> Result<(), CompileError> {
179 let parent = program
180 .iter()
181 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == parent_name));
182 if let Some(sn) = parent {
183 if let Node::Pipeline { body, extends, .. } = &sn.node {
184 if let Some(grandparent) = extends {
186 self.compile_parent_pipeline(program, grandparent)?;
187 }
188 for stmt in body {
190 self.compile_node(stmt)?;
191 if Self::produces_value(&stmt.node) {
192 self.chunk.emit(Op::Pop, self.line);
193 }
194 }
195 }
196 }
197 Ok(())
198 }
199
200 fn emit_default_preamble(&mut self, params: &[TypedParam]) -> Result<(), CompileError> {
205 for (i, param) in params.iter().enumerate() {
206 if let Some(default_expr) = ¶m.default_value {
207 self.chunk.emit(Op::GetArgc, self.line);
208 let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
209 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
210 self.chunk.emit(Op::GreaterEqual, self.line);
212 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
213 self.chunk.emit(Op::Pop, self.line);
215 self.compile_node(default_expr)?;
217 let name_idx = self
218 .chunk
219 .add_constant(Constant::String(param.name.clone()));
220 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
221 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
222 self.chunk.patch_jump(skip_jump);
223 self.chunk.emit(Op::Pop, self.line);
225 self.chunk.patch_jump(end_jump);
226 }
227 }
228 Ok(())
229 }
230
231 fn emit_type_checks(&mut self, params: &[TypedParam]) {
235 for param in params {
236 if let Some(type_expr) = ¶m.type_expr {
237 if let harn_parser::TypeExpr::Shape(fields) = type_expr {
239 let spec = Self::shape_to_spec_string(fields);
240 let fn_idx = self
242 .chunk
243 .add_constant(Constant::String("__assert_shape".into()));
244 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
245 let var_idx = self
246 .chunk
247 .add_constant(Constant::String(param.name.clone()));
248 self.chunk.emit_u16(Op::GetVar, var_idx, self.line);
249 let name_idx = self
250 .chunk
251 .add_constant(Constant::String(param.name.clone()));
252 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
253 let spec_idx = self.chunk.add_constant(Constant::String(spec));
254 self.chunk.emit_u16(Op::Constant, spec_idx, self.line);
255 self.chunk.emit_u8(Op::Call, 3, self.line);
256 self.chunk.emit(Op::Pop, self.line);
257 continue;
258 }
259
260 if let harn_parser::TypeExpr::Named(name) = type_expr {
262 if let Some(methods) = self.interface_methods.get(name) {
263 let fn_idx = self
264 .chunk
265 .add_constant(Constant::String("__assert_interface".into()));
266 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
267 let var_idx = self
268 .chunk
269 .add_constant(Constant::String(param.name.clone()));
270 self.chunk.emit_u16(Op::GetVar, var_idx, self.line);
271 let name_idx = self
272 .chunk
273 .add_constant(Constant::String(param.name.clone()));
274 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
275 let iface_idx = self.chunk.add_constant(Constant::String(name.clone()));
276 self.chunk.emit_u16(Op::Constant, iface_idx, self.line);
277 let methods_str = methods.join(",");
278 let methods_idx = self.chunk.add_constant(Constant::String(methods_str));
279 self.chunk.emit_u16(Op::Constant, methods_idx, self.line);
280 self.chunk.emit_u8(Op::Call, 4, self.line);
281 self.chunk.emit(Op::Pop, self.line);
282 continue;
283 }
284 }
285
286 let type_name = Self::type_expr_to_runtime_name(type_expr);
287 if let Some(type_name) = type_name {
288 let var_idx = self
289 .chunk
290 .add_constant(Constant::String(param.name.clone()));
291 let type_idx = self.chunk.add_constant(Constant::String(type_name));
292 self.chunk.emit_u16(Op::CheckType, var_idx, self.line);
293 let hi = (type_idx >> 8) as u8;
295 let lo = type_idx as u8;
296 self.chunk.code.push(hi);
297 self.chunk.code.push(lo);
298 }
299 }
300 }
301 }
302
303 fn shape_to_spec_string(fields: &[harn_parser::ShapeField]) -> String {
306 fields
307 .iter()
308 .map(|f| {
309 let opt = if f.optional { "?" } else { "" };
310 let type_str = Self::type_expr_to_spec(&f.type_expr);
311 format!("{}:{}{}", f.name, opt, type_str)
312 })
313 .collect::<Vec<_>>()
314 .join(",")
315 }
316
317 fn type_expr_to_spec(type_expr: &harn_parser::TypeExpr) -> String {
319 match type_expr {
320 harn_parser::TypeExpr::Named(name) => name.clone(),
321 harn_parser::TypeExpr::Shape(fields) => {
322 let inner = Self::shape_to_spec_string(fields);
323 format!("{{{}}}", inner)
324 }
325 harn_parser::TypeExpr::List(_) => "list".to_string(),
326 harn_parser::TypeExpr::DictType(_, _) => "dict".to_string(),
327 harn_parser::TypeExpr::Union(members) => {
328 members
330 .iter()
331 .map(Self::type_expr_to_spec)
332 .collect::<Vec<_>>()
333 .join("|")
334 }
335 harn_parser::TypeExpr::FnType { .. } => "closure".to_string(),
336 }
337 }
338
339 fn type_expr_to_runtime_name(type_expr: &harn_parser::TypeExpr) -> Option<String> {
341 match type_expr {
342 harn_parser::TypeExpr::Named(name) => match name.as_str() {
343 "int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
344 | "closure" => Some(name.clone()),
345 _ => None, },
347 _ => None, }
349 }
350
351 fn emit_type_name_extra(&mut self, type_name_idx: u16) {
353 let hi = (type_name_idx >> 8) as u8;
354 let lo = type_name_idx as u8;
355 self.chunk.code.push(hi);
356 self.chunk.code.push(lo);
357 self.chunk.lines.push(self.line);
358 self.chunk.columns.push(self.column);
359 self.chunk.lines.push(self.line);
360 self.chunk.columns.push(self.column);
361 }
362
363 fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
365 if body.is_empty() {
366 self.chunk.emit(Op::Nil, self.line);
367 } else {
368 self.compile_scoped_block(body)?;
369 }
370 Ok(())
371 }
372
373 fn compile_catch_binding(&mut self, error_var: &Option<String>) -> Result<(), CompileError> {
375 if let Some(var_name) = error_var {
376 let idx = self.chunk.add_constant(Constant::String(var_name.clone()));
377 self.chunk.emit_u16(Op::DefLet, idx, self.line);
378 } else {
379 self.chunk.emit(Op::Pop, self.line);
380 }
381 Ok(())
382 }
383
384 fn compile_finally_inline(&mut self, finally_body: &[SNode]) -> Result<(), CompileError> {
386 if !finally_body.is_empty() {
387 self.compile_scoped_block(finally_body)?;
388 if Self::produces_value(&finally_body.last().unwrap().node) {
390 self.chunk.emit(Op::Pop, self.line);
391 }
392 }
393 Ok(())
394 }
395
396 fn compile_rethrow_with_finally(&mut self, finally_body: &[SNode]) -> Result<(), CompileError> {
398 self.temp_counter += 1;
400 let temp_name = format!("__finally_err_{}__", self.temp_counter);
401 let err_idx = self.chunk.add_constant(Constant::String(temp_name.clone()));
402 self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
403 self.compile_finally_inline(finally_body)?;
404 let get_idx = self.chunk.add_constant(Constant::String(temp_name));
405 self.chunk.emit_u16(Op::GetVar, get_idx, self.line);
406 self.chunk.emit(Op::Throw, self.line);
407 Ok(())
408 }
409
410 fn begin_scope(&mut self) {
411 self.chunk.emit(Op::PushScope, self.line);
412 self.scope_depth += 1;
413 }
414
415 fn end_scope(&mut self) {
416 if self.scope_depth > 0 {
417 self.chunk.emit(Op::PopScope, self.line);
418 self.scope_depth -= 1;
419 }
420 }
421
422 fn unwind_scopes_to(&mut self, target_depth: usize) {
423 while self.scope_depth > target_depth {
424 self.chunk.emit(Op::PopScope, self.line);
425 self.scope_depth -= 1;
426 }
427 }
428
429 fn compile_scoped_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
430 self.begin_scope();
431 if stmts.is_empty() {
432 self.chunk.emit(Op::Nil, self.line);
433 } else {
434 self.compile_block(stmts)?;
435 }
436 self.end_scope();
437 Ok(())
438 }
439
440 fn compile_scoped_statements(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
441 self.begin_scope();
442 for sn in stmts {
443 self.compile_node(sn)?;
444 if Self::produces_value(&sn.node) {
445 self.chunk.emit(Op::Pop, self.line);
446 }
447 }
448 self.end_scope();
449 Ok(())
450 }
451
452 fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
453 for (i, snode) in stmts.iter().enumerate() {
454 self.compile_node(snode)?;
455 let is_last = i == stmts.len() - 1;
456 if is_last {
457 if !Self::produces_value(&snode.node) {
460 self.chunk.emit(Op::Nil, self.line);
461 }
462 } else {
463 if Self::produces_value(&snode.node) {
465 self.chunk.emit(Op::Pop, self.line);
466 }
467 }
468 }
469 Ok(())
470 }
471
472 fn compile_node(&mut self, snode: &SNode) -> Result<(), CompileError> {
473 self.line = snode.span.line as u32;
474 self.column = snode.span.column as u32;
475 self.chunk.set_column(self.column);
476 match &snode.node {
477 Node::IntLiteral(n) => {
478 let idx = self.chunk.add_constant(Constant::Int(*n));
479 self.chunk.emit_u16(Op::Constant, idx, self.line);
480 }
481 Node::FloatLiteral(n) => {
482 let idx = self.chunk.add_constant(Constant::Float(*n));
483 self.chunk.emit_u16(Op::Constant, idx, self.line);
484 }
485 Node::StringLiteral(s) => {
486 let idx = self.chunk.add_constant(Constant::String(s.clone()));
487 self.chunk.emit_u16(Op::Constant, idx, self.line);
488 }
489 Node::BoolLiteral(true) => self.chunk.emit(Op::True, self.line),
490 Node::BoolLiteral(false) => self.chunk.emit(Op::False, self.line),
491 Node::NilLiteral => self.chunk.emit(Op::Nil, self.line),
492 Node::DurationLiteral(ms) => {
493 let idx = self.chunk.add_constant(Constant::Duration(*ms));
494 self.chunk.emit_u16(Op::Constant, idx, self.line);
495 }
496
497 Node::Identifier(name) => {
498 let idx = self.chunk.add_constant(Constant::String(name.clone()));
499 self.chunk.emit_u16(Op::GetVar, idx, self.line);
500 }
501
502 Node::LetBinding { pattern, value, .. } => {
503 self.compile_node(value)?;
504 self.compile_destructuring(pattern, false)?;
505 }
506
507 Node::VarBinding { pattern, value, .. } => {
508 self.compile_node(value)?;
509 self.compile_destructuring(pattern, true)?;
510 }
511
512 Node::Assignment {
513 target, value, op, ..
514 } => {
515 if let Node::Identifier(name) = &target.node {
516 let idx = self.chunk.add_constant(Constant::String(name.clone()));
517 if let Some(op) = op {
518 self.chunk.emit_u16(Op::GetVar, idx, self.line);
519 self.compile_node(value)?;
520 self.emit_compound_op(op)?;
521 self.chunk.emit_u16(Op::SetVar, idx, self.line);
522 } else {
523 self.compile_node(value)?;
524 self.chunk.emit_u16(Op::SetVar, idx, self.line);
525 }
526 } else if let Node::PropertyAccess { object, property } = &target.node {
527 if let Some(var_name) = self.root_var_name(object) {
529 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
530 let prop_idx = self.chunk.add_constant(Constant::String(property.clone()));
531 if let Some(op) = op {
532 self.compile_node(target)?; self.compile_node(value)?;
535 self.emit_compound_op(op)?;
536 } else {
537 self.compile_node(value)?;
538 }
539 self.chunk.emit_u16(Op::SetProperty, prop_idx, self.line);
542 let hi = (var_idx >> 8) as u8;
544 let lo = var_idx as u8;
545 self.chunk.code.push(hi);
546 self.chunk.code.push(lo);
547 self.chunk.lines.push(self.line);
548 self.chunk.columns.push(self.column);
549 self.chunk.lines.push(self.line);
550 self.chunk.columns.push(self.column);
551 }
552 } else if let Node::SubscriptAccess { object, index } = &target.node {
553 if let Some(var_name) = self.root_var_name(object) {
555 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
556 if let Some(op) = op {
557 self.compile_node(target)?;
558 self.compile_node(value)?;
559 self.emit_compound_op(op)?;
560 } else {
561 self.compile_node(value)?;
562 }
563 self.compile_node(index)?;
564 self.chunk.emit_u16(Op::SetSubscript, var_idx, self.line);
565 }
566 }
567 }
568
569 Node::BinaryOp { op, left, right } => {
570 match op.as_str() {
572 "&&" => {
573 self.compile_node(left)?;
574 let jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
575 self.chunk.emit(Op::Pop, self.line);
576 self.compile_node(right)?;
577 self.chunk.patch_jump(jump);
578 self.chunk.emit(Op::Not, self.line);
580 self.chunk.emit(Op::Not, self.line);
581 return Ok(());
582 }
583 "||" => {
584 self.compile_node(left)?;
585 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
586 self.chunk.emit(Op::Pop, self.line);
587 self.compile_node(right)?;
588 self.chunk.patch_jump(jump);
589 self.chunk.emit(Op::Not, self.line);
590 self.chunk.emit(Op::Not, self.line);
591 return Ok(());
592 }
593 "??" => {
594 self.compile_node(left)?;
595 self.chunk.emit(Op::Dup, self.line);
596 self.chunk.emit(Op::Nil, self.line);
598 self.chunk.emit(Op::NotEqual, self.line);
599 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
600 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_node(right)?;
603 let end = self.chunk.emit_jump(Op::Jump, self.line);
604 self.chunk.patch_jump(jump);
605 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end);
607 return Ok(());
608 }
609 "|>" => {
610 self.compile_node(left)?;
611 if contains_pipe_placeholder(right) {
614 let replaced = replace_pipe_placeholder(right);
615 let closure_node = SNode::dummy(Node::Closure {
616 params: vec![TypedParam {
617 name: "__pipe".into(),
618 type_expr: None,
619 default_value: None,
620 }],
621 body: vec![replaced],
622 fn_syntax: false,
623 });
624 self.compile_node(&closure_node)?;
625 } else {
626 self.compile_node(right)?;
627 }
628 self.chunk.emit(Op::Pipe, self.line);
629 return Ok(());
630 }
631 _ => {}
632 }
633
634 self.compile_node(left)?;
635 self.compile_node(right)?;
636 match op.as_str() {
637 "+" => self.chunk.emit(Op::Add, self.line),
638 "-" => self.chunk.emit(Op::Sub, self.line),
639 "*" => self.chunk.emit(Op::Mul, self.line),
640 "/" => self.chunk.emit(Op::Div, self.line),
641 "%" => self.chunk.emit(Op::Mod, self.line),
642 "==" => self.chunk.emit(Op::Equal, self.line),
643 "!=" => self.chunk.emit(Op::NotEqual, self.line),
644 "<" => self.chunk.emit(Op::Less, self.line),
645 ">" => self.chunk.emit(Op::Greater, self.line),
646 "<=" => self.chunk.emit(Op::LessEqual, self.line),
647 ">=" => self.chunk.emit(Op::GreaterEqual, self.line),
648 "in" => self.chunk.emit(Op::Contains, self.line),
649 "not_in" => {
650 self.chunk.emit(Op::Contains, self.line);
651 self.chunk.emit(Op::Not, self.line);
652 }
653 _ => {
654 return Err(CompileError {
655 message: format!("Unknown operator: {op}"),
656 line: self.line,
657 })
658 }
659 }
660 }
661
662 Node::UnaryOp { op, operand } => {
663 self.compile_node(operand)?;
664 match op.as_str() {
665 "-" => self.chunk.emit(Op::Negate, self.line),
666 "!" => self.chunk.emit(Op::Not, self.line),
667 _ => {}
668 }
669 }
670
671 Node::Ternary {
672 condition,
673 true_expr,
674 false_expr,
675 } => {
676 self.compile_node(condition)?;
677 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
678 self.chunk.emit(Op::Pop, self.line);
679 self.compile_node(true_expr)?;
680 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
681 self.chunk.patch_jump(else_jump);
682 self.chunk.emit(Op::Pop, self.line);
683 self.compile_node(false_expr)?;
684 self.chunk.patch_jump(end_jump);
685 }
686
687 Node::FunctionCall { name, args } => {
688 let has_spread = args.iter().any(|a| matches!(&a.node, Node::Spread(_)));
689 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
691 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
692
693 if has_spread {
694 self.chunk.emit_u16(Op::BuildList, 0, self.line);
697 let mut pending = 0u16;
698 for arg in args {
699 if let Node::Spread(inner) = &arg.node {
700 if pending > 0 {
701 self.chunk.emit_u16(Op::BuildList, pending, self.line);
702 self.chunk.emit(Op::Add, self.line);
703 pending = 0;
704 }
705 self.compile_node(inner)?;
706 self.chunk.emit(Op::Dup, self.line);
707 let assert_idx = self
708 .chunk
709 .add_constant(Constant::String("__assert_list".into()));
710 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
711 self.chunk.emit(Op::Swap, self.line);
712 self.chunk.emit_u8(Op::Call, 1, self.line);
713 self.chunk.emit(Op::Pop, self.line);
714 self.chunk.emit(Op::Add, self.line);
715 } else {
716 self.compile_node(arg)?;
717 pending += 1;
718 }
719 }
720 if pending > 0 {
721 self.chunk.emit_u16(Op::BuildList, pending, self.line);
722 self.chunk.emit(Op::Add, self.line);
723 }
724 self.chunk.emit(Op::CallSpread, self.line);
725 } else {
726 for arg in args {
728 self.compile_node(arg)?;
729 }
730 self.chunk.emit_u8(Op::Call, args.len() as u8, self.line);
731 }
732 }
733
734 Node::MethodCall {
735 object,
736 method,
737 args,
738 } => {
739 if let Node::Identifier(name) = &object.node {
741 if self.enum_names.contains(name) {
742 for arg in args {
744 self.compile_node(arg)?;
745 }
746 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
747 let var_idx = self.chunk.add_constant(Constant::String(method.clone()));
748 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
749 let hi = (var_idx >> 8) as u8;
750 let lo = var_idx as u8;
751 self.chunk.code.push(hi);
752 self.chunk.code.push(lo);
753 self.chunk.lines.push(self.line);
754 self.chunk.columns.push(self.column);
755 self.chunk.lines.push(self.line);
756 self.chunk.columns.push(self.column);
757 let fc = args.len() as u16;
758 let fhi = (fc >> 8) as u8;
759 let flo = fc as u8;
760 self.chunk.code.push(fhi);
761 self.chunk.code.push(flo);
762 self.chunk.lines.push(self.line);
763 self.chunk.columns.push(self.column);
764 self.chunk.lines.push(self.line);
765 self.chunk.columns.push(self.column);
766 return Ok(());
767 }
768 }
769 let has_spread = args.iter().any(|a| matches!(&a.node, Node::Spread(_)));
770 self.compile_node(object)?;
771 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
772 if has_spread {
773 self.chunk.emit_u16(Op::BuildList, 0, self.line);
775 let mut pending = 0u16;
776 for arg in args {
777 if let Node::Spread(inner) = &arg.node {
778 if pending > 0 {
779 self.chunk.emit_u16(Op::BuildList, pending, self.line);
780 self.chunk.emit(Op::Add, self.line);
781 pending = 0;
782 }
783 self.compile_node(inner)?;
784 self.chunk.emit(Op::Dup, self.line);
785 let assert_idx = self
786 .chunk
787 .add_constant(Constant::String("__assert_list".into()));
788 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
789 self.chunk.emit(Op::Swap, self.line);
790 self.chunk.emit_u8(Op::Call, 1, self.line);
791 self.chunk.emit(Op::Pop, self.line);
792 self.chunk.emit(Op::Add, self.line);
793 } else {
794 self.compile_node(arg)?;
795 pending += 1;
796 }
797 }
798 if pending > 0 {
799 self.chunk.emit_u16(Op::BuildList, pending, self.line);
800 self.chunk.emit(Op::Add, self.line);
801 }
802 self.chunk
803 .emit_u16(Op::MethodCallSpread, name_idx, self.line);
804 } else {
805 for arg in args {
806 self.compile_node(arg)?;
807 }
808 self.chunk
809 .emit_method_call(name_idx, args.len() as u8, self.line);
810 }
811 }
812
813 Node::OptionalMethodCall {
814 object,
815 method,
816 args,
817 } => {
818 self.compile_node(object)?;
819 for arg in args {
820 self.compile_node(arg)?;
821 }
822 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
823 self.chunk
824 .emit_method_call_opt(name_idx, args.len() as u8, self.line);
825 }
826
827 Node::PropertyAccess { object, property } => {
828 if let Node::Identifier(name) = &object.node {
830 if self.enum_names.contains(name) {
831 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
833 let var_idx = self.chunk.add_constant(Constant::String(property.clone()));
834 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
835 let hi = (var_idx >> 8) as u8;
836 let lo = var_idx as u8;
837 self.chunk.code.push(hi);
838 self.chunk.code.push(lo);
839 self.chunk.lines.push(self.line);
840 self.chunk.columns.push(self.column);
841 self.chunk.lines.push(self.line);
842 self.chunk.columns.push(self.column);
843 self.chunk.code.push(0);
845 self.chunk.code.push(0);
846 self.chunk.lines.push(self.line);
847 self.chunk.columns.push(self.column);
848 self.chunk.lines.push(self.line);
849 self.chunk.columns.push(self.column);
850 return Ok(());
851 }
852 }
853 self.compile_node(object)?;
854 let idx = self.chunk.add_constant(Constant::String(property.clone()));
855 self.chunk.emit_u16(Op::GetProperty, idx, self.line);
856 }
857
858 Node::OptionalPropertyAccess { object, property } => {
859 self.compile_node(object)?;
860 let idx = self.chunk.add_constant(Constant::String(property.clone()));
861 self.chunk.emit_u16(Op::GetPropertyOpt, idx, self.line);
862 }
863
864 Node::SubscriptAccess { object, index } => {
865 self.compile_node(object)?;
866 self.compile_node(index)?;
867 self.chunk.emit(Op::Subscript, self.line);
868 }
869
870 Node::SliceAccess { object, start, end } => {
871 self.compile_node(object)?;
872 if let Some(s) = start {
873 self.compile_node(s)?;
874 } else {
875 self.chunk.emit(Op::Nil, self.line);
876 }
877 if let Some(e) = end {
878 self.compile_node(e)?;
879 } else {
880 self.chunk.emit(Op::Nil, self.line);
881 }
882 self.chunk.emit(Op::Slice, self.line);
883 }
884
885 Node::IfElse {
886 condition,
887 then_body,
888 else_body,
889 } => {
890 self.compile_node(condition)?;
891 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
892 self.chunk.emit(Op::Pop, self.line);
893 self.compile_scoped_block(then_body)?;
894 if let Some(else_body) = else_body {
895 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
896 self.chunk.patch_jump(else_jump);
897 self.chunk.emit(Op::Pop, self.line);
898 self.compile_scoped_block(else_body)?;
899 self.chunk.patch_jump(end_jump);
900 } else {
901 self.chunk.patch_jump(else_jump);
902 self.chunk.emit(Op::Pop, self.line);
903 self.chunk.emit(Op::Nil, self.line);
904 }
905 }
906
907 Node::WhileLoop { condition, body } => {
908 let loop_start = self.chunk.current_offset();
909 self.loop_stack.push(LoopContext {
910 start_offset: loop_start,
911 break_patches: Vec::new(),
912 has_iterator: false,
913 handler_depth: self.handler_depth,
914 finally_depth: self.finally_bodies.len(),
915 scope_depth: self.scope_depth,
916 });
917 self.compile_node(condition)?;
918 let exit_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
919 self.chunk.emit(Op::Pop, self.line); self.compile_scoped_statements(body)?;
921 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
923 self.chunk.patch_jump(exit_jump);
924 self.chunk.emit(Op::Pop, self.line); let ctx = self.loop_stack.pop().unwrap();
927 for patch_pos in ctx.break_patches {
928 self.chunk.patch_jump(patch_pos);
929 }
930 self.chunk.emit(Op::Nil, self.line);
931 }
932
933 Node::ForIn {
934 pattern,
935 iterable,
936 body,
937 } => {
938 self.compile_node(iterable)?;
940 self.chunk.emit(Op::IterInit, self.line);
942 let loop_start = self.chunk.current_offset();
943 self.loop_stack.push(LoopContext {
944 start_offset: loop_start,
945 break_patches: Vec::new(),
946 has_iterator: true,
947 handler_depth: self.handler_depth,
948 finally_depth: self.finally_bodies.len(),
949 scope_depth: self.scope_depth,
950 });
951 let exit_jump_pos = self.chunk.emit_jump(Op::IterNext, self.line);
953 self.begin_scope();
954 self.compile_destructuring(pattern, true)?;
956 for sn in body {
957 self.compile_node(sn)?;
958 if Self::produces_value(&sn.node) {
959 self.chunk.emit(Op::Pop, self.line);
960 }
961 }
962 self.end_scope();
963 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
965 self.chunk.patch_jump(exit_jump_pos);
966 let ctx = self.loop_stack.pop().unwrap();
968 for patch_pos in ctx.break_patches {
969 self.chunk.patch_jump(patch_pos);
970 }
971 self.chunk.emit(Op::Nil, self.line);
973 }
974
975 Node::ReturnStmt { value } => {
976 let has_pending_finally = !self.finally_bodies.is_empty();
977
978 if has_pending_finally {
979 if let Some(val) = value {
982 self.compile_node(val)?;
983 } else {
984 self.chunk.emit(Op::Nil, self.line);
985 }
986 self.temp_counter += 1;
987 let temp_name = format!("__return_val_{}__", self.temp_counter);
988 let save_idx = self.chunk.add_constant(Constant::String(temp_name.clone()));
989 self.chunk.emit_u16(Op::DefVar, save_idx, self.line);
990 let finallys: Vec<_> = self.finally_bodies.iter().rev().cloned().collect();
992 for fb in &finallys {
993 self.compile_finally_inline(fb)?;
994 }
995 let restore_idx = self.chunk.add_constant(Constant::String(temp_name));
996 self.chunk.emit_u16(Op::GetVar, restore_idx, self.line);
997 self.chunk.emit(Op::Return, self.line);
998 } else {
999 if let Some(val) = value {
1001 if let Node::FunctionCall { name, args } = &val.node {
1002 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1003 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
1004 for arg in args {
1005 self.compile_node(arg)?;
1006 }
1007 self.chunk
1008 .emit_u8(Op::TailCall, args.len() as u8, self.line);
1009 } else if let Node::BinaryOp { op, left, right } = &val.node {
1010 if op == "|>" {
1011 self.compile_node(left)?;
1012 self.compile_node(right)?;
1013 self.chunk.emit(Op::Swap, self.line);
1014 self.chunk.emit_u8(Op::TailCall, 1, self.line);
1015 } else {
1016 self.compile_node(val)?;
1017 }
1018 } else {
1019 self.compile_node(val)?;
1020 }
1021 } else {
1022 self.chunk.emit(Op::Nil, self.line);
1023 }
1024 self.chunk.emit(Op::Return, self.line);
1025 }
1026 }
1027
1028 Node::BreakStmt => {
1029 if self.loop_stack.is_empty() {
1030 return Err(CompileError {
1031 message: "break outside of loop".to_string(),
1032 line: self.line,
1033 });
1034 }
1035 let ctx = self.loop_stack.last().unwrap();
1037 let finally_depth = ctx.finally_depth;
1038 let handler_depth = ctx.handler_depth;
1039 let has_iterator = ctx.has_iterator;
1040 let scope_depth = ctx.scope_depth;
1041 for _ in handler_depth..self.handler_depth {
1043 self.chunk.emit(Op::PopHandler, self.line);
1044 }
1045 if self.finally_bodies.len() > finally_depth {
1047 let finallys: Vec<_> = self.finally_bodies[finally_depth..]
1048 .iter()
1049 .rev()
1050 .cloned()
1051 .collect();
1052 for fb in &finallys {
1053 self.compile_finally_inline(fb)?;
1054 }
1055 }
1056 self.unwind_scopes_to(scope_depth);
1057 if has_iterator {
1058 self.chunk.emit(Op::PopIterator, self.line);
1059 }
1060 let patch = self.chunk.emit_jump(Op::Jump, self.line);
1061 self.loop_stack
1062 .last_mut()
1063 .unwrap()
1064 .break_patches
1065 .push(patch);
1066 }
1067
1068 Node::ContinueStmt => {
1069 if self.loop_stack.is_empty() {
1070 return Err(CompileError {
1071 message: "continue outside of loop".to_string(),
1072 line: self.line,
1073 });
1074 }
1075 let ctx = self.loop_stack.last().unwrap();
1076 let finally_depth = ctx.finally_depth;
1077 let handler_depth = ctx.handler_depth;
1078 let loop_start = ctx.start_offset;
1079 let scope_depth = ctx.scope_depth;
1080 for _ in handler_depth..self.handler_depth {
1081 self.chunk.emit(Op::PopHandler, self.line);
1082 }
1083 if self.finally_bodies.len() > finally_depth {
1084 let finallys: Vec<_> = self.finally_bodies[finally_depth..]
1085 .iter()
1086 .rev()
1087 .cloned()
1088 .collect();
1089 for fb in &finallys {
1090 self.compile_finally_inline(fb)?;
1091 }
1092 }
1093 self.unwind_scopes_to(scope_depth);
1094 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
1095 }
1096
1097 Node::ListLiteral(elements) => {
1098 let has_spread = elements.iter().any(|e| matches!(&e.node, Node::Spread(_)));
1099 if !has_spread {
1100 for el in elements {
1101 self.compile_node(el)?;
1102 }
1103 self.chunk
1104 .emit_u16(Op::BuildList, elements.len() as u16, self.line);
1105 } else {
1106 self.chunk.emit_u16(Op::BuildList, 0, self.line);
1109 let mut pending = 0u16;
1110 for el in elements {
1111 if let Node::Spread(inner) = &el.node {
1112 if pending > 0 {
1114 self.chunk.emit_u16(Op::BuildList, pending, self.line);
1115 self.chunk.emit(Op::Add, self.line);
1117 pending = 0;
1118 }
1119 self.compile_node(inner)?;
1121 self.chunk.emit(Op::Dup, self.line);
1122 let assert_idx = self
1123 .chunk
1124 .add_constant(Constant::String("__assert_list".into()));
1125 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1126 self.chunk.emit(Op::Swap, self.line);
1127 self.chunk.emit_u8(Op::Call, 1, self.line);
1128 self.chunk.emit(Op::Pop, self.line);
1129 self.chunk.emit(Op::Add, self.line);
1130 } else {
1131 self.compile_node(el)?;
1132 pending += 1;
1133 }
1134 }
1135 if pending > 0 {
1136 self.chunk.emit_u16(Op::BuildList, pending, self.line);
1137 self.chunk.emit(Op::Add, self.line);
1138 }
1139 }
1140 }
1141
1142 Node::DictLiteral(entries) => {
1143 let has_spread = entries
1144 .iter()
1145 .any(|e| matches!(&e.value.node, Node::Spread(_)));
1146 if !has_spread {
1147 for entry in entries {
1148 self.compile_node(&entry.key)?;
1149 self.compile_node(&entry.value)?;
1150 }
1151 self.chunk
1152 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
1153 } else {
1154 self.chunk.emit_u16(Op::BuildDict, 0, self.line);
1156 let mut pending = 0u16;
1157 for entry in entries {
1158 if let Node::Spread(inner) = &entry.value.node {
1159 if pending > 0 {
1161 self.chunk.emit_u16(Op::BuildDict, pending, self.line);
1162 self.chunk.emit(Op::Add, self.line);
1163 pending = 0;
1164 }
1165 self.compile_node(inner)?;
1167 self.chunk.emit(Op::Dup, self.line);
1168 let assert_idx = self
1169 .chunk
1170 .add_constant(Constant::String("__assert_dict".into()));
1171 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1172 self.chunk.emit(Op::Swap, self.line);
1173 self.chunk.emit_u8(Op::Call, 1, self.line);
1174 self.chunk.emit(Op::Pop, self.line);
1175 self.chunk.emit(Op::Add, self.line);
1176 } else {
1177 self.compile_node(&entry.key)?;
1178 self.compile_node(&entry.value)?;
1179 pending += 1;
1180 }
1181 }
1182 if pending > 0 {
1183 self.chunk.emit_u16(Op::BuildDict, pending, self.line);
1184 self.chunk.emit(Op::Add, self.line);
1185 }
1186 }
1187 }
1188
1189 Node::InterpolatedString(segments) => {
1190 let mut part_count = 0u16;
1191 for seg in segments {
1192 match seg {
1193 StringSegment::Literal(s) => {
1194 let idx = self.chunk.add_constant(Constant::String(s.clone()));
1195 self.chunk.emit_u16(Op::Constant, idx, self.line);
1196 part_count += 1;
1197 }
1198 StringSegment::Expression(expr_str, expr_line, expr_col) => {
1199 let mut lexer =
1201 harn_lexer::Lexer::with_position(expr_str, *expr_line, *expr_col);
1202 if let Ok(tokens) = lexer.tokenize() {
1203 let mut parser = harn_parser::Parser::new(tokens);
1204 if let Ok(snode) = parser.parse_single_expression() {
1205 self.compile_node(&snode)?;
1206 let to_str = self
1208 .chunk
1209 .add_constant(Constant::String("to_string".into()));
1210 self.chunk.emit_u16(Op::Constant, to_str, self.line);
1211 self.chunk.emit(Op::Swap, self.line);
1212 self.chunk.emit_u8(Op::Call, 1, self.line);
1213 part_count += 1;
1214 } else {
1215 let idx =
1217 self.chunk.add_constant(Constant::String(expr_str.clone()));
1218 self.chunk.emit_u16(Op::Constant, idx, self.line);
1219 part_count += 1;
1220 }
1221 }
1222 }
1223 }
1224 }
1225 if part_count > 1 {
1226 self.chunk.emit_u16(Op::Concat, part_count, self.line);
1227 }
1228 }
1229
1230 Node::FnDecl {
1231 name, params, body, ..
1232 } => {
1233 let mut fn_compiler = Compiler::new();
1235 fn_compiler.enum_names = self.enum_names.clone();
1236 fn_compiler.emit_default_preamble(params)?;
1237 fn_compiler.emit_type_checks(params);
1238 let is_gen = body_contains_yield(body);
1239 fn_compiler.compile_block(body)?;
1240 fn_compiler.chunk.emit(Op::Nil, self.line);
1241 fn_compiler.chunk.emit(Op::Return, self.line);
1242
1243 let func = CompiledFunction {
1244 name: name.clone(),
1245 params: TypedParam::names(params),
1246 default_start: TypedParam::default_start(params),
1247 chunk: fn_compiler.chunk,
1248 is_generator: is_gen,
1249 };
1250 let fn_idx = self.chunk.functions.len();
1251 self.chunk.functions.push(func);
1252
1253 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1254 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1255 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1256 }
1257
1258 Node::Closure { params, body, .. } => {
1259 let mut fn_compiler = Compiler::new();
1260 fn_compiler.enum_names = self.enum_names.clone();
1261 fn_compiler.emit_default_preamble(params)?;
1262 fn_compiler.emit_type_checks(params);
1263 let is_gen = body_contains_yield(body);
1264 fn_compiler.compile_block(body)?;
1265 fn_compiler.chunk.emit(Op::Return, self.line);
1267
1268 let func = CompiledFunction {
1269 name: "<closure>".to_string(),
1270 params: TypedParam::names(params),
1271 default_start: TypedParam::default_start(params),
1272 chunk: fn_compiler.chunk,
1273 is_generator: is_gen,
1274 };
1275 let fn_idx = self.chunk.functions.len();
1276 self.chunk.functions.push(func);
1277
1278 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1279 }
1280
1281 Node::ThrowStmt { value } => {
1282 self.compile_node(value)?;
1283 self.chunk.emit(Op::Throw, self.line);
1284 }
1285
1286 Node::MatchExpr { value, arms } => {
1287 self.compile_node(value)?;
1288 let mut end_jumps = Vec::new();
1289 for arm in arms {
1290 match &arm.pattern.node {
1291 Node::Identifier(name) if name == "_" => {
1293 self.begin_scope();
1294 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1296 self.end_scope();
1297 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1298 }
1299 Node::EnumConstruct {
1301 enum_name,
1302 variant,
1303 args: pat_args,
1304 } => {
1305 self.chunk.emit(Op::Dup, self.line);
1307 let en_idx =
1308 self.chunk.add_constant(Constant::String(enum_name.clone()));
1309 let vn_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1310 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1311 let hi = (vn_idx >> 8) as u8;
1312 let lo = vn_idx as u8;
1313 self.chunk.code.push(hi);
1314 self.chunk.code.push(lo);
1315 self.chunk.lines.push(self.line);
1316 self.chunk.columns.push(self.column);
1317 self.chunk.lines.push(self.line);
1318 self.chunk.columns.push(self.column);
1319 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1321 self.chunk.emit(Op::Pop, self.line); self.begin_scope();
1323
1324 for (i, pat_arg) in pat_args.iter().enumerate() {
1327 if let Node::Identifier(binding_name) = &pat_arg.node {
1328 self.chunk.emit(Op::Dup, self.line);
1330 let fields_idx = self
1331 .chunk
1332 .add_constant(Constant::String("fields".to_string()));
1333 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
1334 let idx_const =
1335 self.chunk.add_constant(Constant::Int(i as i64));
1336 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1337 self.chunk.emit(Op::Subscript, self.line);
1338 let name_idx = self
1339 .chunk
1340 .add_constant(Constant::String(binding_name.clone()));
1341 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1342 }
1343 }
1344
1345 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1347 self.end_scope();
1348 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1349 self.chunk.patch_jump(skip);
1350 self.chunk.emit(Op::Pop, self.line); }
1352 Node::PropertyAccess { object, property } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
1354 {
1355 let enum_name = if let Node::Identifier(n) = &object.node {
1356 n.clone()
1357 } else {
1358 unreachable!()
1359 };
1360 self.chunk.emit(Op::Dup, self.line);
1361 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
1362 let vn_idx =
1363 self.chunk.add_constant(Constant::String(property.clone()));
1364 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1365 let hi = (vn_idx >> 8) as u8;
1366 let lo = vn_idx as u8;
1367 self.chunk.code.push(hi);
1368 self.chunk.code.push(lo);
1369 self.chunk.lines.push(self.line);
1370 self.chunk.columns.push(self.column);
1371 self.chunk.lines.push(self.line);
1372 self.chunk.columns.push(self.column);
1373 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1374 self.chunk.emit(Op::Pop, self.line); self.begin_scope();
1376 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1378 self.end_scope();
1379 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1380 self.chunk.patch_jump(skip);
1381 self.chunk.emit(Op::Pop, self.line); }
1383 Node::MethodCall {
1386 object,
1387 method,
1388 args: pat_args,
1389 } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
1390 {
1391 let enum_name = if let Node::Identifier(n) = &object.node {
1392 n.clone()
1393 } else {
1394 unreachable!()
1395 };
1396 self.chunk.emit(Op::Dup, self.line);
1398 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
1399 let vn_idx = self.chunk.add_constant(Constant::String(method.clone()));
1400 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1401 let hi = (vn_idx >> 8) as u8;
1402 let lo = vn_idx as u8;
1403 self.chunk.code.push(hi);
1404 self.chunk.code.push(lo);
1405 self.chunk.lines.push(self.line);
1406 self.chunk.columns.push(self.column);
1407 self.chunk.lines.push(self.line);
1408 self.chunk.columns.push(self.column);
1409 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1410 self.chunk.emit(Op::Pop, self.line); self.begin_scope();
1412
1413 for (i, pat_arg) in pat_args.iter().enumerate() {
1415 if let Node::Identifier(binding_name) = &pat_arg.node {
1416 self.chunk.emit(Op::Dup, self.line);
1417 let fields_idx = self
1418 .chunk
1419 .add_constant(Constant::String("fields".to_string()));
1420 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
1421 let idx_const =
1422 self.chunk.add_constant(Constant::Int(i as i64));
1423 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1424 self.chunk.emit(Op::Subscript, self.line);
1425 let name_idx = self
1426 .chunk
1427 .add_constant(Constant::String(binding_name.clone()));
1428 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1429 }
1430 }
1431
1432 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1434 self.end_scope();
1435 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1436 self.chunk.patch_jump(skip);
1437 self.chunk.emit(Op::Pop, self.line); }
1439 Node::Identifier(name) => {
1441 self.begin_scope();
1442 self.chunk.emit(Op::Dup, self.line); let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1445 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1446 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1448 self.end_scope();
1449 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1450 }
1451 Node::DictLiteral(entries)
1453 if entries
1454 .iter()
1455 .all(|e| matches!(&e.key.node, Node::StringLiteral(_))) =>
1456 {
1457 self.chunk.emit(Op::Dup, self.line);
1459 let typeof_idx =
1460 self.chunk.add_constant(Constant::String("type_of".into()));
1461 self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
1462 self.chunk.emit(Op::Swap, self.line);
1463 self.chunk.emit_u8(Op::Call, 1, self.line);
1464 let dict_str = self.chunk.add_constant(Constant::String("dict".into()));
1465 self.chunk.emit_u16(Op::Constant, dict_str, self.line);
1466 self.chunk.emit(Op::Equal, self.line);
1467 let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1468 self.chunk.emit(Op::Pop, self.line); let mut constraint_skips = Vec::new();
1472 let mut bindings = Vec::new();
1473 self.begin_scope();
1474 for entry in entries {
1475 if let Node::StringLiteral(key) = &entry.key.node {
1476 match &entry.value.node {
1477 Node::StringLiteral(_)
1479 | Node::IntLiteral(_)
1480 | Node::FloatLiteral(_)
1481 | Node::BoolLiteral(_)
1482 | Node::NilLiteral => {
1483 self.chunk.emit(Op::Dup, self.line);
1484 let key_idx = self
1485 .chunk
1486 .add_constant(Constant::String(key.clone()));
1487 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1488 self.chunk.emit(Op::Subscript, self.line);
1489 self.compile_node(&entry.value)?;
1490 self.chunk.emit(Op::Equal, self.line);
1491 let skip =
1492 self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1493 self.chunk.emit(Op::Pop, self.line); constraint_skips.push(skip);
1495 }
1496 Node::Identifier(binding) => {
1498 bindings.push((key.clone(), binding.clone()));
1499 }
1500 _ => {
1501 self.chunk.emit(Op::Dup, self.line);
1503 let key_idx = self
1504 .chunk
1505 .add_constant(Constant::String(key.clone()));
1506 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1507 self.chunk.emit(Op::Subscript, self.line);
1508 self.compile_node(&entry.value)?;
1509 self.chunk.emit(Op::Equal, self.line);
1510 let skip =
1511 self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1512 self.chunk.emit(Op::Pop, self.line);
1513 constraint_skips.push(skip);
1514 }
1515 }
1516 }
1517 }
1518
1519 for (key, binding) in &bindings {
1521 self.chunk.emit(Op::Dup, self.line);
1522 let key_idx =
1523 self.chunk.add_constant(Constant::String(key.clone()));
1524 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1525 self.chunk.emit(Op::Subscript, self.line);
1526 let name_idx =
1527 self.chunk.add_constant(Constant::String(binding.clone()));
1528 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1529 }
1530
1531 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1533 self.end_scope();
1534 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1535
1536 let type_fail_target = self.chunk.code.len();
1537 self.chunk.emit(Op::Pop, self.line); let next_arm_jump = self.chunk.emit_jump(Op::Jump, self.line);
1539 let scoped_fail_target = self.chunk.code.len();
1540 self.chunk.emit(Op::PopScope, self.line);
1541 self.chunk.emit(Op::Pop, self.line); let next_arm_target = self.chunk.code.len();
1543
1544 for skip in constraint_skips {
1545 self.chunk.patch_jump_to(skip, scoped_fail_target);
1546 }
1547 self.chunk.patch_jump_to(skip_type, type_fail_target);
1548 self.chunk.patch_jump_to(next_arm_jump, next_arm_target);
1549 }
1550 Node::ListLiteral(elements) => {
1552 self.chunk.emit(Op::Dup, self.line);
1554 let typeof_idx =
1555 self.chunk.add_constant(Constant::String("type_of".into()));
1556 self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
1557 self.chunk.emit(Op::Swap, self.line);
1558 self.chunk.emit_u8(Op::Call, 1, self.line);
1559 let list_str = self.chunk.add_constant(Constant::String("list".into()));
1560 self.chunk.emit_u16(Op::Constant, list_str, self.line);
1561 self.chunk.emit(Op::Equal, self.line);
1562 let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1563 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Dup, self.line);
1567 let len_idx = self.chunk.add_constant(Constant::String("len".into()));
1568 self.chunk.emit_u16(Op::Constant, len_idx, self.line);
1569 self.chunk.emit(Op::Swap, self.line);
1570 self.chunk.emit_u8(Op::Call, 1, self.line);
1571 let count = self
1572 .chunk
1573 .add_constant(Constant::Int(elements.len() as i64));
1574 self.chunk.emit_u16(Op::Constant, count, self.line);
1575 self.chunk.emit(Op::GreaterEqual, self.line);
1576 let skip_len = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1577 self.chunk.emit(Op::Pop, self.line); let mut constraint_skips = Vec::new();
1581 let mut bindings = Vec::new();
1582 self.begin_scope();
1583 for (i, elem) in elements.iter().enumerate() {
1584 match &elem.node {
1585 Node::Identifier(name) if name != "_" => {
1586 bindings.push((i, name.clone()));
1587 }
1588 Node::Identifier(_) => {} _ => {
1591 self.chunk.emit(Op::Dup, self.line);
1592 let idx_const =
1593 self.chunk.add_constant(Constant::Int(i as i64));
1594 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1595 self.chunk.emit(Op::Subscript, self.line);
1596 self.compile_node(elem)?;
1597 self.chunk.emit(Op::Equal, self.line);
1598 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1599 self.chunk.emit(Op::Pop, self.line);
1600 constraint_skips.push(skip);
1601 }
1602 }
1603 }
1604
1605 for (i, name) in &bindings {
1607 self.chunk.emit(Op::Dup, self.line);
1608 let idx_const = self.chunk.add_constant(Constant::Int(*i as i64));
1609 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1610 self.chunk.emit(Op::Subscript, self.line);
1611 let name_idx =
1612 self.chunk.add_constant(Constant::String(name.clone()));
1613 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1614 }
1615
1616 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1618 self.end_scope();
1619 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1620
1621 let pre_scope_fail_target = self.chunk.code.len();
1622 self.chunk.emit(Op::Pop, self.line); let next_arm_jump = self.chunk.emit_jump(Op::Jump, self.line);
1624 let scoped_fail_target = self.chunk.code.len();
1625 self.chunk.emit(Op::PopScope, self.line);
1626 self.chunk.emit(Op::Pop, self.line); let next_arm_target = self.chunk.code.len();
1628 for skip in constraint_skips {
1629 self.chunk.patch_jump_to(skip, scoped_fail_target);
1630 }
1631 self.chunk.patch_jump_to(skip_len, pre_scope_fail_target);
1632 self.chunk.patch_jump_to(skip_type, pre_scope_fail_target);
1633 self.chunk.patch_jump_to(next_arm_jump, next_arm_target);
1634 }
1635 _ => {
1637 self.chunk.emit(Op::Dup, self.line);
1638 self.compile_node(&arm.pattern)?;
1639 self.chunk.emit(Op::Equal, self.line);
1640 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1641 self.chunk.emit(Op::Pop, self.line); self.begin_scope();
1643 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1645 self.end_scope();
1646 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1647 self.chunk.patch_jump(skip);
1648 self.chunk.emit(Op::Pop, self.line); }
1650 }
1651 }
1652 self.chunk.emit(Op::Pop, self.line);
1654 self.chunk.emit(Op::Nil, self.line);
1655 for j in end_jumps {
1656 self.chunk.patch_jump(j);
1657 }
1658 }
1659
1660 Node::RangeExpr {
1661 start,
1662 end,
1663 inclusive,
1664 } => {
1665 let name_idx = self
1667 .chunk
1668 .add_constant(Constant::String("__range__".to_string()));
1669 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
1670 self.compile_node(start)?;
1671 self.compile_node(end)?;
1672 if *inclusive {
1673 self.chunk.emit(Op::True, self.line);
1674 } else {
1675 self.chunk.emit(Op::False, self.line);
1676 }
1677 self.chunk.emit_u8(Op::Call, 3, self.line);
1678 }
1679
1680 Node::GuardStmt {
1681 condition,
1682 else_body,
1683 } => {
1684 self.compile_node(condition)?;
1687 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
1688 self.chunk.emit(Op::Pop, self.line); self.compile_scoped_block(else_body)?;
1691 if !else_body.is_empty() && Self::produces_value(&else_body.last().unwrap().node) {
1693 self.chunk.emit(Op::Pop, self.line);
1694 }
1695 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1696 self.chunk.patch_jump(skip_jump);
1697 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end_jump);
1699 self.chunk.emit(Op::Nil, self.line);
1700 }
1701
1702 Node::RequireStmt { condition, message } => {
1703 self.compile_node(condition)?;
1704 let ok_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
1705 self.chunk.emit(Op::Pop, self.line);
1706 if let Some(message) = message {
1707 self.compile_node(message)?;
1708 } else {
1709 let idx = self
1710 .chunk
1711 .add_constant(Constant::String("require condition failed".to_string()));
1712 self.chunk.emit_u16(Op::Constant, idx, self.line);
1713 }
1714 self.chunk.emit(Op::Throw, self.line);
1715 self.chunk.patch_jump(ok_jump);
1716 self.chunk.emit(Op::Pop, self.line);
1717 }
1718
1719 Node::Block(stmts) => {
1720 self.compile_scoped_block(stmts)?;
1721 }
1722
1723 Node::DeadlineBlock { duration, body } => {
1724 self.compile_node(duration)?;
1725 self.chunk.emit(Op::DeadlineSetup, self.line);
1726 self.compile_scoped_block(body)?;
1727 self.chunk.emit(Op::DeadlineEnd, self.line);
1728 }
1729
1730 Node::MutexBlock { body } => {
1731 self.begin_scope();
1733 for sn in body {
1734 self.compile_node(sn)?;
1735 if Self::produces_value(&sn.node) {
1736 self.chunk.emit(Op::Pop, self.line);
1737 }
1738 }
1739 self.chunk.emit(Op::Nil, self.line);
1740 self.end_scope();
1741 }
1742
1743 Node::YieldExpr { value } => {
1744 if let Some(val) = value {
1745 self.compile_node(val)?;
1746 } else {
1747 self.chunk.emit(Op::Nil, self.line);
1748 }
1749 self.chunk.emit(Op::Yield, self.line);
1750 }
1751
1752 Node::AskExpr { fields } => {
1753 for entry in fields {
1756 self.compile_node(&entry.key)?;
1757 self.compile_node(&entry.value)?;
1758 }
1759 self.chunk
1760 .emit_u16(Op::BuildDict, fields.len() as u16, self.line);
1761 }
1762
1763 Node::EnumConstruct {
1764 enum_name,
1765 variant,
1766 args,
1767 } => {
1768 for arg in args {
1770 self.compile_node(arg)?;
1771 }
1772 let enum_idx = self.chunk.add_constant(Constant::String(enum_name.clone()));
1773 let var_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1774 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
1776 let hi = (var_idx >> 8) as u8;
1777 let lo = var_idx as u8;
1778 self.chunk.code.push(hi);
1779 self.chunk.code.push(lo);
1780 self.chunk.lines.push(self.line);
1781 self.chunk.columns.push(self.column);
1782 self.chunk.lines.push(self.line);
1783 self.chunk.columns.push(self.column);
1784 let fc = args.len() as u16;
1785 let fhi = (fc >> 8) as u8;
1786 let flo = fc as u8;
1787 self.chunk.code.push(fhi);
1788 self.chunk.code.push(flo);
1789 self.chunk.lines.push(self.line);
1790 self.chunk.columns.push(self.column);
1791 self.chunk.lines.push(self.line);
1792 self.chunk.columns.push(self.column);
1793 }
1794
1795 Node::StructConstruct {
1796 struct_name,
1797 fields,
1798 } => {
1799 let struct_key = self
1801 .chunk
1802 .add_constant(Constant::String("__struct__".to_string()));
1803 let struct_val = self
1804 .chunk
1805 .add_constant(Constant::String(struct_name.clone()));
1806 self.chunk.emit_u16(Op::Constant, struct_key, self.line);
1807 self.chunk.emit_u16(Op::Constant, struct_val, self.line);
1808
1809 for entry in fields {
1810 self.compile_node(&entry.key)?;
1811 self.compile_node(&entry.value)?;
1812 }
1813 self.chunk
1814 .emit_u16(Op::BuildDict, (fields.len() + 1) as u16, self.line);
1815 }
1816
1817 Node::ImportDecl { path } => {
1818 let idx = self.chunk.add_constant(Constant::String(path.clone()));
1819 self.chunk.emit_u16(Op::Import, idx, self.line);
1820 }
1821
1822 Node::SelectiveImport { names, path } => {
1823 let path_idx = self.chunk.add_constant(Constant::String(path.clone()));
1824 let names_str = names.join(",");
1825 let names_idx = self.chunk.add_constant(Constant::String(names_str));
1826 self.chunk
1827 .emit_u16(Op::SelectiveImport, path_idx, self.line);
1828 let hi = (names_idx >> 8) as u8;
1829 let lo = names_idx as u8;
1830 self.chunk.code.push(hi);
1831 self.chunk.code.push(lo);
1832 self.chunk.lines.push(self.line);
1833 self.chunk.columns.push(self.column);
1834 self.chunk.lines.push(self.line);
1835 self.chunk.columns.push(self.column);
1836 }
1837
1838 Node::TryOperator { operand } => {
1839 self.compile_node(operand)?;
1840 self.chunk.emit(Op::TryUnwrap, self.line);
1841 }
1842
1843 Node::ImplBlock { type_name, methods } => {
1844 for method_sn in methods {
1847 if let Node::FnDecl {
1848 name, params, body, ..
1849 } = &method_sn.node
1850 {
1851 let key_idx = self.chunk.add_constant(Constant::String(name.clone()));
1853 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1854
1855 let mut fn_compiler = Compiler::new();
1857 fn_compiler.enum_names = self.enum_names.clone();
1858 fn_compiler.emit_default_preamble(params)?;
1859 fn_compiler.emit_type_checks(params);
1860 fn_compiler.compile_block(body)?;
1861 fn_compiler.chunk.emit(Op::Nil, self.line);
1862 fn_compiler.chunk.emit(Op::Return, self.line);
1863
1864 let func = CompiledFunction {
1865 name: format!("{}.{}", type_name, name),
1866 params: TypedParam::names(params),
1867 default_start: TypedParam::default_start(params),
1868 chunk: fn_compiler.chunk,
1869 is_generator: false,
1870 };
1871 let fn_idx = self.chunk.functions.len();
1872 self.chunk.functions.push(func);
1873 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1874 }
1875 }
1876 let method_count = methods
1877 .iter()
1878 .filter(|m| matches!(m.node, Node::FnDecl { .. }))
1879 .count();
1880 self.chunk
1881 .emit_u16(Op::BuildDict, method_count as u16, self.line);
1882 let impl_name = format!("__impl_{}", type_name);
1883 let name_idx = self.chunk.add_constant(Constant::String(impl_name));
1884 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1885 }
1886
1887 Node::StructDecl { name, .. } => {
1888 let mut fn_compiler = Compiler::new();
1890 fn_compiler.enum_names = self.enum_names.clone();
1891 let params = vec![TypedParam::untyped("__fields")];
1892 fn_compiler.emit_default_preamble(¶ms)?;
1893
1894 let make_idx = fn_compiler
1896 .chunk
1897 .add_constant(Constant::String("__make_struct".into()));
1898 fn_compiler
1899 .chunk
1900 .emit_u16(Op::Constant, make_idx, self.line);
1901 let sname_idx = fn_compiler
1902 .chunk
1903 .add_constant(Constant::String(name.clone()));
1904 fn_compiler
1905 .chunk
1906 .emit_u16(Op::Constant, sname_idx, self.line);
1907 let fields_idx = fn_compiler
1908 .chunk
1909 .add_constant(Constant::String("__fields".into()));
1910 fn_compiler
1911 .chunk
1912 .emit_u16(Op::GetVar, fields_idx, self.line);
1913 fn_compiler.chunk.emit_u8(Op::Call, 2, self.line);
1914 fn_compiler.chunk.emit(Op::Return, self.line);
1915
1916 let func = CompiledFunction {
1917 name: name.clone(),
1918 params: TypedParam::names(¶ms),
1919 default_start: None,
1920 chunk: fn_compiler.chunk,
1921 is_generator: false,
1922 };
1923 let fn_idx = self.chunk.functions.len();
1924 self.chunk.functions.push(func);
1925 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1926 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1927 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1928 }
1929
1930 Node::Pipeline { .. }
1932 | Node::OverrideDecl { .. }
1933 | Node::TypeDecl { .. }
1934 | Node::EnumDecl { .. }
1935 | Node::InterfaceDecl { .. } => {
1936 self.chunk.emit(Op::Nil, self.line);
1937 }
1938
1939 Node::TryCatch {
1940 body,
1941 error_var,
1942 error_type,
1943 catch_body,
1944 finally_body,
1945 } => {
1946 let type_name = error_type.as_ref().and_then(|te| {
1948 if let harn_parser::TypeExpr::Named(name) = te {
1949 Some(name.clone())
1950 } else {
1951 None
1952 }
1953 });
1954
1955 let type_name_idx = if let Some(ref tn) = type_name {
1956 self.chunk.add_constant(Constant::String(tn.clone()))
1957 } else {
1958 self.chunk.add_constant(Constant::String(String::new()))
1959 };
1960
1961 let has_catch = !catch_body.is_empty() || error_var.is_some();
1962 let has_finally = finally_body.is_some();
1963
1964 if has_catch && has_finally {
1965 let finally_body = finally_body.as_ref().unwrap();
1967
1968 self.finally_bodies.push(finally_body.clone());
1970
1971 self.handler_depth += 1;
1973 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1974 self.emit_type_name_extra(type_name_idx);
1975
1976 self.compile_try_body(body)?;
1978
1979 self.handler_depth -= 1;
1981 self.chunk.emit(Op::PopHandler, self.line);
1982 self.compile_finally_inline(finally_body)?;
1983 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1984
1985 self.chunk.patch_jump(catch_jump);
1987 self.begin_scope();
1988 self.compile_catch_binding(error_var)?;
1989
1990 self.handler_depth += 1;
1992 let rethrow_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1993 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1994 self.emit_type_name_extra(empty_type);
1995
1996 self.compile_try_body(catch_body)?;
1998
1999 self.handler_depth -= 1;
2001 self.chunk.emit(Op::PopHandler, self.line);
2002 self.compile_finally_inline(finally_body)?;
2003 self.end_scope();
2004 let end_jump2 = self.chunk.emit_jump(Op::Jump, self.line);
2005
2006 self.chunk.patch_jump(rethrow_jump);
2008 self.compile_rethrow_with_finally(finally_body)?;
2009 self.end_scope();
2010
2011 self.chunk.patch_jump(end_jump);
2012 self.chunk.patch_jump(end_jump2);
2013
2014 self.finally_bodies.pop();
2015 } else if has_finally {
2016 let finally_body = finally_body.as_ref().unwrap();
2018
2019 self.finally_bodies.push(finally_body.clone());
2020
2021 self.handler_depth += 1;
2023 let error_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
2024 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
2025 self.emit_type_name_extra(empty_type);
2026
2027 self.compile_try_body(body)?;
2029
2030 self.handler_depth -= 1;
2032 self.chunk.emit(Op::PopHandler, self.line);
2033 self.compile_finally_inline(finally_body)?;
2034 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
2035
2036 self.chunk.patch_jump(error_jump);
2038 self.compile_rethrow_with_finally(finally_body)?;
2039
2040 self.chunk.patch_jump(end_jump);
2041
2042 self.finally_bodies.pop();
2043 } else {
2044 self.handler_depth += 1;
2048 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
2049 self.emit_type_name_extra(type_name_idx);
2050
2051 self.compile_try_body(body)?;
2053
2054 self.handler_depth -= 1;
2056 self.chunk.emit(Op::PopHandler, self.line);
2057 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
2058
2059 self.chunk.patch_jump(catch_jump);
2061 self.begin_scope();
2062 self.compile_catch_binding(error_var)?;
2063
2064 self.compile_try_body(catch_body)?;
2066 self.end_scope();
2067
2068 self.chunk.patch_jump(end_jump);
2070 }
2071 }
2072
2073 Node::TryExpr { body } => {
2074 self.handler_depth += 1;
2078 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
2079 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
2080 self.emit_type_name_extra(empty_type);
2081
2082 self.compile_try_body(body)?;
2084
2085 self.handler_depth -= 1;
2087 self.chunk.emit(Op::PopHandler, self.line);
2088
2089 let ok_idx = self.chunk.add_constant(Constant::String("Ok".to_string()));
2091 self.chunk.emit_u16(Op::Constant, ok_idx, self.line);
2092 self.chunk.emit(Op::Swap, self.line);
2093 self.chunk.emit_u8(Op::Call, 1, self.line);
2094
2095 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
2097
2098 self.chunk.patch_jump(catch_jump);
2100
2101 let err_idx = self.chunk.add_constant(Constant::String("Err".to_string()));
2103 self.chunk.emit_u16(Op::Constant, err_idx, self.line);
2104 self.chunk.emit(Op::Swap, self.line);
2105 self.chunk.emit_u8(Op::Call, 1, self.line);
2106
2107 self.chunk.patch_jump(end_jump);
2109 }
2110
2111 Node::Retry { count, body } => {
2112 self.compile_node(count)?;
2114 let counter_name = "__retry_counter__";
2115 let counter_idx = self
2116 .chunk
2117 .add_constant(Constant::String(counter_name.to_string()));
2118 self.chunk.emit_u16(Op::DefVar, counter_idx, self.line);
2119
2120 self.chunk.emit(Op::Nil, self.line);
2122 let err_name = "__retry_last_error__";
2123 let err_idx = self
2124 .chunk
2125 .add_constant(Constant::String(err_name.to_string()));
2126 self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
2127
2128 let loop_start = self.chunk.current_offset();
2130
2131 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
2133 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
2135 let hi = (empty_type >> 8) as u8;
2136 let lo = empty_type as u8;
2137 self.chunk.code.push(hi);
2138 self.chunk.code.push(lo);
2139 self.chunk.lines.push(self.line);
2140 self.chunk.columns.push(self.column);
2141 self.chunk.lines.push(self.line);
2142 self.chunk.columns.push(self.column);
2143
2144 self.compile_block(body)?;
2146
2147 self.chunk.emit(Op::PopHandler, self.line);
2149 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
2150
2151 self.chunk.patch_jump(catch_jump);
2153 self.chunk.emit(Op::Dup, self.line);
2155 self.chunk.emit_u16(Op::SetVar, err_idx, self.line);
2156 self.chunk.emit(Op::Pop, self.line);
2158
2159 self.chunk.emit_u16(Op::GetVar, counter_idx, self.line);
2161 let one_idx = self.chunk.add_constant(Constant::Int(1));
2162 self.chunk.emit_u16(Op::Constant, one_idx, self.line);
2163 self.chunk.emit(Op::Sub, self.line);
2164 self.chunk.emit(Op::Dup, self.line);
2165 self.chunk.emit_u16(Op::SetVar, counter_idx, self.line);
2166
2167 let zero_idx = self.chunk.add_constant(Constant::Int(0));
2169 self.chunk.emit_u16(Op::Constant, zero_idx, self.line);
2170 self.chunk.emit(Op::Greater, self.line);
2171 let retry_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2172 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
2174
2175 self.chunk.patch_jump(retry_jump);
2177 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::GetVar, err_idx, self.line);
2179 self.chunk.emit(Op::Throw, self.line);
2180
2181 self.chunk.patch_jump(end_jump);
2182 self.chunk.emit(Op::Nil, self.line);
2184 }
2185
2186 Node::Parallel {
2187 count,
2188 variable,
2189 body,
2190 } => {
2191 self.compile_node(count)?;
2192 let mut fn_compiler = Compiler::new();
2193 fn_compiler.enum_names = self.enum_names.clone();
2194 fn_compiler.compile_block(body)?;
2195 fn_compiler.chunk.emit(Op::Return, self.line);
2196 let params = vec![variable.clone().unwrap_or_else(|| "__i__".to_string())];
2197 let func = CompiledFunction {
2198 name: "<parallel>".to_string(),
2199 params,
2200 default_start: None,
2201 chunk: fn_compiler.chunk,
2202 is_generator: false,
2203 };
2204 let fn_idx = self.chunk.functions.len();
2205 self.chunk.functions.push(func);
2206 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2207 self.chunk.emit(Op::Parallel, self.line);
2208 }
2209
2210 Node::ParallelMap {
2211 list,
2212 variable,
2213 body,
2214 } => {
2215 self.compile_node(list)?;
2216 let mut fn_compiler = Compiler::new();
2217 fn_compiler.enum_names = self.enum_names.clone();
2218 fn_compiler.compile_block(body)?;
2219 fn_compiler.chunk.emit(Op::Return, self.line);
2220 let func = CompiledFunction {
2221 name: "<parallel_map>".to_string(),
2222 params: vec![variable.clone()],
2223 default_start: None,
2224 chunk: fn_compiler.chunk,
2225 is_generator: false,
2226 };
2227 let fn_idx = self.chunk.functions.len();
2228 self.chunk.functions.push(func);
2229 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2230 self.chunk.emit(Op::ParallelMap, self.line);
2231 }
2232
2233 Node::ParallelSettle {
2234 list,
2235 variable,
2236 body,
2237 } => {
2238 self.compile_node(list)?;
2239 let mut fn_compiler = Compiler::new();
2240 fn_compiler.enum_names = self.enum_names.clone();
2241 fn_compiler.compile_block(body)?;
2242 fn_compiler.chunk.emit(Op::Return, self.line);
2243 let func = CompiledFunction {
2244 name: "<parallel_settle>".to_string(),
2245 params: vec![variable.clone()],
2246 default_start: None,
2247 chunk: fn_compiler.chunk,
2248 is_generator: false,
2249 };
2250 let fn_idx = self.chunk.functions.len();
2251 self.chunk.functions.push(func);
2252 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2253 self.chunk.emit(Op::ParallelSettle, self.line);
2254 }
2255
2256 Node::SpawnExpr { body } => {
2257 let mut fn_compiler = Compiler::new();
2258 fn_compiler.enum_names = self.enum_names.clone();
2259 fn_compiler.compile_block(body)?;
2260 fn_compiler.chunk.emit(Op::Return, self.line);
2261 let func = CompiledFunction {
2262 name: "<spawn>".to_string(),
2263 params: vec![],
2264 default_start: None,
2265 chunk: fn_compiler.chunk,
2266 is_generator: false,
2267 };
2268 let fn_idx = self.chunk.functions.len();
2269 self.chunk.functions.push(func);
2270 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2271 self.chunk.emit(Op::Spawn, self.line);
2272 }
2273 Node::SelectExpr {
2274 cases,
2275 timeout,
2276 default_body,
2277 } => {
2278 let builtin_name = if timeout.is_some() {
2285 "__select_timeout"
2286 } else if default_body.is_some() {
2287 "__select_try"
2288 } else {
2289 "__select_list"
2290 };
2291
2292 let name_idx = self
2294 .chunk
2295 .add_constant(Constant::String(builtin_name.into()));
2296 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
2297
2298 for case in cases {
2300 self.compile_node(&case.channel)?;
2301 }
2302 self.chunk
2303 .emit_u16(Op::BuildList, cases.len() as u16, self.line);
2304
2305 if let Some((duration_expr, _)) = timeout {
2307 self.compile_node(duration_expr)?;
2308 self.chunk.emit_u8(Op::Call, 2, self.line);
2309 } else {
2310 self.chunk.emit_u8(Op::Call, 1, self.line);
2311 }
2312
2313 self.temp_counter += 1;
2315 let result_name = format!("__sel_result_{}__", self.temp_counter);
2316 let result_idx = self
2317 .chunk
2318 .add_constant(Constant::String(result_name.clone()));
2319 self.chunk.emit_u16(Op::DefVar, result_idx, self.line);
2320
2321 let mut end_jumps = Vec::new();
2323
2324 for (i, case) in cases.iter().enumerate() {
2325 let get_r = self
2326 .chunk
2327 .add_constant(Constant::String(result_name.clone()));
2328 self.chunk.emit_u16(Op::GetVar, get_r, self.line);
2329 let idx_prop = self.chunk.add_constant(Constant::String("index".into()));
2330 self.chunk.emit_u16(Op::GetProperty, idx_prop, self.line);
2331 let case_i = self.chunk.add_constant(Constant::Int(i as i64));
2332 self.chunk.emit_u16(Op::Constant, case_i, self.line);
2333 self.chunk.emit(Op::Equal, self.line);
2334 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2335 self.chunk.emit(Op::Pop, self.line);
2336 self.begin_scope();
2337
2338 let get_r2 = self
2340 .chunk
2341 .add_constant(Constant::String(result_name.clone()));
2342 self.chunk.emit_u16(Op::GetVar, get_r2, self.line);
2343 let val_prop = self.chunk.add_constant(Constant::String("value".into()));
2344 self.chunk.emit_u16(Op::GetProperty, val_prop, self.line);
2345 let var_idx = self
2346 .chunk
2347 .add_constant(Constant::String(case.variable.clone()));
2348 self.chunk.emit_u16(Op::DefLet, var_idx, self.line);
2349
2350 self.compile_try_body(&case.body)?;
2351 self.end_scope();
2352 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
2353 self.chunk.patch_jump(skip);
2354 self.chunk.emit(Op::Pop, self.line);
2355 }
2356
2357 if let Some((_, ref timeout_body)) = timeout {
2359 self.compile_try_body(timeout_body)?;
2360 } else if let Some(ref def_body) = default_body {
2361 self.compile_try_body(def_body)?;
2362 } else {
2363 self.chunk.emit(Op::Nil, self.line);
2364 }
2365
2366 for ej in end_jumps {
2367 self.chunk.patch_jump(ej);
2368 }
2369 }
2370 Node::Spread(_) => {
2371 return Err(CompileError {
2372 message: "spread (...) can only be used inside list literals, dict literals, or function call arguments".into(),
2373 line: self.line,
2374 });
2375 }
2376 }
2377 Ok(())
2378 }
2379
2380 fn compile_destructuring(
2384 &mut self,
2385 pattern: &BindingPattern,
2386 is_mutable: bool,
2387 ) -> Result<(), CompileError> {
2388 let def_op = if is_mutable { Op::DefVar } else { Op::DefLet };
2389 match pattern {
2390 BindingPattern::Identifier(name) => {
2391 let idx = self.chunk.add_constant(Constant::String(name.clone()));
2393 self.chunk.emit_u16(def_op, idx, self.line);
2394 }
2395 BindingPattern::Dict(fields) => {
2396 self.chunk.emit(Op::Dup, self.line);
2399 let assert_idx = self
2400 .chunk
2401 .add_constant(Constant::String("__assert_dict".into()));
2402 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
2403 self.chunk.emit(Op::Swap, self.line);
2404 self.chunk.emit_u8(Op::Call, 1, self.line);
2405 self.chunk.emit(Op::Pop, self.line); let non_rest: Vec<_> = fields.iter().filter(|f| !f.is_rest).collect();
2410 let rest_field = fields.iter().find(|f| f.is_rest);
2411
2412 for field in &non_rest {
2413 self.chunk.emit(Op::Dup, self.line);
2414 let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
2415 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
2416 self.chunk.emit(Op::Subscript, self.line);
2417 let binding_name = field.alias.as_deref().unwrap_or(&field.key);
2418 let name_idx = self
2419 .chunk
2420 .add_constant(Constant::String(binding_name.to_string()));
2421 self.chunk.emit_u16(def_op, name_idx, self.line);
2422 }
2423
2424 if let Some(rest) = rest_field {
2425 let fn_idx = self
2428 .chunk
2429 .add_constant(Constant::String("__dict_rest".into()));
2430 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
2431 self.chunk.emit(Op::Swap, self.line);
2433 for field in &non_rest {
2435 let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
2436 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
2437 }
2438 self.chunk
2439 .emit_u16(Op::BuildList, non_rest.len() as u16, self.line);
2440 self.chunk.emit_u8(Op::Call, 2, self.line);
2442 let rest_name = &rest.key;
2443 let rest_idx = self.chunk.add_constant(Constant::String(rest_name.clone()));
2444 self.chunk.emit_u16(def_op, rest_idx, self.line);
2445 } else {
2446 self.chunk.emit(Op::Pop, self.line);
2448 }
2449 }
2450 BindingPattern::List(elements) => {
2451 self.chunk.emit(Op::Dup, self.line);
2454 let assert_idx = self
2455 .chunk
2456 .add_constant(Constant::String("__assert_list".into()));
2457 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
2458 self.chunk.emit(Op::Swap, self.line);
2459 self.chunk.emit_u8(Op::Call, 1, self.line);
2460 self.chunk.emit(Op::Pop, self.line); let non_rest: Vec<_> = elements.iter().filter(|e| !e.is_rest).collect();
2463 let rest_elem = elements.iter().find(|e| e.is_rest);
2464
2465 for (i, elem) in non_rest.iter().enumerate() {
2466 self.chunk.emit(Op::Dup, self.line);
2467 let idx_const = self.chunk.add_constant(Constant::Int(i as i64));
2468 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
2469 self.chunk.emit(Op::Subscript, self.line);
2470 let name_idx = self.chunk.add_constant(Constant::String(elem.name.clone()));
2471 self.chunk.emit_u16(def_op, name_idx, self.line);
2472 }
2473
2474 if let Some(rest) = rest_elem {
2475 let start_idx = self
2479 .chunk
2480 .add_constant(Constant::Int(non_rest.len() as i64));
2481 self.chunk.emit_u16(Op::Constant, start_idx, self.line);
2482 self.chunk.emit(Op::Nil, self.line); self.chunk.emit(Op::Slice, self.line);
2484 let rest_name_idx =
2485 self.chunk.add_constant(Constant::String(rest.name.clone()));
2486 self.chunk.emit_u16(def_op, rest_name_idx, self.line);
2487 } else {
2488 self.chunk.emit(Op::Pop, self.line);
2490 }
2491 }
2492 }
2493 Ok(())
2494 }
2495
2496 fn produces_value(node: &Node) -> bool {
2498 match node {
2499 Node::LetBinding { .. }
2501 | Node::VarBinding { .. }
2502 | Node::Assignment { .. }
2503 | Node::ReturnStmt { .. }
2504 | Node::FnDecl { .. }
2505 | Node::ImplBlock { .. }
2506 | Node::StructDecl { .. }
2507 | Node::EnumDecl { .. }
2508 | Node::InterfaceDecl { .. }
2509 | Node::TypeDecl { .. }
2510 | Node::ThrowStmt { .. }
2511 | Node::BreakStmt
2512 | Node::ContinueStmt
2513 | Node::RequireStmt { .. } => false,
2514 Node::TryCatch { .. }
2516 | Node::TryExpr { .. }
2517 | Node::Retry { .. }
2518 | Node::GuardStmt { .. }
2519 | Node::DeadlineBlock { .. }
2520 | Node::MutexBlock { .. }
2521 | Node::Spread(_) => true,
2522 _ => true,
2524 }
2525 }
2526}
2527
2528impl Compiler {
2529 pub fn compile_fn_body(
2542 &mut self,
2543 params: &[TypedParam],
2544 body: &[SNode],
2545 source_file: Option<String>,
2546 ) -> Result<CompiledFunction, CompileError> {
2547 let mut fn_compiler = Compiler::new();
2548 fn_compiler.enum_names = self.enum_names.clone();
2549 fn_compiler.emit_default_preamble(params)?;
2550 fn_compiler.emit_type_checks(params);
2551 let is_gen = body_contains_yield(body);
2552 fn_compiler.compile_block(body)?;
2553 fn_compiler.chunk.emit(Op::Nil, 0);
2554 fn_compiler.chunk.emit(Op::Return, 0);
2555 fn_compiler.chunk.source_file = source_file;
2556 Ok(CompiledFunction {
2557 name: String::new(),
2558 params: TypedParam::names(params),
2559 default_start: TypedParam::default_start(params),
2560 chunk: fn_compiler.chunk,
2561 is_generator: is_gen,
2562 })
2563 }
2564
2565 fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
2567 self.begin_scope();
2568 if body.is_empty() {
2569 self.chunk.emit(Op::Nil, self.line);
2570 } else {
2571 self.compile_block(body)?;
2572 if !Self::produces_value(&body.last().unwrap().node) {
2573 self.chunk.emit(Op::Nil, self.line);
2574 }
2575 }
2576 self.end_scope();
2577 Ok(())
2578 }
2579
2580 fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
2582 match op {
2583 "+" => self.chunk.emit(Op::Add, self.line),
2584 "-" => self.chunk.emit(Op::Sub, self.line),
2585 "*" => self.chunk.emit(Op::Mul, self.line),
2586 "/" => self.chunk.emit(Op::Div, self.line),
2587 "%" => self.chunk.emit(Op::Mod, self.line),
2588 _ => {
2589 return Err(CompileError {
2590 message: format!("Unknown compound operator: {op}"),
2591 line: self.line,
2592 })
2593 }
2594 }
2595 Ok(())
2596 }
2597
2598 fn root_var_name(&self, node: &SNode) -> Option<String> {
2600 match &node.node {
2601 Node::Identifier(name) => Some(name.clone()),
2602 Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
2603 self.root_var_name(object)
2604 }
2605 Node::SubscriptAccess { object, .. } => self.root_var_name(object),
2606 _ => None,
2607 }
2608 }
2609
2610 fn compile_top_level_declarations(&mut self, program: &[SNode]) -> Result<(), CompileError> {
2611 for sn in program {
2612 if matches!(
2613 &sn.node,
2614 Node::FnDecl { .. }
2615 | Node::ImplBlock { .. }
2616 | Node::StructDecl { .. }
2617 | Node::EnumDecl { .. }
2618 | Node::InterfaceDecl { .. }
2619 | Node::TypeDecl { .. }
2620 ) {
2621 self.compile_node(sn)?;
2622 }
2623 }
2624 Ok(())
2625 }
2626}
2627
2628impl Compiler {
2629 fn collect_enum_names(nodes: &[SNode], names: &mut std::collections::HashSet<String>) {
2631 for sn in nodes {
2632 match &sn.node {
2633 Node::EnumDecl { name, .. } => {
2634 names.insert(name.clone());
2635 }
2636 Node::Pipeline { body, .. } => {
2637 Self::collect_enum_names(body, names);
2638 }
2639 Node::FnDecl { body, .. } => {
2640 Self::collect_enum_names(body, names);
2641 }
2642 Node::Block(stmts) => {
2643 Self::collect_enum_names(stmts, names);
2644 }
2645 _ => {}
2646 }
2647 }
2648 }
2649
2650 fn collect_interface_methods(
2651 nodes: &[SNode],
2652 interfaces: &mut std::collections::HashMap<String, Vec<String>>,
2653 ) {
2654 for sn in nodes {
2655 match &sn.node {
2656 Node::InterfaceDecl { name, methods, .. } => {
2657 let method_names: Vec<String> =
2658 methods.iter().map(|m| m.name.clone()).collect();
2659 interfaces.insert(name.clone(), method_names);
2660 }
2661 Node::Pipeline { body, .. } | Node::FnDecl { body, .. } => {
2662 Self::collect_interface_methods(body, interfaces);
2663 }
2664 Node::Block(stmts) => {
2665 Self::collect_interface_methods(stmts, interfaces);
2666 }
2667 _ => {}
2668 }
2669 }
2670 }
2671}
2672
2673impl Default for Compiler {
2674 fn default() -> Self {
2675 Self::new()
2676 }
2677}
2678
2679fn body_contains_yield(nodes: &[SNode]) -> bool {
2681 nodes.iter().any(|sn| node_contains_yield(&sn.node))
2682}
2683
2684fn node_contains_yield(node: &Node) -> bool {
2685 match node {
2686 Node::YieldExpr { .. } => true,
2687 Node::FnDecl { .. } | Node::Closure { .. } => false,
2690 Node::Block(stmts) => body_contains_yield(stmts),
2691 Node::IfElse {
2692 condition,
2693 then_body,
2694 else_body,
2695 } => {
2696 node_contains_yield(&condition.node)
2697 || body_contains_yield(then_body)
2698 || else_body.as_ref().is_some_and(|b| body_contains_yield(b))
2699 }
2700 Node::WhileLoop { condition, body } => {
2701 node_contains_yield(&condition.node) || body_contains_yield(body)
2702 }
2703 Node::ForIn { iterable, body, .. } => {
2704 node_contains_yield(&iterable.node) || body_contains_yield(body)
2705 }
2706 Node::TryCatch {
2707 body, catch_body, ..
2708 } => body_contains_yield(body) || body_contains_yield(catch_body),
2709 Node::TryExpr { body } => body_contains_yield(body),
2710 _ => false,
2711 }
2712}
2713
2714fn contains_pipe_placeholder(node: &SNode) -> bool {
2716 match &node.node {
2717 Node::Identifier(name) if name == "_" => true,
2718 Node::FunctionCall { args, .. } => args.iter().any(contains_pipe_placeholder),
2719 Node::MethodCall { object, args, .. } => {
2720 contains_pipe_placeholder(object) || args.iter().any(contains_pipe_placeholder)
2721 }
2722 Node::BinaryOp { left, right, .. } => {
2723 contains_pipe_placeholder(left) || contains_pipe_placeholder(right)
2724 }
2725 Node::UnaryOp { operand, .. } => contains_pipe_placeholder(operand),
2726 Node::ListLiteral(items) => items.iter().any(contains_pipe_placeholder),
2727 Node::PropertyAccess { object, .. } => contains_pipe_placeholder(object),
2728 Node::SubscriptAccess { object, index } => {
2729 contains_pipe_placeholder(object) || contains_pipe_placeholder(index)
2730 }
2731 _ => false,
2732 }
2733}
2734
2735fn replace_pipe_placeholder(node: &SNode) -> SNode {
2737 let new_node = match &node.node {
2738 Node::Identifier(name) if name == "_" => Node::Identifier("__pipe".into()),
2739 Node::FunctionCall { name, args } => Node::FunctionCall {
2740 name: name.clone(),
2741 args: args.iter().map(replace_pipe_placeholder).collect(),
2742 },
2743 Node::MethodCall {
2744 object,
2745 method,
2746 args,
2747 } => Node::MethodCall {
2748 object: Box::new(replace_pipe_placeholder(object)),
2749 method: method.clone(),
2750 args: args.iter().map(replace_pipe_placeholder).collect(),
2751 },
2752 Node::BinaryOp { op, left, right } => Node::BinaryOp {
2753 op: op.clone(),
2754 left: Box::new(replace_pipe_placeholder(left)),
2755 right: Box::new(replace_pipe_placeholder(right)),
2756 },
2757 Node::UnaryOp { op, operand } => Node::UnaryOp {
2758 op: op.clone(),
2759 operand: Box::new(replace_pipe_placeholder(operand)),
2760 },
2761 Node::ListLiteral(items) => {
2762 Node::ListLiteral(items.iter().map(replace_pipe_placeholder).collect())
2763 }
2764 Node::PropertyAccess { object, property } => Node::PropertyAccess {
2765 object: Box::new(replace_pipe_placeholder(object)),
2766 property: property.clone(),
2767 },
2768 Node::SubscriptAccess { object, index } => Node::SubscriptAccess {
2769 object: Box::new(replace_pipe_placeholder(object)),
2770 index: Box::new(replace_pipe_placeholder(index)),
2771 },
2772 _ => return node.clone(),
2773 };
2774 SNode::new(new_node, node.span)
2775}
2776
2777#[cfg(test)]
2778mod tests {
2779 use super::*;
2780 use harn_lexer::Lexer;
2781 use harn_parser::Parser;
2782
2783 fn compile_source(source: &str) -> Chunk {
2784 let mut lexer = Lexer::new(source);
2785 let tokens = lexer.tokenize().unwrap();
2786 let mut parser = Parser::new(tokens);
2787 let program = parser.parse().unwrap();
2788 Compiler::new().compile(&program).unwrap()
2789 }
2790
2791 #[test]
2792 fn test_compile_arithmetic() {
2793 let chunk = compile_source("pipeline test(task) { let x = 2 + 3 }");
2794 assert!(!chunk.code.is_empty());
2795 assert!(chunk.constants.contains(&Constant::Int(2)));
2797 assert!(chunk.constants.contains(&Constant::Int(3)));
2798 }
2799
2800 #[test]
2801 fn test_compile_function_call() {
2802 let chunk = compile_source("pipeline test(task) { log(42) }");
2803 let disasm = chunk.disassemble("test");
2804 assert!(disasm.contains("CALL"));
2805 }
2806
2807 #[test]
2808 fn test_compile_if_else() {
2809 let chunk =
2810 compile_source(r#"pipeline test(task) { if true { log("yes") } else { log("no") } }"#);
2811 let disasm = chunk.disassemble("test");
2812 assert!(disasm.contains("JUMP_IF_FALSE"));
2813 assert!(disasm.contains("JUMP"));
2814 }
2815
2816 #[test]
2817 fn test_compile_while() {
2818 let chunk = compile_source("pipeline test(task) { var i = 0\n while i < 5 { i = i + 1 } }");
2819 let disasm = chunk.disassemble("test");
2820 assert!(disasm.contains("JUMP_IF_FALSE"));
2821 assert!(disasm.contains("JUMP"));
2823 }
2824
2825 #[test]
2826 fn test_compile_closure() {
2827 let chunk = compile_source("pipeline test(task) { let f = { x -> x * 2 } }");
2828 assert!(!chunk.functions.is_empty());
2829 assert_eq!(chunk.functions[0].params, vec!["x"]);
2830 }
2831
2832 #[test]
2833 fn test_compile_list() {
2834 let chunk = compile_source("pipeline test(task) { let a = [1, 2, 3] }");
2835 let disasm = chunk.disassemble("test");
2836 assert!(disasm.contains("BUILD_LIST"));
2837 }
2838
2839 #[test]
2840 fn test_compile_dict() {
2841 let chunk = compile_source(r#"pipeline test(task) { let d = {name: "test"} }"#);
2842 let disasm = chunk.disassemble("test");
2843 assert!(disasm.contains("BUILD_DICT"));
2844 }
2845
2846 #[test]
2847 fn test_disassemble() {
2848 let chunk = compile_source("pipeline test(task) { log(2 + 3) }");
2849 let disasm = chunk.disassemble("test");
2850 assert!(disasm.contains("CONSTANT"));
2852 assert!(disasm.contains("ADD"));
2853 assert!(disasm.contains("CALL"));
2854 }
2855}