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