1use harn_lexer::StringSegment;
2use harn_parser::{BindingPattern, Node, SNode, TypedParam};
3
4use crate::chunk::{Chunk, CompiledFunction, Constant, Op};
5
6#[derive(Debug)]
8pub struct CompileError {
9 pub message: String,
10 pub line: u32,
11}
12
13impl std::fmt::Display for CompileError {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 write!(f, "Compile error at line {}: {}", self.line, self.message)
16 }
17}
18
19impl std::error::Error for CompileError {}
20
21struct LoopContext {
23 start_offset: usize,
25 break_patches: Vec<usize>,
27 has_iterator: bool,
29 handler_depth: usize,
31 finally_depth: usize,
33}
34
35pub struct Compiler {
37 chunk: Chunk,
38 line: u32,
39 column: u32,
40 enum_names: std::collections::HashSet<String>,
42 loop_stack: Vec<LoopContext>,
44 handler_depth: usize,
46 finally_bodies: Vec<Vec<SNode>>,
48 temp_counter: usize,
50}
51
52impl Compiler {
53 pub fn new() -> Self {
54 Self {
55 chunk: Chunk::new(),
56 line: 1,
57 column: 1,
58 enum_names: std::collections::HashSet::new(),
59 loop_stack: Vec::new(),
60 handler_depth: 0,
61 finally_bodies: Vec::new(),
62 temp_counter: 0,
63 }
64 }
65
66 pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
69 Self::collect_enum_names(program, &mut self.enum_names);
72 self.enum_names.insert("Result".to_string());
74
75 for sn in program {
77 match &sn.node {
78 Node::ImportDecl { .. } | Node::SelectiveImport { .. } => {
79 self.compile_node(sn)?;
80 }
81 _ => {}
82 }
83 }
84
85 let main = program
87 .iter()
88 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == "default"))
89 .or_else(|| {
90 program
91 .iter()
92 .find(|sn| matches!(&sn.node, Node::Pipeline { .. }))
93 });
94
95 if let Some(sn) = main {
96 if let Node::Pipeline { body, extends, .. } = &sn.node {
97 if let Some(parent_name) = extends {
99 self.compile_parent_pipeline(program, parent_name)?;
100 }
101 self.compile_block(body)?;
102 }
103 }
104
105 self.chunk.emit(Op::Nil, self.line);
106 self.chunk.emit(Op::Return, self.line);
107 Ok(self.chunk)
108 }
109
110 pub fn compile_named(
112 mut self,
113 program: &[SNode],
114 pipeline_name: &str,
115 ) -> Result<Chunk, CompileError> {
116 Self::collect_enum_names(program, &mut self.enum_names);
117
118 for sn in program {
119 if matches!(
120 &sn.node,
121 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
122 ) {
123 self.compile_node(sn)?;
124 }
125 }
126
127 let target = program
128 .iter()
129 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == pipeline_name));
130
131 if let Some(sn) = target {
132 if let Node::Pipeline { body, extends, .. } = &sn.node {
133 if let Some(parent_name) = extends {
134 self.compile_parent_pipeline(program, parent_name)?;
135 }
136 self.compile_block(body)?;
137 }
138 }
139
140 self.chunk.emit(Op::Nil, self.line);
141 self.chunk.emit(Op::Return, self.line);
142 Ok(self.chunk)
143 }
144
145 fn compile_parent_pipeline(
147 &mut self,
148 program: &[SNode],
149 parent_name: &str,
150 ) -> Result<(), CompileError> {
151 let parent = program
152 .iter()
153 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == parent_name));
154 if let Some(sn) = parent {
155 if let Node::Pipeline { body, extends, .. } = &sn.node {
156 if let Some(grandparent) = extends {
158 self.compile_parent_pipeline(program, grandparent)?;
159 }
160 for stmt in body {
162 self.compile_node(stmt)?;
163 if Self::produces_value(&stmt.node) {
164 self.chunk.emit(Op::Pop, self.line);
165 }
166 }
167 }
168 }
169 Ok(())
170 }
171
172 fn emit_default_preamble(&mut self, params: &[TypedParam]) -> Result<(), CompileError> {
177 for (i, param) in params.iter().enumerate() {
178 if let Some(default_expr) = ¶m.default_value {
179 self.chunk.emit(Op::GetArgc, self.line);
180 let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
181 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
182 self.chunk.emit(Op::GreaterEqual, self.line);
184 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
185 self.chunk.emit(Op::Pop, self.line);
187 self.compile_node(default_expr)?;
189 let name_idx = self
190 .chunk
191 .add_constant(Constant::String(param.name.clone()));
192 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
193 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
194 self.chunk.patch_jump(skip_jump);
195 self.chunk.emit(Op::Pop, self.line);
197 self.chunk.patch_jump(end_jump);
198 }
199 }
200 Ok(())
201 }
202
203 fn emit_type_checks(&mut self, params: &[TypedParam]) {
207 for param in params {
208 if let Some(type_expr) = ¶m.type_expr {
209 if let harn_parser::TypeExpr::Shape(fields) = type_expr {
211 let spec = Self::shape_to_spec_string(fields);
212 let fn_idx = self
214 .chunk
215 .add_constant(Constant::String("__assert_shape".into()));
216 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
217 let var_idx = self
218 .chunk
219 .add_constant(Constant::String(param.name.clone()));
220 self.chunk.emit_u16(Op::GetVar, var_idx, self.line);
221 let name_idx = self
222 .chunk
223 .add_constant(Constant::String(param.name.clone()));
224 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
225 let spec_idx = self.chunk.add_constant(Constant::String(spec));
226 self.chunk.emit_u16(Op::Constant, spec_idx, self.line);
227 self.chunk.emit_u8(Op::Call, 3, self.line);
228 self.chunk.emit(Op::Pop, self.line);
229 continue;
230 }
231
232 let type_name = Self::type_expr_to_runtime_name(type_expr);
233 if let Some(type_name) = type_name {
234 let var_idx = self
235 .chunk
236 .add_constant(Constant::String(param.name.clone()));
237 let type_idx = self.chunk.add_constant(Constant::String(type_name));
238 self.chunk.emit_u16(Op::CheckType, var_idx, self.line);
239 let hi = (type_idx >> 8) as u8;
241 let lo = type_idx as u8;
242 self.chunk.code.push(hi);
243 self.chunk.code.push(lo);
244 }
245 }
246 }
247 }
248
249 fn shape_to_spec_string(fields: &[harn_parser::ShapeField]) -> String {
252 fields
253 .iter()
254 .map(|f| {
255 let opt = if f.optional { "?" } else { "" };
256 let type_str = Self::type_expr_to_spec(&f.type_expr);
257 format!("{}:{}{}", f.name, opt, type_str)
258 })
259 .collect::<Vec<_>>()
260 .join(",")
261 }
262
263 fn type_expr_to_spec(type_expr: &harn_parser::TypeExpr) -> String {
265 match type_expr {
266 harn_parser::TypeExpr::Named(name) => name.clone(),
267 harn_parser::TypeExpr::Shape(fields) => {
268 let inner = Self::shape_to_spec_string(fields);
269 format!("{{{}}}", inner)
270 }
271 harn_parser::TypeExpr::List(_) => "list".to_string(),
272 harn_parser::TypeExpr::DictType(_, _) => "dict".to_string(),
273 harn_parser::TypeExpr::Union(_) => {
274 "any".to_string()
276 }
277 harn_parser::TypeExpr::FnType { .. } => "closure".to_string(),
278 }
279 }
280
281 fn type_expr_to_runtime_name(type_expr: &harn_parser::TypeExpr) -> Option<String> {
283 match type_expr {
284 harn_parser::TypeExpr::Named(name) => match name.as_str() {
285 "int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
286 | "closure" => Some(name.clone()),
287 _ => None, },
289 _ => None, }
291 }
292
293 fn emit_type_name_extra(&mut self, type_name_idx: u16) {
295 let hi = (type_name_idx >> 8) as u8;
296 let lo = type_name_idx as u8;
297 self.chunk.code.push(hi);
298 self.chunk.code.push(lo);
299 self.chunk.lines.push(self.line);
300 self.chunk.columns.push(self.column);
301 self.chunk.lines.push(self.line);
302 self.chunk.columns.push(self.column);
303 }
304
305 fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
307 if body.is_empty() {
308 self.chunk.emit(Op::Nil, self.line);
309 } else {
310 self.compile_block(body)?;
311 if !Self::produces_value(&body.last().unwrap().node) {
312 self.chunk.emit(Op::Nil, self.line);
313 }
314 }
315 Ok(())
316 }
317
318 fn compile_catch_binding(&mut self, error_var: &Option<String>) -> Result<(), CompileError> {
320 if let Some(var_name) = error_var {
321 let idx = self.chunk.add_constant(Constant::String(var_name.clone()));
322 self.chunk.emit_u16(Op::DefLet, idx, self.line);
323 } else {
324 self.chunk.emit(Op::Pop, self.line);
325 }
326 Ok(())
327 }
328
329 fn compile_finally_inline(&mut self, finally_body: &[SNode]) -> Result<(), CompileError> {
331 if !finally_body.is_empty() {
332 self.compile_block(finally_body)?;
333 if Self::produces_value(&finally_body.last().unwrap().node) {
335 self.chunk.emit(Op::Pop, self.line);
336 }
337 }
338 Ok(())
339 }
340
341 fn compile_rethrow_with_finally(&mut self, finally_body: &[SNode]) -> Result<(), CompileError> {
343 self.temp_counter += 1;
345 let temp_name = format!("__finally_err_{}__", self.temp_counter);
346 let err_idx = self.chunk.add_constant(Constant::String(temp_name.clone()));
347 self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
348 self.compile_finally_inline(finally_body)?;
349 let get_idx = self.chunk.add_constant(Constant::String(temp_name));
350 self.chunk.emit_u16(Op::GetVar, get_idx, self.line);
351 self.chunk.emit(Op::Throw, self.line);
352 Ok(())
353 }
354
355 fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
356 for (i, snode) in stmts.iter().enumerate() {
357 self.compile_node(snode)?;
358 let is_last = i == stmts.len() - 1;
359 if is_last {
360 if !Self::produces_value(&snode.node) {
363 self.chunk.emit(Op::Nil, self.line);
364 }
365 } else {
366 if Self::produces_value(&snode.node) {
368 self.chunk.emit(Op::Pop, self.line);
369 }
370 }
371 }
372 Ok(())
373 }
374
375 fn compile_node(&mut self, snode: &SNode) -> Result<(), CompileError> {
376 self.line = snode.span.line as u32;
377 self.column = snode.span.column as u32;
378 self.chunk.set_column(self.column);
379 match &snode.node {
380 Node::IntLiteral(n) => {
381 let idx = self.chunk.add_constant(Constant::Int(*n));
382 self.chunk.emit_u16(Op::Constant, idx, self.line);
383 }
384 Node::FloatLiteral(n) => {
385 let idx = self.chunk.add_constant(Constant::Float(*n));
386 self.chunk.emit_u16(Op::Constant, idx, self.line);
387 }
388 Node::StringLiteral(s) => {
389 let idx = self.chunk.add_constant(Constant::String(s.clone()));
390 self.chunk.emit_u16(Op::Constant, idx, self.line);
391 }
392 Node::BoolLiteral(true) => self.chunk.emit(Op::True, self.line),
393 Node::BoolLiteral(false) => self.chunk.emit(Op::False, self.line),
394 Node::NilLiteral => self.chunk.emit(Op::Nil, self.line),
395 Node::DurationLiteral(ms) => {
396 let idx = self.chunk.add_constant(Constant::Duration(*ms));
397 self.chunk.emit_u16(Op::Constant, idx, self.line);
398 }
399
400 Node::Identifier(name) => {
401 let idx = self.chunk.add_constant(Constant::String(name.clone()));
402 self.chunk.emit_u16(Op::GetVar, idx, self.line);
403 }
404
405 Node::LetBinding { pattern, value, .. } => {
406 self.compile_node(value)?;
407 self.compile_destructuring(pattern, false)?;
408 }
409
410 Node::VarBinding { pattern, value, .. } => {
411 self.compile_node(value)?;
412 self.compile_destructuring(pattern, true)?;
413 }
414
415 Node::Assignment {
416 target, value, op, ..
417 } => {
418 if let Node::Identifier(name) = &target.node {
419 let idx = self.chunk.add_constant(Constant::String(name.clone()));
420 if let Some(op) = op {
421 self.chunk.emit_u16(Op::GetVar, idx, self.line);
422 self.compile_node(value)?;
423 self.emit_compound_op(op)?;
424 self.chunk.emit_u16(Op::SetVar, idx, self.line);
425 } else {
426 self.compile_node(value)?;
427 self.chunk.emit_u16(Op::SetVar, idx, self.line);
428 }
429 } else if let Node::PropertyAccess { object, property } = &target.node {
430 if let Some(var_name) = self.root_var_name(object) {
432 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
433 let prop_idx = self.chunk.add_constant(Constant::String(property.clone()));
434 if let Some(op) = op {
435 self.compile_node(target)?; self.compile_node(value)?;
438 self.emit_compound_op(op)?;
439 } else {
440 self.compile_node(value)?;
441 }
442 self.chunk.emit_u16(Op::SetProperty, prop_idx, self.line);
445 let hi = (var_idx >> 8) as u8;
447 let lo = var_idx as u8;
448 self.chunk.code.push(hi);
449 self.chunk.code.push(lo);
450 self.chunk.lines.push(self.line);
451 self.chunk.columns.push(self.column);
452 self.chunk.lines.push(self.line);
453 self.chunk.columns.push(self.column);
454 }
455 } else if let Node::SubscriptAccess { object, index } = &target.node {
456 if let Some(var_name) = self.root_var_name(object) {
458 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
459 if let Some(op) = op {
460 self.compile_node(target)?;
461 self.compile_node(value)?;
462 self.emit_compound_op(op)?;
463 } else {
464 self.compile_node(value)?;
465 }
466 self.compile_node(index)?;
467 self.chunk.emit_u16(Op::SetSubscript, var_idx, self.line);
468 }
469 }
470 }
471
472 Node::BinaryOp { op, left, right } => {
473 match op.as_str() {
475 "&&" => {
476 self.compile_node(left)?;
477 let jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
478 self.chunk.emit(Op::Pop, self.line);
479 self.compile_node(right)?;
480 self.chunk.patch_jump(jump);
481 self.chunk.emit(Op::Not, self.line);
483 self.chunk.emit(Op::Not, self.line);
484 return Ok(());
485 }
486 "||" => {
487 self.compile_node(left)?;
488 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
489 self.chunk.emit(Op::Pop, self.line);
490 self.compile_node(right)?;
491 self.chunk.patch_jump(jump);
492 self.chunk.emit(Op::Not, self.line);
493 self.chunk.emit(Op::Not, self.line);
494 return Ok(());
495 }
496 "??" => {
497 self.compile_node(left)?;
498 self.chunk.emit(Op::Dup, self.line);
499 self.chunk.emit(Op::Nil, self.line);
501 self.chunk.emit(Op::NotEqual, self.line);
502 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
503 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_node(right)?;
506 let end = self.chunk.emit_jump(Op::Jump, self.line);
507 self.chunk.patch_jump(jump);
508 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end);
510 return Ok(());
511 }
512 "|>" => {
513 self.compile_node(left)?;
514 if contains_pipe_placeholder(right) {
517 let replaced = replace_pipe_placeholder(right);
518 let closure_node = SNode::dummy(Node::Closure {
519 params: vec![TypedParam {
520 name: "__pipe".into(),
521 type_expr: None,
522 default_value: None,
523 }],
524 body: vec![replaced],
525 });
526 self.compile_node(&closure_node)?;
527 } else {
528 self.compile_node(right)?;
529 }
530 self.chunk.emit(Op::Pipe, self.line);
531 return Ok(());
532 }
533 _ => {}
534 }
535
536 self.compile_node(left)?;
537 self.compile_node(right)?;
538 match op.as_str() {
539 "+" => self.chunk.emit(Op::Add, self.line),
540 "-" => self.chunk.emit(Op::Sub, self.line),
541 "*" => self.chunk.emit(Op::Mul, self.line),
542 "/" => self.chunk.emit(Op::Div, self.line),
543 "%" => self.chunk.emit(Op::Mod, self.line),
544 "==" => self.chunk.emit(Op::Equal, self.line),
545 "!=" => self.chunk.emit(Op::NotEqual, self.line),
546 "<" => self.chunk.emit(Op::Less, self.line),
547 ">" => self.chunk.emit(Op::Greater, self.line),
548 "<=" => self.chunk.emit(Op::LessEqual, self.line),
549 ">=" => self.chunk.emit(Op::GreaterEqual, self.line),
550 _ => {
551 return Err(CompileError {
552 message: format!("Unknown operator: {op}"),
553 line: self.line,
554 })
555 }
556 }
557 }
558
559 Node::UnaryOp { op, operand } => {
560 self.compile_node(operand)?;
561 match op.as_str() {
562 "-" => self.chunk.emit(Op::Negate, self.line),
563 "!" => self.chunk.emit(Op::Not, self.line),
564 _ => {}
565 }
566 }
567
568 Node::Ternary {
569 condition,
570 true_expr,
571 false_expr,
572 } => {
573 self.compile_node(condition)?;
574 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
575 self.chunk.emit(Op::Pop, self.line);
576 self.compile_node(true_expr)?;
577 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
578 self.chunk.patch_jump(else_jump);
579 self.chunk.emit(Op::Pop, self.line);
580 self.compile_node(false_expr)?;
581 self.chunk.patch_jump(end_jump);
582 }
583
584 Node::FunctionCall { name, args } => {
585 let has_spread = args.iter().any(|a| matches!(&a.node, Node::Spread(_)));
586 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
588 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
589
590 if has_spread {
591 self.chunk.emit_u16(Op::BuildList, 0, self.line);
594 let mut pending = 0u16;
595 for arg in args {
596 if let Node::Spread(inner) = &arg.node {
597 if pending > 0 {
598 self.chunk.emit_u16(Op::BuildList, pending, self.line);
599 self.chunk.emit(Op::Add, self.line);
600 pending = 0;
601 }
602 self.compile_node(inner)?;
603 self.chunk.emit(Op::Dup, self.line);
604 let assert_idx = self
605 .chunk
606 .add_constant(Constant::String("__assert_list".into()));
607 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
608 self.chunk.emit(Op::Swap, self.line);
609 self.chunk.emit_u8(Op::Call, 1, self.line);
610 self.chunk.emit(Op::Pop, self.line);
611 self.chunk.emit(Op::Add, self.line);
612 } else {
613 self.compile_node(arg)?;
614 pending += 1;
615 }
616 }
617 if pending > 0 {
618 self.chunk.emit_u16(Op::BuildList, pending, self.line);
619 self.chunk.emit(Op::Add, self.line);
620 }
621 self.chunk.emit(Op::CallSpread, self.line);
622 } else {
623 for arg in args {
625 self.compile_node(arg)?;
626 }
627 self.chunk.emit_u8(Op::Call, args.len() as u8, self.line);
628 }
629 }
630
631 Node::MethodCall {
632 object,
633 method,
634 args,
635 } => {
636 if let Node::Identifier(name) = &object.node {
638 if self.enum_names.contains(name) {
639 for arg in args {
641 self.compile_node(arg)?;
642 }
643 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
644 let var_idx = self.chunk.add_constant(Constant::String(method.clone()));
645 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
646 let hi = (var_idx >> 8) as u8;
647 let lo = var_idx as u8;
648 self.chunk.code.push(hi);
649 self.chunk.code.push(lo);
650 self.chunk.lines.push(self.line);
651 self.chunk.columns.push(self.column);
652 self.chunk.lines.push(self.line);
653 self.chunk.columns.push(self.column);
654 let fc = args.len() as u16;
655 let fhi = (fc >> 8) as u8;
656 let flo = fc as u8;
657 self.chunk.code.push(fhi);
658 self.chunk.code.push(flo);
659 self.chunk.lines.push(self.line);
660 self.chunk.columns.push(self.column);
661 self.chunk.lines.push(self.line);
662 self.chunk.columns.push(self.column);
663 return Ok(());
664 }
665 }
666 let has_spread = args.iter().any(|a| matches!(&a.node, Node::Spread(_)));
667 self.compile_node(object)?;
668 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
669 if has_spread {
670 self.chunk.emit_u16(Op::BuildList, 0, self.line);
672 let mut pending = 0u16;
673 for arg in args {
674 if let Node::Spread(inner) = &arg.node {
675 if pending > 0 {
676 self.chunk.emit_u16(Op::BuildList, pending, self.line);
677 self.chunk.emit(Op::Add, self.line);
678 pending = 0;
679 }
680 self.compile_node(inner)?;
681 self.chunk.emit(Op::Dup, self.line);
682 let assert_idx = self
683 .chunk
684 .add_constant(Constant::String("__assert_list".into()));
685 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
686 self.chunk.emit(Op::Swap, self.line);
687 self.chunk.emit_u8(Op::Call, 1, self.line);
688 self.chunk.emit(Op::Pop, self.line);
689 self.chunk.emit(Op::Add, self.line);
690 } else {
691 self.compile_node(arg)?;
692 pending += 1;
693 }
694 }
695 if pending > 0 {
696 self.chunk.emit_u16(Op::BuildList, pending, self.line);
697 self.chunk.emit(Op::Add, self.line);
698 }
699 self.chunk
700 .emit_u16(Op::MethodCallSpread, name_idx, self.line);
701 } else {
702 for arg in args {
703 self.compile_node(arg)?;
704 }
705 self.chunk
706 .emit_method_call(name_idx, args.len() as u8, self.line);
707 }
708 }
709
710 Node::OptionalMethodCall {
711 object,
712 method,
713 args,
714 } => {
715 self.compile_node(object)?;
716 for arg in args {
717 self.compile_node(arg)?;
718 }
719 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
720 self.chunk
721 .emit_method_call_opt(name_idx, args.len() as u8, self.line);
722 }
723
724 Node::PropertyAccess { object, property } => {
725 if let Node::Identifier(name) = &object.node {
727 if self.enum_names.contains(name) {
728 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
730 let var_idx = self.chunk.add_constant(Constant::String(property.clone()));
731 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
732 let hi = (var_idx >> 8) as u8;
733 let lo = var_idx as u8;
734 self.chunk.code.push(hi);
735 self.chunk.code.push(lo);
736 self.chunk.lines.push(self.line);
737 self.chunk.columns.push(self.column);
738 self.chunk.lines.push(self.line);
739 self.chunk.columns.push(self.column);
740 self.chunk.code.push(0);
742 self.chunk.code.push(0);
743 self.chunk.lines.push(self.line);
744 self.chunk.columns.push(self.column);
745 self.chunk.lines.push(self.line);
746 self.chunk.columns.push(self.column);
747 return Ok(());
748 }
749 }
750 self.compile_node(object)?;
751 let idx = self.chunk.add_constant(Constant::String(property.clone()));
752 self.chunk.emit_u16(Op::GetProperty, idx, self.line);
753 }
754
755 Node::OptionalPropertyAccess { object, property } => {
756 self.compile_node(object)?;
757 let idx = self.chunk.add_constant(Constant::String(property.clone()));
758 self.chunk.emit_u16(Op::GetPropertyOpt, idx, self.line);
759 }
760
761 Node::SubscriptAccess { object, index } => {
762 self.compile_node(object)?;
763 self.compile_node(index)?;
764 self.chunk.emit(Op::Subscript, self.line);
765 }
766
767 Node::SliceAccess { object, start, end } => {
768 self.compile_node(object)?;
769 if let Some(s) = start {
770 self.compile_node(s)?;
771 } else {
772 self.chunk.emit(Op::Nil, self.line);
773 }
774 if let Some(e) = end {
775 self.compile_node(e)?;
776 } else {
777 self.chunk.emit(Op::Nil, self.line);
778 }
779 self.chunk.emit(Op::Slice, self.line);
780 }
781
782 Node::IfElse {
783 condition,
784 then_body,
785 else_body,
786 } => {
787 self.compile_node(condition)?;
788 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
789 self.chunk.emit(Op::Pop, self.line);
790 self.compile_block(then_body)?;
791 if let Some(else_body) = else_body {
792 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
793 self.chunk.patch_jump(else_jump);
794 self.chunk.emit(Op::Pop, self.line);
795 self.compile_block(else_body)?;
796 self.chunk.patch_jump(end_jump);
797 } else {
798 self.chunk.patch_jump(else_jump);
799 self.chunk.emit(Op::Pop, self.line);
800 self.chunk.emit(Op::Nil, self.line);
801 }
802 }
803
804 Node::WhileLoop { condition, body } => {
805 let loop_start = self.chunk.current_offset();
806 self.loop_stack.push(LoopContext {
807 start_offset: loop_start,
808 break_patches: Vec::new(),
809 has_iterator: false,
810 handler_depth: self.handler_depth,
811 finally_depth: self.finally_bodies.len(),
812 });
813 self.compile_node(condition)?;
814 let exit_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
815 self.chunk.emit(Op::Pop, self.line); for sn in body {
818 self.compile_node(sn)?;
819 if Self::produces_value(&sn.node) {
820 self.chunk.emit(Op::Pop, self.line);
821 }
822 }
823 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
825 self.chunk.patch_jump(exit_jump);
826 self.chunk.emit(Op::Pop, self.line); let ctx = self.loop_stack.pop().unwrap();
829 for patch_pos in ctx.break_patches {
830 self.chunk.patch_jump(patch_pos);
831 }
832 self.chunk.emit(Op::Nil, self.line);
833 }
834
835 Node::ForIn {
836 pattern,
837 iterable,
838 body,
839 } => {
840 self.compile_node(iterable)?;
842 self.chunk.emit(Op::IterInit, self.line);
844 let loop_start = self.chunk.current_offset();
845 self.loop_stack.push(LoopContext {
846 start_offset: loop_start,
847 break_patches: Vec::new(),
848 has_iterator: true,
849 handler_depth: self.handler_depth,
850 finally_depth: self.finally_bodies.len(),
851 });
852 let exit_jump_pos = self.chunk.emit_jump(Op::IterNext, self.line);
854 self.compile_destructuring(pattern, true)?;
856 for sn in body {
858 self.compile_node(sn)?;
859 if Self::produces_value(&sn.node) {
860 self.chunk.emit(Op::Pop, self.line);
861 }
862 }
863 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
865 self.chunk.patch_jump(exit_jump_pos);
866 let ctx = self.loop_stack.pop().unwrap();
868 for patch_pos in ctx.break_patches {
869 self.chunk.patch_jump(patch_pos);
870 }
871 self.chunk.emit(Op::Nil, self.line);
873 }
874
875 Node::ReturnStmt { value } => {
876 let has_pending_finally = !self.finally_bodies.is_empty();
877
878 if has_pending_finally {
879 if let Some(val) = value {
882 self.compile_node(val)?;
883 } else {
884 self.chunk.emit(Op::Nil, self.line);
885 }
886 self.temp_counter += 1;
887 let temp_name = format!("__return_val_{}__", self.temp_counter);
888 let save_idx = self.chunk.add_constant(Constant::String(temp_name.clone()));
889 self.chunk.emit_u16(Op::DefVar, save_idx, self.line);
890 let finallys: Vec<_> = self.finally_bodies.iter().rev().cloned().collect();
892 for fb in &finallys {
893 self.compile_finally_inline(fb)?;
894 }
895 let restore_idx = self.chunk.add_constant(Constant::String(temp_name));
896 self.chunk.emit_u16(Op::GetVar, restore_idx, self.line);
897 self.chunk.emit(Op::Return, self.line);
898 } else {
899 if let Some(val) = value {
901 if let Node::FunctionCall { name, args } = &val.node {
902 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
903 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
904 for arg in args {
905 self.compile_node(arg)?;
906 }
907 self.chunk
908 .emit_u8(Op::TailCall, args.len() as u8, self.line);
909 } else if let Node::BinaryOp { op, left, right } = &val.node {
910 if op == "|>" {
911 self.compile_node(left)?;
912 self.compile_node(right)?;
913 self.chunk.emit(Op::Swap, self.line);
914 self.chunk.emit_u8(Op::TailCall, 1, self.line);
915 } else {
916 self.compile_node(val)?;
917 }
918 } else {
919 self.compile_node(val)?;
920 }
921 } else {
922 self.chunk.emit(Op::Nil, self.line);
923 }
924 self.chunk.emit(Op::Return, self.line);
925 }
926 }
927
928 Node::BreakStmt => {
929 if self.loop_stack.is_empty() {
930 return Err(CompileError {
931 message: "break outside of loop".to_string(),
932 line: self.line,
933 });
934 }
935 let ctx = self.loop_stack.last().unwrap();
937 let finally_depth = ctx.finally_depth;
938 let handler_depth = ctx.handler_depth;
939 let has_iterator = ctx.has_iterator;
940 for _ in handler_depth..self.handler_depth {
942 self.chunk.emit(Op::PopHandler, self.line);
943 }
944 if self.finally_bodies.len() > finally_depth {
946 let finallys: Vec<_> = self.finally_bodies[finally_depth..]
947 .iter()
948 .rev()
949 .cloned()
950 .collect();
951 for fb in &finallys {
952 self.compile_finally_inline(fb)?;
953 }
954 }
955 if has_iterator {
956 self.chunk.emit(Op::PopIterator, self.line);
957 }
958 let patch = self.chunk.emit_jump(Op::Jump, self.line);
959 self.loop_stack
960 .last_mut()
961 .unwrap()
962 .break_patches
963 .push(patch);
964 }
965
966 Node::ContinueStmt => {
967 if self.loop_stack.is_empty() {
968 return Err(CompileError {
969 message: "continue outside of loop".to_string(),
970 line: self.line,
971 });
972 }
973 let ctx = self.loop_stack.last().unwrap();
974 let finally_depth = ctx.finally_depth;
975 let handler_depth = ctx.handler_depth;
976 let loop_start = ctx.start_offset;
977 for _ in handler_depth..self.handler_depth {
978 self.chunk.emit(Op::PopHandler, self.line);
979 }
980 if self.finally_bodies.len() > finally_depth {
981 let finallys: Vec<_> = self.finally_bodies[finally_depth..]
982 .iter()
983 .rev()
984 .cloned()
985 .collect();
986 for fb in &finallys {
987 self.compile_finally_inline(fb)?;
988 }
989 }
990 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
991 }
992
993 Node::ListLiteral(elements) => {
994 let has_spread = elements.iter().any(|e| matches!(&e.node, Node::Spread(_)));
995 if !has_spread {
996 for el in elements {
997 self.compile_node(el)?;
998 }
999 self.chunk
1000 .emit_u16(Op::BuildList, elements.len() as u16, self.line);
1001 } else {
1002 self.chunk.emit_u16(Op::BuildList, 0, self.line);
1005 let mut pending = 0u16;
1006 for el in elements {
1007 if let Node::Spread(inner) = &el.node {
1008 if pending > 0 {
1010 self.chunk.emit_u16(Op::BuildList, pending, self.line);
1011 self.chunk.emit(Op::Add, self.line);
1013 pending = 0;
1014 }
1015 self.compile_node(inner)?;
1017 self.chunk.emit(Op::Dup, self.line);
1018 let assert_idx = self
1019 .chunk
1020 .add_constant(Constant::String("__assert_list".into()));
1021 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1022 self.chunk.emit(Op::Swap, self.line);
1023 self.chunk.emit_u8(Op::Call, 1, self.line);
1024 self.chunk.emit(Op::Pop, self.line);
1025 self.chunk.emit(Op::Add, self.line);
1026 } else {
1027 self.compile_node(el)?;
1028 pending += 1;
1029 }
1030 }
1031 if pending > 0 {
1032 self.chunk.emit_u16(Op::BuildList, pending, self.line);
1033 self.chunk.emit(Op::Add, self.line);
1034 }
1035 }
1036 }
1037
1038 Node::DictLiteral(entries) => {
1039 let has_spread = entries
1040 .iter()
1041 .any(|e| matches!(&e.value.node, Node::Spread(_)));
1042 if !has_spread {
1043 for entry in entries {
1044 self.compile_node(&entry.key)?;
1045 self.compile_node(&entry.value)?;
1046 }
1047 self.chunk
1048 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
1049 } else {
1050 self.chunk.emit_u16(Op::BuildDict, 0, self.line);
1052 let mut pending = 0u16;
1053 for entry in entries {
1054 if let Node::Spread(inner) = &entry.value.node {
1055 if pending > 0 {
1057 self.chunk.emit_u16(Op::BuildDict, pending, self.line);
1058 self.chunk.emit(Op::Add, self.line);
1059 pending = 0;
1060 }
1061 self.compile_node(inner)?;
1063 self.chunk.emit(Op::Dup, self.line);
1064 let assert_idx = self
1065 .chunk
1066 .add_constant(Constant::String("__assert_dict".into()));
1067 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1068 self.chunk.emit(Op::Swap, self.line);
1069 self.chunk.emit_u8(Op::Call, 1, self.line);
1070 self.chunk.emit(Op::Pop, self.line);
1071 self.chunk.emit(Op::Add, self.line);
1072 } else {
1073 self.compile_node(&entry.key)?;
1074 self.compile_node(&entry.value)?;
1075 pending += 1;
1076 }
1077 }
1078 if pending > 0 {
1079 self.chunk.emit_u16(Op::BuildDict, pending, self.line);
1080 self.chunk.emit(Op::Add, self.line);
1081 }
1082 }
1083 }
1084
1085 Node::InterpolatedString(segments) => {
1086 let mut part_count = 0u16;
1087 for seg in segments {
1088 match seg {
1089 StringSegment::Literal(s) => {
1090 let idx = self.chunk.add_constant(Constant::String(s.clone()));
1091 self.chunk.emit_u16(Op::Constant, idx, self.line);
1092 part_count += 1;
1093 }
1094 StringSegment::Expression(expr_str) => {
1095 let mut lexer = harn_lexer::Lexer::new(expr_str);
1097 if let Ok(tokens) = lexer.tokenize() {
1098 let mut parser = harn_parser::Parser::new(tokens);
1099 if let Ok(snode) = parser.parse_single_expression() {
1100 self.compile_node(&snode)?;
1101 let to_str = self
1103 .chunk
1104 .add_constant(Constant::String("to_string".into()));
1105 self.chunk.emit_u16(Op::Constant, to_str, self.line);
1106 self.chunk.emit(Op::Swap, self.line);
1107 self.chunk.emit_u8(Op::Call, 1, self.line);
1108 part_count += 1;
1109 } else {
1110 let idx =
1112 self.chunk.add_constant(Constant::String(expr_str.clone()));
1113 self.chunk.emit_u16(Op::Constant, idx, self.line);
1114 part_count += 1;
1115 }
1116 }
1117 }
1118 }
1119 }
1120 if part_count > 1 {
1121 self.chunk.emit_u16(Op::Concat, part_count, self.line);
1122 }
1123 }
1124
1125 Node::FnDecl {
1126 name, params, body, ..
1127 } => {
1128 let mut fn_compiler = Compiler::new();
1130 fn_compiler.enum_names = self.enum_names.clone();
1131 fn_compiler.emit_default_preamble(params)?;
1132 fn_compiler.emit_type_checks(params);
1133 fn_compiler.compile_block(body)?;
1134 fn_compiler.chunk.emit(Op::Nil, self.line);
1135 fn_compiler.chunk.emit(Op::Return, self.line);
1136
1137 let func = CompiledFunction {
1138 name: name.clone(),
1139 params: TypedParam::names(params),
1140 default_start: TypedParam::default_start(params),
1141 chunk: fn_compiler.chunk,
1142 };
1143 let fn_idx = self.chunk.functions.len();
1144 self.chunk.functions.push(func);
1145
1146 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1147 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1148 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1149 }
1150
1151 Node::Closure { params, body } => {
1152 let mut fn_compiler = Compiler::new();
1153 fn_compiler.enum_names = self.enum_names.clone();
1154 fn_compiler.emit_default_preamble(params)?;
1155 fn_compiler.emit_type_checks(params);
1156 fn_compiler.compile_block(body)?;
1157 fn_compiler.chunk.emit(Op::Return, self.line);
1159
1160 let func = CompiledFunction {
1161 name: "<closure>".to_string(),
1162 params: TypedParam::names(params),
1163 default_start: TypedParam::default_start(params),
1164 chunk: fn_compiler.chunk,
1165 };
1166 let fn_idx = self.chunk.functions.len();
1167 self.chunk.functions.push(func);
1168
1169 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1170 }
1171
1172 Node::ThrowStmt { value } => {
1173 self.compile_node(value)?;
1174 self.chunk.emit(Op::Throw, self.line);
1175 }
1176
1177 Node::MatchExpr { value, arms } => {
1178 self.compile_node(value)?;
1179 let mut end_jumps = Vec::new();
1180 for arm in arms {
1181 match &arm.pattern.node {
1182 Node::Identifier(name) if name == "_" => {
1184 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1186 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1187 }
1188 Node::EnumConstruct {
1190 enum_name,
1191 variant,
1192 args: pat_args,
1193 } => {
1194 self.chunk.emit(Op::Dup, self.line);
1196 let en_idx =
1197 self.chunk.add_constant(Constant::String(enum_name.clone()));
1198 let vn_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1199 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1200 let hi = (vn_idx >> 8) as u8;
1201 let lo = vn_idx as u8;
1202 self.chunk.code.push(hi);
1203 self.chunk.code.push(lo);
1204 self.chunk.lines.push(self.line);
1205 self.chunk.columns.push(self.column);
1206 self.chunk.lines.push(self.line);
1207 self.chunk.columns.push(self.column);
1208 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1210 self.chunk.emit(Op::Pop, self.line); for (i, pat_arg) in pat_args.iter().enumerate() {
1215 if let Node::Identifier(binding_name) = &pat_arg.node {
1216 self.chunk.emit(Op::Dup, self.line);
1218 let fields_idx = self
1219 .chunk
1220 .add_constant(Constant::String("fields".to_string()));
1221 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
1222 let idx_const =
1223 self.chunk.add_constant(Constant::Int(i as i64));
1224 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1225 self.chunk.emit(Op::Subscript, self.line);
1226 let name_idx = self
1227 .chunk
1228 .add_constant(Constant::String(binding_name.clone()));
1229 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1230 }
1231 }
1232
1233 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1235 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1236 self.chunk.patch_jump(skip);
1237 self.chunk.emit(Op::Pop, self.line); }
1239 Node::PropertyAccess { object, property } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
1241 {
1242 let enum_name = if let Node::Identifier(n) = &object.node {
1243 n.clone()
1244 } else {
1245 unreachable!()
1246 };
1247 self.chunk.emit(Op::Dup, self.line);
1248 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
1249 let vn_idx =
1250 self.chunk.add_constant(Constant::String(property.clone()));
1251 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1252 let hi = (vn_idx >> 8) as u8;
1253 let lo = vn_idx as u8;
1254 self.chunk.code.push(hi);
1255 self.chunk.code.push(lo);
1256 self.chunk.lines.push(self.line);
1257 self.chunk.columns.push(self.column);
1258 self.chunk.lines.push(self.line);
1259 self.chunk.columns.push(self.column);
1260 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1261 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1264 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1265 self.chunk.patch_jump(skip);
1266 self.chunk.emit(Op::Pop, self.line); }
1268 Node::MethodCall {
1271 object,
1272 method,
1273 args: pat_args,
1274 } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
1275 {
1276 let enum_name = if let Node::Identifier(n) = &object.node {
1277 n.clone()
1278 } else {
1279 unreachable!()
1280 };
1281 self.chunk.emit(Op::Dup, self.line);
1283 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
1284 let vn_idx = self.chunk.add_constant(Constant::String(method.clone()));
1285 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1286 let hi = (vn_idx >> 8) as u8;
1287 let lo = vn_idx as u8;
1288 self.chunk.code.push(hi);
1289 self.chunk.code.push(lo);
1290 self.chunk.lines.push(self.line);
1291 self.chunk.columns.push(self.column);
1292 self.chunk.lines.push(self.line);
1293 self.chunk.columns.push(self.column);
1294 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1295 self.chunk.emit(Op::Pop, self.line); for (i, pat_arg) in pat_args.iter().enumerate() {
1299 if let Node::Identifier(binding_name) = &pat_arg.node {
1300 self.chunk.emit(Op::Dup, self.line);
1301 let fields_idx = self
1302 .chunk
1303 .add_constant(Constant::String("fields".to_string()));
1304 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
1305 let idx_const =
1306 self.chunk.add_constant(Constant::Int(i as i64));
1307 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1308 self.chunk.emit(Op::Subscript, self.line);
1309 let name_idx = self
1310 .chunk
1311 .add_constant(Constant::String(binding_name.clone()));
1312 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1313 }
1314 }
1315
1316 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1318 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1319 self.chunk.patch_jump(skip);
1320 self.chunk.emit(Op::Pop, self.line); }
1322 Node::Identifier(name) => {
1324 self.chunk.emit(Op::Dup, self.line); let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1327 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1328 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1330 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1331 }
1332 Node::DictLiteral(entries)
1334 if entries
1335 .iter()
1336 .all(|e| matches!(&e.key.node, Node::StringLiteral(_))) =>
1337 {
1338 self.chunk.emit(Op::Dup, self.line);
1340 let typeof_idx =
1341 self.chunk.add_constant(Constant::String("type_of".into()));
1342 self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
1343 self.chunk.emit(Op::Swap, self.line);
1344 self.chunk.emit_u8(Op::Call, 1, self.line);
1345 let dict_str = self.chunk.add_constant(Constant::String("dict".into()));
1346 self.chunk.emit_u16(Op::Constant, dict_str, self.line);
1347 self.chunk.emit(Op::Equal, self.line);
1348 let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1349 self.chunk.emit(Op::Pop, self.line); let mut constraint_skips = Vec::new();
1353 let mut bindings = Vec::new();
1354 for entry in entries {
1355 if let Node::StringLiteral(key) = &entry.key.node {
1356 match &entry.value.node {
1357 Node::StringLiteral(_)
1359 | Node::IntLiteral(_)
1360 | Node::FloatLiteral(_)
1361 | Node::BoolLiteral(_)
1362 | Node::NilLiteral => {
1363 self.chunk.emit(Op::Dup, self.line);
1364 let key_idx = self
1365 .chunk
1366 .add_constant(Constant::String(key.clone()));
1367 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1368 self.chunk.emit(Op::Subscript, self.line);
1369 self.compile_node(&entry.value)?;
1370 self.chunk.emit(Op::Equal, self.line);
1371 let skip =
1372 self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1373 self.chunk.emit(Op::Pop, self.line); constraint_skips.push(skip);
1375 }
1376 Node::Identifier(binding) => {
1378 bindings.push((key.clone(), binding.clone()));
1379 }
1380 _ => {
1381 self.chunk.emit(Op::Dup, self.line);
1383 let key_idx = self
1384 .chunk
1385 .add_constant(Constant::String(key.clone()));
1386 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1387 self.chunk.emit(Op::Subscript, self.line);
1388 self.compile_node(&entry.value)?;
1389 self.chunk.emit(Op::Equal, self.line);
1390 let skip =
1391 self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1392 self.chunk.emit(Op::Pop, self.line);
1393 constraint_skips.push(skip);
1394 }
1395 }
1396 }
1397 }
1398
1399 for (key, binding) in &bindings {
1401 self.chunk.emit(Op::Dup, self.line);
1402 let key_idx =
1403 self.chunk.add_constant(Constant::String(key.clone()));
1404 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1405 self.chunk.emit(Op::Subscript, self.line);
1406 let name_idx =
1407 self.chunk.add_constant(Constant::String(binding.clone()));
1408 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1409 }
1410
1411 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1413 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1414
1415 let fail_target = self.chunk.code.len();
1417 self.chunk.emit(Op::Pop, self.line); for skip in constraint_skips {
1420 self.chunk.patch_jump_to(skip, fail_target);
1421 }
1422 self.chunk.patch_jump_to(skip_type, fail_target);
1423 }
1424 Node::ListLiteral(elements) => {
1426 self.chunk.emit(Op::Dup, self.line);
1428 let typeof_idx =
1429 self.chunk.add_constant(Constant::String("type_of".into()));
1430 self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
1431 self.chunk.emit(Op::Swap, self.line);
1432 self.chunk.emit_u8(Op::Call, 1, self.line);
1433 let list_str = self.chunk.add_constant(Constant::String("list".into()));
1434 self.chunk.emit_u16(Op::Constant, list_str, self.line);
1435 self.chunk.emit(Op::Equal, self.line);
1436 let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1437 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Dup, self.line);
1441 let len_idx = self.chunk.add_constant(Constant::String("len".into()));
1442 self.chunk.emit_u16(Op::Constant, len_idx, self.line);
1443 self.chunk.emit(Op::Swap, self.line);
1444 self.chunk.emit_u8(Op::Call, 1, self.line);
1445 let count = self
1446 .chunk
1447 .add_constant(Constant::Int(elements.len() as i64));
1448 self.chunk.emit_u16(Op::Constant, count, self.line);
1449 self.chunk.emit(Op::GreaterEqual, self.line);
1450 let skip_len = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1451 self.chunk.emit(Op::Pop, self.line); let mut constraint_skips = Vec::new();
1455 let mut bindings = Vec::new();
1456 for (i, elem) in elements.iter().enumerate() {
1457 match &elem.node {
1458 Node::Identifier(name) if name != "_" => {
1459 bindings.push((i, name.clone()));
1460 }
1461 Node::Identifier(_) => {} _ => {
1464 self.chunk.emit(Op::Dup, self.line);
1465 let idx_const =
1466 self.chunk.add_constant(Constant::Int(i as i64));
1467 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1468 self.chunk.emit(Op::Subscript, self.line);
1469 self.compile_node(elem)?;
1470 self.chunk.emit(Op::Equal, self.line);
1471 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1472 self.chunk.emit(Op::Pop, self.line);
1473 constraint_skips.push(skip);
1474 }
1475 }
1476 }
1477
1478 for (i, name) in &bindings {
1480 self.chunk.emit(Op::Dup, self.line);
1481 let idx_const = self.chunk.add_constant(Constant::Int(*i as i64));
1482 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1483 self.chunk.emit(Op::Subscript, self.line);
1484 let name_idx =
1485 self.chunk.add_constant(Constant::String(name.clone()));
1486 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1487 }
1488
1489 self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1491 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1492
1493 let fail_target = self.chunk.code.len();
1495 self.chunk.emit(Op::Pop, self.line); for skip in constraint_skips {
1497 self.chunk.patch_jump_to(skip, fail_target);
1498 }
1499 self.chunk.patch_jump_to(skip_len, fail_target);
1500 self.chunk.patch_jump_to(skip_type, fail_target);
1501 }
1502 _ => {
1504 self.chunk.emit(Op::Dup, self.line);
1505 self.compile_node(&arm.pattern)?;
1506 self.chunk.emit(Op::Equal, self.line);
1507 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1508 self.chunk.emit(Op::Pop, self.line); self.chunk.emit(Op::Pop, self.line); self.compile_match_body(&arm.body)?;
1511 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1512 self.chunk.patch_jump(skip);
1513 self.chunk.emit(Op::Pop, self.line); }
1515 }
1516 }
1517 self.chunk.emit(Op::Pop, self.line);
1519 self.chunk.emit(Op::Nil, self.line);
1520 for j in end_jumps {
1521 self.chunk.patch_jump(j);
1522 }
1523 }
1524
1525 Node::RangeExpr {
1526 start,
1527 end,
1528 inclusive,
1529 } => {
1530 let name_idx = self
1532 .chunk
1533 .add_constant(Constant::String("__range__".to_string()));
1534 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
1535 self.compile_node(start)?;
1536 self.compile_node(end)?;
1537 if *inclusive {
1538 self.chunk.emit(Op::True, self.line);
1539 } else {
1540 self.chunk.emit(Op::False, self.line);
1541 }
1542 self.chunk.emit_u8(Op::Call, 3, self.line);
1543 }
1544
1545 Node::GuardStmt {
1546 condition,
1547 else_body,
1548 } => {
1549 self.compile_node(condition)?;
1552 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
1553 self.chunk.emit(Op::Pop, self.line); self.compile_block(else_body)?;
1556 if !else_body.is_empty() && Self::produces_value(&else_body.last().unwrap().node) {
1558 self.chunk.emit(Op::Pop, self.line);
1559 }
1560 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1561 self.chunk.patch_jump(skip_jump);
1562 self.chunk.emit(Op::Pop, self.line); self.chunk.patch_jump(end_jump);
1564 self.chunk.emit(Op::Nil, self.line);
1565 }
1566
1567 Node::Block(stmts) => {
1568 if stmts.is_empty() {
1569 self.chunk.emit(Op::Nil, self.line);
1570 } else {
1571 self.compile_block(stmts)?;
1572 }
1573 }
1574
1575 Node::DeadlineBlock { duration, body } => {
1576 self.compile_node(duration)?;
1577 self.chunk.emit(Op::DeadlineSetup, self.line);
1578 if body.is_empty() {
1579 self.chunk.emit(Op::Nil, self.line);
1580 } else {
1581 self.compile_block(body)?;
1582 }
1583 self.chunk.emit(Op::DeadlineEnd, self.line);
1584 }
1585
1586 Node::MutexBlock { body } => {
1587 if body.is_empty() {
1589 self.chunk.emit(Op::Nil, self.line);
1590 } else {
1591 for sn in body {
1594 self.compile_node(sn)?;
1595 if Self::produces_value(&sn.node) {
1596 self.chunk.emit(Op::Pop, self.line);
1597 }
1598 }
1599 self.chunk.emit(Op::Nil, self.line);
1600 }
1601 }
1602
1603 Node::YieldExpr { .. } => {
1604 self.chunk.emit(Op::Nil, self.line);
1606 }
1607
1608 Node::AskExpr { fields } => {
1609 for entry in fields {
1612 self.compile_node(&entry.key)?;
1613 self.compile_node(&entry.value)?;
1614 }
1615 self.chunk
1616 .emit_u16(Op::BuildDict, fields.len() as u16, self.line);
1617 }
1618
1619 Node::EnumConstruct {
1620 enum_name,
1621 variant,
1622 args,
1623 } => {
1624 for arg in args {
1626 self.compile_node(arg)?;
1627 }
1628 let enum_idx = self.chunk.add_constant(Constant::String(enum_name.clone()));
1629 let var_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1630 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
1632 let hi = (var_idx >> 8) as u8;
1633 let lo = var_idx as u8;
1634 self.chunk.code.push(hi);
1635 self.chunk.code.push(lo);
1636 self.chunk.lines.push(self.line);
1637 self.chunk.columns.push(self.column);
1638 self.chunk.lines.push(self.line);
1639 self.chunk.columns.push(self.column);
1640 let fc = args.len() as u16;
1641 let fhi = (fc >> 8) as u8;
1642 let flo = fc as u8;
1643 self.chunk.code.push(fhi);
1644 self.chunk.code.push(flo);
1645 self.chunk.lines.push(self.line);
1646 self.chunk.columns.push(self.column);
1647 self.chunk.lines.push(self.line);
1648 self.chunk.columns.push(self.column);
1649 }
1650
1651 Node::StructConstruct {
1652 struct_name,
1653 fields,
1654 } => {
1655 let struct_key = self
1657 .chunk
1658 .add_constant(Constant::String("__struct__".to_string()));
1659 let struct_val = self
1660 .chunk
1661 .add_constant(Constant::String(struct_name.clone()));
1662 self.chunk.emit_u16(Op::Constant, struct_key, self.line);
1663 self.chunk.emit_u16(Op::Constant, struct_val, self.line);
1664
1665 for entry in fields {
1666 self.compile_node(&entry.key)?;
1667 self.compile_node(&entry.value)?;
1668 }
1669 self.chunk
1670 .emit_u16(Op::BuildDict, (fields.len() + 1) as u16, self.line);
1671 }
1672
1673 Node::ImportDecl { path } => {
1674 let idx = self.chunk.add_constant(Constant::String(path.clone()));
1675 self.chunk.emit_u16(Op::Import, idx, self.line);
1676 }
1677
1678 Node::SelectiveImport { names, path } => {
1679 let path_idx = self.chunk.add_constant(Constant::String(path.clone()));
1680 let names_str = names.join(",");
1681 let names_idx = self.chunk.add_constant(Constant::String(names_str));
1682 self.chunk
1683 .emit_u16(Op::SelectiveImport, path_idx, self.line);
1684 let hi = (names_idx >> 8) as u8;
1685 let lo = names_idx as u8;
1686 self.chunk.code.push(hi);
1687 self.chunk.code.push(lo);
1688 self.chunk.lines.push(self.line);
1689 self.chunk.columns.push(self.column);
1690 self.chunk.lines.push(self.line);
1691 self.chunk.columns.push(self.column);
1692 }
1693
1694 Node::TryOperator { operand } => {
1695 self.compile_node(operand)?;
1696 self.chunk.emit(Op::TryUnwrap, self.line);
1697 }
1698
1699 Node::ImplBlock { type_name, methods } => {
1700 for method_sn in methods {
1703 if let Node::FnDecl {
1704 name, params, body, ..
1705 } = &method_sn.node
1706 {
1707 let key_idx = self.chunk.add_constant(Constant::String(name.clone()));
1709 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1710
1711 let mut fn_compiler = Compiler::new();
1713 fn_compiler.enum_names = self.enum_names.clone();
1714 fn_compiler.emit_default_preamble(params)?;
1715 fn_compiler.emit_type_checks(params);
1716 fn_compiler.compile_block(body)?;
1717 fn_compiler.chunk.emit(Op::Nil, self.line);
1718 fn_compiler.chunk.emit(Op::Return, self.line);
1719
1720 let func = CompiledFunction {
1721 name: format!("{}.{}", type_name, name),
1722 params: TypedParam::names(params),
1723 default_start: TypedParam::default_start(params),
1724 chunk: fn_compiler.chunk,
1725 };
1726 let fn_idx = self.chunk.functions.len();
1727 self.chunk.functions.push(func);
1728 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1729 }
1730 }
1731 let method_count = methods
1732 .iter()
1733 .filter(|m| matches!(m.node, Node::FnDecl { .. }))
1734 .count();
1735 self.chunk
1736 .emit_u16(Op::BuildDict, method_count as u16, self.line);
1737 let impl_name = format!("__impl_{}", type_name);
1738 let name_idx = self.chunk.add_constant(Constant::String(impl_name));
1739 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1740 }
1741
1742 Node::StructDecl { name, .. } => {
1743 let mut fn_compiler = Compiler::new();
1745 fn_compiler.enum_names = self.enum_names.clone();
1746 let params = vec![TypedParam::untyped("__fields")];
1747 fn_compiler.emit_default_preamble(¶ms)?;
1748
1749 let make_idx = fn_compiler
1751 .chunk
1752 .add_constant(Constant::String("__make_struct".into()));
1753 fn_compiler
1754 .chunk
1755 .emit_u16(Op::Constant, make_idx, self.line);
1756 let sname_idx = fn_compiler
1757 .chunk
1758 .add_constant(Constant::String(name.clone()));
1759 fn_compiler
1760 .chunk
1761 .emit_u16(Op::Constant, sname_idx, self.line);
1762 let fields_idx = fn_compiler
1763 .chunk
1764 .add_constant(Constant::String("__fields".into()));
1765 fn_compiler
1766 .chunk
1767 .emit_u16(Op::GetVar, fields_idx, self.line);
1768 fn_compiler.chunk.emit_u8(Op::Call, 2, self.line);
1769 fn_compiler.chunk.emit(Op::Return, self.line);
1770
1771 let func = CompiledFunction {
1772 name: name.clone(),
1773 params: TypedParam::names(¶ms),
1774 default_start: None,
1775 chunk: fn_compiler.chunk,
1776 };
1777 let fn_idx = self.chunk.functions.len();
1778 self.chunk.functions.push(func);
1779 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1780 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1781 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1782 }
1783
1784 Node::Pipeline { .. }
1786 | Node::OverrideDecl { .. }
1787 | Node::TypeDecl { .. }
1788 | Node::EnumDecl { .. }
1789 | Node::InterfaceDecl { .. } => {
1790 self.chunk.emit(Op::Nil, self.line);
1791 }
1792
1793 Node::TryCatch {
1794 body,
1795 error_var,
1796 error_type,
1797 catch_body,
1798 finally_body,
1799 } => {
1800 let type_name = error_type.as_ref().and_then(|te| {
1802 if let harn_parser::TypeExpr::Named(name) = te {
1803 Some(name.clone())
1804 } else {
1805 None
1806 }
1807 });
1808
1809 let type_name_idx = if let Some(ref tn) = type_name {
1810 self.chunk.add_constant(Constant::String(tn.clone()))
1811 } else {
1812 self.chunk.add_constant(Constant::String(String::new()))
1813 };
1814
1815 let has_catch = !catch_body.is_empty() || error_var.is_some();
1816 let has_finally = finally_body.is_some();
1817
1818 if has_catch && has_finally {
1819 let finally_body = finally_body.as_ref().unwrap();
1821
1822 self.finally_bodies.push(finally_body.clone());
1824
1825 self.handler_depth += 1;
1827 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1828 self.emit_type_name_extra(type_name_idx);
1829
1830 self.compile_try_body(body)?;
1832
1833 self.handler_depth -= 1;
1835 self.chunk.emit(Op::PopHandler, self.line);
1836 self.compile_finally_inline(finally_body)?;
1837 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1838
1839 self.chunk.patch_jump(catch_jump);
1841 self.compile_catch_binding(error_var)?;
1842
1843 self.handler_depth += 1;
1845 let rethrow_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1846 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1847 self.emit_type_name_extra(empty_type);
1848
1849 self.compile_try_body(catch_body)?;
1851
1852 self.handler_depth -= 1;
1854 self.chunk.emit(Op::PopHandler, self.line);
1855 self.compile_finally_inline(finally_body)?;
1856 let end_jump2 = self.chunk.emit_jump(Op::Jump, self.line);
1857
1858 self.chunk.patch_jump(rethrow_jump);
1860 self.compile_rethrow_with_finally(finally_body)?;
1861
1862 self.chunk.patch_jump(end_jump);
1863 self.chunk.patch_jump(end_jump2);
1864
1865 self.finally_bodies.pop();
1866 } else if has_finally {
1867 let finally_body = finally_body.as_ref().unwrap();
1869
1870 self.finally_bodies.push(finally_body.clone());
1871
1872 self.handler_depth += 1;
1874 let error_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1875 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1876 self.emit_type_name_extra(empty_type);
1877
1878 self.compile_try_body(body)?;
1880
1881 self.handler_depth -= 1;
1883 self.chunk.emit(Op::PopHandler, self.line);
1884 self.compile_finally_inline(finally_body)?;
1885 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1886
1887 self.chunk.patch_jump(error_jump);
1889 self.compile_rethrow_with_finally(finally_body)?;
1890
1891 self.chunk.patch_jump(end_jump);
1892
1893 self.finally_bodies.pop();
1894 } else {
1895 self.handler_depth += 1;
1899 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1900 self.emit_type_name_extra(type_name_idx);
1901
1902 self.compile_try_body(body)?;
1904
1905 self.handler_depth -= 1;
1907 self.chunk.emit(Op::PopHandler, self.line);
1908 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1909
1910 self.chunk.patch_jump(catch_jump);
1912 self.compile_catch_binding(error_var)?;
1913
1914 self.compile_try_body(catch_body)?;
1916
1917 self.chunk.patch_jump(end_jump);
1919 }
1920 }
1921
1922 Node::TryExpr { body } => {
1923 self.handler_depth += 1;
1927 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1928 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1929 self.emit_type_name_extra(empty_type);
1930
1931 self.compile_try_body(body)?;
1933
1934 self.handler_depth -= 1;
1936 self.chunk.emit(Op::PopHandler, self.line);
1937
1938 let ok_idx = self.chunk.add_constant(Constant::String("Ok".to_string()));
1940 self.chunk.emit_u16(Op::Constant, ok_idx, self.line);
1941 self.chunk.emit(Op::Swap, self.line);
1942 self.chunk.emit_u8(Op::Call, 1, self.line);
1943
1944 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1946
1947 self.chunk.patch_jump(catch_jump);
1949
1950 let err_idx = self.chunk.add_constant(Constant::String("Err".to_string()));
1952 self.chunk.emit_u16(Op::Constant, err_idx, self.line);
1953 self.chunk.emit(Op::Swap, self.line);
1954 self.chunk.emit_u8(Op::Call, 1, self.line);
1955
1956 self.chunk.patch_jump(end_jump);
1958 }
1959
1960 Node::Retry { count, body } => {
1961 self.compile_node(count)?;
1963 let counter_name = "__retry_counter__";
1964 let counter_idx = self
1965 .chunk
1966 .add_constant(Constant::String(counter_name.to_string()));
1967 self.chunk.emit_u16(Op::DefVar, counter_idx, self.line);
1968
1969 self.chunk.emit(Op::Nil, self.line);
1971 let err_name = "__retry_last_error__";
1972 let err_idx = self
1973 .chunk
1974 .add_constant(Constant::String(err_name.to_string()));
1975 self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
1976
1977 let loop_start = self.chunk.current_offset();
1979
1980 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1982 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1984 let hi = (empty_type >> 8) as u8;
1985 let lo = empty_type as u8;
1986 self.chunk.code.push(hi);
1987 self.chunk.code.push(lo);
1988 self.chunk.lines.push(self.line);
1989 self.chunk.columns.push(self.column);
1990 self.chunk.lines.push(self.line);
1991 self.chunk.columns.push(self.column);
1992
1993 self.compile_block(body)?;
1995
1996 self.chunk.emit(Op::PopHandler, self.line);
1998 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1999
2000 self.chunk.patch_jump(catch_jump);
2002 self.chunk.emit(Op::Dup, self.line);
2004 self.chunk.emit_u16(Op::SetVar, err_idx, self.line);
2005 self.chunk.emit(Op::Pop, self.line);
2007
2008 self.chunk.emit_u16(Op::GetVar, counter_idx, self.line);
2010 let one_idx = self.chunk.add_constant(Constant::Int(1));
2011 self.chunk.emit_u16(Op::Constant, one_idx, self.line);
2012 self.chunk.emit(Op::Sub, self.line);
2013 self.chunk.emit(Op::Dup, self.line);
2014 self.chunk.emit_u16(Op::SetVar, counter_idx, self.line);
2015
2016 let zero_idx = self.chunk.add_constant(Constant::Int(0));
2018 self.chunk.emit_u16(Op::Constant, zero_idx, self.line);
2019 self.chunk.emit(Op::Greater, self.line);
2020 let retry_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2021 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
2023
2024 self.chunk.patch_jump(retry_jump);
2026 self.chunk.emit(Op::Pop, self.line); self.chunk.emit_u16(Op::GetVar, err_idx, self.line);
2028 self.chunk.emit(Op::Throw, self.line);
2029
2030 self.chunk.patch_jump(end_jump);
2031 self.chunk.emit(Op::Nil, self.line);
2033 }
2034
2035 Node::Parallel {
2036 count,
2037 variable,
2038 body,
2039 } => {
2040 self.compile_node(count)?;
2041 let mut fn_compiler = Compiler::new();
2042 fn_compiler.enum_names = self.enum_names.clone();
2043 fn_compiler.compile_block(body)?;
2044 fn_compiler.chunk.emit(Op::Return, self.line);
2045 let params = vec![variable.clone().unwrap_or_else(|| "__i__".to_string())];
2046 let func = CompiledFunction {
2047 name: "<parallel>".to_string(),
2048 params,
2049 default_start: None,
2050 chunk: fn_compiler.chunk,
2051 };
2052 let fn_idx = self.chunk.functions.len();
2053 self.chunk.functions.push(func);
2054 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2055 self.chunk.emit(Op::Parallel, self.line);
2056 }
2057
2058 Node::ParallelMap {
2059 list,
2060 variable,
2061 body,
2062 } => {
2063 self.compile_node(list)?;
2064 let mut fn_compiler = Compiler::new();
2065 fn_compiler.enum_names = self.enum_names.clone();
2066 fn_compiler.compile_block(body)?;
2067 fn_compiler.chunk.emit(Op::Return, self.line);
2068 let func = CompiledFunction {
2069 name: "<parallel_map>".to_string(),
2070 params: vec![variable.clone()],
2071 default_start: None,
2072 chunk: fn_compiler.chunk,
2073 };
2074 let fn_idx = self.chunk.functions.len();
2075 self.chunk.functions.push(func);
2076 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2077 self.chunk.emit(Op::ParallelMap, self.line);
2078 }
2079
2080 Node::SpawnExpr { body } => {
2081 let mut fn_compiler = Compiler::new();
2082 fn_compiler.enum_names = self.enum_names.clone();
2083 fn_compiler.compile_block(body)?;
2084 fn_compiler.chunk.emit(Op::Return, self.line);
2085 let func = CompiledFunction {
2086 name: "<spawn>".to_string(),
2087 params: vec![],
2088 default_start: None,
2089 chunk: fn_compiler.chunk,
2090 };
2091 let fn_idx = self.chunk.functions.len();
2092 self.chunk.functions.push(func);
2093 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2094 self.chunk.emit(Op::Spawn, self.line);
2095 }
2096 Node::SelectExpr {
2097 cases,
2098 timeout,
2099 default_body,
2100 } => {
2101 let builtin_name = if timeout.is_some() {
2108 "__select_timeout"
2109 } else if default_body.is_some() {
2110 "__select_try"
2111 } else {
2112 "__select_list"
2113 };
2114
2115 let name_idx = self
2117 .chunk
2118 .add_constant(Constant::String(builtin_name.into()));
2119 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
2120
2121 for case in cases {
2123 self.compile_node(&case.channel)?;
2124 }
2125 self.chunk
2126 .emit_u16(Op::BuildList, cases.len() as u16, self.line);
2127
2128 if let Some((duration_expr, _)) = timeout {
2130 self.compile_node(duration_expr)?;
2131 self.chunk.emit_u8(Op::Call, 2, self.line);
2132 } else {
2133 self.chunk.emit_u8(Op::Call, 1, self.line);
2134 }
2135
2136 self.temp_counter += 1;
2138 let result_name = format!("__sel_result_{}__", self.temp_counter);
2139 let result_idx = self
2140 .chunk
2141 .add_constant(Constant::String(result_name.clone()));
2142 self.chunk.emit_u16(Op::DefVar, result_idx, self.line);
2143
2144 let mut end_jumps = Vec::new();
2146
2147 for (i, case) in cases.iter().enumerate() {
2148 let get_r = self
2149 .chunk
2150 .add_constant(Constant::String(result_name.clone()));
2151 self.chunk.emit_u16(Op::GetVar, get_r, self.line);
2152 let idx_prop = self.chunk.add_constant(Constant::String("index".into()));
2153 self.chunk.emit_u16(Op::GetProperty, idx_prop, self.line);
2154 let case_i = self.chunk.add_constant(Constant::Int(i as i64));
2155 self.chunk.emit_u16(Op::Constant, case_i, self.line);
2156 self.chunk.emit(Op::Equal, self.line);
2157 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2158 self.chunk.emit(Op::Pop, self.line);
2159
2160 let get_r2 = self
2162 .chunk
2163 .add_constant(Constant::String(result_name.clone()));
2164 self.chunk.emit_u16(Op::GetVar, get_r2, self.line);
2165 let val_prop = self.chunk.add_constant(Constant::String("value".into()));
2166 self.chunk.emit_u16(Op::GetProperty, val_prop, self.line);
2167 let var_idx = self
2168 .chunk
2169 .add_constant(Constant::String(case.variable.clone()));
2170 self.chunk.emit_u16(Op::DefLet, var_idx, self.line);
2171
2172 self.compile_try_body(&case.body)?;
2173 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
2174 self.chunk.patch_jump(skip);
2175 self.chunk.emit(Op::Pop, self.line);
2176 }
2177
2178 if let Some((_, ref timeout_body)) = timeout {
2180 self.compile_try_body(timeout_body)?;
2181 } else if let Some(ref def_body) = default_body {
2182 self.compile_try_body(def_body)?;
2183 } else {
2184 self.chunk.emit(Op::Nil, self.line);
2185 }
2186
2187 for ej in end_jumps {
2188 self.chunk.patch_jump(ej);
2189 }
2190 }
2191 Node::Spread(_) => {
2192 return Err(CompileError {
2193 message: "spread (...) can only be used inside list literals, dict literals, or function call arguments".into(),
2194 line: self.line,
2195 });
2196 }
2197 }
2198 Ok(())
2199 }
2200
2201 fn compile_destructuring(
2205 &mut self,
2206 pattern: &BindingPattern,
2207 is_mutable: bool,
2208 ) -> Result<(), CompileError> {
2209 let def_op = if is_mutable { Op::DefVar } else { Op::DefLet };
2210 match pattern {
2211 BindingPattern::Identifier(name) => {
2212 let idx = self.chunk.add_constant(Constant::String(name.clone()));
2214 self.chunk.emit_u16(def_op, idx, self.line);
2215 }
2216 BindingPattern::Dict(fields) => {
2217 self.chunk.emit(Op::Dup, self.line);
2220 let assert_idx = self
2221 .chunk
2222 .add_constant(Constant::String("__assert_dict".into()));
2223 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
2224 self.chunk.emit(Op::Swap, self.line);
2225 self.chunk.emit_u8(Op::Call, 1, self.line);
2226 self.chunk.emit(Op::Pop, self.line); let non_rest: Vec<_> = fields.iter().filter(|f| !f.is_rest).collect();
2231 let rest_field = fields.iter().find(|f| f.is_rest);
2232
2233 for field in &non_rest {
2234 self.chunk.emit(Op::Dup, self.line);
2235 let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
2236 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
2237 self.chunk.emit(Op::Subscript, self.line);
2238 let binding_name = field.alias.as_deref().unwrap_or(&field.key);
2239 let name_idx = self
2240 .chunk
2241 .add_constant(Constant::String(binding_name.to_string()));
2242 self.chunk.emit_u16(def_op, name_idx, self.line);
2243 }
2244
2245 if let Some(rest) = rest_field {
2246 let fn_idx = self
2249 .chunk
2250 .add_constant(Constant::String("__dict_rest".into()));
2251 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
2252 self.chunk.emit(Op::Swap, self.line);
2254 for field in &non_rest {
2256 let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
2257 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
2258 }
2259 self.chunk
2260 .emit_u16(Op::BuildList, non_rest.len() as u16, self.line);
2261 self.chunk.emit_u8(Op::Call, 2, self.line);
2263 let rest_name = &rest.key;
2264 let rest_idx = self.chunk.add_constant(Constant::String(rest_name.clone()));
2265 self.chunk.emit_u16(def_op, rest_idx, self.line);
2266 } else {
2267 self.chunk.emit(Op::Pop, self.line);
2269 }
2270 }
2271 BindingPattern::List(elements) => {
2272 self.chunk.emit(Op::Dup, self.line);
2275 let assert_idx = self
2276 .chunk
2277 .add_constant(Constant::String("__assert_list".into()));
2278 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
2279 self.chunk.emit(Op::Swap, self.line);
2280 self.chunk.emit_u8(Op::Call, 1, self.line);
2281 self.chunk.emit(Op::Pop, self.line); let non_rest: Vec<_> = elements.iter().filter(|e| !e.is_rest).collect();
2284 let rest_elem = elements.iter().find(|e| e.is_rest);
2285
2286 for (i, elem) in non_rest.iter().enumerate() {
2287 self.chunk.emit(Op::Dup, self.line);
2288 let idx_const = self.chunk.add_constant(Constant::Int(i as i64));
2289 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
2290 self.chunk.emit(Op::Subscript, self.line);
2291 let name_idx = self.chunk.add_constant(Constant::String(elem.name.clone()));
2292 self.chunk.emit_u16(def_op, name_idx, self.line);
2293 }
2294
2295 if let Some(rest) = rest_elem {
2296 let start_idx = self
2300 .chunk
2301 .add_constant(Constant::Int(non_rest.len() as i64));
2302 self.chunk.emit_u16(Op::Constant, start_idx, self.line);
2303 self.chunk.emit(Op::Nil, self.line); self.chunk.emit(Op::Slice, self.line);
2305 let rest_name_idx =
2306 self.chunk.add_constant(Constant::String(rest.name.clone()));
2307 self.chunk.emit_u16(def_op, rest_name_idx, self.line);
2308 } else {
2309 self.chunk.emit(Op::Pop, self.line);
2311 }
2312 }
2313 }
2314 Ok(())
2315 }
2316
2317 fn produces_value(node: &Node) -> bool {
2319 match node {
2320 Node::LetBinding { .. }
2322 | Node::VarBinding { .. }
2323 | Node::Assignment { .. }
2324 | Node::ReturnStmt { .. }
2325 | Node::FnDecl { .. }
2326 | Node::ImplBlock { .. }
2327 | Node::StructDecl { .. }
2328 | Node::ThrowStmt { .. }
2329 | Node::BreakStmt
2330 | Node::ContinueStmt => false,
2331 Node::TryCatch { .. }
2333 | Node::TryExpr { .. }
2334 | Node::Retry { .. }
2335 | Node::GuardStmt { .. }
2336 | Node::DeadlineBlock { .. }
2337 | Node::MutexBlock { .. }
2338 | Node::Spread(_) => true,
2339 _ => true,
2341 }
2342 }
2343}
2344
2345impl Compiler {
2346 pub fn compile_fn_body(
2348 &mut self,
2349 params: &[TypedParam],
2350 body: &[SNode],
2351 ) -> Result<CompiledFunction, CompileError> {
2352 let mut fn_compiler = Compiler::new();
2353 fn_compiler.compile_block(body)?;
2354 fn_compiler.chunk.emit(Op::Nil, 0);
2355 fn_compiler.chunk.emit(Op::Return, 0);
2356 Ok(CompiledFunction {
2357 name: String::new(),
2358 params: TypedParam::names(params),
2359 default_start: TypedParam::default_start(params),
2360 chunk: fn_compiler.chunk,
2361 })
2362 }
2363
2364 fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
2366 if body.is_empty() {
2367 self.chunk.emit(Op::Nil, self.line);
2368 } else {
2369 self.compile_block(body)?;
2370 if !Self::produces_value(&body.last().unwrap().node) {
2372 self.chunk.emit(Op::Nil, self.line);
2373 }
2374 }
2375 Ok(())
2376 }
2377
2378 fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
2380 match op {
2381 "+" => self.chunk.emit(Op::Add, self.line),
2382 "-" => self.chunk.emit(Op::Sub, self.line),
2383 "*" => self.chunk.emit(Op::Mul, self.line),
2384 "/" => self.chunk.emit(Op::Div, self.line),
2385 "%" => self.chunk.emit(Op::Mod, self.line),
2386 _ => {
2387 return Err(CompileError {
2388 message: format!("Unknown compound operator: {op}"),
2389 line: self.line,
2390 })
2391 }
2392 }
2393 Ok(())
2394 }
2395
2396 fn root_var_name(&self, node: &SNode) -> Option<String> {
2398 match &node.node {
2399 Node::Identifier(name) => Some(name.clone()),
2400 Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
2401 self.root_var_name(object)
2402 }
2403 Node::SubscriptAccess { object, .. } => self.root_var_name(object),
2404 _ => None,
2405 }
2406 }
2407}
2408
2409impl Compiler {
2410 fn collect_enum_names(nodes: &[SNode], names: &mut std::collections::HashSet<String>) {
2412 for sn in nodes {
2413 match &sn.node {
2414 Node::EnumDecl { name, .. } => {
2415 names.insert(name.clone());
2416 }
2417 Node::Pipeline { body, .. } => {
2418 Self::collect_enum_names(body, names);
2419 }
2420 Node::FnDecl { body, .. } => {
2421 Self::collect_enum_names(body, names);
2422 }
2423 Node::Block(stmts) => {
2424 Self::collect_enum_names(stmts, names);
2425 }
2426 _ => {}
2427 }
2428 }
2429 }
2430}
2431
2432impl Default for Compiler {
2433 fn default() -> Self {
2434 Self::new()
2435 }
2436}
2437
2438fn contains_pipe_placeholder(node: &SNode) -> bool {
2440 match &node.node {
2441 Node::Identifier(name) if name == "_" => true,
2442 Node::FunctionCall { args, .. } => args.iter().any(contains_pipe_placeholder),
2443 Node::MethodCall { object, args, .. } => {
2444 contains_pipe_placeholder(object) || args.iter().any(contains_pipe_placeholder)
2445 }
2446 Node::BinaryOp { left, right, .. } => {
2447 contains_pipe_placeholder(left) || contains_pipe_placeholder(right)
2448 }
2449 Node::UnaryOp { operand, .. } => contains_pipe_placeholder(operand),
2450 Node::ListLiteral(items) => items.iter().any(contains_pipe_placeholder),
2451 Node::PropertyAccess { object, .. } => contains_pipe_placeholder(object),
2452 Node::SubscriptAccess { object, index } => {
2453 contains_pipe_placeholder(object) || contains_pipe_placeholder(index)
2454 }
2455 _ => false,
2456 }
2457}
2458
2459fn replace_pipe_placeholder(node: &SNode) -> SNode {
2461 let new_node = match &node.node {
2462 Node::Identifier(name) if name == "_" => Node::Identifier("__pipe".into()),
2463 Node::FunctionCall { name, args } => Node::FunctionCall {
2464 name: name.clone(),
2465 args: args.iter().map(replace_pipe_placeholder).collect(),
2466 },
2467 Node::MethodCall {
2468 object,
2469 method,
2470 args,
2471 } => Node::MethodCall {
2472 object: Box::new(replace_pipe_placeholder(object)),
2473 method: method.clone(),
2474 args: args.iter().map(replace_pipe_placeholder).collect(),
2475 },
2476 Node::BinaryOp { op, left, right } => Node::BinaryOp {
2477 op: op.clone(),
2478 left: Box::new(replace_pipe_placeholder(left)),
2479 right: Box::new(replace_pipe_placeholder(right)),
2480 },
2481 Node::UnaryOp { op, operand } => Node::UnaryOp {
2482 op: op.clone(),
2483 operand: Box::new(replace_pipe_placeholder(operand)),
2484 },
2485 Node::ListLiteral(items) => {
2486 Node::ListLiteral(items.iter().map(replace_pipe_placeholder).collect())
2487 }
2488 Node::PropertyAccess { object, property } => Node::PropertyAccess {
2489 object: Box::new(replace_pipe_placeholder(object)),
2490 property: property.clone(),
2491 },
2492 Node::SubscriptAccess { object, index } => Node::SubscriptAccess {
2493 object: Box::new(replace_pipe_placeholder(object)),
2494 index: Box::new(replace_pipe_placeholder(index)),
2495 },
2496 _ => return node.clone(),
2497 };
2498 SNode::new(new_node, node.span)
2499}
2500
2501#[cfg(test)]
2502mod tests {
2503 use super::*;
2504 use harn_lexer::Lexer;
2505 use harn_parser::Parser;
2506
2507 fn compile_source(source: &str) -> Chunk {
2508 let mut lexer = Lexer::new(source);
2509 let tokens = lexer.tokenize().unwrap();
2510 let mut parser = Parser::new(tokens);
2511 let program = parser.parse().unwrap();
2512 Compiler::new().compile(&program).unwrap()
2513 }
2514
2515 #[test]
2516 fn test_compile_arithmetic() {
2517 let chunk = compile_source("pipeline test(task) { let x = 2 + 3 }");
2518 assert!(!chunk.code.is_empty());
2519 assert!(chunk.constants.contains(&Constant::Int(2)));
2521 assert!(chunk.constants.contains(&Constant::Int(3)));
2522 }
2523
2524 #[test]
2525 fn test_compile_function_call() {
2526 let chunk = compile_source("pipeline test(task) { log(42) }");
2527 let disasm = chunk.disassemble("test");
2528 assert!(disasm.contains("CALL"));
2529 }
2530
2531 #[test]
2532 fn test_compile_if_else() {
2533 let chunk =
2534 compile_source(r#"pipeline test(task) { if true { log("yes") } else { log("no") } }"#);
2535 let disasm = chunk.disassemble("test");
2536 assert!(disasm.contains("JUMP_IF_FALSE"));
2537 assert!(disasm.contains("JUMP"));
2538 }
2539
2540 #[test]
2541 fn test_compile_while() {
2542 let chunk = compile_source("pipeline test(task) { var i = 0\n while i < 5 { i = i + 1 } }");
2543 let disasm = chunk.disassemble("test");
2544 assert!(disasm.contains("JUMP_IF_FALSE"));
2545 assert!(disasm.contains("JUMP"));
2547 }
2548
2549 #[test]
2550 fn test_compile_closure() {
2551 let chunk = compile_source("pipeline test(task) { let f = { x -> x * 2 } }");
2552 assert!(!chunk.functions.is_empty());
2553 assert_eq!(chunk.functions[0].params, vec!["x"]);
2554 }
2555
2556 #[test]
2557 fn test_compile_list() {
2558 let chunk = compile_source("pipeline test(task) { let a = [1, 2, 3] }");
2559 let disasm = chunk.disassemble("test");
2560 assert!(disasm.contains("BUILD_LIST"));
2561 }
2562
2563 #[test]
2564 fn test_compile_dict() {
2565 let chunk = compile_source(r#"pipeline test(task) { let d = {name: "test"} }"#);
2566 let disasm = chunk.disassemble("test");
2567 assert!(disasm.contains("BUILD_DICT"));
2568 }
2569
2570 #[test]
2571 fn test_disassemble() {
2572 let chunk = compile_source("pipeline test(task) { log(2 + 3) }");
2573 let disasm = chunk.disassemble("test");
2574 assert!(disasm.contains("CONSTANT"));
2576 assert!(disasm.contains("ADD"));
2577 assert!(disasm.contains("CALL"));
2578 }
2579}