1use std::collections::BTreeMap;
2use std::rc::Rc;
3
4use harn_lexer::StringSegment;
5use harn_parser::{BindingPattern, Node, ParallelMode, SNode, ShapeField, TypeExpr, TypedParam};
6
7use crate::chunk::{Chunk, CompiledFunction, Constant, Op};
8use crate::schema;
9use crate::value::VmValue;
10
11#[derive(Debug)]
13pub struct CompileError {
14 pub message: String,
15 pub line: u32,
16}
17
18impl std::fmt::Display for CompileError {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 write!(f, "Compile error at line {}: {}", self.line, self.message)
21 }
22}
23
24impl std::error::Error for CompileError {}
25
26#[derive(Clone, Debug)]
29enum FinallyEntry {
30 Finally(Vec<SNode>),
31 CatchBarrier,
32}
33
34struct LoopContext {
36 start_offset: usize,
38 break_patches: Vec<usize>,
40 has_iterator: bool,
42 handler_depth: usize,
44 finally_depth: usize,
46 scope_depth: usize,
48}
49
50pub struct Compiler {
52 chunk: Chunk,
53 line: u32,
54 column: u32,
55 enum_names: std::collections::HashSet<String>,
57 interface_methods: std::collections::HashMap<String, Vec<String>>,
59 loop_stack: Vec<LoopContext>,
61 handler_depth: usize,
63 finally_bodies: Vec<FinallyEntry>,
75 temp_counter: usize,
77 scope_depth: usize,
79 type_aliases: std::collections::HashMap<String, TypeExpr>,
82 module_level: bool,
88}
89
90impl Compiler {
91 pub fn new() -> Self {
92 Self {
93 chunk: Chunk::new(),
94 line: 1,
95 column: 1,
96 enum_names: std::collections::HashSet::new(),
97 interface_methods: std::collections::HashMap::new(),
98 loop_stack: Vec::new(),
99 handler_depth: 0,
100 finally_bodies: Vec::new(),
101 temp_counter: 0,
102 scope_depth: 0,
103 type_aliases: std::collections::HashMap::new(),
104 module_level: true,
105 }
106 }
107
108 fn for_nested_body() -> Self {
112 let mut c = Self::new();
113 c.module_level = false;
114 c
115 }
116
117 fn collect_type_aliases(&mut self, program: &[SNode]) {
121 for sn in program {
122 if let Node::TypeDecl {
123 name,
124 type_expr,
125 type_params: _,
126 } = &sn.node
127 {
128 self.type_aliases.insert(name.clone(), type_expr.clone());
129 }
130 }
131 }
132
133 fn expand_alias(&self, ty: &TypeExpr) -> TypeExpr {
137 match ty {
138 TypeExpr::Named(name) => {
139 if let Some(target) = self.type_aliases.get(name) {
140 self.expand_alias(target)
141 } else {
142 TypeExpr::Named(name.clone())
143 }
144 }
145 TypeExpr::Union(types) => {
146 TypeExpr::Union(types.iter().map(|t| self.expand_alias(t)).collect())
147 }
148 TypeExpr::Shape(fields) => TypeExpr::Shape(
149 fields
150 .iter()
151 .map(|field| ShapeField {
152 name: field.name.clone(),
153 type_expr: self.expand_alias(&field.type_expr),
154 optional: field.optional,
155 })
156 .collect(),
157 ),
158 TypeExpr::List(inner) => TypeExpr::List(Box::new(self.expand_alias(inner))),
159 TypeExpr::Iter(inner) => TypeExpr::Iter(Box::new(self.expand_alias(inner))),
160 TypeExpr::DictType(k, v) => TypeExpr::DictType(
161 Box::new(self.expand_alias(k)),
162 Box::new(self.expand_alias(v)),
163 ),
164 TypeExpr::FnType {
165 params,
166 return_type,
167 } => TypeExpr::FnType {
168 params: params.iter().map(|p| self.expand_alias(p)).collect(),
169 return_type: Box::new(self.expand_alias(return_type)),
170 },
171 TypeExpr::Applied { name, args } => TypeExpr::Applied {
172 name: name.clone(),
173 args: args.iter().map(|a| self.expand_alias(a)).collect(),
174 },
175 TypeExpr::Never => TypeExpr::Never,
176 TypeExpr::LitString(s) => TypeExpr::LitString(s.clone()),
177 TypeExpr::LitInt(v) => TypeExpr::LitInt(*v),
178 }
179 }
180
181 fn schema_value_for_alias(&self, name: &str) -> Option<VmValue> {
184 let ty = self.type_aliases.get(name)?;
185 let expanded = self.expand_alias(ty);
186 Self::type_expr_to_schema_value(&expanded)
187 }
188
189 fn is_schema_guard(name: &str) -> bool {
193 matches!(
194 name,
195 "schema_is"
196 | "schema_expect"
197 | "schema_parse"
198 | "schema_check"
199 | "is_type"
200 | "json_validate"
201 )
202 }
203
204 fn entry_key_is(key: &SNode, keyword: &str) -> bool {
207 matches!(
208 &key.node,
209 Node::Identifier(name) | Node::StringLiteral(name) | Node::RawStringLiteral(name)
210 if name == keyword
211 )
212 }
213
214 pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
217 Self::collect_enum_names(program, &mut self.enum_names);
220 self.enum_names.insert("Result".to_string());
221 Self::collect_interface_methods(program, &mut self.interface_methods);
222 self.collect_type_aliases(program);
223
224 for sn in program {
225 match &sn.node {
226 Node::ImportDecl { .. } | Node::SelectiveImport { .. } => {
227 self.compile_node(sn)?;
228 }
229 _ => {}
230 }
231 }
232 let main = program
233 .iter()
234 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == "default"))
235 .or_else(|| {
236 program
237 .iter()
238 .find(|sn| matches!(&sn.node, Node::Pipeline { .. }))
239 });
240
241 if let Some(sn) = main {
242 self.compile_top_level_declarations(program)?;
243 if let Node::Pipeline { body, extends, .. } = &sn.node {
244 if let Some(parent_name) = extends {
245 self.compile_parent_pipeline(program, parent_name)?;
246 }
247 let saved = std::mem::replace(&mut self.module_level, false);
248 self.compile_block(body)?;
249 self.module_level = saved;
250 }
251 } else {
252 let top_level: Vec<&SNode> = program
254 .iter()
255 .filter(|sn| {
256 !matches!(
257 &sn.node,
258 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
259 )
260 })
261 .collect();
262 for sn in &top_level {
263 self.compile_node(sn)?;
264 if Self::produces_value(&sn.node) {
265 self.chunk.emit(Op::Pop, self.line);
266 }
267 }
268 }
269
270 for fb in self.all_pending_finallys() {
271 self.compile_finally_inline(&fb)?;
272 }
273 self.chunk.emit(Op::Nil, self.line);
274 self.chunk.emit(Op::Return, self.line);
275 Ok(self.chunk)
276 }
277
278 pub fn compile_named(
280 mut self,
281 program: &[SNode],
282 pipeline_name: &str,
283 ) -> Result<Chunk, CompileError> {
284 Self::collect_enum_names(program, &mut self.enum_names);
285 Self::collect_interface_methods(program, &mut self.interface_methods);
286 self.collect_type_aliases(program);
287
288 for sn in program {
289 if matches!(
290 &sn.node,
291 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
292 ) {
293 self.compile_node(sn)?;
294 }
295 }
296 let target = program
297 .iter()
298 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == pipeline_name));
299
300 if let Some(sn) = target {
301 self.compile_top_level_declarations(program)?;
302 if let Node::Pipeline { body, extends, .. } = &sn.node {
303 if let Some(parent_name) = extends {
304 self.compile_parent_pipeline(program, parent_name)?;
305 }
306 let saved = std::mem::replace(&mut self.module_level, false);
307 self.compile_block(body)?;
308 self.module_level = saved;
309 }
310 }
311
312 for fb in self.all_pending_finallys() {
313 self.compile_finally_inline(&fb)?;
314 }
315 self.chunk.emit(Op::Nil, self.line);
316 self.chunk.emit(Op::Return, self.line);
317 Ok(self.chunk)
318 }
319
320 fn compile_parent_pipeline(
322 &mut self,
323 program: &[SNode],
324 parent_name: &str,
325 ) -> Result<(), CompileError> {
326 let parent = program
327 .iter()
328 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == parent_name));
329 if let Some(sn) = parent {
330 if let Node::Pipeline { body, extends, .. } = &sn.node {
331 if let Some(grandparent) = extends {
332 self.compile_parent_pipeline(program, grandparent)?;
333 }
334 for stmt in body {
335 self.compile_node(stmt)?;
336 if Self::produces_value(&stmt.node) {
337 self.chunk.emit(Op::Pop, self.line);
338 }
339 }
340 }
341 }
342 Ok(())
343 }
344
345 fn emit_default_preamble(&mut self, params: &[TypedParam]) -> Result<(), CompileError> {
350 for (i, param) in params.iter().enumerate() {
351 if let Some(default_expr) = ¶m.default_value {
352 self.chunk.emit(Op::GetArgc, self.line);
353 let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
354 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
355 self.chunk.emit(Op::GreaterEqual, self.line);
356 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
357 self.chunk.emit(Op::Pop, self.line);
359 self.compile_node(default_expr)?;
360 let name_idx = self
361 .chunk
362 .add_constant(Constant::String(param.name.clone()));
363 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
364 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
365 self.chunk.patch_jump(skip_jump);
366 self.chunk.emit(Op::Pop, self.line);
367 self.chunk.patch_jump(end_jump);
368 }
369 }
370 Ok(())
371 }
372
373 fn emit_type_checks(&mut self, params: &[TypedParam]) {
378 for param in params {
379 if let Some(type_expr) = ¶m.type_expr {
380 if let harn_parser::TypeExpr::Named(name) = type_expr {
381 if let Some(methods) = self.interface_methods.get(name) {
382 let fn_idx = self
383 .chunk
384 .add_constant(Constant::String("__assert_interface".into()));
385 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
386 let var_idx = self
387 .chunk
388 .add_constant(Constant::String(param.name.clone()));
389 self.chunk.emit_u16(Op::GetVar, var_idx, self.line);
390 let name_idx = self
391 .chunk
392 .add_constant(Constant::String(param.name.clone()));
393 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
394 let iface_idx = self.chunk.add_constant(Constant::String(name.clone()));
395 self.chunk.emit_u16(Op::Constant, iface_idx, self.line);
396 let methods_str = methods.join(",");
397 let methods_idx = self.chunk.add_constant(Constant::String(methods_str));
398 self.chunk.emit_u16(Op::Constant, methods_idx, self.line);
399 self.chunk.emit_u8(Op::Call, 4, self.line);
400 self.chunk.emit(Op::Pop, self.line);
401 continue;
402 }
403 }
404
405 if let Some(schema) = Self::type_expr_to_schema_value(type_expr) {
406 let fn_idx = self
407 .chunk
408 .add_constant(Constant::String("__assert_schema".into()));
409 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
410 let var_idx = self
411 .chunk
412 .add_constant(Constant::String(param.name.clone()));
413 self.chunk.emit_u16(Op::GetVar, var_idx, self.line);
414 let name_idx = self
415 .chunk
416 .add_constant(Constant::String(param.name.clone()));
417 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
418 self.emit_vm_value_literal(&schema);
419 self.chunk.emit_u8(Op::Call, 3, self.line);
420 self.chunk.emit(Op::Pop, self.line);
421 }
422 }
423 }
424 }
425
426 fn type_expr_to_schema_value(type_expr: &harn_parser::TypeExpr) -> Option<VmValue> {
427 match type_expr {
428 harn_parser::TypeExpr::Named(name) => match name.as_str() {
429 "int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
430 | "closure" => Some(VmValue::Dict(Rc::new(BTreeMap::from([(
431 "type".to_string(),
432 VmValue::String(Rc::from(name.as_str())),
433 )])))),
434 _ => None,
435 },
436 harn_parser::TypeExpr::Shape(fields) => {
437 let mut properties = BTreeMap::new();
438 let mut required = Vec::new();
439 for field in fields {
440 let field_schema = Self::type_expr_to_schema_value(&field.type_expr)?;
441 properties.insert(field.name.clone(), field_schema);
442 if !field.optional {
443 required.push(VmValue::String(Rc::from(field.name.as_str())));
444 }
445 }
446 let mut out = BTreeMap::new();
447 out.insert("type".to_string(), VmValue::String(Rc::from("dict")));
448 out.insert("properties".to_string(), VmValue::Dict(Rc::new(properties)));
449 if !required.is_empty() {
450 out.insert("required".to_string(), VmValue::List(Rc::new(required)));
451 }
452 Some(VmValue::Dict(Rc::new(out)))
453 }
454 harn_parser::TypeExpr::List(inner) => {
455 let mut out = BTreeMap::new();
456 out.insert("type".to_string(), VmValue::String(Rc::from("list")));
457 if let Some(item_schema) = Self::type_expr_to_schema_value(inner) {
458 out.insert("items".to_string(), item_schema);
459 }
460 Some(VmValue::Dict(Rc::new(out)))
461 }
462 harn_parser::TypeExpr::DictType(key, value) => {
463 let mut out = BTreeMap::new();
464 out.insert("type".to_string(), VmValue::String(Rc::from("dict")));
465 if matches!(key.as_ref(), harn_parser::TypeExpr::Named(name) if name == "string") {
466 if let Some(value_schema) = Self::type_expr_to_schema_value(value) {
467 out.insert("additional_properties".to_string(), value_schema);
468 }
469 }
470 Some(VmValue::Dict(Rc::new(out)))
471 }
472 harn_parser::TypeExpr::Union(members) => {
473 if !members.is_empty()
478 && members
479 .iter()
480 .all(|m| matches!(m, harn_parser::TypeExpr::LitString(_)))
481 {
482 let values = members
483 .iter()
484 .map(|m| match m {
485 harn_parser::TypeExpr::LitString(s) => {
486 VmValue::String(Rc::from(s.as_str()))
487 }
488 _ => unreachable!(),
489 })
490 .collect::<Vec<_>>();
491 return Some(VmValue::Dict(Rc::new(BTreeMap::from([
492 ("type".to_string(), VmValue::String(Rc::from("string"))),
493 ("enum".to_string(), VmValue::List(Rc::new(values))),
494 ]))));
495 }
496 if !members.is_empty()
497 && members
498 .iter()
499 .all(|m| matches!(m, harn_parser::TypeExpr::LitInt(_)))
500 {
501 let values = members
502 .iter()
503 .map(|m| match m {
504 harn_parser::TypeExpr::LitInt(v) => VmValue::Int(*v),
505 _ => unreachable!(),
506 })
507 .collect::<Vec<_>>();
508 return Some(VmValue::Dict(Rc::new(BTreeMap::from([
509 ("type".to_string(), VmValue::String(Rc::from("int"))),
510 ("enum".to_string(), VmValue::List(Rc::new(values))),
511 ]))));
512 }
513 let branches = members
514 .iter()
515 .filter_map(Self::type_expr_to_schema_value)
516 .collect::<Vec<_>>();
517 if branches.is_empty() {
518 None
519 } else {
520 Some(VmValue::Dict(Rc::new(BTreeMap::from([(
521 "union".to_string(),
522 VmValue::List(Rc::new(branches)),
523 )]))))
524 }
525 }
526 harn_parser::TypeExpr::FnType { .. } => {
527 Some(VmValue::Dict(Rc::new(BTreeMap::from([(
528 "type".to_string(),
529 VmValue::String(Rc::from("closure")),
530 )]))))
531 }
532 harn_parser::TypeExpr::Applied { .. } => None,
533 harn_parser::TypeExpr::Iter(_) => None,
534 harn_parser::TypeExpr::Never => None,
535 harn_parser::TypeExpr::LitString(s) => Some(VmValue::Dict(Rc::new(BTreeMap::from([
536 ("type".to_string(), VmValue::String(Rc::from("string"))),
537 ("const".to_string(), VmValue::String(Rc::from(s.as_str()))),
538 ])))),
539 harn_parser::TypeExpr::LitInt(v) => Some(VmValue::Dict(Rc::new(BTreeMap::from([
540 ("type".to_string(), VmValue::String(Rc::from("int"))),
541 ("const".to_string(), VmValue::Int(*v)),
542 ])))),
543 }
544 }
545
546 fn emit_vm_value_literal(&mut self, value: &VmValue) {
547 match value {
548 VmValue::String(text) => {
549 let idx = self.chunk.add_constant(Constant::String(text.to_string()));
550 self.chunk.emit_u16(Op::Constant, idx, self.line);
551 }
552 VmValue::Int(number) => {
553 let idx = self.chunk.add_constant(Constant::Int(*number));
554 self.chunk.emit_u16(Op::Constant, idx, self.line);
555 }
556 VmValue::Float(number) => {
557 let idx = self.chunk.add_constant(Constant::Float(*number));
558 self.chunk.emit_u16(Op::Constant, idx, self.line);
559 }
560 VmValue::Bool(value) => {
561 let idx = self.chunk.add_constant(Constant::Bool(*value));
562 self.chunk.emit_u16(Op::Constant, idx, self.line);
563 }
564 VmValue::Nil => self.chunk.emit(Op::Nil, self.line),
565 VmValue::List(items) => {
566 for item in items.iter() {
567 self.emit_vm_value_literal(item);
568 }
569 self.chunk
570 .emit_u16(Op::BuildList, items.len() as u16, self.line);
571 }
572 VmValue::Dict(entries) => {
573 for (key, item) in entries.iter() {
574 let key_idx = self.chunk.add_constant(Constant::String(key.clone()));
575 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
576 self.emit_vm_value_literal(item);
577 }
578 self.chunk
579 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
580 }
581 _ => {}
582 }
583 }
584
585 fn emit_type_name_extra(&mut self, type_name_idx: u16) {
587 let hi = (type_name_idx >> 8) as u8;
588 let lo = type_name_idx as u8;
589 self.chunk.code.push(hi);
590 self.chunk.code.push(lo);
591 self.chunk.lines.push(self.line);
592 self.chunk.columns.push(self.column);
593 self.chunk.lines.push(self.line);
594 self.chunk.columns.push(self.column);
595 }
596
597 fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
599 if body.is_empty() {
600 self.chunk.emit(Op::Nil, self.line);
601 } else {
602 self.compile_scoped_block(body)?;
603 }
604 Ok(())
605 }
606
607 fn compile_catch_binding(&mut self, error_var: &Option<String>) -> Result<(), CompileError> {
609 if let Some(var_name) = error_var {
610 let idx = self.chunk.add_constant(Constant::String(var_name.clone()));
611 self.chunk.emit_u16(Op::DefLet, idx, self.line);
612 } else {
613 self.chunk.emit(Op::Pop, self.line);
614 }
615 Ok(())
616 }
617
618 fn compile_finally_inline(&mut self, finally_body: &[SNode]) -> Result<(), CompileError> {
625 if !finally_body.is_empty() {
626 self.compile_scoped_block(finally_body)?;
627 self.chunk.emit(Op::Pop, self.line);
628 }
629 Ok(())
630 }
631
632 fn pending_finallys_until_barrier(&self) -> Vec<Vec<SNode>> {
637 let mut out = Vec::new();
638 for entry in self.finally_bodies.iter().rev() {
639 match entry {
640 FinallyEntry::CatchBarrier => break,
641 FinallyEntry::Finally(body) => out.push(body.clone()),
642 }
643 }
644 out
645 }
646
647 fn pending_finallys_down_to(&self, floor: usize) -> Vec<Vec<SNode>> {
653 let mut out = Vec::new();
654 for entry in self.finally_bodies[floor..].iter().rev() {
655 if let FinallyEntry::Finally(body) = entry {
656 out.push(body.clone());
657 }
658 }
659 out
660 }
661
662 fn all_pending_finallys(&self) -> Vec<Vec<SNode>> {
664 self.pending_finallys_down_to(0)
665 }
666
667 fn has_pending_finally(&self) -> bool {
669 self.finally_bodies
670 .iter()
671 .any(|e| matches!(e, FinallyEntry::Finally(_)))
672 }
673
674 fn compile_plain_rethrow(&mut self) -> Result<(), CompileError> {
683 self.temp_counter += 1;
684 let temp_name = format!("__finally_err_{}__", self.temp_counter);
685 let err_idx = self.chunk.add_constant(Constant::String(temp_name.clone()));
686 self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
687 let get_idx = self.chunk.add_constant(Constant::String(temp_name));
688 self.chunk.emit_u16(Op::GetVar, get_idx, self.line);
689 self.chunk.emit(Op::Throw, self.line);
690 Ok(())
691 }
692
693 fn begin_scope(&mut self) {
694 self.chunk.emit(Op::PushScope, self.line);
695 self.scope_depth += 1;
696 }
697
698 fn end_scope(&mut self) {
699 if self.scope_depth > 0 {
700 self.chunk.emit(Op::PopScope, self.line);
701 self.scope_depth -= 1;
702 }
703 }
704
705 fn unwind_scopes_to(&mut self, target_depth: usize) {
706 while self.scope_depth > target_depth {
707 self.chunk.emit(Op::PopScope, self.line);
708 self.scope_depth -= 1;
709 }
710 }
711
712 fn compile_scoped_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
713 self.begin_scope();
714 if stmts.is_empty() {
715 self.chunk.emit(Op::Nil, self.line);
716 } else {
717 self.compile_block(stmts)?;
718 }
719 self.end_scope();
720 Ok(())
721 }
722
723 fn compile_scoped_statements(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
724 self.begin_scope();
725 for sn in stmts {
726 self.compile_node(sn)?;
727 if Self::produces_value(&sn.node) {
728 self.chunk.emit(Op::Pop, self.line);
729 }
730 }
731 self.end_scope();
732 Ok(())
733 }
734
735 fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
736 for (i, snode) in stmts.iter().enumerate() {
737 self.compile_node(snode)?;
738 let is_last = i == stmts.len() - 1;
739 if is_last {
740 if !Self::produces_value(&snode.node) {
742 self.chunk.emit(Op::Nil, self.line);
743 }
744 } else {
745 if Self::produces_value(&snode.node) {
746 self.chunk.emit(Op::Pop, self.line);
747 }
748 }
749 }
750 Ok(())
751 }
752
753 fn compile_node(&mut self, snode: &SNode) -> Result<(), CompileError> {
754 self.line = snode.span.line as u32;
755 self.column = snode.span.column as u32;
756 self.chunk.set_column(self.column);
757 match &snode.node {
758 Node::IntLiteral(n) => {
759 let idx = self.chunk.add_constant(Constant::Int(*n));
760 self.chunk.emit_u16(Op::Constant, idx, self.line);
761 }
762 Node::FloatLiteral(n) => {
763 let idx = self.chunk.add_constant(Constant::Float(*n));
764 self.chunk.emit_u16(Op::Constant, idx, self.line);
765 }
766 Node::StringLiteral(s) | Node::RawStringLiteral(s) => {
767 let idx = self.chunk.add_constant(Constant::String(s.clone()));
768 self.chunk.emit_u16(Op::Constant, idx, self.line);
769 }
770 Node::BoolLiteral(true) => self.chunk.emit(Op::True, self.line),
771 Node::BoolLiteral(false) => self.chunk.emit(Op::False, self.line),
772 Node::NilLiteral => self.chunk.emit(Op::Nil, self.line),
773 Node::DurationLiteral(ms) => {
774 let idx = self.chunk.add_constant(Constant::Duration(*ms));
775 self.chunk.emit_u16(Op::Constant, idx, self.line);
776 }
777
778 Node::Identifier(name) => {
779 let idx = self.chunk.add_constant(Constant::String(name.clone()));
780 self.chunk.emit_u16(Op::GetVar, idx, self.line);
781 }
782
783 Node::LetBinding { pattern, value, .. } => {
784 self.compile_node(value)?;
785 self.compile_destructuring(pattern, false)?;
786 }
787
788 Node::VarBinding { pattern, value, .. } => {
789 self.compile_node(value)?;
790 self.compile_destructuring(pattern, true)?;
791 }
792
793 Node::Assignment {
794 target, value, op, ..
795 } => {
796 if let Node::Identifier(name) = &target.node {
797 let idx = self.chunk.add_constant(Constant::String(name.clone()));
798 if let Some(op) = op {
799 self.chunk.emit_u16(Op::GetVar, idx, self.line);
800 self.compile_node(value)?;
801 self.emit_compound_op(op)?;
802 self.chunk.emit_u16(Op::SetVar, idx, self.line);
803 } else {
804 self.compile_node(value)?;
805 self.chunk.emit_u16(Op::SetVar, idx, self.line);
806 }
807 } else if let Node::PropertyAccess { object, property } = &target.node {
808 if let Some(var_name) = self.root_var_name(object) {
809 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
810 let prop_idx = self.chunk.add_constant(Constant::String(property.clone()));
811 if let Some(op) = op {
812 self.compile_node(target)?;
813 self.compile_node(value)?;
814 self.emit_compound_op(op)?;
815 } else {
816 self.compile_node(value)?;
817 }
818 self.chunk.emit_u16(Op::SetProperty, prop_idx, self.line);
821 let hi = (var_idx >> 8) as u8;
822 let lo = var_idx as u8;
823 self.chunk.code.push(hi);
824 self.chunk.code.push(lo);
825 self.chunk.lines.push(self.line);
826 self.chunk.columns.push(self.column);
827 self.chunk.lines.push(self.line);
828 self.chunk.columns.push(self.column);
829 }
830 } else if let Node::SubscriptAccess { object, index } = &target.node {
831 if let Some(var_name) = self.root_var_name(object) {
832 let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
833 if let Some(op) = op {
834 self.compile_node(target)?;
835 self.compile_node(value)?;
836 self.emit_compound_op(op)?;
837 } else {
838 self.compile_node(value)?;
839 }
840 self.compile_node(index)?;
841 self.chunk.emit_u16(Op::SetSubscript, var_idx, self.line);
842 }
843 }
844 }
845
846 Node::BinaryOp { op, left, right } => {
847 match op.as_str() {
848 "&&" => {
849 self.compile_node(left)?;
850 let jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
851 self.chunk.emit(Op::Pop, self.line);
852 self.compile_node(right)?;
853 self.chunk.patch_jump(jump);
854 self.chunk.emit(Op::Not, self.line);
856 self.chunk.emit(Op::Not, self.line);
857 return Ok(());
858 }
859 "||" => {
860 self.compile_node(left)?;
861 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
862 self.chunk.emit(Op::Pop, self.line);
863 self.compile_node(right)?;
864 self.chunk.patch_jump(jump);
865 self.chunk.emit(Op::Not, self.line);
866 self.chunk.emit(Op::Not, self.line);
867 return Ok(());
868 }
869 "??" => {
870 self.compile_node(left)?;
871 self.chunk.emit(Op::Dup, self.line);
872 self.chunk.emit(Op::Nil, self.line);
873 self.chunk.emit(Op::NotEqual, self.line);
874 let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
875 self.chunk.emit(Op::Pop, self.line);
876 self.chunk.emit(Op::Pop, self.line);
877 self.compile_node(right)?;
878 let end = self.chunk.emit_jump(Op::Jump, self.line);
879 self.chunk.patch_jump(jump);
880 self.chunk.emit(Op::Pop, self.line);
881 self.chunk.patch_jump(end);
882 return Ok(());
883 }
884 "|>" => {
885 self.compile_node(left)?;
886 if contains_pipe_placeholder(right) {
888 let replaced = replace_pipe_placeholder(right);
889 let closure_node = SNode::dummy(Node::Closure {
890 params: vec![TypedParam {
891 name: "__pipe".into(),
892 type_expr: None,
893 default_value: None,
894 rest: false,
895 }],
896 body: vec![replaced],
897 fn_syntax: false,
898 });
899 self.compile_node(&closure_node)?;
900 } else {
901 self.compile_node(right)?;
902 }
903 self.chunk.emit(Op::Pipe, self.line);
904 return Ok(());
905 }
906 _ => {}
907 }
908
909 self.compile_node(left)?;
910 self.compile_node(right)?;
911 match op.as_str() {
912 "+" => self.chunk.emit(Op::Add, self.line),
913 "-" => self.chunk.emit(Op::Sub, self.line),
914 "*" => self.chunk.emit(Op::Mul, self.line),
915 "/" => self.chunk.emit(Op::Div, self.line),
916 "%" => self.chunk.emit(Op::Mod, self.line),
917 "**" => self.chunk.emit(Op::Pow, self.line),
918 "==" => self.chunk.emit(Op::Equal, self.line),
919 "!=" => self.chunk.emit(Op::NotEqual, self.line),
920 "<" => self.chunk.emit(Op::Less, self.line),
921 ">" => self.chunk.emit(Op::Greater, self.line),
922 "<=" => self.chunk.emit(Op::LessEqual, self.line),
923 ">=" => self.chunk.emit(Op::GreaterEqual, self.line),
924 "in" => self.chunk.emit(Op::Contains, self.line),
925 "not_in" => {
926 self.chunk.emit(Op::Contains, self.line);
927 self.chunk.emit(Op::Not, self.line);
928 }
929 _ => {
930 return Err(CompileError {
931 message: format!("Unknown operator: {op}"),
932 line: self.line,
933 })
934 }
935 }
936 }
937
938 Node::UnaryOp { op, operand } => {
939 self.compile_node(operand)?;
940 match op.as_str() {
941 "-" => self.chunk.emit(Op::Negate, self.line),
942 "!" => self.chunk.emit(Op::Not, self.line),
943 _ => {}
944 }
945 }
946
947 Node::Ternary {
948 condition,
949 true_expr,
950 false_expr,
951 } => {
952 self.compile_node(condition)?;
953 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
954 self.chunk.emit(Op::Pop, self.line);
955 self.compile_node(true_expr)?;
956 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
957 self.chunk.patch_jump(else_jump);
958 self.chunk.emit(Op::Pop, self.line);
959 self.compile_node(false_expr)?;
960 self.chunk.patch_jump(end_jump);
961 }
962
963 Node::FunctionCall { name, args } => {
964 if name == "schema_of" && args.len() == 1 {
970 if let Node::Identifier(alias) = &args[0].node {
971 if let Some(schema) = self.schema_value_for_alias(alias) {
972 self.emit_vm_value_literal(&schema);
973 return Ok(());
974 }
975 }
976 }
977 if Self::is_schema_guard(name) && args.len() >= 2 {
984 if let Node::Identifier(alias) = &args[1].node {
985 if let Some(schema) = self.schema_value_for_alias(alias) {
986 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
987 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
988 self.compile_node(&args[0])?;
989 self.emit_vm_value_literal(&schema);
990 for arg in &args[2..] {
991 self.compile_node(arg)?;
992 }
993 self.chunk.emit_u8(Op::Call, args.len() as u8, self.line);
994 return Ok(());
995 }
996 }
997 }
998
999 let has_spread = args.iter().any(|a| matches!(&a.node, Node::Spread(_)));
1000 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1001 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
1002
1003 if has_spread {
1004 self.chunk.emit_u16(Op::BuildList, 0, self.line);
1007 let mut pending = 0u16;
1008 for arg in args {
1009 if let Node::Spread(inner) = &arg.node {
1010 if pending > 0 {
1011 self.chunk.emit_u16(Op::BuildList, pending, self.line);
1012 self.chunk.emit(Op::Add, self.line);
1013 pending = 0;
1014 }
1015 self.compile_node(inner)?;
1016 self.chunk.emit(Op::Dup, self.line);
1017 let assert_idx = self
1018 .chunk
1019 .add_constant(Constant::String("__assert_list".into()));
1020 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1021 self.chunk.emit(Op::Swap, self.line);
1022 self.chunk.emit_u8(Op::Call, 1, self.line);
1023 self.chunk.emit(Op::Pop, self.line);
1024 self.chunk.emit(Op::Add, self.line);
1025 } else {
1026 self.compile_node(arg)?;
1027 pending += 1;
1028 }
1029 }
1030 if pending > 0 {
1031 self.chunk.emit_u16(Op::BuildList, pending, self.line);
1032 self.chunk.emit(Op::Add, self.line);
1033 }
1034 self.chunk.emit(Op::CallSpread, self.line);
1035 } else {
1036 for arg in args {
1037 self.compile_node(arg)?;
1038 }
1039 self.chunk.emit_u8(Op::Call, args.len() as u8, self.line);
1040 }
1041 }
1042
1043 Node::MethodCall {
1044 object,
1045 method,
1046 args,
1047 } => {
1048 if let Node::Identifier(name) = &object.node {
1050 if self.enum_names.contains(name) {
1051 for arg in args {
1052 self.compile_node(arg)?;
1053 }
1054 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
1055 let var_idx = self.chunk.add_constant(Constant::String(method.clone()));
1056 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
1057 let hi = (var_idx >> 8) as u8;
1058 let lo = var_idx as u8;
1059 self.chunk.code.push(hi);
1060 self.chunk.code.push(lo);
1061 self.chunk.lines.push(self.line);
1062 self.chunk.columns.push(self.column);
1063 self.chunk.lines.push(self.line);
1064 self.chunk.columns.push(self.column);
1065 let fc = args.len() as u16;
1066 let fhi = (fc >> 8) as u8;
1067 let flo = fc as u8;
1068 self.chunk.code.push(fhi);
1069 self.chunk.code.push(flo);
1070 self.chunk.lines.push(self.line);
1071 self.chunk.columns.push(self.column);
1072 self.chunk.lines.push(self.line);
1073 self.chunk.columns.push(self.column);
1074 return Ok(());
1075 }
1076 }
1077 let has_spread = args.iter().any(|a| matches!(&a.node, Node::Spread(_)));
1078 self.compile_node(object)?;
1079 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
1080 if has_spread {
1081 self.chunk.emit_u16(Op::BuildList, 0, self.line);
1082 let mut pending = 0u16;
1083 for arg in args {
1084 if let Node::Spread(inner) = &arg.node {
1085 if pending > 0 {
1086 self.chunk.emit_u16(Op::BuildList, pending, self.line);
1087 self.chunk.emit(Op::Add, self.line);
1088 pending = 0;
1089 }
1090 self.compile_node(inner)?;
1091 self.chunk.emit(Op::Dup, self.line);
1092 let assert_idx = self
1093 .chunk
1094 .add_constant(Constant::String("__assert_list".into()));
1095 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1096 self.chunk.emit(Op::Swap, self.line);
1097 self.chunk.emit_u8(Op::Call, 1, self.line);
1098 self.chunk.emit(Op::Pop, self.line);
1099 self.chunk.emit(Op::Add, self.line);
1100 } else {
1101 self.compile_node(arg)?;
1102 pending += 1;
1103 }
1104 }
1105 if pending > 0 {
1106 self.chunk.emit_u16(Op::BuildList, pending, self.line);
1107 self.chunk.emit(Op::Add, self.line);
1108 }
1109 self.chunk
1110 .emit_u16(Op::MethodCallSpread, name_idx, self.line);
1111 } else {
1112 for arg in args {
1113 self.compile_node(arg)?;
1114 }
1115 self.chunk
1116 .emit_method_call(name_idx, args.len() as u8, self.line);
1117 }
1118 }
1119
1120 Node::OptionalMethodCall {
1121 object,
1122 method,
1123 args,
1124 } => {
1125 self.compile_node(object)?;
1126 for arg in args {
1127 self.compile_node(arg)?;
1128 }
1129 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
1130 self.chunk
1131 .emit_method_call_opt(name_idx, args.len() as u8, self.line);
1132 }
1133
1134 Node::PropertyAccess { object, property } => {
1135 if let Node::Identifier(name) = &object.node {
1137 if self.enum_names.contains(name) {
1138 let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
1139 let var_idx = self.chunk.add_constant(Constant::String(property.clone()));
1140 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
1141 let hi = (var_idx >> 8) as u8;
1142 let lo = var_idx as u8;
1143 self.chunk.code.push(hi);
1144 self.chunk.code.push(lo);
1145 self.chunk.lines.push(self.line);
1146 self.chunk.columns.push(self.column);
1147 self.chunk.lines.push(self.line);
1148 self.chunk.columns.push(self.column);
1149 self.chunk.code.push(0);
1150 self.chunk.code.push(0);
1151 self.chunk.lines.push(self.line);
1152 self.chunk.columns.push(self.column);
1153 self.chunk.lines.push(self.line);
1154 self.chunk.columns.push(self.column);
1155 return Ok(());
1156 }
1157 }
1158 self.compile_node(object)?;
1159 let idx = self.chunk.add_constant(Constant::String(property.clone()));
1160 self.chunk.emit_u16(Op::GetProperty, idx, self.line);
1161 }
1162
1163 Node::OptionalPropertyAccess { object, property } => {
1164 self.compile_node(object)?;
1165 let idx = self.chunk.add_constant(Constant::String(property.clone()));
1166 self.chunk.emit_u16(Op::GetPropertyOpt, idx, self.line);
1167 }
1168
1169 Node::SubscriptAccess { object, index } => {
1170 self.compile_node(object)?;
1171 self.compile_node(index)?;
1172 self.chunk.emit(Op::Subscript, self.line);
1173 }
1174
1175 Node::SliceAccess { object, start, end } => {
1176 self.compile_node(object)?;
1177 if let Some(s) = start {
1178 self.compile_node(s)?;
1179 } else {
1180 self.chunk.emit(Op::Nil, self.line);
1181 }
1182 if let Some(e) = end {
1183 self.compile_node(e)?;
1184 } else {
1185 self.chunk.emit(Op::Nil, self.line);
1186 }
1187 self.chunk.emit(Op::Slice, self.line);
1188 }
1189
1190 Node::IfElse {
1191 condition,
1192 then_body,
1193 else_body,
1194 } => {
1195 self.compile_node(condition)?;
1196 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1197 self.chunk.emit(Op::Pop, self.line);
1198 self.compile_scoped_block(then_body)?;
1199 if let Some(else_body) = else_body {
1200 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1201 self.chunk.patch_jump(else_jump);
1202 self.chunk.emit(Op::Pop, self.line);
1203 self.compile_scoped_block(else_body)?;
1204 self.chunk.patch_jump(end_jump);
1205 } else {
1206 self.chunk.patch_jump(else_jump);
1207 self.chunk.emit(Op::Pop, self.line);
1208 self.chunk.emit(Op::Nil, self.line);
1209 }
1210 }
1211
1212 Node::WhileLoop { condition, body } => {
1213 let loop_start = self.chunk.current_offset();
1214 self.loop_stack.push(LoopContext {
1215 start_offset: loop_start,
1216 break_patches: Vec::new(),
1217 has_iterator: false,
1218 handler_depth: self.handler_depth,
1219 finally_depth: self.finally_bodies.len(),
1220 scope_depth: self.scope_depth,
1221 });
1222 self.compile_node(condition)?;
1223 let exit_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1224 self.chunk.emit(Op::Pop, self.line);
1225 self.compile_scoped_statements(body)?;
1226 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
1228 self.chunk.patch_jump(exit_jump);
1229 self.chunk.emit(Op::Pop, self.line);
1230 let ctx = self.loop_stack.pop().unwrap();
1231 for patch_pos in ctx.break_patches {
1232 self.chunk.patch_jump(patch_pos);
1233 }
1234 self.chunk.emit(Op::Nil, self.line);
1235 }
1236
1237 Node::ForIn {
1238 pattern,
1239 iterable,
1240 body,
1241 } => {
1242 self.compile_node(iterable)?;
1243 self.chunk.emit(Op::IterInit, self.line);
1244 let loop_start = self.chunk.current_offset();
1245 self.loop_stack.push(LoopContext {
1246 start_offset: loop_start,
1247 break_patches: Vec::new(),
1248 has_iterator: true,
1249 handler_depth: self.handler_depth,
1250 finally_depth: self.finally_bodies.len(),
1251 scope_depth: self.scope_depth,
1252 });
1253 let exit_jump_pos = self.chunk.emit_jump(Op::IterNext, self.line);
1255 self.begin_scope();
1256 self.compile_destructuring(pattern, true)?;
1257 for sn in body {
1258 self.compile_node(sn)?;
1259 if Self::produces_value(&sn.node) {
1260 self.chunk.emit(Op::Pop, self.line);
1261 }
1262 }
1263 self.end_scope();
1264 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
1265 self.chunk.patch_jump(exit_jump_pos);
1266 let ctx = self.loop_stack.pop().unwrap();
1267 for patch_pos in ctx.break_patches {
1268 self.chunk.patch_jump(patch_pos);
1269 }
1270 self.chunk.emit(Op::Nil, self.line);
1271 }
1272
1273 Node::ReturnStmt { value } => {
1274 if self.has_pending_finally() {
1275 if let Some(val) = value {
1278 self.compile_node(val)?;
1279 } else {
1280 self.chunk.emit(Op::Nil, self.line);
1281 }
1282 self.temp_counter += 1;
1283 let temp_name = format!("__return_val_{}__", self.temp_counter);
1284 let save_idx = self.chunk.add_constant(Constant::String(temp_name.clone()));
1285 self.chunk.emit_u16(Op::DefVar, save_idx, self.line);
1286 for fb in self.all_pending_finallys() {
1289 self.compile_finally_inline(&fb)?;
1290 }
1291 let restore_idx = self.chunk.add_constant(Constant::String(temp_name));
1292 self.chunk.emit_u16(Op::GetVar, restore_idx, self.line);
1293 self.chunk.emit(Op::Return, self.line);
1294 } else {
1295 if let Some(val) = value {
1297 if let Node::FunctionCall { name, args } = &val.node {
1298 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1299 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
1300 for arg in args {
1301 self.compile_node(arg)?;
1302 }
1303 self.chunk
1304 .emit_u8(Op::TailCall, args.len() as u8, self.line);
1305 } else if let Node::BinaryOp { op, left, right } = &val.node {
1306 if op == "|>" {
1307 self.compile_node(left)?;
1308 self.compile_node(right)?;
1309 self.chunk.emit(Op::Swap, self.line);
1310 self.chunk.emit_u8(Op::TailCall, 1, self.line);
1311 } else {
1312 self.compile_node(val)?;
1313 }
1314 } else {
1315 self.compile_node(val)?;
1316 }
1317 } else {
1318 self.chunk.emit(Op::Nil, self.line);
1319 }
1320 self.chunk.emit(Op::Return, self.line);
1321 }
1322 }
1323
1324 Node::BreakStmt => {
1325 if self.loop_stack.is_empty() {
1326 return Err(CompileError {
1327 message: "break outside of loop".to_string(),
1328 line: self.line,
1329 });
1330 }
1331 let ctx = self.loop_stack.last().unwrap();
1333 let finally_depth = ctx.finally_depth;
1334 let handler_depth = ctx.handler_depth;
1335 let has_iterator = ctx.has_iterator;
1336 let scope_depth = ctx.scope_depth;
1337 for _ in handler_depth..self.handler_depth {
1338 self.chunk.emit(Op::PopHandler, self.line);
1339 }
1340 for fb in self.pending_finallys_down_to(finally_depth) {
1341 self.compile_finally_inline(&fb)?;
1342 }
1343 self.unwind_scopes_to(scope_depth);
1344 if has_iterator {
1345 self.chunk.emit(Op::PopIterator, self.line);
1346 }
1347 let patch = self.chunk.emit_jump(Op::Jump, self.line);
1348 self.loop_stack
1349 .last_mut()
1350 .unwrap()
1351 .break_patches
1352 .push(patch);
1353 }
1354
1355 Node::ContinueStmt => {
1356 if self.loop_stack.is_empty() {
1357 return Err(CompileError {
1358 message: "continue outside of loop".to_string(),
1359 line: self.line,
1360 });
1361 }
1362 let ctx = self.loop_stack.last().unwrap();
1363 let finally_depth = ctx.finally_depth;
1364 let handler_depth = ctx.handler_depth;
1365 let loop_start = ctx.start_offset;
1366 let scope_depth = ctx.scope_depth;
1367 for _ in handler_depth..self.handler_depth {
1368 self.chunk.emit(Op::PopHandler, self.line);
1369 }
1370 for fb in self.pending_finallys_down_to(finally_depth) {
1371 self.compile_finally_inline(&fb)?;
1372 }
1373 self.unwind_scopes_to(scope_depth);
1374 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
1375 }
1376
1377 Node::ListLiteral(elements) => {
1378 let has_spread = elements.iter().any(|e| matches!(&e.node, Node::Spread(_)));
1379 if !has_spread {
1380 for el in elements {
1381 self.compile_node(el)?;
1382 }
1383 self.chunk
1384 .emit_u16(Op::BuildList, elements.len() as u16, self.line);
1385 } else {
1386 self.chunk.emit_u16(Op::BuildList, 0, self.line);
1388 let mut pending = 0u16;
1389 for el in elements {
1390 if let Node::Spread(inner) = &el.node {
1391 if pending > 0 {
1392 self.chunk.emit_u16(Op::BuildList, pending, self.line);
1393 self.chunk.emit(Op::Add, self.line);
1394 pending = 0;
1395 }
1396 self.compile_node(inner)?;
1397 self.chunk.emit(Op::Dup, self.line);
1398 let assert_idx = self
1399 .chunk
1400 .add_constant(Constant::String("__assert_list".into()));
1401 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1402 self.chunk.emit(Op::Swap, self.line);
1403 self.chunk.emit_u8(Op::Call, 1, self.line);
1404 self.chunk.emit(Op::Pop, self.line);
1405 self.chunk.emit(Op::Add, self.line);
1406 } else {
1407 self.compile_node(el)?;
1408 pending += 1;
1409 }
1410 }
1411 if pending > 0 {
1412 self.chunk.emit_u16(Op::BuildList, pending, self.line);
1413 self.chunk.emit(Op::Add, self.line);
1414 }
1415 }
1416 }
1417
1418 Node::DictLiteral(entries) => {
1419 let has_spread = entries
1420 .iter()
1421 .any(|e| matches!(&e.value.node, Node::Spread(_)));
1422 if !has_spread {
1423 for entry in entries {
1424 self.compile_node(&entry.key)?;
1425 if Self::entry_key_is(&entry.key, "output_schema") {
1433 if let Node::Identifier(alias) = &entry.value.node {
1434 if let Some(schema) = self.schema_value_for_alias(alias) {
1435 self.emit_vm_value_literal(&schema);
1436 continue;
1437 }
1438 }
1439 }
1440 self.compile_node(&entry.value)?;
1441 }
1442 self.chunk
1443 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
1444 } else {
1445 self.chunk.emit_u16(Op::BuildDict, 0, self.line);
1447 let mut pending = 0u16;
1448 for entry in entries {
1449 if let Node::Spread(inner) = &entry.value.node {
1450 if pending > 0 {
1451 self.chunk.emit_u16(Op::BuildDict, pending, self.line);
1452 self.chunk.emit(Op::Add, self.line);
1453 pending = 0;
1454 }
1455 self.compile_node(inner)?;
1456 self.chunk.emit(Op::Dup, self.line);
1457 let assert_idx = self
1458 .chunk
1459 .add_constant(Constant::String("__assert_dict".into()));
1460 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1461 self.chunk.emit(Op::Swap, self.line);
1462 self.chunk.emit_u8(Op::Call, 1, self.line);
1463 self.chunk.emit(Op::Pop, self.line);
1464 self.chunk.emit(Op::Add, self.line);
1465 } else {
1466 self.compile_node(&entry.key)?;
1467 self.compile_node(&entry.value)?;
1468 pending += 1;
1469 }
1470 }
1471 if pending > 0 {
1472 self.chunk.emit_u16(Op::BuildDict, pending, self.line);
1473 self.chunk.emit(Op::Add, self.line);
1474 }
1475 }
1476 }
1477
1478 Node::InterpolatedString(segments) => {
1479 let mut part_count = 0u16;
1480 for seg in segments {
1481 match seg {
1482 StringSegment::Literal(s) => {
1483 let idx = self.chunk.add_constant(Constant::String(s.clone()));
1484 self.chunk.emit_u16(Op::Constant, idx, self.line);
1485 part_count += 1;
1486 }
1487 StringSegment::Expression(expr_str, expr_line, expr_col) => {
1488 let mut lexer =
1489 harn_lexer::Lexer::with_position(expr_str, *expr_line, *expr_col);
1490 if let Ok(tokens) = lexer.tokenize() {
1491 let mut parser = harn_parser::Parser::new(tokens);
1492 if let Ok(snode) = parser.parse_single_expression() {
1493 self.compile_node(&snode)?;
1494 let to_str = self
1495 .chunk
1496 .add_constant(Constant::String("to_string".into()));
1497 self.chunk.emit_u16(Op::Constant, to_str, self.line);
1498 self.chunk.emit(Op::Swap, self.line);
1499 self.chunk.emit_u8(Op::Call, 1, self.line);
1500 part_count += 1;
1501 } else {
1502 let idx =
1504 self.chunk.add_constant(Constant::String(expr_str.clone()));
1505 self.chunk.emit_u16(Op::Constant, idx, self.line);
1506 part_count += 1;
1507 }
1508 }
1509 }
1510 }
1511 }
1512 if part_count > 1 {
1513 self.chunk.emit_u16(Op::Concat, part_count, self.line);
1514 }
1515 }
1516
1517 Node::FnDecl {
1518 name, params, body, ..
1519 } => {
1520 let mut fn_compiler = Compiler::for_nested_body();
1521 fn_compiler.enum_names = self.enum_names.clone();
1522 fn_compiler.emit_default_preamble(params)?;
1523 fn_compiler.emit_type_checks(params);
1524 let is_gen = body_contains_yield(body);
1525 fn_compiler.compile_block(body)?;
1526 for fb in fn_compiler.all_pending_finallys() {
1528 fn_compiler.compile_finally_inline(&fb)?;
1529 }
1530 fn_compiler.chunk.emit(Op::Nil, self.line);
1531 fn_compiler.chunk.emit(Op::Return, self.line);
1532
1533 let func = CompiledFunction {
1534 name: name.clone(),
1535 params: TypedParam::names(params),
1536 default_start: TypedParam::default_start(params),
1537 chunk: fn_compiler.chunk,
1538 is_generator: is_gen,
1539 has_rest_param: params.last().is_some_and(|p| p.rest),
1540 };
1541 let fn_idx = self.chunk.functions.len();
1542 self.chunk.functions.push(func);
1543
1544 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1545 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1546 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1547 }
1548
1549 Node::ToolDecl {
1550 name,
1551 description,
1552 params,
1553 return_type,
1554 body,
1555 ..
1556 } => {
1557 let mut fn_compiler = Compiler::for_nested_body();
1559 fn_compiler.enum_names = self.enum_names.clone();
1560 fn_compiler.emit_default_preamble(params)?;
1561 fn_compiler.emit_type_checks(params);
1562 fn_compiler.compile_block(body)?;
1563 for fb in fn_compiler.all_pending_finallys() {
1565 fn_compiler.compile_finally_inline(&fb)?;
1566 }
1567 fn_compiler.chunk.emit(Op::Return, self.line);
1568
1569 let func = CompiledFunction {
1570 name: name.clone(),
1571 params: TypedParam::names(params),
1572 default_start: TypedParam::default_start(params),
1573 chunk: fn_compiler.chunk,
1574 is_generator: false,
1575 has_rest_param: params.last().is_some_and(|p| p.rest),
1576 };
1577 let fn_idx = self.chunk.functions.len();
1578 self.chunk.functions.push(func);
1579
1580 let define_name = self
1581 .chunk
1582 .add_constant(Constant::String("tool_define".into()));
1583 self.chunk.emit_u16(Op::Constant, define_name, self.line);
1584
1585 let reg_name = self
1586 .chunk
1587 .add_constant(Constant::String("tool_registry".into()));
1588 self.chunk.emit_u16(Op::Constant, reg_name, self.line);
1589 self.chunk.emit_u8(Op::Call, 0, self.line);
1590
1591 let tool_name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1592 self.chunk.emit_u16(Op::Constant, tool_name_idx, self.line);
1593
1594 let desc = description.as_deref().unwrap_or("");
1595 let desc_idx = self.chunk.add_constant(Constant::String(desc.to_string()));
1596 self.chunk.emit_u16(Op::Constant, desc_idx, self.line);
1597
1598 let mut param_count: u16 = 0;
1602 for p in params {
1603 let pn_idx = self.chunk.add_constant(Constant::String(p.name.clone()));
1604 self.chunk.emit_u16(Op::Constant, pn_idx, self.line);
1605
1606 let base_schema = p
1607 .type_expr
1608 .as_ref()
1609 .and_then(Self::type_expr_to_schema_value)
1610 .unwrap_or_else(|| {
1611 VmValue::Dict(Rc::new(BTreeMap::from([(
1612 "type".to_string(),
1613 VmValue::String(Rc::from("any")),
1614 )])))
1615 });
1616 let public_schema =
1617 schema::schema_to_json_schema_value(&base_schema).map_err(|error| {
1618 CompileError {
1619 message: format!(
1620 "failed to lower tool parameter schema for '{}': {}",
1621 p.name, error
1622 ),
1623 line: self.line,
1624 }
1625 })?;
1626 let mut param_schema = match public_schema {
1627 VmValue::Dict(map) => (*map).clone(),
1628 _ => BTreeMap::new(),
1629 };
1630
1631 if p.default_value.is_some() {
1632 param_schema.insert("required".to_string(), VmValue::Bool(false));
1633 }
1634
1635 self.emit_vm_value_literal(&VmValue::Dict(Rc::new(param_schema)));
1636
1637 if let Some(default_value) = p.default_value.as_ref() {
1638 let default_key =
1639 self.chunk.add_constant(Constant::String("default".into()));
1640 self.chunk.emit_u16(Op::Constant, default_key, self.line);
1641 self.compile_node(default_value)?;
1642 self.chunk.emit_u16(Op::BuildDict, 1, self.line);
1643 self.chunk.emit(Op::Add, self.line);
1644 }
1645
1646 param_count += 1;
1647 }
1648 self.chunk.emit_u16(Op::BuildDict, param_count, self.line);
1649
1650 let params_key = self
1651 .chunk
1652 .add_constant(Constant::String("parameters".into()));
1653 self.chunk.emit_u16(Op::Constant, params_key, self.line);
1654 self.chunk.emit(Op::Swap, self.line);
1655
1656 let handler_key = self.chunk.add_constant(Constant::String("handler".into()));
1657 self.chunk.emit_u16(Op::Constant, handler_key, self.line);
1658 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1659
1660 let mut config_entries = 2u16;
1661 if let Some(return_type) = return_type
1662 .as_ref()
1663 .and_then(Self::type_expr_to_schema_value)
1664 {
1665 let return_type =
1666 schema::schema_to_json_schema_value(&return_type).map_err(|error| {
1667 CompileError {
1668 message: format!(
1669 "failed to lower tool return schema for '{}': {}",
1670 name, error
1671 ),
1672 line: self.line,
1673 }
1674 })?;
1675 let returns_key = self.chunk.add_constant(Constant::String("returns".into()));
1676 self.chunk.emit_u16(Op::Constant, returns_key, self.line);
1677 self.emit_vm_value_literal(&return_type);
1678 config_entries += 1;
1679 }
1680
1681 self.chunk
1682 .emit_u16(Op::BuildDict, config_entries, self.line);
1683
1684 self.chunk.emit_u8(Op::Call, 4, self.line);
1685
1686 let bind_idx = self.chunk.add_constant(Constant::String(name.clone()));
1687 self.chunk.emit_u16(Op::DefLet, bind_idx, self.line);
1688 }
1689
1690 Node::Closure { params, body, .. } => {
1691 let mut fn_compiler = Compiler::for_nested_body();
1692 fn_compiler.enum_names = self.enum_names.clone();
1693 fn_compiler.emit_default_preamble(params)?;
1694 fn_compiler.emit_type_checks(params);
1695 let is_gen = body_contains_yield(body);
1696 fn_compiler.compile_block(body)?;
1697 for fb in fn_compiler.all_pending_finallys() {
1699 fn_compiler.compile_finally_inline(&fb)?;
1700 }
1701 fn_compiler.chunk.emit(Op::Return, self.line);
1702
1703 let func = CompiledFunction {
1704 name: "<closure>".to_string(),
1705 params: TypedParam::names(params),
1706 default_start: TypedParam::default_start(params),
1707 chunk: fn_compiler.chunk,
1708 is_generator: is_gen,
1709 has_rest_param: false,
1710 };
1711 let fn_idx = self.chunk.functions.len();
1712 self.chunk.functions.push(func);
1713
1714 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1715 }
1716
1717 Node::ThrowStmt { value } => {
1718 let pending = self.pending_finallys_until_barrier();
1724 if !pending.is_empty() {
1725 self.compile_node(value)?;
1726 self.temp_counter += 1;
1727 let temp_name = format!("__throw_val_{}__", self.temp_counter);
1728 let save_idx = self.chunk.add_constant(Constant::String(temp_name.clone()));
1729 self.chunk.emit_u16(Op::DefVar, save_idx, self.line);
1730 for fb in &pending {
1731 self.compile_finally_inline(fb)?;
1732 }
1733 let restore_idx = self.chunk.add_constant(Constant::String(temp_name));
1734 self.chunk.emit_u16(Op::GetVar, restore_idx, self.line);
1735 self.chunk.emit(Op::Throw, self.line);
1736 } else {
1737 self.compile_node(value)?;
1738 self.chunk.emit(Op::Throw, self.line);
1739 }
1740 }
1741
1742 Node::MatchExpr { value, arms } => {
1743 self.compile_node(value)?;
1744 let mut end_jumps = Vec::new();
1745 for arm in arms {
1746 match &arm.pattern.node {
1747 Node::Identifier(name) if name == "_" => {
1749 if let Some(ref guard) = arm.guard {
1750 self.compile_node(guard)?;
1751 let guard_skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1752 self.chunk.emit(Op::Pop, self.line);
1753 self.begin_scope();
1754 self.chunk.emit(Op::Pop, self.line);
1755 self.compile_match_body(&arm.body)?;
1756 self.end_scope();
1757 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1758 self.chunk.patch_jump(guard_skip);
1759 self.chunk.emit(Op::Pop, self.line);
1760 } else {
1761 self.begin_scope();
1762 self.chunk.emit(Op::Pop, self.line);
1763 self.compile_match_body(&arm.body)?;
1764 self.end_scope();
1765 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1766 }
1767 }
1768 Node::EnumConstruct {
1770 enum_name,
1771 variant,
1772 args: pat_args,
1773 } => {
1774 self.chunk.emit(Op::Dup, self.line);
1775 let en_idx =
1776 self.chunk.add_constant(Constant::String(enum_name.clone()));
1777 let vn_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1778 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1779 let hi = (vn_idx >> 8) as u8;
1780 let lo = vn_idx as u8;
1781 self.chunk.code.push(hi);
1782 self.chunk.code.push(lo);
1783 self.chunk.lines.push(self.line);
1784 self.chunk.columns.push(self.column);
1785 self.chunk.lines.push(self.line);
1786 self.chunk.columns.push(self.column);
1787 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1788 self.chunk.emit(Op::Pop, self.line);
1789 self.begin_scope();
1790
1791 for (i, pat_arg) in pat_args.iter().enumerate() {
1794 if let Node::Identifier(binding_name) = &pat_arg.node {
1795 self.chunk.emit(Op::Dup, self.line);
1796 let fields_idx = self
1797 .chunk
1798 .add_constant(Constant::String("fields".to_string()));
1799 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
1800 let idx_const =
1801 self.chunk.add_constant(Constant::Int(i as i64));
1802 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1803 self.chunk.emit(Op::Subscript, self.line);
1804 let name_idx = self
1805 .chunk
1806 .add_constant(Constant::String(binding_name.clone()));
1807 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1808 }
1809 }
1810
1811 if let Some(ref guard) = arm.guard {
1813 self.compile_node(guard)?;
1814 let guard_skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1815 self.chunk.emit(Op::Pop, self.line);
1816 self.chunk.emit(Op::Pop, self.line);
1817 self.compile_match_body(&arm.body)?;
1818 self.end_scope();
1819 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1820 self.chunk.patch_jump(guard_skip);
1821 self.chunk.emit(Op::Pop, self.line);
1822 self.end_scope();
1823 } else {
1824 self.chunk.emit(Op::Pop, self.line);
1825 self.compile_match_body(&arm.body)?;
1826 self.end_scope();
1827 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1828 }
1829 self.chunk.patch_jump(skip);
1830 self.chunk.emit(Op::Pop, self.line);
1831 }
1832 Node::PropertyAccess { object, property } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
1834 {
1835 let enum_name = if let Node::Identifier(n) = &object.node {
1836 n.clone()
1837 } else {
1838 unreachable!()
1839 };
1840 self.chunk.emit(Op::Dup, self.line);
1841 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
1842 let vn_idx =
1843 self.chunk.add_constant(Constant::String(property.clone()));
1844 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1845 let hi = (vn_idx >> 8) as u8;
1846 let lo = vn_idx as u8;
1847 self.chunk.code.push(hi);
1848 self.chunk.code.push(lo);
1849 self.chunk.lines.push(self.line);
1850 self.chunk.columns.push(self.column);
1851 self.chunk.lines.push(self.line);
1852 self.chunk.columns.push(self.column);
1853 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1854 self.chunk.emit(Op::Pop, self.line);
1855 if let Some(ref guard) = arm.guard {
1857 self.compile_node(guard)?;
1858 let guard_skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1859 self.chunk.emit(Op::Pop, self.line);
1860 self.begin_scope();
1861 self.chunk.emit(Op::Pop, self.line);
1862 self.compile_match_body(&arm.body)?;
1863 self.end_scope();
1864 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1865 self.chunk.patch_jump(guard_skip);
1866 self.chunk.emit(Op::Pop, self.line);
1867 } else {
1868 self.begin_scope();
1869 self.chunk.emit(Op::Pop, self.line);
1870 self.compile_match_body(&arm.body)?;
1871 self.end_scope();
1872 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1873 }
1874 self.chunk.patch_jump(skip);
1875 self.chunk.emit(Op::Pop, self.line);
1876 }
1877 Node::MethodCall {
1880 object,
1881 method,
1882 args: pat_args,
1883 } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
1884 {
1885 let enum_name = if let Node::Identifier(n) = &object.node {
1886 n.clone()
1887 } else {
1888 unreachable!()
1889 };
1890 self.chunk.emit(Op::Dup, self.line);
1891 let en_idx = self.chunk.add_constant(Constant::String(enum_name));
1892 let vn_idx = self.chunk.add_constant(Constant::String(method.clone()));
1893 self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1894 let hi = (vn_idx >> 8) as u8;
1895 let lo = vn_idx as u8;
1896 self.chunk.code.push(hi);
1897 self.chunk.code.push(lo);
1898 self.chunk.lines.push(self.line);
1899 self.chunk.columns.push(self.column);
1900 self.chunk.lines.push(self.line);
1901 self.chunk.columns.push(self.column);
1902 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1903 self.chunk.emit(Op::Pop, self.line);
1904 self.begin_scope();
1905
1906 for (i, pat_arg) in pat_args.iter().enumerate() {
1907 if let Node::Identifier(binding_name) = &pat_arg.node {
1908 self.chunk.emit(Op::Dup, self.line);
1909 let fields_idx = self
1910 .chunk
1911 .add_constant(Constant::String("fields".to_string()));
1912 self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
1913 let idx_const =
1914 self.chunk.add_constant(Constant::Int(i as i64));
1915 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1916 self.chunk.emit(Op::Subscript, self.line);
1917 let name_idx = self
1918 .chunk
1919 .add_constant(Constant::String(binding_name.clone()));
1920 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1921 }
1922 }
1923
1924 if let Some(ref guard) = arm.guard {
1926 self.compile_node(guard)?;
1927 let guard_skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1928 self.chunk.emit(Op::Pop, self.line);
1929 self.chunk.emit(Op::Pop, self.line);
1930 self.compile_match_body(&arm.body)?;
1931 self.end_scope();
1932 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1933 self.chunk.patch_jump(guard_skip);
1934 self.chunk.emit(Op::Pop, self.line);
1935 self.end_scope();
1936 } else {
1937 self.chunk.emit(Op::Pop, self.line);
1938 self.compile_match_body(&arm.body)?;
1939 self.end_scope();
1940 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1941 }
1942 self.chunk.patch_jump(skip);
1943 self.chunk.emit(Op::Pop, self.line);
1944 }
1945 Node::Identifier(name) => {
1947 self.begin_scope();
1948 self.chunk.emit(Op::Dup, self.line);
1949 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1950 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1951 if let Some(ref guard) = arm.guard {
1953 self.compile_node(guard)?;
1954 let guard_skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1955 self.chunk.emit(Op::Pop, self.line);
1956 self.chunk.emit(Op::Pop, self.line);
1957 self.compile_match_body(&arm.body)?;
1958 self.end_scope();
1959 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1960 self.chunk.patch_jump(guard_skip);
1961 self.chunk.emit(Op::Pop, self.line);
1962 self.end_scope();
1963 } else {
1964 self.chunk.emit(Op::Pop, self.line);
1965 self.compile_match_body(&arm.body)?;
1966 self.end_scope();
1967 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1968 }
1969 }
1970 Node::DictLiteral(entries)
1972 if entries
1973 .iter()
1974 .all(|e| matches!(&e.key.node, Node::StringLiteral(_))) =>
1975 {
1976 self.chunk.emit(Op::Dup, self.line);
1977 let typeof_idx =
1978 self.chunk.add_constant(Constant::String("type_of".into()));
1979 self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
1980 self.chunk.emit(Op::Swap, self.line);
1981 self.chunk.emit_u8(Op::Call, 1, self.line);
1982 let dict_str = self.chunk.add_constant(Constant::String("dict".into()));
1983 self.chunk.emit_u16(Op::Constant, dict_str, self.line);
1984 self.chunk.emit(Op::Equal, self.line);
1985 let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1986 self.chunk.emit(Op::Pop, self.line);
1987
1988 let mut constraint_skips = Vec::new();
1989 let mut bindings = Vec::new();
1990 self.begin_scope();
1991 for entry in entries {
1992 if let Node::StringLiteral(key) = &entry.key.node {
1993 match &entry.value.node {
1994 Node::StringLiteral(_)
1995 | Node::IntLiteral(_)
1996 | Node::FloatLiteral(_)
1997 | Node::BoolLiteral(_)
1998 | Node::NilLiteral => {
1999 self.chunk.emit(Op::Dup, self.line);
2000 let key_idx = self
2001 .chunk
2002 .add_constant(Constant::String(key.clone()));
2003 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
2004 self.chunk.emit(Op::Subscript, self.line);
2005 self.compile_node(&entry.value)?;
2006 self.chunk.emit(Op::Equal, self.line);
2007 let skip =
2008 self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2009 self.chunk.emit(Op::Pop, self.line);
2010 constraint_skips.push(skip);
2011 }
2012 Node::Identifier(binding) => {
2013 bindings.push((key.clone(), binding.clone()));
2014 }
2015 _ => {
2016 self.chunk.emit(Op::Dup, self.line);
2018 let key_idx = self
2019 .chunk
2020 .add_constant(Constant::String(key.clone()));
2021 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
2022 self.chunk.emit(Op::Subscript, self.line);
2023 self.compile_node(&entry.value)?;
2024 self.chunk.emit(Op::Equal, self.line);
2025 let skip =
2026 self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2027 self.chunk.emit(Op::Pop, self.line);
2028 constraint_skips.push(skip);
2029 }
2030 }
2031 }
2032 }
2033
2034 for (key, binding) in &bindings {
2035 self.chunk.emit(Op::Dup, self.line);
2036 let key_idx =
2037 self.chunk.add_constant(Constant::String(key.clone()));
2038 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
2039 self.chunk.emit(Op::Subscript, self.line);
2040 let name_idx =
2041 self.chunk.add_constant(Constant::String(binding.clone()));
2042 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
2043 }
2044
2045 if let Some(ref guard) = arm.guard {
2047 self.compile_node(guard)?;
2048 let guard_skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2049 self.chunk.emit(Op::Pop, self.line);
2050 self.chunk.emit(Op::Pop, self.line);
2051 self.compile_match_body(&arm.body)?;
2052 self.end_scope();
2053 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
2054 self.chunk.patch_jump(guard_skip);
2055 self.chunk.emit(Op::Pop, self.line);
2057 } else {
2058 self.chunk.emit(Op::Pop, self.line);
2059 self.compile_match_body(&arm.body)?;
2060 self.end_scope();
2061 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
2062 }
2063
2064 let type_fail_target = self.chunk.code.len();
2065 self.chunk.emit(Op::Pop, self.line);
2066 let next_arm_jump = self.chunk.emit_jump(Op::Jump, self.line);
2067 let scoped_fail_target = self.chunk.code.len();
2068 self.chunk.emit(Op::PopScope, self.line);
2069 self.chunk.emit(Op::Pop, self.line);
2070 let next_arm_target = self.chunk.code.len();
2071
2072 for skip in constraint_skips {
2073 self.chunk.patch_jump_to(skip, scoped_fail_target);
2074 }
2075 self.chunk.patch_jump_to(skip_type, type_fail_target);
2076 self.chunk.patch_jump_to(next_arm_jump, next_arm_target);
2077 }
2078 Node::ListLiteral(elements) => {
2080 self.chunk.emit(Op::Dup, self.line);
2081 let typeof_idx =
2082 self.chunk.add_constant(Constant::String("type_of".into()));
2083 self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
2084 self.chunk.emit(Op::Swap, self.line);
2085 self.chunk.emit_u8(Op::Call, 1, self.line);
2086 let list_str = self.chunk.add_constant(Constant::String("list".into()));
2087 self.chunk.emit_u16(Op::Constant, list_str, self.line);
2088 self.chunk.emit(Op::Equal, self.line);
2089 let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2090 self.chunk.emit(Op::Pop, self.line);
2091
2092 self.chunk.emit(Op::Dup, self.line);
2093 let len_idx = self.chunk.add_constant(Constant::String("len".into()));
2094 self.chunk.emit_u16(Op::Constant, len_idx, self.line);
2095 self.chunk.emit(Op::Swap, self.line);
2096 self.chunk.emit_u8(Op::Call, 1, self.line);
2097 let count = self
2098 .chunk
2099 .add_constant(Constant::Int(elements.len() as i64));
2100 self.chunk.emit_u16(Op::Constant, count, self.line);
2101 self.chunk.emit(Op::GreaterEqual, self.line);
2102 let skip_len = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2103 self.chunk.emit(Op::Pop, self.line);
2104
2105 let mut constraint_skips = Vec::new();
2106 let mut bindings = Vec::new();
2107 self.begin_scope();
2108 for (i, elem) in elements.iter().enumerate() {
2109 match &elem.node {
2110 Node::Identifier(name) if name != "_" => {
2111 bindings.push((i, name.clone()));
2112 }
2113 Node::Identifier(_) => {} _ => {
2115 self.chunk.emit(Op::Dup, self.line);
2116 let idx_const =
2117 self.chunk.add_constant(Constant::Int(i as i64));
2118 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
2119 self.chunk.emit(Op::Subscript, self.line);
2120 self.compile_node(elem)?;
2121 self.chunk.emit(Op::Equal, self.line);
2122 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2123 self.chunk.emit(Op::Pop, self.line);
2124 constraint_skips.push(skip);
2125 }
2126 }
2127 }
2128
2129 for (i, name) in &bindings {
2130 self.chunk.emit(Op::Dup, self.line);
2131 let idx_const = self.chunk.add_constant(Constant::Int(*i as i64));
2132 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
2133 self.chunk.emit(Op::Subscript, self.line);
2134 let name_idx =
2135 self.chunk.add_constant(Constant::String(name.clone()));
2136 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
2137 }
2138
2139 if let Some(ref guard) = arm.guard {
2141 self.compile_node(guard)?;
2142 let guard_skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2143 self.chunk.emit(Op::Pop, self.line);
2144 self.chunk.emit(Op::Pop, self.line);
2145 self.compile_match_body(&arm.body)?;
2146 self.end_scope();
2147 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
2148 self.chunk.patch_jump(guard_skip);
2149 self.chunk.emit(Op::Pop, self.line);
2150 } else {
2151 self.chunk.emit(Op::Pop, self.line);
2152 self.compile_match_body(&arm.body)?;
2153 self.end_scope();
2154 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
2155 }
2156
2157 let pre_scope_fail_target = self.chunk.code.len();
2158 self.chunk.emit(Op::Pop, self.line);
2159 let next_arm_jump = self.chunk.emit_jump(Op::Jump, self.line);
2160 let scoped_fail_target = self.chunk.code.len();
2161 self.chunk.emit(Op::PopScope, self.line);
2162 self.chunk.emit(Op::Pop, self.line);
2163 let next_arm_target = self.chunk.code.len();
2164 for skip in constraint_skips {
2165 self.chunk.patch_jump_to(skip, scoped_fail_target);
2166 }
2167 self.chunk.patch_jump_to(skip_len, pre_scope_fail_target);
2168 self.chunk.patch_jump_to(skip_type, pre_scope_fail_target);
2169 self.chunk.patch_jump_to(next_arm_jump, next_arm_target);
2170 }
2171 _ => {
2173 self.chunk.emit(Op::Dup, self.line);
2174 self.compile_node(&arm.pattern)?;
2175 self.chunk.emit(Op::Equal, self.line);
2176 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2177 self.chunk.emit(Op::Pop, self.line);
2178 if let Some(ref guard) = arm.guard {
2179 self.compile_node(guard)?;
2180 let guard_skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2181 self.chunk.emit(Op::Pop, self.line);
2182 self.begin_scope();
2183 self.chunk.emit(Op::Pop, self.line);
2184 self.compile_match_body(&arm.body)?;
2185 self.end_scope();
2186 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
2187 self.chunk.patch_jump(guard_skip);
2188 self.chunk.emit(Op::Pop, self.line);
2189 } else {
2190 self.begin_scope();
2191 self.chunk.emit(Op::Pop, self.line);
2192 self.compile_match_body(&arm.body)?;
2193 self.end_scope();
2194 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
2195 }
2196 self.chunk.patch_jump(skip);
2197 self.chunk.emit(Op::Pop, self.line);
2198 }
2199 }
2200 }
2201 let msg_idx = self.chunk.add_constant(Constant::String(
2202 "No match arm matched the value".to_string(),
2203 ));
2204 self.chunk.emit(Op::Pop, self.line);
2205 self.chunk.emit_u16(Op::Constant, msg_idx, self.line);
2206 self.chunk.emit(Op::Throw, self.line);
2207 for j in end_jumps {
2208 self.chunk.patch_jump(j);
2209 }
2210 }
2211
2212 Node::RangeExpr {
2213 start,
2214 end,
2215 inclusive,
2216 } => {
2217 let name_idx = self
2218 .chunk
2219 .add_constant(Constant::String("__range__".to_string()));
2220 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
2221 self.compile_node(start)?;
2222 self.compile_node(end)?;
2223 if *inclusive {
2224 self.chunk.emit(Op::True, self.line);
2225 } else {
2226 self.chunk.emit(Op::False, self.line);
2227 }
2228 self.chunk.emit_u8(Op::Call, 3, self.line);
2229 }
2230
2231 Node::GuardStmt {
2232 condition,
2233 else_body,
2234 } => {
2235 self.compile_node(condition)?;
2236 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
2237 self.chunk.emit(Op::Pop, self.line);
2238 self.compile_scoped_block(else_body)?;
2239 if !else_body.is_empty() && Self::produces_value(&else_body.last().unwrap().node) {
2241 self.chunk.emit(Op::Pop, self.line);
2242 }
2243 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
2244 self.chunk.patch_jump(skip_jump);
2245 self.chunk.emit(Op::Pop, self.line);
2246 self.chunk.patch_jump(end_jump);
2247 self.chunk.emit(Op::Nil, self.line);
2248 }
2249
2250 Node::RequireStmt { condition, message } => {
2251 self.compile_node(condition)?;
2252 let ok_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
2253 self.chunk.emit(Op::Pop, self.line);
2254 if let Some(message) = message {
2255 self.compile_node(message)?;
2256 } else {
2257 let idx = self
2258 .chunk
2259 .add_constant(Constant::String("require condition failed".to_string()));
2260 self.chunk.emit_u16(Op::Constant, idx, self.line);
2261 }
2262 self.chunk.emit(Op::Throw, self.line);
2263 self.chunk.patch_jump(ok_jump);
2264 self.chunk.emit(Op::Pop, self.line);
2265 }
2266
2267 Node::Block(stmts) => {
2268 self.compile_scoped_block(stmts)?;
2269 }
2270
2271 Node::DeadlineBlock { duration, body } => {
2272 self.compile_node(duration)?;
2273 self.chunk.emit(Op::DeadlineSetup, self.line);
2274 self.compile_scoped_block(body)?;
2275 self.chunk.emit(Op::DeadlineEnd, self.line);
2276 }
2277
2278 Node::MutexBlock { body } => {
2279 self.begin_scope();
2281 for sn in body {
2282 self.compile_node(sn)?;
2283 if Self::produces_value(&sn.node) {
2284 self.chunk.emit(Op::Pop, self.line);
2285 }
2286 }
2287 self.chunk.emit(Op::Nil, self.line);
2288 self.end_scope();
2289 }
2290
2291 Node::DeferStmt { body } => {
2292 self.finally_bodies
2294 .push(FinallyEntry::Finally(body.clone()));
2295 self.chunk.emit(Op::Nil, self.line);
2296 }
2297
2298 Node::YieldExpr { value } => {
2299 if let Some(val) = value {
2300 self.compile_node(val)?;
2301 } else {
2302 self.chunk.emit(Op::Nil, self.line);
2303 }
2304 self.chunk.emit(Op::Yield, self.line);
2305 }
2306
2307 Node::EnumConstruct {
2308 enum_name,
2309 variant,
2310 args,
2311 } => {
2312 for arg in args {
2313 self.compile_node(arg)?;
2314 }
2315 let enum_idx = self.chunk.add_constant(Constant::String(enum_name.clone()));
2316 let var_idx = self.chunk.add_constant(Constant::String(variant.clone()));
2317 self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
2319 let hi = (var_idx >> 8) as u8;
2320 let lo = var_idx as u8;
2321 self.chunk.code.push(hi);
2322 self.chunk.code.push(lo);
2323 self.chunk.lines.push(self.line);
2324 self.chunk.columns.push(self.column);
2325 self.chunk.lines.push(self.line);
2326 self.chunk.columns.push(self.column);
2327 let fc = args.len() as u16;
2328 let fhi = (fc >> 8) as u8;
2329 let flo = fc as u8;
2330 self.chunk.code.push(fhi);
2331 self.chunk.code.push(flo);
2332 self.chunk.lines.push(self.line);
2333 self.chunk.columns.push(self.column);
2334 self.chunk.lines.push(self.line);
2335 self.chunk.columns.push(self.column);
2336 }
2337
2338 Node::StructConstruct {
2339 struct_name,
2340 fields,
2341 } => {
2342 let make_idx = self
2344 .chunk
2345 .add_constant(Constant::String("__make_struct".to_string()));
2346 let struct_name_idx = self
2347 .chunk
2348 .add_constant(Constant::String(struct_name.clone()));
2349 self.chunk.emit_u16(Op::Constant, make_idx, self.line);
2350 self.chunk
2351 .emit_u16(Op::Constant, struct_name_idx, self.line);
2352
2353 for entry in fields {
2354 self.compile_node(&entry.key)?;
2355 self.compile_node(&entry.value)?;
2356 }
2357 self.chunk
2358 .emit_u16(Op::BuildDict, fields.len() as u16, self.line);
2359 self.chunk.emit_u8(Op::Call, 2, self.line);
2360 }
2361
2362 Node::ImportDecl { path } => {
2363 let idx = self.chunk.add_constant(Constant::String(path.clone()));
2364 self.chunk.emit_u16(Op::Import, idx, self.line);
2365 }
2366
2367 Node::SelectiveImport { names, path } => {
2368 let path_idx = self.chunk.add_constant(Constant::String(path.clone()));
2369 let names_str = names.join(",");
2370 let names_idx = self.chunk.add_constant(Constant::String(names_str));
2371 self.chunk
2372 .emit_u16(Op::SelectiveImport, path_idx, self.line);
2373 let hi = (names_idx >> 8) as u8;
2374 let lo = names_idx as u8;
2375 self.chunk.code.push(hi);
2376 self.chunk.code.push(lo);
2377 self.chunk.lines.push(self.line);
2378 self.chunk.columns.push(self.column);
2379 self.chunk.lines.push(self.line);
2380 self.chunk.columns.push(self.column);
2381 }
2382
2383 Node::TryOperator { operand } => {
2384 self.compile_node(operand)?;
2385 self.chunk.emit(Op::TryUnwrap, self.line);
2386 }
2387
2388 Node::TryStar { operand } => {
2404 if self.module_level {
2405 return Err(CompileError {
2406 message: "try* requires an enclosing function (fn, tool, or pipeline) so the rethrow has a target".into(),
2407 line: self.line,
2408 });
2409 }
2410 self.handler_depth += 1;
2411 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
2412 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
2413 self.emit_type_name_extra(empty_type);
2414
2415 self.compile_node(operand)?;
2416
2417 self.handler_depth -= 1;
2418 self.chunk.emit(Op::PopHandler, self.line);
2419 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
2420
2421 self.chunk.patch_jump(catch_jump);
2425 let pending = self.pending_finallys_until_barrier();
2426 if pending.is_empty() {
2427 self.chunk.emit(Op::Throw, self.line);
2428 } else {
2429 self.temp_counter += 1;
2430 let temp_name = format!("__try_star_err_{}__", self.temp_counter);
2431 let save_idx = self.chunk.add_constant(Constant::String(temp_name.clone()));
2432 self.chunk.emit_u16(Op::DefVar, save_idx, self.line);
2433 for fb in &pending {
2434 self.compile_finally_inline(fb)?;
2435 }
2436 let restore_idx = self.chunk.add_constant(Constant::String(temp_name));
2437 self.chunk.emit_u16(Op::GetVar, restore_idx, self.line);
2438 self.chunk.emit(Op::Throw, self.line);
2439 }
2440
2441 self.chunk.patch_jump(end_jump);
2442 }
2443
2444 Node::ImplBlock { type_name, methods } => {
2445 for method_sn in methods {
2447 if let Node::FnDecl {
2448 name, params, body, ..
2449 } = &method_sn.node
2450 {
2451 let key_idx = self.chunk.add_constant(Constant::String(name.clone()));
2452 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
2453
2454 let mut fn_compiler = Compiler::for_nested_body();
2455 fn_compiler.enum_names = self.enum_names.clone();
2456 fn_compiler.emit_default_preamble(params)?;
2457 fn_compiler.emit_type_checks(params);
2458 fn_compiler.compile_block(body)?;
2459 fn_compiler.chunk.emit(Op::Nil, self.line);
2460 fn_compiler.chunk.emit(Op::Return, self.line);
2461
2462 let func = CompiledFunction {
2463 name: format!("{}.{}", type_name, name),
2464 params: TypedParam::names(params),
2465 default_start: TypedParam::default_start(params),
2466 chunk: fn_compiler.chunk,
2467 is_generator: false,
2468 has_rest_param: false,
2469 };
2470 let fn_idx = self.chunk.functions.len();
2471 self.chunk.functions.push(func);
2472 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2473 }
2474 }
2475 let method_count = methods
2476 .iter()
2477 .filter(|m| matches!(m.node, Node::FnDecl { .. }))
2478 .count();
2479 self.chunk
2480 .emit_u16(Op::BuildDict, method_count as u16, self.line);
2481 let impl_name = format!("__impl_{}", type_name);
2482 let name_idx = self.chunk.add_constant(Constant::String(impl_name));
2483 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
2484 }
2485
2486 Node::StructDecl { name, .. } => {
2487 let mut fn_compiler = Compiler::for_nested_body();
2489 fn_compiler.enum_names = self.enum_names.clone();
2490 let params = vec![TypedParam::untyped("__fields")];
2491 fn_compiler.emit_default_preamble(¶ms)?;
2492
2493 let make_idx = fn_compiler
2494 .chunk
2495 .add_constant(Constant::String("__make_struct".into()));
2496 fn_compiler
2497 .chunk
2498 .emit_u16(Op::Constant, make_idx, self.line);
2499 let sname_idx = fn_compiler
2500 .chunk
2501 .add_constant(Constant::String(name.clone()));
2502 fn_compiler
2503 .chunk
2504 .emit_u16(Op::Constant, sname_idx, self.line);
2505 let fields_idx = fn_compiler
2506 .chunk
2507 .add_constant(Constant::String("__fields".into()));
2508 fn_compiler
2509 .chunk
2510 .emit_u16(Op::GetVar, fields_idx, self.line);
2511 fn_compiler.chunk.emit_u8(Op::Call, 2, self.line);
2512 fn_compiler.chunk.emit(Op::Return, self.line);
2513
2514 let func = CompiledFunction {
2515 name: name.clone(),
2516 params: TypedParam::names(¶ms),
2517 default_start: None,
2518 chunk: fn_compiler.chunk,
2519 is_generator: false,
2520 has_rest_param: false,
2521 };
2522 let fn_idx = self.chunk.functions.len();
2523 self.chunk.functions.push(func);
2524 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2525 let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
2526 self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
2527 }
2528
2529 Node::Pipeline { .. }
2531 | Node::OverrideDecl { .. }
2532 | Node::TypeDecl { .. }
2533 | Node::EnumDecl { .. }
2534 | Node::InterfaceDecl { .. } => {
2535 self.chunk.emit(Op::Nil, self.line);
2536 }
2537
2538 Node::TryCatch {
2539 body,
2540 error_var,
2541 error_type,
2542 catch_body,
2543 finally_body,
2544 } => {
2545 let type_name = error_type.as_ref().and_then(|te| {
2547 if let harn_parser::TypeExpr::Named(name) = te {
2548 Some(name.clone())
2549 } else {
2550 None
2551 }
2552 });
2553
2554 let type_name_idx = if let Some(ref tn) = type_name {
2555 self.chunk.add_constant(Constant::String(tn.clone()))
2556 } else {
2557 self.chunk.add_constant(Constant::String(String::new()))
2558 };
2559
2560 let has_catch = !catch_body.is_empty() || error_var.is_some();
2561 let has_finally = finally_body.is_some();
2562
2563 if has_catch && has_finally {
2564 let finally_body = finally_body.as_ref().unwrap();
2565 self.finally_bodies.push(FinallyEntry::CatchBarrier);
2571 self.finally_bodies
2572 .push(FinallyEntry::Finally(finally_body.clone()));
2573
2574 self.handler_depth += 1;
2575 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
2576 self.emit_type_name_extra(type_name_idx);
2577
2578 self.compile_try_body(body)?;
2579
2580 self.handler_depth -= 1;
2581 self.chunk.emit(Op::PopHandler, self.line);
2582 self.compile_finally_inline(finally_body)?;
2585 self.finally_bodies.pop(); self.finally_bodies.pop(); let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
2590
2591 self.chunk.patch_jump(catch_jump);
2592 self.begin_scope();
2593 self.compile_catch_binding(error_var)?;
2594
2595 self.handler_depth += 1;
2599 let rethrow_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
2600 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
2601 self.emit_type_name_extra(empty_type);
2602
2603 self.compile_try_body(catch_body)?;
2604
2605 self.handler_depth -= 1;
2606 self.chunk.emit(Op::PopHandler, self.line);
2607 self.end_scope();
2608 let end_jump2 = self.chunk.emit_jump(Op::Jump, self.line);
2609
2610 self.chunk.patch_jump(rethrow_jump);
2614 self.compile_plain_rethrow()?;
2615 self.end_scope();
2616
2617 self.chunk.patch_jump(end_jump);
2618 self.chunk.patch_jump(end_jump2);
2619 } else if has_finally {
2620 let finally_body = finally_body.as_ref().unwrap();
2621 self.finally_bodies
2625 .push(FinallyEntry::Finally(finally_body.clone()));
2626
2627 self.handler_depth += 1;
2628 let error_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
2629 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
2630 self.emit_type_name_extra(empty_type);
2631
2632 self.compile_try_body(body)?;
2633
2634 self.handler_depth -= 1;
2635 self.chunk.emit(Op::PopHandler, self.line);
2636 self.compile_finally_inline(finally_body)?;
2637 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
2638
2639 self.chunk.patch_jump(error_jump);
2642 self.compile_plain_rethrow()?;
2643
2644 self.chunk.patch_jump(end_jump);
2645
2646 self.finally_bodies.pop(); } else {
2648 self.finally_bodies.push(FinallyEntry::CatchBarrier);
2652
2653 self.handler_depth += 1;
2654 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
2655 self.emit_type_name_extra(type_name_idx);
2656
2657 self.compile_try_body(body)?;
2658
2659 self.handler_depth -= 1;
2660 self.chunk.emit(Op::PopHandler, self.line);
2661 self.finally_bodies.pop(); let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
2663
2664 self.chunk.patch_jump(catch_jump);
2665 self.begin_scope();
2666 self.compile_catch_binding(error_var)?;
2667
2668 self.compile_try_body(catch_body)?;
2669 self.end_scope();
2670
2671 self.chunk.patch_jump(end_jump);
2672 }
2673 }
2674
2675 Node::TryExpr { body } => {
2676 self.handler_depth += 1;
2678 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
2679 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
2680 self.emit_type_name_extra(empty_type);
2681
2682 self.compile_try_body(body)?;
2683
2684 self.handler_depth -= 1;
2685 self.chunk.emit(Op::PopHandler, self.line);
2686
2687 let ok_idx = self.chunk.add_constant(Constant::String("Ok".to_string()));
2689 self.chunk.emit_u16(Op::Constant, ok_idx, self.line);
2690 self.chunk.emit(Op::Swap, self.line);
2691 self.chunk.emit_u8(Op::Call, 1, self.line);
2692
2693 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
2694
2695 self.chunk.patch_jump(catch_jump);
2697
2698 let err_idx = self.chunk.add_constant(Constant::String("Err".to_string()));
2699 self.chunk.emit_u16(Op::Constant, err_idx, self.line);
2700 self.chunk.emit(Op::Swap, self.line);
2701 self.chunk.emit_u8(Op::Call, 1, self.line);
2702
2703 self.chunk.patch_jump(end_jump);
2704 }
2705
2706 Node::Retry { count, body } => {
2707 self.compile_node(count)?;
2708 let counter_name = "__retry_counter__";
2709 let counter_idx = self
2710 .chunk
2711 .add_constant(Constant::String(counter_name.to_string()));
2712 self.chunk.emit_u16(Op::DefVar, counter_idx, self.line);
2713
2714 self.chunk.emit(Op::Nil, self.line);
2716 let err_name = "__retry_last_error__";
2717 let err_idx = self
2718 .chunk
2719 .add_constant(Constant::String(err_name.to_string()));
2720 self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
2721
2722 let loop_start = self.chunk.current_offset();
2723
2724 let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
2725 let empty_type = self.chunk.add_constant(Constant::String(String::new()));
2727 let hi = (empty_type >> 8) as u8;
2728 let lo = empty_type as u8;
2729 self.chunk.code.push(hi);
2730 self.chunk.code.push(lo);
2731 self.chunk.lines.push(self.line);
2732 self.chunk.columns.push(self.column);
2733 self.chunk.lines.push(self.line);
2734 self.chunk.columns.push(self.column);
2735
2736 self.compile_block(body)?;
2737
2738 self.chunk.emit(Op::PopHandler, self.line);
2739 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
2740
2741 self.chunk.patch_jump(catch_jump);
2742 self.chunk.emit(Op::Dup, self.line);
2743 self.chunk.emit_u16(Op::SetVar, err_idx, self.line);
2744 self.chunk.emit(Op::Pop, self.line);
2745
2746 self.chunk.emit_u16(Op::GetVar, counter_idx, self.line);
2747 let one_idx = self.chunk.add_constant(Constant::Int(1));
2748 self.chunk.emit_u16(Op::Constant, one_idx, self.line);
2749 self.chunk.emit(Op::Sub, self.line);
2750 self.chunk.emit(Op::Dup, self.line);
2751 self.chunk.emit_u16(Op::SetVar, counter_idx, self.line);
2752
2753 let zero_idx = self.chunk.add_constant(Constant::Int(0));
2754 self.chunk.emit_u16(Op::Constant, zero_idx, self.line);
2755 self.chunk.emit(Op::Greater, self.line);
2756 let retry_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2757 self.chunk.emit(Op::Pop, self.line);
2758 self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
2759
2760 self.chunk.patch_jump(retry_jump);
2762 self.chunk.emit(Op::Pop, self.line);
2763 self.chunk.emit_u16(Op::GetVar, err_idx, self.line);
2764 self.chunk.emit(Op::Throw, self.line);
2765
2766 self.chunk.patch_jump(end_jump);
2767 self.chunk.emit(Op::Nil, self.line);
2768 }
2769
2770 Node::Parallel {
2771 mode,
2772 expr,
2773 variable,
2774 body,
2775 options,
2776 } => {
2777 let cap_expr = options
2782 .iter()
2783 .find(|(key, _)| key == "max_concurrent")
2784 .map(|(_, value)| value);
2785 if let Some(cap_expr) = cap_expr {
2786 self.compile_node(cap_expr)?;
2787 } else {
2788 let zero_idx = self.chunk.add_constant(Constant::Int(0));
2789 self.chunk.emit_u16(Op::Constant, zero_idx, self.line);
2790 }
2791 self.compile_node(expr)?;
2792 let mut fn_compiler = Compiler::for_nested_body();
2793 fn_compiler.enum_names = self.enum_names.clone();
2794 fn_compiler.compile_block(body)?;
2795 fn_compiler.chunk.emit(Op::Return, self.line);
2796 let (fn_name, params) = match mode {
2797 ParallelMode::Count => (
2798 "<parallel>",
2799 vec![variable.clone().unwrap_or_else(|| "__i__".to_string())],
2800 ),
2801 ParallelMode::Each => (
2802 "<parallel_each>",
2803 vec![variable.clone().unwrap_or_else(|| "__item__".to_string())],
2804 ),
2805 ParallelMode::Settle => (
2806 "<parallel_settle>",
2807 vec![variable.clone().unwrap_or_else(|| "__item__".to_string())],
2808 ),
2809 };
2810 let func = CompiledFunction {
2811 name: fn_name.to_string(),
2812 params,
2813 default_start: None,
2814 chunk: fn_compiler.chunk,
2815 is_generator: false,
2816 has_rest_param: false,
2817 };
2818 let fn_idx = self.chunk.functions.len();
2819 self.chunk.functions.push(func);
2820 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2821 let op = match mode {
2822 ParallelMode::Count => Op::Parallel,
2823 ParallelMode::Each => Op::ParallelMap,
2824 ParallelMode::Settle => Op::ParallelSettle,
2825 };
2826 self.chunk.emit(op, self.line);
2827 }
2828
2829 Node::SpawnExpr { body } => {
2830 let mut fn_compiler = Compiler::for_nested_body();
2831 fn_compiler.enum_names = self.enum_names.clone();
2832 fn_compiler.compile_block(body)?;
2833 fn_compiler.chunk.emit(Op::Return, self.line);
2834 let func = CompiledFunction {
2835 name: "<spawn>".to_string(),
2836 params: vec![],
2837 default_start: None,
2838 chunk: fn_compiler.chunk,
2839 is_generator: false,
2840 has_rest_param: false,
2841 };
2842 let fn_idx = self.chunk.functions.len();
2843 self.chunk.functions.push(func);
2844 self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2845 self.chunk.emit(Op::Spawn, self.line);
2846 }
2847 Node::SelectExpr {
2848 cases,
2849 timeout,
2850 default_body,
2851 } => {
2852 let builtin_name = if timeout.is_some() {
2856 "__select_timeout"
2857 } else if default_body.is_some() {
2858 "__select_try"
2859 } else {
2860 "__select_list"
2861 };
2862
2863 let name_idx = self
2864 .chunk
2865 .add_constant(Constant::String(builtin_name.into()));
2866 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
2867
2868 for case in cases {
2869 self.compile_node(&case.channel)?;
2870 }
2871 self.chunk
2872 .emit_u16(Op::BuildList, cases.len() as u16, self.line);
2873
2874 if let Some((duration_expr, _)) = timeout {
2875 self.compile_node(duration_expr)?;
2876 self.chunk.emit_u8(Op::Call, 2, self.line);
2877 } else {
2878 self.chunk.emit_u8(Op::Call, 1, self.line);
2879 }
2880
2881 self.temp_counter += 1;
2882 let result_name = format!("__sel_result_{}__", self.temp_counter);
2883 let result_idx = self
2884 .chunk
2885 .add_constant(Constant::String(result_name.clone()));
2886 self.chunk.emit_u16(Op::DefVar, result_idx, self.line);
2887
2888 let mut end_jumps = Vec::new();
2889
2890 for (i, case) in cases.iter().enumerate() {
2891 let get_r = self
2892 .chunk
2893 .add_constant(Constant::String(result_name.clone()));
2894 self.chunk.emit_u16(Op::GetVar, get_r, self.line);
2895 let idx_prop = self.chunk.add_constant(Constant::String("index".into()));
2896 self.chunk.emit_u16(Op::GetProperty, idx_prop, self.line);
2897 let case_i = self.chunk.add_constant(Constant::Int(i as i64));
2898 self.chunk.emit_u16(Op::Constant, case_i, self.line);
2899 self.chunk.emit(Op::Equal, self.line);
2900 let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2901 self.chunk.emit(Op::Pop, self.line);
2902 self.begin_scope();
2903
2904 let get_r2 = self
2905 .chunk
2906 .add_constant(Constant::String(result_name.clone()));
2907 self.chunk.emit_u16(Op::GetVar, get_r2, self.line);
2908 let val_prop = self.chunk.add_constant(Constant::String("value".into()));
2909 self.chunk.emit_u16(Op::GetProperty, val_prop, self.line);
2910 let var_idx = self
2911 .chunk
2912 .add_constant(Constant::String(case.variable.clone()));
2913 self.chunk.emit_u16(Op::DefLet, var_idx, self.line);
2914
2915 self.compile_try_body(&case.body)?;
2916 self.end_scope();
2917 end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
2918 self.chunk.patch_jump(skip);
2919 self.chunk.emit(Op::Pop, self.line);
2920 }
2921
2922 if let Some((_, ref timeout_body)) = timeout {
2923 self.compile_try_body(timeout_body)?;
2924 } else if let Some(ref def_body) = default_body {
2925 self.compile_try_body(def_body)?;
2926 } else {
2927 self.chunk.emit(Op::Nil, self.line);
2928 }
2929
2930 for ej in end_jumps {
2931 self.chunk.patch_jump(ej);
2932 }
2933 }
2934 Node::Spread(_) => {
2935 return Err(CompileError {
2936 message: "spread (...) can only be used inside list literals, dict literals, or function call arguments".into(),
2937 line: self.line,
2938 });
2939 }
2940 }
2941 Ok(())
2942 }
2943
2944 fn compile_destructuring(
2948 &mut self,
2949 pattern: &BindingPattern,
2950 is_mutable: bool,
2951 ) -> Result<(), CompileError> {
2952 let def_op = if is_mutable { Op::DefVar } else { Op::DefLet };
2953 match pattern {
2954 BindingPattern::Identifier(name) => {
2955 let idx = self.chunk.add_constant(Constant::String(name.clone()));
2956 self.chunk.emit_u16(def_op, idx, self.line);
2957 }
2958 BindingPattern::Dict(fields) => {
2959 self.chunk.emit(Op::Dup, self.line);
2961 let assert_idx = self
2962 .chunk
2963 .add_constant(Constant::String("__assert_dict".into()));
2964 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
2965 self.chunk.emit(Op::Swap, self.line);
2966 self.chunk.emit_u8(Op::Call, 1, self.line);
2967 self.chunk.emit(Op::Pop, self.line);
2968
2969 let non_rest: Vec<_> = fields.iter().filter(|f| !f.is_rest).collect();
2970 let rest_field = fields.iter().find(|f| f.is_rest);
2971
2972 for field in &non_rest {
2973 self.chunk.emit(Op::Dup, self.line);
2974 let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
2975 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
2976 self.chunk.emit(Op::Subscript, self.line);
2977 if let Some(default_expr) = &field.default_value {
2978 self.chunk.emit(Op::Dup, self.line);
2980 self.chunk.emit(Op::Nil, self.line);
2981 self.chunk.emit(Op::NotEqual, self.line);
2982 let skip_default = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
2983 self.chunk.emit(Op::Pop, self.line);
2984 self.chunk.emit(Op::Pop, self.line);
2985 self.compile_node(default_expr)?;
2986 let end = self.chunk.emit_jump(Op::Jump, self.line);
2987 self.chunk.patch_jump(skip_default);
2988 self.chunk.emit(Op::Pop, self.line);
2989 self.chunk.patch_jump(end);
2990 }
2991 let binding_name = field.alias.as_deref().unwrap_or(&field.key);
2992 let name_idx = self
2993 .chunk
2994 .add_constant(Constant::String(binding_name.to_string()));
2995 self.chunk.emit_u16(def_op, name_idx, self.line);
2996 }
2997
2998 if let Some(rest) = rest_field {
2999 let fn_idx = self
3001 .chunk
3002 .add_constant(Constant::String("__dict_rest".into()));
3003 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
3004 self.chunk.emit(Op::Swap, self.line);
3005 for field in &non_rest {
3006 let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
3007 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
3008 }
3009 self.chunk
3010 .emit_u16(Op::BuildList, non_rest.len() as u16, self.line);
3011 self.chunk.emit_u8(Op::Call, 2, self.line);
3012 let rest_name = &rest.key;
3013 let rest_idx = self.chunk.add_constant(Constant::String(rest_name.clone()));
3014 self.chunk.emit_u16(def_op, rest_idx, self.line);
3015 } else {
3016 self.chunk.emit(Op::Pop, self.line);
3017 }
3018 }
3019 BindingPattern::Pair(first_name, second_name) => {
3020 self.chunk.emit(Op::Dup, self.line);
3021 let first_key_idx = self
3022 .chunk
3023 .add_constant(Constant::String("first".to_string()));
3024 self.chunk
3025 .emit_u16(Op::GetProperty, first_key_idx, self.line);
3026 let first_name_idx = self
3027 .chunk
3028 .add_constant(Constant::String(first_name.clone()));
3029 self.chunk.emit_u16(def_op, first_name_idx, self.line);
3030
3031 let second_key_idx = self
3032 .chunk
3033 .add_constant(Constant::String("second".to_string()));
3034 self.chunk
3035 .emit_u16(Op::GetProperty, second_key_idx, self.line);
3036 let second_name_idx = self
3037 .chunk
3038 .add_constant(Constant::String(second_name.clone()));
3039 self.chunk.emit_u16(def_op, second_name_idx, self.line);
3040 }
3042 BindingPattern::List(elements) => {
3043 self.chunk.emit(Op::Dup, self.line);
3045 let assert_idx = self
3046 .chunk
3047 .add_constant(Constant::String("__assert_list".into()));
3048 self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
3049 self.chunk.emit(Op::Swap, self.line);
3050 self.chunk.emit_u8(Op::Call, 1, self.line);
3051 self.chunk.emit(Op::Pop, self.line);
3052
3053 let non_rest: Vec<_> = elements.iter().filter(|e| !e.is_rest).collect();
3054 let rest_elem = elements.iter().find(|e| e.is_rest);
3055
3056 for (i, elem) in non_rest.iter().enumerate() {
3057 self.chunk.emit(Op::Dup, self.line);
3058 let idx_const = self.chunk.add_constant(Constant::Int(i as i64));
3059 self.chunk.emit_u16(Op::Constant, idx_const, self.line);
3060 self.chunk.emit(Op::Subscript, self.line);
3061 if let Some(default_expr) = &elem.default_value {
3062 self.chunk.emit(Op::Dup, self.line);
3064 self.chunk.emit(Op::Nil, self.line);
3065 self.chunk.emit(Op::NotEqual, self.line);
3066 let skip_default = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
3067 self.chunk.emit(Op::Pop, self.line);
3068 self.chunk.emit(Op::Pop, self.line);
3069 self.compile_node(default_expr)?;
3070 let end = self.chunk.emit_jump(Op::Jump, self.line);
3071 self.chunk.patch_jump(skip_default);
3072 self.chunk.emit(Op::Pop, self.line);
3073 self.chunk.patch_jump(end);
3074 }
3075 let name_idx = self.chunk.add_constant(Constant::String(elem.name.clone()));
3076 self.chunk.emit_u16(def_op, name_idx, self.line);
3077 }
3078
3079 if let Some(rest) = rest_elem {
3080 let start_idx = self
3083 .chunk
3084 .add_constant(Constant::Int(non_rest.len() as i64));
3085 self.chunk.emit_u16(Op::Constant, start_idx, self.line);
3086 self.chunk.emit(Op::Nil, self.line);
3087 self.chunk.emit(Op::Slice, self.line);
3088 let rest_name_idx =
3089 self.chunk.add_constant(Constant::String(rest.name.clone()));
3090 self.chunk.emit_u16(def_op, rest_name_idx, self.line);
3091 } else {
3092 self.chunk.emit(Op::Pop, self.line);
3093 }
3094 }
3095 }
3096 Ok(())
3097 }
3098
3099 fn produces_value(node: &Node) -> bool {
3101 match node {
3102 Node::LetBinding { .. }
3103 | Node::VarBinding { .. }
3104 | Node::Assignment { .. }
3105 | Node::ReturnStmt { .. }
3106 | Node::FnDecl { .. }
3107 | Node::ToolDecl { .. }
3108 | Node::ImplBlock { .. }
3109 | Node::StructDecl { .. }
3110 | Node::EnumDecl { .. }
3111 | Node::InterfaceDecl { .. }
3112 | Node::TypeDecl { .. }
3113 | Node::ThrowStmt { .. }
3114 | Node::BreakStmt
3115 | Node::ContinueStmt
3116 | Node::RequireStmt { .. }
3117 | Node::DeferStmt { .. } => false,
3118 Node::TryCatch { .. }
3119 | Node::TryExpr { .. }
3120 | Node::Retry { .. }
3121 | Node::GuardStmt { .. }
3122 | Node::DeadlineBlock { .. }
3123 | Node::MutexBlock { .. }
3124 | Node::Spread(_) => true,
3125 _ => true,
3126 }
3127 }
3128}
3129
3130impl Compiler {
3131 pub fn compile_fn_body(
3144 &mut self,
3145 params: &[TypedParam],
3146 body: &[SNode],
3147 source_file: Option<String>,
3148 ) -> Result<CompiledFunction, CompileError> {
3149 let mut fn_compiler = Compiler::for_nested_body();
3150 fn_compiler.enum_names = self.enum_names.clone();
3151 fn_compiler.emit_default_preamble(params)?;
3152 fn_compiler.emit_type_checks(params);
3153 let is_gen = body_contains_yield(body);
3154 fn_compiler.compile_block(body)?;
3155 fn_compiler.chunk.emit(Op::Nil, 0);
3156 fn_compiler.chunk.emit(Op::Return, 0);
3157 fn_compiler.chunk.source_file = source_file;
3158 Ok(CompiledFunction {
3159 name: String::new(),
3160 params: TypedParam::names(params),
3161 default_start: TypedParam::default_start(params),
3162 chunk: fn_compiler.chunk,
3163 is_generator: is_gen,
3164 has_rest_param: false,
3165 })
3166 }
3167
3168 fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
3170 self.begin_scope();
3171 if body.is_empty() {
3172 self.chunk.emit(Op::Nil, self.line);
3173 } else {
3174 self.compile_block(body)?;
3175 if !Self::produces_value(&body.last().unwrap().node) {
3176 self.chunk.emit(Op::Nil, self.line);
3177 }
3178 }
3179 self.end_scope();
3180 Ok(())
3181 }
3182
3183 fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
3185 match op {
3186 "+" => self.chunk.emit(Op::Add, self.line),
3187 "-" => self.chunk.emit(Op::Sub, self.line),
3188 "*" => self.chunk.emit(Op::Mul, self.line),
3189 "/" => self.chunk.emit(Op::Div, self.line),
3190 "%" => self.chunk.emit(Op::Mod, self.line),
3191 _ => {
3192 return Err(CompileError {
3193 message: format!("Unknown compound operator: {op}"),
3194 line: self.line,
3195 })
3196 }
3197 }
3198 Ok(())
3199 }
3200
3201 fn root_var_name(&self, node: &SNode) -> Option<String> {
3203 match &node.node {
3204 Node::Identifier(name) => Some(name.clone()),
3205 Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
3206 self.root_var_name(object)
3207 }
3208 Node::SubscriptAccess { object, .. } => self.root_var_name(object),
3209 _ => None,
3210 }
3211 }
3212
3213 fn compile_top_level_declarations(&mut self, program: &[SNode]) -> Result<(), CompileError> {
3214 for sn in program {
3222 if matches!(&sn.node, Node::LetBinding { .. } | Node::VarBinding { .. }) {
3223 self.compile_node(sn)?;
3224 }
3225 }
3226 for sn in program {
3230 if matches!(
3231 &sn.node,
3232 Node::FnDecl { .. }
3233 | Node::ToolDecl { .. }
3234 | Node::ImplBlock { .. }
3235 | Node::StructDecl { .. }
3236 | Node::EnumDecl { .. }
3237 | Node::InterfaceDecl { .. }
3238 | Node::TypeDecl { .. }
3239 ) {
3240 self.compile_node(sn)?;
3241 }
3242 }
3243 Ok(())
3244 }
3245}
3246
3247impl Compiler {
3248 fn collect_enum_names(nodes: &[SNode], names: &mut std::collections::HashSet<String>) {
3250 for sn in nodes {
3251 match &sn.node {
3252 Node::EnumDecl { name, .. } => {
3253 names.insert(name.clone());
3254 }
3255 Node::Pipeline { body, .. } => {
3256 Self::collect_enum_names(body, names);
3257 }
3258 Node::FnDecl { body, .. } | Node::ToolDecl { body, .. } => {
3259 Self::collect_enum_names(body, names);
3260 }
3261 Node::Block(stmts) => {
3262 Self::collect_enum_names(stmts, names);
3263 }
3264 _ => {}
3265 }
3266 }
3267 }
3268
3269 fn collect_interface_methods(
3270 nodes: &[SNode],
3271 interfaces: &mut std::collections::HashMap<String, Vec<String>>,
3272 ) {
3273 for sn in nodes {
3274 match &sn.node {
3275 Node::InterfaceDecl { name, methods, .. } => {
3276 let method_names: Vec<String> =
3277 methods.iter().map(|m| m.name.clone()).collect();
3278 interfaces.insert(name.clone(), method_names);
3279 }
3280 Node::Pipeline { body, .. }
3281 | Node::FnDecl { body, .. }
3282 | Node::ToolDecl { body, .. } => {
3283 Self::collect_interface_methods(body, interfaces);
3284 }
3285 Node::Block(stmts) => {
3286 Self::collect_interface_methods(stmts, interfaces);
3287 }
3288 _ => {}
3289 }
3290 }
3291 }
3292}
3293
3294impl Default for Compiler {
3295 fn default() -> Self {
3296 Self::new()
3297 }
3298}
3299
3300fn body_contains_yield(nodes: &[SNode]) -> bool {
3302 nodes.iter().any(|sn| node_contains_yield(&sn.node))
3303}
3304
3305fn node_contains_yield(node: &Node) -> bool {
3306 match node {
3307 Node::YieldExpr { .. } => true,
3308 Node::FnDecl { .. } | Node::Closure { .. } => false,
3311 Node::Block(stmts) => body_contains_yield(stmts),
3312 Node::IfElse {
3313 condition,
3314 then_body,
3315 else_body,
3316 } => {
3317 node_contains_yield(&condition.node)
3318 || body_contains_yield(then_body)
3319 || else_body.as_ref().is_some_and(|b| body_contains_yield(b))
3320 }
3321 Node::WhileLoop { condition, body } => {
3322 node_contains_yield(&condition.node) || body_contains_yield(body)
3323 }
3324 Node::ForIn { iterable, body, .. } => {
3325 node_contains_yield(&iterable.node) || body_contains_yield(body)
3326 }
3327 Node::TryCatch {
3328 body, catch_body, ..
3329 } => body_contains_yield(body) || body_contains_yield(catch_body),
3330 Node::TryExpr { body } => body_contains_yield(body),
3331 _ => false,
3332 }
3333}
3334
3335fn contains_pipe_placeholder(node: &SNode) -> bool {
3337 match &node.node {
3338 Node::Identifier(name) if name == "_" => true,
3339 Node::FunctionCall { args, .. } => args.iter().any(contains_pipe_placeholder),
3340 Node::MethodCall { object, args, .. } => {
3341 contains_pipe_placeholder(object) || args.iter().any(contains_pipe_placeholder)
3342 }
3343 Node::BinaryOp { left, right, .. } => {
3344 contains_pipe_placeholder(left) || contains_pipe_placeholder(right)
3345 }
3346 Node::UnaryOp { operand, .. } => contains_pipe_placeholder(operand),
3347 Node::ListLiteral(items) => items.iter().any(contains_pipe_placeholder),
3348 Node::PropertyAccess { object, .. } => contains_pipe_placeholder(object),
3349 Node::SubscriptAccess { object, index } => {
3350 contains_pipe_placeholder(object) || contains_pipe_placeholder(index)
3351 }
3352 _ => false,
3353 }
3354}
3355
3356fn replace_pipe_placeholder(node: &SNode) -> SNode {
3358 let new_node = match &node.node {
3359 Node::Identifier(name) if name == "_" => Node::Identifier("__pipe".into()),
3360 Node::FunctionCall { name, args } => Node::FunctionCall {
3361 name: name.clone(),
3362 args: args.iter().map(replace_pipe_placeholder).collect(),
3363 },
3364 Node::MethodCall {
3365 object,
3366 method,
3367 args,
3368 } => Node::MethodCall {
3369 object: Box::new(replace_pipe_placeholder(object)),
3370 method: method.clone(),
3371 args: args.iter().map(replace_pipe_placeholder).collect(),
3372 },
3373 Node::BinaryOp { op, left, right } => Node::BinaryOp {
3374 op: op.clone(),
3375 left: Box::new(replace_pipe_placeholder(left)),
3376 right: Box::new(replace_pipe_placeholder(right)),
3377 },
3378 Node::UnaryOp { op, operand } => Node::UnaryOp {
3379 op: op.clone(),
3380 operand: Box::new(replace_pipe_placeholder(operand)),
3381 },
3382 Node::ListLiteral(items) => {
3383 Node::ListLiteral(items.iter().map(replace_pipe_placeholder).collect())
3384 }
3385 Node::PropertyAccess { object, property } => Node::PropertyAccess {
3386 object: Box::new(replace_pipe_placeholder(object)),
3387 property: property.clone(),
3388 },
3389 Node::SubscriptAccess { object, index } => Node::SubscriptAccess {
3390 object: Box::new(replace_pipe_placeholder(object)),
3391 index: Box::new(replace_pipe_placeholder(index)),
3392 },
3393 _ => return node.clone(),
3394 };
3395 SNode::new(new_node, node.span)
3396}
3397
3398#[cfg(test)]
3399mod tests {
3400 use super::*;
3401 use harn_lexer::Lexer;
3402 use harn_parser::Parser;
3403
3404 fn compile_source(source: &str) -> Chunk {
3405 let mut lexer = Lexer::new(source);
3406 let tokens = lexer.tokenize().unwrap();
3407 let mut parser = Parser::new(tokens);
3408 let program = parser.parse().unwrap();
3409 Compiler::new().compile(&program).unwrap()
3410 }
3411
3412 #[test]
3413 fn test_compile_arithmetic() {
3414 let chunk = compile_source("pipeline test(task) { let x = 2 + 3 }");
3415 assert!(!chunk.code.is_empty());
3416 assert!(chunk.constants.contains(&Constant::Int(2)));
3417 assert!(chunk.constants.contains(&Constant::Int(3)));
3418 }
3419
3420 #[test]
3421 fn test_compile_function_call() {
3422 let chunk = compile_source("pipeline test(task) { log(42) }");
3423 let disasm = chunk.disassemble("test");
3424 assert!(disasm.contains("CALL"));
3425 }
3426
3427 #[test]
3428 fn test_compile_if_else() {
3429 let chunk =
3430 compile_source(r#"pipeline test(task) { if true { log("yes") } else { log("no") } }"#);
3431 let disasm = chunk.disassemble("test");
3432 assert!(disasm.contains("JUMP_IF_FALSE"));
3433 assert!(disasm.contains("JUMP"));
3434 }
3435
3436 #[test]
3437 fn test_compile_while() {
3438 let chunk = compile_source("pipeline test(task) { var i = 0\n while i < 5 { i = i + 1 } }");
3439 let disasm = chunk.disassemble("test");
3440 assert!(disasm.contains("JUMP_IF_FALSE"));
3441 assert!(disasm.contains("JUMP"));
3442 }
3443
3444 #[test]
3445 fn test_compile_closure() {
3446 let chunk = compile_source("pipeline test(task) { let f = { x -> x * 2 } }");
3447 assert!(!chunk.functions.is_empty());
3448 assert_eq!(chunk.functions[0].params, vec!["x"]);
3449 }
3450
3451 #[test]
3452 fn test_compile_list() {
3453 let chunk = compile_source("pipeline test(task) { let a = [1, 2, 3] }");
3454 let disasm = chunk.disassemble("test");
3455 assert!(disasm.contains("BUILD_LIST"));
3456 }
3457
3458 #[test]
3459 fn test_compile_dict() {
3460 let chunk = compile_source(r#"pipeline test(task) { let d = {name: "test"} }"#);
3461 let disasm = chunk.disassemble("test");
3462 assert!(disasm.contains("BUILD_DICT"));
3463 }
3464
3465 #[test]
3466 fn test_disassemble() {
3467 let chunk = compile_source("pipeline test(task) { log(2 + 3) }");
3468 let disasm = chunk.disassemble("test");
3469 assert!(disasm.contains("CONSTANT"));
3470 assert!(disasm.contains("ADD"));
3471 assert!(disasm.contains("CALL"));
3472 }
3473}