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) => {
1199 let mut lexer = harn_lexer::Lexer::new(expr_str);
1201 if let Ok(tokens) = lexer.tokenize() {
1202 let mut parser = harn_parser::Parser::new(tokens);
1203 if let Ok(snode) = parser.parse_single_expression() {
1204 self.compile_node(&snode)?;
1205 let to_str = self
1207 .chunk
1208 .add_constant(Constant::String("to_string".into()));
1209 self.chunk.emit_u16(Op::Constant, to_str, self.line);
1210 self.chunk.emit(Op::Swap, self.line);
1211 self.chunk.emit_u8(Op::Call, 1, self.line);
1212 part_count += 1;
1213 } else {
1214 let idx =
1216 self.chunk.add_constant(Constant::String(expr_str.clone()));
1217 self.chunk.emit_u16(Op::Constant, idx, self.line);
1218 part_count += 1;
1219 }
1220 }
1221 }
1222 }
1223 }
1224 if part_count > 1 {
1225 self.chunk.emit_u16(Op::Concat, part_count, self.line);
1226 }
1227 }
1228
1229 Node::FnDecl {
1230 name, params, body, ..
1231 } => {
1232 let mut fn_compiler = Compiler::new();
1234 fn_compiler.enum_names = self.enum_names.clone();
1235 fn_compiler.emit_default_preamble(params)?;
1236 fn_compiler.emit_type_checks(params);
1237 let is_gen = body_contains_yield(body);
1238 fn_compiler.compile_block(body)?;
1239 fn_compiler.chunk.emit(Op::Nil, self.line);
1240 fn_compiler.chunk.emit(Op::Return, self.line);
1241
1242 let func = CompiledFunction {
1243 name: name.clone(),
1244 params: TypedParam::names(params),
1245 default_start: TypedParam::default_start(params),
1246 chunk: fn_compiler.chunk,
1247 is_generator: is_gen,
1248 };
1249 let fn_idx = self.chunk.functions.len();
1250 self.chunk.functions.push(func);
1251
1252 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1253 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1254 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1255 }
1256
1257 Node::Closure { params, body, .. } => {
1258 let mut fn_compiler = Compiler::new();
1259 fn_compiler.enum_names = self.enum_names.clone();
1260 fn_compiler.emit_default_preamble(params)?;
1261 fn_compiler.emit_type_checks(params);
1262 let is_gen = body_contains_yield(body);
1263 fn_compiler.compile_block(body)?;
1264 fn_compiler.chunk.emit(Op::Return, self.line);
1266
1267 let func = CompiledFunction {
1268 name: "<closure>".to_string(),
1269 params: TypedParam::names(params),
1270 default_start: TypedParam::default_start(params),
1271 chunk: fn_compiler.chunk,
1272 is_generator: is_gen,
1273 };
1274 let fn_idx = self.chunk.functions.len();
1275 self.chunk.functions.push(func);
1276
1277 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1278 }
1279
1280 Node::ThrowStmt { value } => {
1281 self.compile_node(value)?;
1282 self.chunk.emit(Op::Throw, self.line);
1283 }
1284
1285 Node::MatchExpr { value, arms } => {
1286 self.compile_node(value)?;
1287 let mut end_jumps = Vec::new();
1288 for arm in arms {
1289 match &arm.pattern.node {
1290 Node::Identifier(name) if name == "_" => {
1292 self.begin_scope();
1293 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1295 self.end_scope();
1296 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1297 }
1298 Node::EnumConstruct {
1300 enum_name,
1301 variant,
1302 args: pat_args,
1303 } => {
1304 self.chunk.emit(Op::Dup, self.line);
1306 let en_idx =
1307 self.chunk.add_constant(Constant::String(enum_name.clone()));
1308 let vn_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1309 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1310 let hi = (vn_idx >> 8) as u8;
1311 let lo = vn_idx as u8;
1312 self.chunk.code.push(hi);
1313 self.chunk.code.push(lo);
1314 self.chunk.lines.push(self.line);
1315 self.chunk.columns.push(self.column);
1316 self.chunk.lines.push(self.line);
1317 self.chunk.columns.push(self.column);
1318 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1320 self.chunk.emit(Op::Pop, self.line); self.begin_scope();
1322
1323 for (i, pat_arg) in pat_args.iter().enumerate() {
1326 if let Node::Identifier(binding_name) = &pat_arg.node {
1327 self.chunk.emit(Op::Dup, self.line);
1329 let fields_idx = self
1330 .chunk
1331 .add_constant(Constant::String("fields".to_string()));
1332 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
1333 let idx_const =
1334 self.chunk.add_constant(Constant::Int(i as i64));
1335 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1336 self.chunk.emit(Op::Subscript, self.line);
1337 let name_idx = self
1338 .chunk
1339 .add_constant(Constant::String(binding_name.clone()));
1340 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1341 }
1342 }
1343
1344 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1346 self.end_scope();
1347 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1348 self.chunk.patch_jump(skip);
1349 self.chunk.emit(Op::Pop, self.line); }
1351 Node::PropertyAccess { object, property } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
1353 {
1354 let enum_name = if let Node::Identifier(n) = &object.node {
1355 n.clone()
1356 } else {
1357 unreachable!()
1358 };
1359 self.chunk.emit(Op::Dup, self.line);
1360 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
1361 let vn_idx =
1362 self.chunk.add_constant(Constant::String(property.clone()));
1363 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1364 let hi = (vn_idx >> 8) as u8;
1365 let lo = vn_idx as u8;
1366 self.chunk.code.push(hi);
1367 self.chunk.code.push(lo);
1368 self.chunk.lines.push(self.line);
1369 self.chunk.columns.push(self.column);
1370 self.chunk.lines.push(self.line);
1371 self.chunk.columns.push(self.column);
1372 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1373 self.chunk.emit(Op::Pop, self.line); self.begin_scope();
1375 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1377 self.end_scope();
1378 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1379 self.chunk.patch_jump(skip);
1380 self.chunk.emit(Op::Pop, self.line); }
1382 Node::MethodCall {
1385 object,
1386 method,
1387 args: pat_args,
1388 } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
1389 {
1390 let enum_name = if let Node::Identifier(n) = &object.node {
1391 n.clone()
1392 } else {
1393 unreachable!()
1394 };
1395 self.chunk.emit(Op::Dup, self.line);
1397 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
1398 let vn_idx = self.chunk.add_constant(Constant::String(method.clone()));
1399 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1400 let hi = (vn_idx >> 8) as u8;
1401 let lo = vn_idx as u8;
1402 self.chunk.code.push(hi);
1403 self.chunk.code.push(lo);
1404 self.chunk.lines.push(self.line);
1405 self.chunk.columns.push(self.column);
1406 self.chunk.lines.push(self.line);
1407 self.chunk.columns.push(self.column);
1408 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1409 self.chunk.emit(Op::Pop, self.line); self.begin_scope();
1411
1412 for (i, pat_arg) in pat_args.iter().enumerate() {
1414 if let Node::Identifier(binding_name) = &pat_arg.node {
1415 self.chunk.emit(Op::Dup, self.line);
1416 let fields_idx = self
1417 .chunk
1418 .add_constant(Constant::String("fields".to_string()));
1419 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
1420 let idx_const =
1421 self.chunk.add_constant(Constant::Int(i as i64));
1422 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1423 self.chunk.emit(Op::Subscript, self.line);
1424 let name_idx = self
1425 .chunk
1426 .add_constant(Constant::String(binding_name.clone()));
1427 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1428 }
1429 }
1430
1431 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1433 self.end_scope();
1434 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1435 self.chunk.patch_jump(skip);
1436 self.chunk.emit(Op::Pop, self.line); }
1438 Node::Identifier(name) => {
1440 self.begin_scope();
1441 self.chunk.emit(Op::Dup, self.line); let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1444 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1445 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1447 self.end_scope();
1448 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1449 }
1450 Node::DictLiteral(entries)
1452 if entries
1453 .iter()
1454 .all(|e| matches!(&e.key.node, Node::StringLiteral(_))) =>
1455 {
1456 self.chunk.emit(Op::Dup, self.line);
1458 let typeof_idx =
1459 self.chunk.add_constant(Constant::String("type_of".into()));
1460 self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
1461 self.chunk.emit(Op::Swap, self.line);
1462 self.chunk.emit_u8(Op::Call, 1, self.line);
1463 let dict_str = self.chunk.add_constant(Constant::String("dict".into()));
1464 self.chunk.emit_u16(Op::Constant, dict_str, self.line);
1465 self.chunk.emit(Op::Equal, self.line);
1466 let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1467 self.chunk.emit(Op::Pop, self.line); let mut constraint_skips = Vec::new();
1471 let mut bindings = Vec::new();
1472 self.begin_scope();
1473 for entry in entries {
1474 if let Node::StringLiteral(key) = &entry.key.node {
1475 match &entry.value.node {
1476 Node::StringLiteral(_)
1478 | Node::IntLiteral(_)
1479 | Node::FloatLiteral(_)
1480 | Node::BoolLiteral(_)
1481 | Node::NilLiteral => {
1482 self.chunk.emit(Op::Dup, self.line);
1483 let key_idx = self
1484 .chunk
1485 .add_constant(Constant::String(key.clone()));
1486 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1487 self.chunk.emit(Op::Subscript, self.line);
1488 self.compile_node(&entry.value)?;
1489 self.chunk.emit(Op::Equal, self.line);
1490 let skip =
1491 self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1492 self.chunk.emit(Op::Pop, self.line); constraint_skips.push(skip);
1494 }
1495 Node::Identifier(binding) => {
1497 bindings.push((key.clone(), binding.clone()));
1498 }
1499 _ => {
1500 self.chunk.emit(Op::Dup, self.line);
1502 let key_idx = self
1503 .chunk
1504 .add_constant(Constant::String(key.clone()));
1505 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1506 self.chunk.emit(Op::Subscript, self.line);
1507 self.compile_node(&entry.value)?;
1508 self.chunk.emit(Op::Equal, self.line);
1509 let skip =
1510 self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1511 self.chunk.emit(Op::Pop, self.line);
1512 constraint_skips.push(skip);
1513 }
1514 }
1515 }
1516 }
1517
1518 for (key, binding) in &bindings {
1520 self.chunk.emit(Op::Dup, self.line);
1521 let key_idx =
1522 self.chunk.add_constant(Constant::String(key.clone()));
1523 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1524 self.chunk.emit(Op::Subscript, self.line);
1525 let name_idx =
1526 self.chunk.add_constant(Constant::String(binding.clone()));
1527 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1528 }
1529
1530 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1532 self.end_scope();
1533 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1534
1535 let type_fail_target = self.chunk.code.len();
1536 self.chunk.emit(Op::Pop, self.line); let next_arm_jump = self.chunk.emit_jump(Op::Jump, self.line);
1538 let scoped_fail_target = self.chunk.code.len();
1539 self.chunk.emit(Op::PopScope, self.line);
1540 self.chunk.emit(Op::Pop, self.line); let next_arm_target = self.chunk.code.len();
1542
1543 for skip in constraint_skips {
1544 self.chunk.patch_jump_to(skip, scoped_fail_target);
1545 }
1546 self.chunk.patch_jump_to(skip_type, type_fail_target);
1547 self.chunk.patch_jump_to(next_arm_jump, next_arm_target);
1548 }
1549 Node::ListLiteral(elements) => {
1551 self.chunk.emit(Op::Dup, self.line);
1553 let typeof_idx =
1554 self.chunk.add_constant(Constant::String("type_of".into()));
1555 self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
1556 self.chunk.emit(Op::Swap, self.line);
1557 self.chunk.emit_u8(Op::Call, 1, self.line);
1558 let list_str = self.chunk.add_constant(Constant::String("list".into()));
1559 self.chunk.emit_u16(Op::Constant, list_str, self.line);
1560 self.chunk.emit(Op::Equal, self.line);
1561 let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1562 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Dup, self.line);
1566 let len_idx = self.chunk.add_constant(Constant::String("len".into()));
1567 self.chunk.emit_u16(Op::Constant, len_idx, self.line);
1568 self.chunk.emit(Op::Swap, self.line);
1569 self.chunk.emit_u8(Op::Call, 1, self.line);
1570 let count = self
1571 .chunk
1572 .add_constant(Constant::Int(elements.len() as i64));
1573 self.chunk.emit_u16(Op::Constant, count, self.line);
1574 self.chunk.emit(Op::GreaterEqual, self.line);
1575 let skip_len = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1576 self.chunk.emit(Op::Pop, self.line); let mut constraint_skips = Vec::new();
1580 let mut bindings = Vec::new();
1581 self.begin_scope();
1582 for (i, elem) in elements.iter().enumerate() {
1583 match &elem.node {
1584 Node::Identifier(name) if name != "_" => {
1585 bindings.push((i, name.clone()));
1586 }
1587 Node::Identifier(_) => {} _ => {
1590 self.chunk.emit(Op::Dup, self.line);
1591 let idx_const =
1592 self.chunk.add_constant(Constant::Int(i as i64));
1593 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1594 self.chunk.emit(Op::Subscript, self.line);
1595 self.compile_node(elem)?;
1596 self.chunk.emit(Op::Equal, self.line);
1597 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1598 self.chunk.emit(Op::Pop, self.line);
1599 constraint_skips.push(skip);
1600 }
1601 }
1602 }
1603
1604 for (i, name) in &bindings {
1606 self.chunk.emit(Op::Dup, self.line);
1607 let idx_const = self.chunk.add_constant(Constant::Int(*i as i64));
1608 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1609 self.chunk.emit(Op::Subscript, self.line);
1610 let name_idx =
1611 self.chunk.add_constant(Constant::String(name.clone()));
1612 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1613 }
1614
1615 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1617 self.end_scope();
1618 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1619
1620 let pre_scope_fail_target = self.chunk.code.len();
1621 self.chunk.emit(Op::Pop, self.line); let next_arm_jump = self.chunk.emit_jump(Op::Jump, self.line);
1623 let scoped_fail_target = self.chunk.code.len();
1624 self.chunk.emit(Op::PopScope, self.line);
1625 self.chunk.emit(Op::Pop, self.line); let next_arm_target = self.chunk.code.len();
1627 for skip in constraint_skips {
1628 self.chunk.patch_jump_to(skip, scoped_fail_target);
1629 }
1630 self.chunk.patch_jump_to(skip_len, pre_scope_fail_target);
1631 self.chunk.patch_jump_to(skip_type, pre_scope_fail_target);
1632 self.chunk.patch_jump_to(next_arm_jump, next_arm_target);
1633 }
1634 _ => {
1636 self.chunk.emit(Op::Dup, self.line);
1637 self.compile_node(&arm.pattern)?;
1638 self.chunk.emit(Op::Equal, self.line);
1639 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1640 self.chunk.emit(Op::Pop, self.line); self.begin_scope();
1642 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1644 self.end_scope();
1645 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1646 self.chunk.patch_jump(skip);
1647 self.chunk.emit(Op::Pop, self.line); }
1649 }
1650 }
1651 self.chunk.emit(Op::Pop, self.line);
1653 self.chunk.emit(Op::Nil, self.line);
1654 for j in end_jumps {
1655 self.chunk.patch_jump(j);
1656 }
1657 }
1658
1659 Node::RangeExpr {
1660 start,
1661 end,
1662 inclusive,
1663 } => {
1664 let name_idx = self
1666 .chunk
1667 .add_constant(Constant::String("__range__".to_string()));
1668 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
1669 self.compile_node(start)?;
1670 self.compile_node(end)?;
1671 if *inclusive {
1672 self.chunk.emit(Op::True, self.line);
1673 } else {
1674 self.chunk.emit(Op::False, self.line);
1675 }
1676 self.chunk.emit_u8(Op::Call, 3, self.line);
1677 }
1678
1679 Node::GuardStmt {
1680 condition,
1681 else_body,
1682 } => {
1683 self.compile_node(condition)?;
1686 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
1687 self.chunk.emit(Op::Pop, self.line); self.compile_scoped_block(else_body)?;
1690 if !else_body.is_empty() && Self::produces_value(&else_body.last().unwrap().node) {
1692 self.chunk.emit(Op::Pop, self.line);
1693 }
1694 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1695 self.chunk.patch_jump(skip_jump);
1696 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end_jump);
1698 self.chunk.emit(Op::Nil, self.line);
1699 }
1700
1701 Node::RequireStmt { condition, message } => {
1702 self.compile_node(condition)?;
1703 let ok_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
1704 self.chunk.emit(Op::Pop, self.line);
1705 if let Some(message) = message {
1706 self.compile_node(message)?;
1707 } else {
1708 let idx = self
1709 .chunk
1710 .add_constant(Constant::String("require condition failed".to_string()));
1711 self.chunk.emit_u16(Op::Constant, idx, self.line);
1712 }
1713 self.chunk.emit(Op::Throw, self.line);
1714 self.chunk.patch_jump(ok_jump);
1715 self.chunk.emit(Op::Pop, self.line);
1716 }
1717
1718 Node::Block(stmts) => {
1719 self.compile_scoped_block(stmts)?;
1720 }
1721
1722 Node::DeadlineBlock { duration, body } => {
1723 self.compile_node(duration)?;
1724 self.chunk.emit(Op::DeadlineSetup, self.line);
1725 self.compile_scoped_block(body)?;
1726 self.chunk.emit(Op::DeadlineEnd, self.line);
1727 }
1728
1729 Node::MutexBlock { body } => {
1730 self.begin_scope();
1732 for sn in body {
1733 self.compile_node(sn)?;
1734 if Self::produces_value(&sn.node) {
1735 self.chunk.emit(Op::Pop, self.line);
1736 }
1737 }
1738 self.chunk.emit(Op::Nil, self.line);
1739 self.end_scope();
1740 }
1741
1742 Node::YieldExpr { value } => {
1743 if let Some(val) = value {
1744 self.compile_node(val)?;
1745 } else {
1746 self.chunk.emit(Op::Nil, self.line);
1747 }
1748 self.chunk.emit(Op::Yield, self.line);
1749 }
1750
1751 Node::AskExpr { fields } => {
1752 for entry in fields {
1755 self.compile_node(&entry.key)?;
1756 self.compile_node(&entry.value)?;
1757 }
1758 self.chunk
1759 .emit_u16(Op::BuildDict, fields.len() as u16, self.line);
1760 }
1761
1762 Node::EnumConstruct {
1763 enum_name,
1764 variant,
1765 args,
1766 } => {
1767 for arg in args {
1769 self.compile_node(arg)?;
1770 }
1771 let enum_idx = self.chunk.add_constant(Constant::String(enum_name.clone()));
1772 let var_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1773 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
1775 let hi = (var_idx >> 8) as u8;
1776 let lo = var_idx as u8;
1777 self.chunk.code.push(hi);
1778 self.chunk.code.push(lo);
1779 self.chunk.lines.push(self.line);
1780 self.chunk.columns.push(self.column);
1781 self.chunk.lines.push(self.line);
1782 self.chunk.columns.push(self.column);
1783 let fc = args.len() as u16;
1784 let fhi = (fc >> 8) as u8;
1785 let flo = fc as u8;
1786 self.chunk.code.push(fhi);
1787 self.chunk.code.push(flo);
1788 self.chunk.lines.push(self.line);
1789 self.chunk.columns.push(self.column);
1790 self.chunk.lines.push(self.line);
1791 self.chunk.columns.push(self.column);
1792 }
1793
1794 Node::StructConstruct {
1795 struct_name,
1796 fields,
1797 } => {
1798 let struct_key = self
1800 .chunk
1801 .add_constant(Constant::String("__struct__".to_string()));
1802 let struct_val = self
1803 .chunk
1804 .add_constant(Constant::String(struct_name.clone()));
1805 self.chunk.emit_u16(Op::Constant, struct_key, self.line);
1806 self.chunk.emit_u16(Op::Constant, struct_val, self.line);
1807
1808 for entry in fields {
1809 self.compile_node(&entry.key)?;
1810 self.compile_node(&entry.value)?;
1811 }
1812 self.chunk
1813 .emit_u16(Op::BuildDict, (fields.len() + 1) as u16, self.line);
1814 }
1815
1816 Node::ImportDecl { path } => {
1817 let idx = self.chunk.add_constant(Constant::String(path.clone()));
1818 self.chunk.emit_u16(Op::Import, idx, self.line);
1819 }
1820
1821 Node::SelectiveImport { names, path } => {
1822 let path_idx = self.chunk.add_constant(Constant::String(path.clone()));
1823 let names_str = names.join(",");
1824 let names_idx = self.chunk.add_constant(Constant::String(names_str));
1825 self.chunk
1826 .emit_u16(Op::SelectiveImport, path_idx, self.line);
1827 let hi = (names_idx >> 8) as u8;
1828 let lo = names_idx as u8;
1829 self.chunk.code.push(hi);
1830 self.chunk.code.push(lo);
1831 self.chunk.lines.push(self.line);
1832 self.chunk.columns.push(self.column);
1833 self.chunk.lines.push(self.line);
1834 self.chunk.columns.push(self.column);
1835 }
1836
1837 Node::TryOperator { operand } => {
1838 self.compile_node(operand)?;
1839 self.chunk.emit(Op::TryUnwrap, self.line);
1840 }
1841
1842 Node::ImplBlock { type_name, methods } => {
1843 for method_sn in methods {
1846 if let Node::FnDecl {
1847 name, params, body, ..
1848 } = &method_sn.node
1849 {
1850 let key_idx = self.chunk.add_constant(Constant::String(name.clone()));
1852 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1853
1854 let mut fn_compiler = Compiler::new();
1856 fn_compiler.enum_names = self.enum_names.clone();
1857 fn_compiler.emit_default_preamble(params)?;
1858 fn_compiler.emit_type_checks(params);
1859 fn_compiler.compile_block(body)?;
1860 fn_compiler.chunk.emit(Op::Nil, self.line);
1861 fn_compiler.chunk.emit(Op::Return, self.line);
1862
1863 let func = CompiledFunction {
1864 name: format!("{}.{}", type_name, name),
1865 params: TypedParam::names(params),
1866 default_start: TypedParam::default_start(params),
1867 chunk: fn_compiler.chunk,
1868 is_generator: false,
1869 };
1870 let fn_idx = self.chunk.functions.len();
1871 self.chunk.functions.push(func);
1872 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1873 }
1874 }
1875 let method_count = methods
1876 .iter()
1877 .filter(|m| matches!(m.node, Node::FnDecl { .. }))
1878 .count();
1879 self.chunk
1880 .emit_u16(Op::BuildDict, method_count as u16, self.line);
1881 let impl_name = format!("__impl_{}", type_name);
1882 let name_idx = self.chunk.add_constant(Constant::String(impl_name));
1883 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1884 }
1885
1886 Node::StructDecl { name, .. } => {
1887 let mut fn_compiler = Compiler::new();
1889 fn_compiler.enum_names = self.enum_names.clone();
1890 let params = vec![TypedParam::untyped("__fields")];
1891 fn_compiler.emit_default_preamble(¶ms)?;
1892
1893 let make_idx = fn_compiler
1895 .chunk
1896 .add_constant(Constant::String("__make_struct".into()));
1897 fn_compiler
1898 .chunk
1899 .emit_u16(Op::Constant, make_idx, self.line);
1900 let sname_idx = fn_compiler
1901 .chunk
1902 .add_constant(Constant::String(name.clone()));
1903 fn_compiler
1904 .chunk
1905 .emit_u16(Op::Constant, sname_idx, self.line);
1906 let fields_idx = fn_compiler
1907 .chunk
1908 .add_constant(Constant::String("__fields".into()));
1909 fn_compiler
1910 .chunk
1911 .emit_u16(Op::GetVar, fields_idx, self.line);
1912 fn_compiler.chunk.emit_u8(Op::Call, 2, self.line);
1913 fn_compiler.chunk.emit(Op::Return, self.line);
1914
1915 let func = CompiledFunction {
1916 name: name.clone(),
1917 params: TypedParam::names(¶ms),
1918 default_start: None,
1919 chunk: fn_compiler.chunk,
1920 is_generator: false,
1921 };
1922 let fn_idx = self.chunk.functions.len();
1923 self.chunk.functions.push(func);
1924 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1925 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1926 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1927 }
1928
1929 Node::Pipeline { .. }
1931 | Node::OverrideDecl { .. }
1932 | Node::TypeDecl { .. }
1933 | Node::EnumDecl { .. }
1934 | Node::InterfaceDecl { .. } => {
1935 self.chunk.emit(Op::Nil, self.line);
1936 }
1937
1938 Node::TryCatch {
1939 body,
1940 error_var,
1941 error_type,
1942 catch_body,
1943 finally_body,
1944 } => {
1945 let type_name = error_type.as_ref().and_then(|te| {
1947 if let harn_parser::TypeExpr::Named(name) = te {
1948 Some(name.clone())
1949 } else {
1950 None
1951 }
1952 });
1953
1954 let type_name_idx = if let Some(ref tn) = type_name {
1955 self.chunk.add_constant(Constant::String(tn.clone()))
1956 } else {
1957 self.chunk.add_constant(Constant::String(String::new()))
1958 };
1959
1960 let has_catch = !catch_body.is_empty() || error_var.is_some();
1961 let has_finally = finally_body.is_some();
1962
1963 if has_catch && has_finally {
1964 let finally_body = finally_body.as_ref().unwrap();
1966
1967 self.finally_bodies.push(finally_body.clone());
1969
1970 self.handler_depth += 1;
1972 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1973 self.emit_type_name_extra(type_name_idx);
1974
1975 self.compile_try_body(body)?;
1977
1978 self.handler_depth -= 1;
1980 self.chunk.emit(Op::PopHandler, self.line);
1981 self.compile_finally_inline(finally_body)?;
1982 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1983
1984 self.chunk.patch_jump(catch_jump);
1986 self.begin_scope();
1987 self.compile_catch_binding(error_var)?;
1988
1989 self.handler_depth += 1;
1991 let rethrow_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1992 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1993 self.emit_type_name_extra(empty_type);
1994
1995 self.compile_try_body(catch_body)?;
1997
1998 self.handler_depth -= 1;
2000 self.chunk.emit(Op::PopHandler, self.line);
2001 self.compile_finally_inline(finally_body)?;
2002 self.end_scope();
2003 let end_jump2 = self.chunk.emit_jump(Op::Jump, self.line);
2004
2005 self.chunk.patch_jump(rethrow_jump);
2007 self.compile_rethrow_with_finally(finally_body)?;
2008 self.end_scope();
2009
2010 self.chunk.patch_jump(end_jump);
2011 self.chunk.patch_jump(end_jump2);
2012
2013 self.finally_bodies.pop();
2014 } else if has_finally {
2015 let finally_body = finally_body.as_ref().unwrap();
2017
2018 self.finally_bodies.push(finally_body.clone());
2019
2020 self.handler_depth += 1;
2022 let error_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
2023 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
2024 self.emit_type_name_extra(empty_type);
2025
2026 self.compile_try_body(body)?;
2028
2029 self.handler_depth -= 1;
2031 self.chunk.emit(Op::PopHandler, self.line);
2032 self.compile_finally_inline(finally_body)?;
2033 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
2034
2035 self.chunk.patch_jump(error_jump);
2037 self.compile_rethrow_with_finally(finally_body)?;
2038
2039 self.chunk.patch_jump(end_jump);
2040
2041 self.finally_bodies.pop();
2042 } else {
2043 self.handler_depth += 1;
2047 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
2048 self.emit_type_name_extra(type_name_idx);
2049
2050 self.compile_try_body(body)?;
2052
2053 self.handler_depth -= 1;
2055 self.chunk.emit(Op::PopHandler, self.line);
2056 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
2057
2058 self.chunk.patch_jump(catch_jump);
2060 self.begin_scope();
2061 self.compile_catch_binding(error_var)?;
2062
2063 self.compile_try_body(catch_body)?;
2065 self.end_scope();
2066
2067 self.chunk.patch_jump(end_jump);
2069 }
2070 }
2071
2072 Node::TryExpr { body } => {
2073 self.handler_depth += 1;
2077 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
2078 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
2079 self.emit_type_name_extra(empty_type);
2080
2081 self.compile_try_body(body)?;
2083
2084 self.handler_depth -= 1;
2086 self.chunk.emit(Op::PopHandler, self.line);
2087
2088 let ok_idx = self.chunk.add_constant(Constant::String("Ok".to_string()));
2090 self.chunk.emit_u16(Op::Constant, ok_idx, self.line);
2091 self.chunk.emit(Op::Swap, self.line);
2092 self.chunk.emit_u8(Op::Call, 1, self.line);
2093
2094 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
2096
2097 self.chunk.patch_jump(catch_jump);
2099
2100 let err_idx = self.chunk.add_constant(Constant::String("Err".to_string()));
2102 self.chunk.emit_u16(Op::Constant, err_idx, self.line);
2103 self.chunk.emit(Op::Swap, self.line);
2104 self.chunk.emit_u8(Op::Call, 1, self.line);
2105
2106 self.chunk.patch_jump(end_jump);
2108 }
2109
2110 Node::Retry { count, body } => {
2111 self.compile_node(count)?;
2113 let counter_name = "__retry_counter__";
2114 let counter_idx = self
2115 .chunk
2116 .add_constant(Constant::String(counter_name.to_string()));
2117 self.chunk.emit_u16(Op::DefVar, counter_idx, self.line);
2118
2119 self.chunk.emit(Op::Nil, self.line);
2121 let err_name = "__retry_last_error__";
2122 let err_idx = self
2123 .chunk
2124 .add_constant(Constant::String(err_name.to_string()));
2125 self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
2126
2127 let loop_start = self.chunk.current_offset();
2129
2130 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
2132 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
2134 let hi = (empty_type >> 8) as u8;
2135 let lo = empty_type as u8;
2136 self.chunk.code.push(hi);
2137 self.chunk.code.push(lo);
2138 self.chunk.lines.push(self.line);
2139 self.chunk.columns.push(self.column);
2140 self.chunk.lines.push(self.line);
2141 self.chunk.columns.push(self.column);
2142
2143 self.compile_block(body)?;
2145
2146 self.chunk.emit(Op::PopHandler, self.line);
2148 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
2149
2150 self.chunk.patch_jump(catch_jump);
2152 self.chunk.emit(Op::Dup, self.line);
2154 self.chunk.emit_u16(Op::SetVar, err_idx, self.line);
2155 self.chunk.emit(Op::Pop, self.line);
2157
2158 self.chunk.emit_u16(Op::GetVar, counter_idx, self.line);
2160 let one_idx = self.chunk.add_constant(Constant::Int(1));
2161 self.chunk.emit_u16(Op::Constant, one_idx, self.line);
2162 self.chunk.emit(Op::Sub, self.line);
2163 self.chunk.emit(Op::Dup, self.line);
2164 self.chunk.emit_u16(Op::SetVar, counter_idx, self.line);
2165
2166 let zero_idx = self.chunk.add_constant(Constant::Int(0));
2168 self.chunk.emit_u16(Op::Constant, zero_idx, self.line);
2169 self.chunk.emit(Op::Greater, self.line);
2170 let retry_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2171 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
2173
2174 self.chunk.patch_jump(retry_jump);
2176 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::GetVar, err_idx, self.line);
2178 self.chunk.emit(Op::Throw, self.line);
2179
2180 self.chunk.patch_jump(end_jump);
2181 self.chunk.emit(Op::Nil, self.line);
2183 }
2184
2185 Node::Parallel {
2186 count,
2187 variable,
2188 body,
2189 } => {
2190 self.compile_node(count)?;
2191 let mut fn_compiler = Compiler::new();
2192 fn_compiler.enum_names = self.enum_names.clone();
2193 fn_compiler.compile_block(body)?;
2194 fn_compiler.chunk.emit(Op::Return, self.line);
2195 let params = vec![variable.clone().unwrap_or_else(|| "__i__".to_string())];
2196 let func = CompiledFunction {
2197 name: "<parallel>".to_string(),
2198 params,
2199 default_start: None,
2200 chunk: fn_compiler.chunk,
2201 is_generator: false,
2202 };
2203 let fn_idx = self.chunk.functions.len();
2204 self.chunk.functions.push(func);
2205 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2206 self.chunk.emit(Op::Parallel, self.line);
2207 }
2208
2209 Node::ParallelMap {
2210 list,
2211 variable,
2212 body,
2213 } => {
2214 self.compile_node(list)?;
2215 let mut fn_compiler = Compiler::new();
2216 fn_compiler.enum_names = self.enum_names.clone();
2217 fn_compiler.compile_block(body)?;
2218 fn_compiler.chunk.emit(Op::Return, self.line);
2219 let func = CompiledFunction {
2220 name: "<parallel_map>".to_string(),
2221 params: vec![variable.clone()],
2222 default_start: None,
2223 chunk: fn_compiler.chunk,
2224 is_generator: false,
2225 };
2226 let fn_idx = self.chunk.functions.len();
2227 self.chunk.functions.push(func);
2228 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2229 self.chunk.emit(Op::ParallelMap, self.line);
2230 }
2231
2232 Node::ParallelSettle {
2233 list,
2234 variable,
2235 body,
2236 } => {
2237 self.compile_node(list)?;
2238 let mut fn_compiler = Compiler::new();
2239 fn_compiler.enum_names = self.enum_names.clone();
2240 fn_compiler.compile_block(body)?;
2241 fn_compiler.chunk.emit(Op::Return, self.line);
2242 let func = CompiledFunction {
2243 name: "<parallel_settle>".to_string(),
2244 params: vec![variable.clone()],
2245 default_start: None,
2246 chunk: fn_compiler.chunk,
2247 is_generator: false,
2248 };
2249 let fn_idx = self.chunk.functions.len();
2250 self.chunk.functions.push(func);
2251 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2252 self.chunk.emit(Op::ParallelSettle, self.line);
2253 }
2254
2255 Node::SpawnExpr { body } => {
2256 let mut fn_compiler = Compiler::new();
2257 fn_compiler.enum_names = self.enum_names.clone();
2258 fn_compiler.compile_block(body)?;
2259 fn_compiler.chunk.emit(Op::Return, self.line);
2260 let func = CompiledFunction {
2261 name: "<spawn>".to_string(),
2262 params: vec![],
2263 default_start: None,
2264 chunk: fn_compiler.chunk,
2265 is_generator: false,
2266 };
2267 let fn_idx = self.chunk.functions.len();
2268 self.chunk.functions.push(func);
2269 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2270 self.chunk.emit(Op::Spawn, self.line);
2271 }
2272 Node::SelectExpr {
2273 cases,
2274 timeout,
2275 default_body,
2276 } => {
2277 let builtin_name = if timeout.is_some() {
2284 "__select_timeout"
2285 } else if default_body.is_some() {
2286 "__select_try"
2287 } else {
2288 "__select_list"
2289 };
2290
2291 let name_idx = self
2293 .chunk
2294 .add_constant(Constant::String(builtin_name.into()));
2295 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
2296
2297 for case in cases {
2299 self.compile_node(&case.channel)?;
2300 }
2301 self.chunk
2302 .emit_u16(Op::BuildList, cases.len() as u16, self.line);
2303
2304 if let Some((duration_expr, _)) = timeout {
2306 self.compile_node(duration_expr)?;
2307 self.chunk.emit_u8(Op::Call, 2, self.line);
2308 } else {
2309 self.chunk.emit_u8(Op::Call, 1, self.line);
2310 }
2311
2312 self.temp_counter += 1;
2314 let result_name = format!("__sel_result_{}__", self.temp_counter);
2315 let result_idx = self
2316 .chunk
2317 .add_constant(Constant::String(result_name.clone()));
2318 self.chunk.emit_u16(Op::DefVar, result_idx, self.line);
2319
2320 let mut end_jumps = Vec::new();
2322
2323 for (i, case) in cases.iter().enumerate() {
2324 let get_r = self
2325 .chunk
2326 .add_constant(Constant::String(result_name.clone()));
2327 self.chunk.emit_u16(Op::GetVar, get_r, self.line);
2328 let idx_prop = self.chunk.add_constant(Constant::String("index".into()));
2329 self.chunk.emit_u16(Op::GetProperty, idx_prop, self.line);
2330 let case_i = self.chunk.add_constant(Constant::Int(i as i64));
2331 self.chunk.emit_u16(Op::Constant, case_i, self.line);
2332 self.chunk.emit(Op::Equal, self.line);
2333 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2334 self.chunk.emit(Op::Pop, self.line);
2335 self.begin_scope();
2336
2337 let get_r2 = self
2339 .chunk
2340 .add_constant(Constant::String(result_name.clone()));
2341 self.chunk.emit_u16(Op::GetVar, get_r2, self.line);
2342 let val_prop = self.chunk.add_constant(Constant::String("value".into()));
2343 self.chunk.emit_u16(Op::GetProperty, val_prop, self.line);
2344 let var_idx = self
2345 .chunk
2346 .add_constant(Constant::String(case.variable.clone()));
2347 self.chunk.emit_u16(Op::DefLet, var_idx, self.line);
2348
2349 self.compile_try_body(&case.body)?;
2350 self.end_scope();
2351 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
2352 self.chunk.patch_jump(skip);
2353 self.chunk.emit(Op::Pop, self.line);
2354 }
2355
2356 if let Some((_, ref timeout_body)) = timeout {
2358 self.compile_try_body(timeout_body)?;
2359 } else if let Some(ref def_body) = default_body {
2360 self.compile_try_body(def_body)?;
2361 } else {
2362 self.chunk.emit(Op::Nil, self.line);
2363 }
2364
2365 for ej in end_jumps {
2366 self.chunk.patch_jump(ej);
2367 }
2368 }
2369 Node::Spread(_) => {
2370 return Err(CompileError {
2371 message: "spread (...) can only be used inside list literals, dict literals, or function call arguments".into(),
2372 line: self.line,
2373 });
2374 }
2375 }
2376 Ok(())
2377 }
2378
2379 fn compile_destructuring(
2383 &mut self,
2384 pattern: &BindingPattern,
2385 is_mutable: bool,
2386 ) -> Result<(), CompileError> {
2387 let def_op = if is_mutable { Op::DefVar } else { Op::DefLet };
2388 match pattern {
2389 BindingPattern::Identifier(name) => {
2390 let idx = self.chunk.add_constant(Constant::String(name.clone()));
2392 self.chunk.emit_u16(def_op, idx, self.line);
2393 }
2394 BindingPattern::Dict(fields) => {
2395 self.chunk.emit(Op::Dup, self.line);
2398 let assert_idx = self
2399 .chunk
2400 .add_constant(Constant::String("__assert_dict".into()));
2401 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
2402 self.chunk.emit(Op::Swap, self.line);
2403 self.chunk.emit_u8(Op::Call, 1, self.line);
2404 self.chunk.emit(Op::Pop, self.line); let non_rest: Vec<_> = fields.iter().filter(|f| !f.is_rest).collect();
2409 let rest_field = fields.iter().find(|f| f.is_rest);
2410
2411 for field in &non_rest {
2412 self.chunk.emit(Op::Dup, self.line);
2413 let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
2414 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
2415 self.chunk.emit(Op::Subscript, self.line);
2416 let binding_name = field.alias.as_deref().unwrap_or(&field.key);
2417 let name_idx = self
2418 .chunk
2419 .add_constant(Constant::String(binding_name.to_string()));
2420 self.chunk.emit_u16(def_op, name_idx, self.line);
2421 }
2422
2423 if let Some(rest) = rest_field {
2424 let fn_idx = self
2427 .chunk
2428 .add_constant(Constant::String("__dict_rest".into()));
2429 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
2430 self.chunk.emit(Op::Swap, self.line);
2432 for field in &non_rest {
2434 let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
2435 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
2436 }
2437 self.chunk
2438 .emit_u16(Op::BuildList, non_rest.len() as u16, self.line);
2439 self.chunk.emit_u8(Op::Call, 2, self.line);
2441 let rest_name = &rest.key;
2442 let rest_idx = self.chunk.add_constant(Constant::String(rest_name.clone()));
2443 self.chunk.emit_u16(def_op, rest_idx, self.line);
2444 } else {
2445 self.chunk.emit(Op::Pop, self.line);
2447 }
2448 }
2449 BindingPattern::List(elements) => {
2450 self.chunk.emit(Op::Dup, self.line);
2453 let assert_idx = self
2454 .chunk
2455 .add_constant(Constant::String("__assert_list".into()));
2456 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
2457 self.chunk.emit(Op::Swap, self.line);
2458 self.chunk.emit_u8(Op::Call, 1, self.line);
2459 self.chunk.emit(Op::Pop, self.line); let non_rest: Vec<_> = elements.iter().filter(|e| !e.is_rest).collect();
2462 let rest_elem = elements.iter().find(|e| e.is_rest);
2463
2464 for (i, elem) in non_rest.iter().enumerate() {
2465 self.chunk.emit(Op::Dup, self.line);
2466 let idx_const = self.chunk.add_constant(Constant::Int(i as i64));
2467 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
2468 self.chunk.emit(Op::Subscript, self.line);
2469 let name_idx = self.chunk.add_constant(Constant::String(elem.name.clone()));
2470 self.chunk.emit_u16(def_op, name_idx, self.line);
2471 }
2472
2473 if let Some(rest) = rest_elem {
2474 let start_idx = self
2478 .chunk
2479 .add_constant(Constant::Int(non_rest.len() as i64));
2480 self.chunk.emit_u16(Op::Constant, start_idx, self.line);
2481 self.chunk.emit(Op::Nil, self.line); self.chunk.emit(Op::Slice, self.line);
2483 let rest_name_idx =
2484 self.chunk.add_constant(Constant::String(rest.name.clone()));
2485 self.chunk.emit_u16(def_op, rest_name_idx, self.line);
2486 } else {
2487 self.chunk.emit(Op::Pop, self.line);
2489 }
2490 }
2491 }
2492 Ok(())
2493 }
2494
2495 fn produces_value(node: &Node) -> bool {
2497 match node {
2498 Node::LetBinding { .. }
2500 | Node::VarBinding { .. }
2501 | Node::Assignment { .. }
2502 | Node::ReturnStmt { .. }
2503 | Node::FnDecl { .. }
2504 | Node::ImplBlock { .. }
2505 | Node::StructDecl { .. }
2506 | Node::EnumDecl { .. }
2507 | Node::InterfaceDecl { .. }
2508 | Node::TypeDecl { .. }
2509 | Node::ThrowStmt { .. }
2510 | Node::BreakStmt
2511 | Node::ContinueStmt
2512 | Node::RequireStmt { .. } => false,
2513 Node::TryCatch { .. }
2515 | Node::TryExpr { .. }
2516 | Node::Retry { .. }
2517 | Node::GuardStmt { .. }
2518 | Node::DeadlineBlock { .. }
2519 | Node::MutexBlock { .. }
2520 | Node::Spread(_) => true,
2521 _ => true,
2523 }
2524 }
2525}
2526
2527impl Compiler {
2528 pub fn compile_fn_body(
2530 &mut self,
2531 params: &[TypedParam],
2532 body: &[SNode],
2533 ) -> Result<CompiledFunction, CompileError> {
2534 let mut fn_compiler = Compiler::new();
2535 fn_compiler.compile_block(body)?;
2536 fn_compiler.chunk.emit(Op::Nil, 0);
2537 fn_compiler.chunk.emit(Op::Return, 0);
2538 Ok(CompiledFunction {
2539 name: String::new(),
2540 params: TypedParam::names(params),
2541 default_start: TypedParam::default_start(params),
2542 chunk: fn_compiler.chunk,
2543 is_generator: false,
2544 })
2545 }
2546
2547 fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
2549 self.begin_scope();
2550 if body.is_empty() {
2551 self.chunk.emit(Op::Nil, self.line);
2552 } else {
2553 self.compile_block(body)?;
2554 if !Self::produces_value(&body.last().unwrap().node) {
2555 self.chunk.emit(Op::Nil, self.line);
2556 }
2557 }
2558 self.end_scope();
2559 Ok(())
2560 }
2561
2562 fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
2564 match op {
2565 "+" => self.chunk.emit(Op::Add, self.line),
2566 "-" => self.chunk.emit(Op::Sub, self.line),
2567 "*" => self.chunk.emit(Op::Mul, self.line),
2568 "/" => self.chunk.emit(Op::Div, self.line),
2569 "%" => self.chunk.emit(Op::Mod, self.line),
2570 _ => {
2571 return Err(CompileError {
2572 message: format!("Unknown compound operator: {op}"),
2573 line: self.line,
2574 })
2575 }
2576 }
2577 Ok(())
2578 }
2579
2580 fn root_var_name(&self, node: &SNode) -> Option<String> {
2582 match &node.node {
2583 Node::Identifier(name) => Some(name.clone()),
2584 Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
2585 self.root_var_name(object)
2586 }
2587 Node::SubscriptAccess { object, .. } => self.root_var_name(object),
2588 _ => None,
2589 }
2590 }
2591
2592 fn compile_top_level_declarations(&mut self, program: &[SNode]) -> Result<(), CompileError> {
2593 for sn in program {
2594 if matches!(
2595 &sn.node,
2596 Node::FnDecl { .. }
2597 | Node::ImplBlock { .. }
2598 | Node::StructDecl { .. }
2599 | Node::EnumDecl { .. }
2600 | Node::InterfaceDecl { .. }
2601 | Node::TypeDecl { .. }
2602 ) {
2603 self.compile_node(sn)?;
2604 }
2605 }
2606 Ok(())
2607 }
2608}
2609
2610impl Compiler {
2611 fn collect_enum_names(nodes: &[SNode], names: &mut std::collections::HashSet<String>) {
2613 for sn in nodes {
2614 match &sn.node {
2615 Node::EnumDecl { name, .. } => {
2616 names.insert(name.clone());
2617 }
2618 Node::Pipeline { body, .. } => {
2619 Self::collect_enum_names(body, names);
2620 }
2621 Node::FnDecl { body, .. } => {
2622 Self::collect_enum_names(body, names);
2623 }
2624 Node::Block(stmts) => {
2625 Self::collect_enum_names(stmts, names);
2626 }
2627 _ => {}
2628 }
2629 }
2630 }
2631
2632 fn collect_interface_methods(
2633 nodes: &[SNode],
2634 interfaces: &mut std::collections::HashMap<String, Vec<String>>,
2635 ) {
2636 for sn in nodes {
2637 match &sn.node {
2638 Node::InterfaceDecl { name, methods } => {
2639 let method_names: Vec<String> =
2640 methods.iter().map(|m| m.name.clone()).collect();
2641 interfaces.insert(name.clone(), method_names);
2642 }
2643 Node::Pipeline { body, .. } | Node::FnDecl { body, .. } => {
2644 Self::collect_interface_methods(body, interfaces);
2645 }
2646 Node::Block(stmts) => {
2647 Self::collect_interface_methods(stmts, interfaces);
2648 }
2649 _ => {}
2650 }
2651 }
2652 }
2653}
2654
2655impl Default for Compiler {
2656 fn default() -> Self {
2657 Self::new()
2658 }
2659}
2660
2661fn body_contains_yield(nodes: &[SNode]) -> bool {
2663 nodes.iter().any(|sn| node_contains_yield(&sn.node))
2664}
2665
2666fn node_contains_yield(node: &Node) -> bool {
2667 match node {
2668 Node::YieldExpr { .. } => true,
2669 Node::FnDecl { .. } | Node::Closure { .. } => false,
2672 Node::Block(stmts) => body_contains_yield(stmts),
2673 Node::IfElse {
2674 condition,
2675 then_body,
2676 else_body,
2677 } => {
2678 node_contains_yield(&condition.node)
2679 || body_contains_yield(then_body)
2680 || else_body.as_ref().is_some_and(|b| body_contains_yield(b))
2681 }
2682 Node::WhileLoop { condition, body } => {
2683 node_contains_yield(&condition.node) || body_contains_yield(body)
2684 }
2685 Node::ForIn { iterable, body, .. } => {
2686 node_contains_yield(&iterable.node) || body_contains_yield(body)
2687 }
2688 Node::TryCatch {
2689 body, catch_body, ..
2690 } => body_contains_yield(body) || body_contains_yield(catch_body),
2691 Node::TryExpr { body } => body_contains_yield(body),
2692 _ => false,
2693 }
2694}
2695
2696fn contains_pipe_placeholder(node: &SNode) -> bool {
2698 match &node.node {
2699 Node::Identifier(name) if name == "_" => true,
2700 Node::FunctionCall { args, .. } => args.iter().any(contains_pipe_placeholder),
2701 Node::MethodCall { object, args, .. } => {
2702 contains_pipe_placeholder(object) || args.iter().any(contains_pipe_placeholder)
2703 }
2704 Node::BinaryOp { left, right, .. } => {
2705 contains_pipe_placeholder(left) || contains_pipe_placeholder(right)
2706 }
2707 Node::UnaryOp { operand, .. } => contains_pipe_placeholder(operand),
2708 Node::ListLiteral(items) => items.iter().any(contains_pipe_placeholder),
2709 Node::PropertyAccess { object, .. } => contains_pipe_placeholder(object),
2710 Node::SubscriptAccess { object, index } => {
2711 contains_pipe_placeholder(object) || contains_pipe_placeholder(index)
2712 }
2713 _ => false,
2714 }
2715}
2716
2717fn replace_pipe_placeholder(node: &SNode) -> SNode {
2719 let new_node = match &node.node {
2720 Node::Identifier(name) if name == "_" => Node::Identifier("__pipe".into()),
2721 Node::FunctionCall { name, args } => Node::FunctionCall {
2722 name: name.clone(),
2723 args: args.iter().map(replace_pipe_placeholder).collect(),
2724 },
2725 Node::MethodCall {
2726 object,
2727 method,
2728 args,
2729 } => Node::MethodCall {
2730 object: Box::new(replace_pipe_placeholder(object)),
2731 method: method.clone(),
2732 args: args.iter().map(replace_pipe_placeholder).collect(),
2733 },
2734 Node::BinaryOp { op, left, right } => Node::BinaryOp {
2735 op: op.clone(),
2736 left: Box::new(replace_pipe_placeholder(left)),
2737 right: Box::new(replace_pipe_placeholder(right)),
2738 },
2739 Node::UnaryOp { op, operand } => Node::UnaryOp {
2740 op: op.clone(),
2741 operand: Box::new(replace_pipe_placeholder(operand)),
2742 },
2743 Node::ListLiteral(items) => {
2744 Node::ListLiteral(items.iter().map(replace_pipe_placeholder).collect())
2745 }
2746 Node::PropertyAccess { object, property } => Node::PropertyAccess {
2747 object: Box::new(replace_pipe_placeholder(object)),
2748 property: property.clone(),
2749 },
2750 Node::SubscriptAccess { object, index } => Node::SubscriptAccess {
2751 object: Box::new(replace_pipe_placeholder(object)),
2752 index: Box::new(replace_pipe_placeholder(index)),
2753 },
2754 _ => return node.clone(),
2755 };
2756 SNode::new(new_node, node.span)
2757}
2758
2759#[cfg(test)]
2760mod tests {
2761 use super::*;
2762 use harn_lexer::Lexer;
2763 use harn_parser::Parser;
2764
2765 fn compile_source(source: &str) -> Chunk {
2766 let mut lexer = Lexer::new(source);
2767 let tokens = lexer.tokenize().unwrap();
2768 let mut parser = Parser::new(tokens);
2769 let program = parser.parse().unwrap();
2770 Compiler::new().compile(&program).unwrap()
2771 }
2772
2773 #[test]
2774 fn test_compile_arithmetic() {
2775 let chunk = compile_source("pipeline test(task) { let x = 2 + 3 }");
2776 assert!(!chunk.code.is_empty());
2777 assert!(chunk.constants.contains(&Constant::Int(2)));
2779 assert!(chunk.constants.contains(&Constant::Int(3)));
2780 }
2781
2782 #[test]
2783 fn test_compile_function_call() {
2784 let chunk = compile_source("pipeline test(task) { log(42) }");
2785 let disasm = chunk.disassemble("test");
2786 assert!(disasm.contains("CALL"));
2787 }
2788
2789 #[test]
2790 fn test_compile_if_else() {
2791 let chunk =
2792 compile_source(r#"pipeline test(task) { if true { log("yes") } else { log("no") } }"#);
2793 let disasm = chunk.disassemble("test");
2794 assert!(disasm.contains("JUMP_IF_FALSE"));
2795 assert!(disasm.contains("JUMP"));
2796 }
2797
2798 #[test]
2799 fn test_compile_while() {
2800 let chunk = compile_source("pipeline test(task) { var i = 0\n while i < 5 { i = i + 1 } }");
2801 let disasm = chunk.disassemble("test");
2802 assert!(disasm.contains("JUMP_IF_FALSE"));
2803 assert!(disasm.contains("JUMP"));
2805 }
2806
2807 #[test]
2808 fn test_compile_closure() {
2809 let chunk = compile_source("pipeline test(task) { let f = { x -> x * 2 } }");
2810 assert!(!chunk.functions.is_empty());
2811 assert_eq!(chunk.functions[0].params, vec!["x"]);
2812 }
2813
2814 #[test]
2815 fn test_compile_list() {
2816 let chunk = compile_source("pipeline test(task) { let a = [1, 2, 3] }");
2817 let disasm = chunk.disassemble("test");
2818 assert!(disasm.contains("BUILD_LIST"));
2819 }
2820
2821 #[test]
2822 fn test_compile_dict() {
2823 let chunk = compile_source(r#"pipeline test(task) { let d = {name: "test"} }"#);
2824 let disasm = chunk.disassemble("test");
2825 assert!(disasm.contains("BUILD_DICT"));
2826 }
2827
2828 #[test]
2829 fn test_disassemble() {
2830 let chunk = compile_source("pipeline test(task) { log(2 + 3) }");
2831 let disasm = chunk.disassemble("test");
2832 assert!(disasm.contains("CONSTANT"));
2834 assert!(disasm.contains("ADD"));
2835 assert!(disasm.contains("CALL"));
2836 }
2837}