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