1use std::collections::BTreeMap;
2use std::rc::Rc;
3
4use harn_parser::{Node, SNode, ShapeField, TypeExpr, TypedParam};
5
6use crate::chunk::{Chunk, CompiledFunction, Constant, Op};
7use crate::value::VmValue;
8
9use super::error::CompileError;
10use super::yield_scan::body_contains_yield;
11use super::{peel_node, Compiler, FinallyEntry};
12
13impl Compiler {
14 pub fn new() -> Self {
15 Self {
16 chunk: Chunk::new(),
17 line: 1,
18 column: 1,
19 enum_names: std::collections::HashSet::new(),
20 struct_layouts: std::collections::HashMap::new(),
21 interface_methods: std::collections::HashMap::new(),
22 loop_stack: Vec::new(),
23 handler_depth: 0,
24 finally_bodies: Vec::new(),
25 temp_counter: 0,
26 scope_depth: 0,
27 type_aliases: std::collections::HashMap::new(),
28 type_scopes: vec![std::collections::HashMap::new()],
29 local_scopes: vec![std::collections::HashMap::new()],
30 module_level: true,
31 }
32 }
33
34 pub(super) fn for_nested_body() -> Self {
38 let mut c = Self::new();
39 c.module_level = false;
40 c
41 }
42
43 pub(super) fn collect_type_aliases(&mut self, program: &[SNode]) {
47 for sn in program {
48 if let Node::TypeDecl {
49 name,
50 type_expr,
51 type_params: _,
52 } = &sn.node
53 {
54 self.type_aliases.insert(name.clone(), type_expr.clone());
55 }
56 }
57 }
58
59 pub(super) fn expand_alias(&self, ty: &TypeExpr) -> TypeExpr {
63 match ty {
64 TypeExpr::Named(name) => {
65 if let Some(target) = self.type_aliases.get(name) {
66 self.expand_alias(target)
67 } else {
68 TypeExpr::Named(name.clone())
69 }
70 }
71 TypeExpr::Union(types) => {
72 TypeExpr::Union(types.iter().map(|t| self.expand_alias(t)).collect())
73 }
74 TypeExpr::Shape(fields) => TypeExpr::Shape(
75 fields
76 .iter()
77 .map(|field| ShapeField {
78 name: field.name.clone(),
79 type_expr: self.expand_alias(&field.type_expr),
80 optional: field.optional,
81 })
82 .collect(),
83 ),
84 TypeExpr::List(inner) => TypeExpr::List(Box::new(self.expand_alias(inner))),
85 TypeExpr::Iter(inner) => TypeExpr::Iter(Box::new(self.expand_alias(inner))),
86 TypeExpr::DictType(k, v) => TypeExpr::DictType(
87 Box::new(self.expand_alias(k)),
88 Box::new(self.expand_alias(v)),
89 ),
90 TypeExpr::FnType {
91 params,
92 return_type,
93 } => TypeExpr::FnType {
94 params: params.iter().map(|p| self.expand_alias(p)).collect(),
95 return_type: Box::new(self.expand_alias(return_type)),
96 },
97 TypeExpr::Applied { name, args } => TypeExpr::Applied {
98 name: name.clone(),
99 args: args.iter().map(|a| self.expand_alias(a)).collect(),
100 },
101 TypeExpr::Never => TypeExpr::Never,
102 TypeExpr::LitString(s) => TypeExpr::LitString(s.clone()),
103 TypeExpr::LitInt(v) => TypeExpr::LitInt(*v),
104 }
105 }
106
107 pub(super) fn schema_value_for_alias(&self, name: &str) -> Option<VmValue> {
110 let ty = self.type_aliases.get(name)?;
111 let expanded = self.expand_alias(ty);
112 Self::type_expr_to_schema_value(&expanded)
113 }
114
115 pub(super) fn is_schema_guard(name: &str) -> bool {
119 matches!(
120 name,
121 "schema_is"
122 | "schema_expect"
123 | "schema_parse"
124 | "schema_check"
125 | "is_type"
126 | "json_validate"
127 )
128 }
129
130 pub(super) fn entry_key_is(key: &SNode, keyword: &str) -> bool {
133 matches!(
134 &key.node,
135 Node::Identifier(name) | Node::StringLiteral(name) | Node::RawStringLiteral(name)
136 if name == keyword
137 )
138 }
139
140 pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
143 Self::collect_enum_names(program, &mut self.enum_names);
146 self.enum_names.insert("Result".to_string());
147 Self::collect_struct_layouts(program, &mut self.struct_layouts);
148 Self::collect_interface_methods(program, &mut self.interface_methods);
149 self.collect_type_aliases(program);
150
151 for sn in program {
152 match &sn.node {
153 Node::ImportDecl { .. } | Node::SelectiveImport { .. } => {
154 self.compile_node(sn)?;
155 }
156 _ => {}
157 }
158 }
159 let main = program
160 .iter()
161 .find(|sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == "default"))
162 .or_else(|| {
163 program
164 .iter()
165 .find(|sn| matches!(peel_node(sn), Node::Pipeline { .. }))
166 });
167
168 if let Some(sn) = main {
169 self.compile_top_level_declarations(program)?;
170 if let Node::Pipeline { body, extends, .. } = peel_node(sn) {
171 if let Some(parent_name) = extends {
172 self.compile_parent_pipeline(program, parent_name)?;
173 }
174 let saved = std::mem::replace(&mut self.module_level, false);
175 self.compile_block(body)?;
176 self.module_level = saved;
177 }
178 } else {
179 let top_level: Vec<&SNode> = program
181 .iter()
182 .filter(|sn| {
183 !matches!(
184 &sn.node,
185 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
186 )
187 })
188 .collect();
189 for sn in &top_level {
190 self.compile_node(sn)?;
191 if Self::produces_value(&sn.node) {
192 self.chunk.emit(Op::Pop, self.line);
193 }
194 }
195 }
196
197 for fb in self.all_pending_finallys() {
198 self.compile_finally_inline(&fb)?;
199 }
200 self.chunk.emit(Op::Nil, self.line);
201 self.chunk.emit(Op::Return, self.line);
202 Ok(self.chunk)
203 }
204
205 pub fn compile_named(
207 mut self,
208 program: &[SNode],
209 pipeline_name: &str,
210 ) -> Result<Chunk, CompileError> {
211 Self::collect_enum_names(program, &mut self.enum_names);
212 self.enum_names.insert("Result".to_string());
213 Self::collect_struct_layouts(program, &mut self.struct_layouts);
214 Self::collect_interface_methods(program, &mut self.interface_methods);
215 self.collect_type_aliases(program);
216
217 for sn in program {
218 if matches!(
219 &sn.node,
220 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
221 ) {
222 self.compile_node(sn)?;
223 }
224 }
225 let target = program.iter().find(
226 |sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == pipeline_name),
227 );
228
229 if let Some(sn) = target {
230 self.compile_top_level_declarations(program)?;
231 if let Node::Pipeline { body, extends, .. } = peel_node(sn) {
232 if let Some(parent_name) = extends {
233 self.compile_parent_pipeline(program, parent_name)?;
234 }
235 let saved = std::mem::replace(&mut self.module_level, false);
236 self.compile_block(body)?;
237 self.module_level = saved;
238 }
239 }
240
241 for fb in self.all_pending_finallys() {
242 self.compile_finally_inline(&fb)?;
243 }
244 self.chunk.emit(Op::Nil, self.line);
245 self.chunk.emit(Op::Return, self.line);
246 Ok(self.chunk)
247 }
248
249 pub(super) fn compile_parent_pipeline(
251 &mut self,
252 program: &[SNode],
253 parent_name: &str,
254 ) -> Result<(), CompileError> {
255 let parent = program
256 .iter()
257 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == parent_name));
258 if let Some(sn) = parent {
259 if let Node::Pipeline { body, extends, .. } = &sn.node {
260 if let Some(grandparent) = extends {
261 self.compile_parent_pipeline(program, grandparent)?;
262 }
263 for stmt in body {
264 self.compile_node(stmt)?;
265 if Self::produces_value(&stmt.node) {
266 self.chunk.emit(Op::Pop, self.line);
267 }
268 }
269 }
270 }
271 Ok(())
272 }
273
274 pub(super) fn emit_default_preamble(
279 &mut self,
280 params: &[TypedParam],
281 ) -> Result<(), CompileError> {
282 for (i, param) in params.iter().enumerate() {
283 if let Some(default_expr) = ¶m.default_value {
284 self.chunk.emit(Op::GetArgc, self.line);
285 let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
286 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
287 self.chunk.emit(Op::GreaterEqual, self.line);
288 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
289 self.chunk.emit(Op::Pop, self.line);
291 self.compile_node(default_expr)?;
292 self.emit_init_or_define_binding(¶m.name, false);
293 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
294 self.chunk.patch_jump(skip_jump);
295 self.chunk.emit(Op::Pop, self.line);
296 self.chunk.patch_jump(end_jump);
297 }
298 }
299 Ok(())
300 }
301
302 pub(super) fn emit_type_checks(&mut self, params: &[TypedParam]) {
307 for param in params {
308 if let Some(type_expr) = ¶m.type_expr {
309 if let harn_parser::TypeExpr::Named(name) = type_expr {
310 if let Some(methods) = self.interface_methods.get(name).cloned() {
311 let fn_idx = self
312 .chunk
313 .add_constant(Constant::String("__assert_interface".into()));
314 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
315 self.emit_get_binding(¶m.name);
316 let name_idx = self
317 .chunk
318 .add_constant(Constant::String(param.name.clone()));
319 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
320 let iface_idx = self.chunk.add_constant(Constant::String(name.clone()));
321 self.chunk.emit_u16(Op::Constant, iface_idx, self.line);
322 let methods_str = methods.join(",");
323 let methods_idx = self.chunk.add_constant(Constant::String(methods_str));
324 self.chunk.emit_u16(Op::Constant, methods_idx, self.line);
325 self.chunk.emit_u8(Op::Call, 4, self.line);
326 self.chunk.emit(Op::Pop, self.line);
327 continue;
328 }
329 }
330
331 if let Some(schema) = Self::type_expr_to_schema_value(type_expr) {
332 let fn_idx = self
333 .chunk
334 .add_constant(Constant::String("__assert_schema".into()));
335 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
336 self.emit_get_binding(¶m.name);
337 let name_idx = self
338 .chunk
339 .add_constant(Constant::String(param.name.clone()));
340 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
341 self.emit_vm_value_literal(&schema);
342 self.chunk.emit_u8(Op::Call, 3, self.line);
343 self.chunk.emit(Op::Pop, self.line);
344 }
345 }
346 }
347 }
348
349 pub(crate) fn type_expr_to_schema_value(type_expr: &harn_parser::TypeExpr) -> Option<VmValue> {
350 match type_expr {
351 harn_parser::TypeExpr::Named(name) => match name.as_str() {
352 "int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
353 | "closure" | "bytes" => Some(VmValue::Dict(Rc::new(BTreeMap::from([(
354 "type".to_string(),
355 VmValue::String(Rc::from(name.as_str())),
356 )])))),
357 _ => None,
358 },
359 harn_parser::TypeExpr::Shape(fields) => {
360 let mut properties = BTreeMap::new();
361 let mut required = Vec::new();
362 for field in fields {
363 let field_schema = Self::type_expr_to_schema_value(&field.type_expr)?;
364 properties.insert(field.name.clone(), field_schema);
365 if !field.optional {
366 required.push(VmValue::String(Rc::from(field.name.as_str())));
367 }
368 }
369 let mut out = BTreeMap::new();
370 out.insert("type".to_string(), VmValue::String(Rc::from("dict")));
371 out.insert("properties".to_string(), VmValue::Dict(Rc::new(properties)));
372 if !required.is_empty() {
373 out.insert("required".to_string(), VmValue::List(Rc::new(required)));
374 }
375 Some(VmValue::Dict(Rc::new(out)))
376 }
377 harn_parser::TypeExpr::List(inner) => {
378 let mut out = BTreeMap::new();
379 out.insert("type".to_string(), VmValue::String(Rc::from("list")));
380 if let Some(item_schema) = Self::type_expr_to_schema_value(inner) {
381 out.insert("items".to_string(), item_schema);
382 }
383 Some(VmValue::Dict(Rc::new(out)))
384 }
385 harn_parser::TypeExpr::DictType(key, value) => {
386 let mut out = BTreeMap::new();
387 out.insert("type".to_string(), VmValue::String(Rc::from("dict")));
388 if matches!(key.as_ref(), harn_parser::TypeExpr::Named(name) if name == "string") {
389 if let Some(value_schema) = Self::type_expr_to_schema_value(value) {
390 out.insert("additional_properties".to_string(), value_schema);
391 }
392 }
393 Some(VmValue::Dict(Rc::new(out)))
394 }
395 harn_parser::TypeExpr::Union(members) => {
396 if !members.is_empty()
401 && members
402 .iter()
403 .all(|m| matches!(m, harn_parser::TypeExpr::LitString(_)))
404 {
405 let values = members
406 .iter()
407 .map(|m| match m {
408 harn_parser::TypeExpr::LitString(s) => {
409 VmValue::String(Rc::from(s.as_str()))
410 }
411 _ => unreachable!(),
412 })
413 .collect::<Vec<_>>();
414 return Some(VmValue::Dict(Rc::new(BTreeMap::from([
415 ("type".to_string(), VmValue::String(Rc::from("string"))),
416 ("enum".to_string(), VmValue::List(Rc::new(values))),
417 ]))));
418 }
419 if !members.is_empty()
420 && members
421 .iter()
422 .all(|m| matches!(m, harn_parser::TypeExpr::LitInt(_)))
423 {
424 let values = members
425 .iter()
426 .map(|m| match m {
427 harn_parser::TypeExpr::LitInt(v) => VmValue::Int(*v),
428 _ => unreachable!(),
429 })
430 .collect::<Vec<_>>();
431 return Some(VmValue::Dict(Rc::new(BTreeMap::from([
432 ("type".to_string(), VmValue::String(Rc::from("int"))),
433 ("enum".to_string(), VmValue::List(Rc::new(values))),
434 ]))));
435 }
436 let branches = members
437 .iter()
438 .filter_map(Self::type_expr_to_schema_value)
439 .collect::<Vec<_>>();
440 if branches.is_empty() {
441 None
442 } else {
443 Some(VmValue::Dict(Rc::new(BTreeMap::from([(
444 "union".to_string(),
445 VmValue::List(Rc::new(branches)),
446 )]))))
447 }
448 }
449 harn_parser::TypeExpr::FnType { .. } => {
450 Some(VmValue::Dict(Rc::new(BTreeMap::from([(
451 "type".to_string(),
452 VmValue::String(Rc::from("closure")),
453 )]))))
454 }
455 harn_parser::TypeExpr::Applied { .. } => None,
456 harn_parser::TypeExpr::Iter(_) => None,
457 harn_parser::TypeExpr::Never => None,
458 harn_parser::TypeExpr::LitString(s) => Some(VmValue::Dict(Rc::new(BTreeMap::from([
459 ("type".to_string(), VmValue::String(Rc::from("string"))),
460 ("const".to_string(), VmValue::String(Rc::from(s.as_str()))),
461 ])))),
462 harn_parser::TypeExpr::LitInt(v) => Some(VmValue::Dict(Rc::new(BTreeMap::from([
463 ("type".to_string(), VmValue::String(Rc::from("int"))),
464 ("const".to_string(), VmValue::Int(*v)),
465 ])))),
466 }
467 }
468
469 pub(super) fn emit_vm_value_literal(&mut self, value: &VmValue) {
470 match value {
471 VmValue::String(text) => {
472 let idx = self.chunk.add_constant(Constant::String(text.to_string()));
473 self.chunk.emit_u16(Op::Constant, idx, self.line);
474 }
475 VmValue::Int(number) => {
476 let idx = self.chunk.add_constant(Constant::Int(*number));
477 self.chunk.emit_u16(Op::Constant, idx, self.line);
478 }
479 VmValue::Float(number) => {
480 let idx = self.chunk.add_constant(Constant::Float(*number));
481 self.chunk.emit_u16(Op::Constant, idx, self.line);
482 }
483 VmValue::Bool(value) => {
484 let idx = self.chunk.add_constant(Constant::Bool(*value));
485 self.chunk.emit_u16(Op::Constant, idx, self.line);
486 }
487 VmValue::Nil => self.chunk.emit(Op::Nil, self.line),
488 VmValue::List(items) => {
489 for item in items.iter() {
490 self.emit_vm_value_literal(item);
491 }
492 self.chunk
493 .emit_u16(Op::BuildList, items.len() as u16, self.line);
494 }
495 VmValue::Dict(entries) => {
496 for (key, item) in entries.iter() {
497 let key_idx = self.chunk.add_constant(Constant::String(key.clone()));
498 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
499 self.emit_vm_value_literal(item);
500 }
501 self.chunk
502 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
503 }
504 _ => {}
505 }
506 }
507
508 pub(super) fn emit_type_name_extra(&mut self, type_name_idx: u16) {
510 let hi = (type_name_idx >> 8) as u8;
511 let lo = type_name_idx as u8;
512 self.chunk.code.push(hi);
513 self.chunk.code.push(lo);
514 self.chunk.lines.push(self.line);
515 self.chunk.columns.push(self.column);
516 self.chunk.lines.push(self.line);
517 self.chunk.columns.push(self.column);
518 }
519
520 pub(super) fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
522 if body.is_empty() {
523 self.chunk.emit(Op::Nil, self.line);
524 } else {
525 self.compile_scoped_block(body)?;
526 }
527 Ok(())
528 }
529
530 pub(super) fn compile_catch_binding(
532 &mut self,
533 error_var: &Option<String>,
534 ) -> Result<(), CompileError> {
535 if let Some(var_name) = error_var {
536 self.emit_define_binding(var_name, false);
537 } else {
538 self.chunk.emit(Op::Pop, self.line);
539 }
540 Ok(())
541 }
542
543 pub(super) fn compile_finally_inline(
550 &mut self,
551 finally_body: &[SNode],
552 ) -> Result<(), CompileError> {
553 if !finally_body.is_empty() {
554 self.compile_scoped_block(finally_body)?;
555 self.chunk.emit(Op::Pop, self.line);
556 }
557 Ok(())
558 }
559
560 pub(super) fn pending_finallys_until_barrier(&self) -> Vec<Vec<SNode>> {
565 let mut out = Vec::new();
566 for entry in self.finally_bodies.iter().rev() {
567 match entry {
568 FinallyEntry::CatchBarrier => break,
569 FinallyEntry::Finally(body) => out.push(body.clone()),
570 }
571 }
572 out
573 }
574
575 pub(super) fn pending_finallys_down_to(&self, floor: usize) -> Vec<Vec<SNode>> {
581 let mut out = Vec::new();
582 for entry in self.finally_bodies[floor..].iter().rev() {
583 if let FinallyEntry::Finally(body) = entry {
584 out.push(body.clone());
585 }
586 }
587 out
588 }
589
590 pub(super) fn all_pending_finallys(&self) -> Vec<Vec<SNode>> {
592 self.pending_finallys_down_to(0)
593 }
594
595 pub(super) fn has_pending_finally(&self) -> bool {
597 self.finally_bodies
598 .iter()
599 .any(|e| matches!(e, FinallyEntry::Finally(_)))
600 }
601
602 pub(super) fn compile_plain_rethrow(&mut self) -> Result<(), CompileError> {
611 self.temp_counter += 1;
612 let temp_name = format!("__finally_err_{}__", self.temp_counter);
613 self.emit_define_binding(&temp_name, true);
614 self.emit_get_binding(&temp_name);
615 self.chunk.emit(Op::Throw, self.line);
616 Ok(())
617 }
618
619 pub(super) fn declare_param_slots(&mut self, params: &[TypedParam]) {
620 for param in params {
621 self.define_local_slot(¶m.name, false);
622 }
623 }
624
625 fn define_local_slot(&mut self, name: &str, mutable: bool) -> Option<u16> {
626 if self.module_level || harn_parser::is_discard_name(name) {
627 return None;
628 }
629 let current = self.local_scopes.last_mut()?;
630 if let Some(existing) = current.get_mut(name) {
631 if existing.mutable || mutable {
632 if mutable {
633 existing.mutable = true;
634 if let Some(info) = self.chunk.local_slots.get_mut(existing.slot as usize) {
635 info.mutable = true;
636 }
637 }
638 return Some(existing.slot);
639 }
640 return None;
641 }
642 let slot = self
643 .chunk
644 .add_local_slot(name.to_string(), mutable, self.scope_depth);
645 current.insert(name.to_string(), super::LocalBinding { slot, mutable });
646 Some(slot)
647 }
648
649 pub(super) fn resolve_local_slot(&self, name: &str) -> Option<super::LocalBinding> {
650 if self.module_level {
651 return None;
652 }
653 self.local_scopes
654 .iter()
655 .rev()
656 .find_map(|scope| scope.get(name).copied())
657 }
658
659 pub(super) fn emit_get_binding(&mut self, name: &str) {
660 if let Some(binding) = self.resolve_local_slot(name) {
661 self.chunk
662 .emit_u16(Op::GetLocalSlot, binding.slot, self.line);
663 } else {
664 let idx = self.chunk.add_constant(Constant::String(name.to_string()));
665 self.chunk.emit_u16(Op::GetVar, idx, self.line);
666 }
667 }
668
669 pub(super) fn emit_define_binding(&mut self, name: &str, mutable: bool) {
670 if let Some(slot) = self.define_local_slot(name, mutable) {
671 self.chunk.emit_u16(Op::DefLocalSlot, slot, self.line);
672 } else {
673 let idx = self.chunk.add_constant(Constant::String(name.to_string()));
674 let op = if mutable { Op::DefVar } else { Op::DefLet };
675 self.chunk.emit_u16(op, idx, self.line);
676 }
677 }
678
679 pub(super) fn emit_init_or_define_binding(&mut self, name: &str, mutable: bool) {
680 if let Some(binding) = self.resolve_local_slot(name) {
681 self.chunk
682 .emit_u16(Op::DefLocalSlot, binding.slot, self.line);
683 } else {
684 self.emit_define_binding(name, mutable);
685 }
686 }
687
688 pub(super) fn emit_set_binding(&mut self, name: &str) {
689 if let Some(binding) = self.resolve_local_slot(name) {
690 let _ = binding.mutable;
691 self.chunk
692 .emit_u16(Op::SetLocalSlot, binding.slot, self.line);
693 } else {
694 let idx = self.chunk.add_constant(Constant::String(name.to_string()));
695 self.chunk.emit_u16(Op::SetVar, idx, self.line);
696 }
697 }
698
699 pub(super) fn begin_scope(&mut self) {
700 self.chunk.emit(Op::PushScope, self.line);
701 self.scope_depth += 1;
702 self.type_scopes.push(std::collections::HashMap::new());
703 self.local_scopes.push(std::collections::HashMap::new());
704 }
705
706 pub(super) fn end_scope(&mut self) {
707 if self.scope_depth > 0 {
708 self.chunk.emit(Op::PopScope, self.line);
709 self.scope_depth -= 1;
710 self.type_scopes.pop();
711 self.local_scopes.pop();
712 }
713 }
714
715 pub(super) 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 self.type_scopes.pop();
720 self.local_scopes.pop();
721 }
722 }
723
724 pub(super) fn compile_scoped_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
725 self.begin_scope();
726 if stmts.is_empty() {
727 self.chunk.emit(Op::Nil, self.line);
728 } else {
729 self.compile_block(stmts)?;
730 }
731 self.end_scope();
732 Ok(())
733 }
734
735 pub(super) fn compile_scoped_statements(
736 &mut self,
737 stmts: &[SNode],
738 ) -> Result<(), CompileError> {
739 self.begin_scope();
740 for sn in stmts {
741 self.compile_node(sn)?;
742 if Self::produces_value(&sn.node) {
743 self.chunk.emit(Op::Pop, self.line);
744 }
745 }
746 self.end_scope();
747 Ok(())
748 }
749
750 pub(super) fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
751 for (i, snode) in stmts.iter().enumerate() {
752 self.compile_node(snode)?;
753 let is_last = i == stmts.len() - 1;
754 if is_last {
755 if !Self::produces_value(&snode.node) {
757 self.chunk.emit(Op::Nil, self.line);
758 }
759 } else if Self::produces_value(&snode.node) {
760 self.chunk.emit(Op::Pop, self.line);
761 }
762 }
763 Ok(())
764 }
765
766 pub(super) fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
768 self.begin_scope();
769 if body.is_empty() {
770 self.chunk.emit(Op::Nil, self.line);
771 } else {
772 self.compile_block(body)?;
773 if !Self::produces_value(&body.last().unwrap().node) {
774 self.chunk.emit(Op::Nil, self.line);
775 }
776 }
777 self.end_scope();
778 Ok(())
779 }
780
781 pub(super) fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
783 match op {
784 "+" => self.chunk.emit(Op::Add, self.line),
785 "-" => self.chunk.emit(Op::Sub, self.line),
786 "*" => self.chunk.emit(Op::Mul, self.line),
787 "/" => self.chunk.emit(Op::Div, self.line),
788 "%" => self.chunk.emit(Op::Mod, self.line),
789 _ => {
790 return Err(CompileError {
791 message: format!("Unknown compound operator: {op}"),
792 line: self.line,
793 })
794 }
795 }
796 Ok(())
797 }
798
799 pub(super) fn root_var_name(&self, node: &SNode) -> Option<String> {
801 match &node.node {
802 Node::Identifier(name) => Some(name.clone()),
803 Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
804 self.root_var_name(object)
805 }
806 Node::SubscriptAccess { object, .. } | Node::OptionalSubscriptAccess { object, .. } => {
807 self.root_var_name(object)
808 }
809 _ => None,
810 }
811 }
812
813 pub(super) fn compile_top_level_declarations(
814 &mut self,
815 program: &[SNode],
816 ) -> Result<(), CompileError> {
817 for sn in program {
825 if matches!(&sn.node, Node::LetBinding { .. } | Node::VarBinding { .. }) {
826 self.compile_node(sn)?;
827 }
828 }
829 for sn in program {
835 let inner_kind = match &sn.node {
836 Node::AttributedDecl { inner, .. } => &inner.node,
837 other => other,
838 };
839 if matches!(
840 inner_kind,
841 Node::FnDecl { .. }
842 | Node::ToolDecl { .. }
843 | Node::SkillDecl { .. }
844 | Node::ImplBlock { .. }
845 | Node::StructDecl { .. }
846 | Node::EnumDecl { .. }
847 | Node::InterfaceDecl { .. }
848 | Node::TypeDecl { .. }
849 ) {
850 self.compile_node(sn)?;
851 }
852 }
853 Ok(())
854 }
855
856 pub(super) fn collect_enum_names(
858 nodes: &[SNode],
859 names: &mut std::collections::HashSet<String>,
860 ) {
861 for sn in nodes {
862 match &sn.node {
863 Node::EnumDecl { name, .. } => {
864 names.insert(name.clone());
865 }
866 Node::Pipeline { body, .. } => {
867 Self::collect_enum_names(body, names);
868 }
869 Node::FnDecl { body, .. } | Node::ToolDecl { body, .. } => {
870 Self::collect_enum_names(body, names);
871 }
872 Node::SkillDecl { fields, .. } => {
873 for (_k, v) in fields {
874 Self::collect_enum_names(std::slice::from_ref(v), names);
875 }
876 }
877 Node::Block(stmts) => {
878 Self::collect_enum_names(stmts, names);
879 }
880 Node::AttributedDecl { inner, .. } => {
881 Self::collect_enum_names(std::slice::from_ref(inner), names);
882 }
883 _ => {}
884 }
885 }
886 }
887
888 pub(super) fn collect_struct_layouts(
889 nodes: &[SNode],
890 layouts: &mut std::collections::HashMap<String, Vec<String>>,
891 ) {
892 for sn in nodes {
893 match &sn.node {
894 Node::StructDecl { name, fields, .. } => {
895 layouts.insert(
896 name.clone(),
897 fields.iter().map(|field| field.name.clone()).collect(),
898 );
899 }
900 Node::Pipeline { body, .. }
901 | Node::FnDecl { body, .. }
902 | Node::ToolDecl { body, .. } => {
903 Self::collect_struct_layouts(body, layouts);
904 }
905 Node::SkillDecl { fields, .. } => {
906 for (_k, v) in fields {
907 Self::collect_struct_layouts(std::slice::from_ref(v), layouts);
908 }
909 }
910 Node::Block(stmts) => {
911 Self::collect_struct_layouts(stmts, layouts);
912 }
913 Node::AttributedDecl { inner, .. } => {
914 Self::collect_struct_layouts(std::slice::from_ref(inner), layouts);
915 }
916 _ => {}
917 }
918 }
919 }
920
921 pub(super) fn collect_interface_methods(
922 nodes: &[SNode],
923 interfaces: &mut std::collections::HashMap<String, Vec<String>>,
924 ) {
925 for sn in nodes {
926 match &sn.node {
927 Node::InterfaceDecl { name, methods, .. } => {
928 let method_names: Vec<String> =
929 methods.iter().map(|m| m.name.clone()).collect();
930 interfaces.insert(name.clone(), method_names);
931 }
932 Node::Pipeline { body, .. }
933 | Node::FnDecl { body, .. }
934 | Node::ToolDecl { body, .. } => {
935 Self::collect_interface_methods(body, interfaces);
936 }
937 Node::SkillDecl { fields, .. } => {
938 for (_k, v) in fields {
939 Self::collect_interface_methods(std::slice::from_ref(v), interfaces);
940 }
941 }
942 Node::Block(stmts) => {
943 Self::collect_interface_methods(stmts, interfaces);
944 }
945 Node::AttributedDecl { inner, .. } => {
946 Self::collect_interface_methods(std::slice::from_ref(inner), interfaces);
947 }
948 _ => {}
949 }
950 }
951 }
952
953 pub fn compile_fn_body(
966 &mut self,
967 params: &[TypedParam],
968 body: &[SNode],
969 source_file: Option<String>,
970 ) -> Result<CompiledFunction, CompileError> {
971 let mut fn_compiler = Compiler::for_nested_body();
972 fn_compiler.enum_names = self.enum_names.clone();
973 fn_compiler.interface_methods = self.interface_methods.clone();
974 fn_compiler.type_aliases = self.type_aliases.clone();
975 fn_compiler.struct_layouts = self.struct_layouts.clone();
976 fn_compiler.declare_param_slots(params);
977 fn_compiler.record_param_types(params);
978 fn_compiler.emit_default_preamble(params)?;
979 fn_compiler.emit_type_checks(params);
980 let is_gen = body_contains_yield(body);
981 fn_compiler.compile_block(body)?;
982 fn_compiler.chunk.emit(Op::Nil, 0);
983 fn_compiler.chunk.emit(Op::Return, 0);
984 fn_compiler.chunk.source_file = source_file;
985 Ok(CompiledFunction {
986 name: String::new(),
987 params: TypedParam::names(params),
988 default_start: TypedParam::default_start(params),
989 chunk: Rc::new(fn_compiler.chunk),
990 is_generator: is_gen,
991 has_rest_param: false,
992 })
993 }
994
995 pub(super) fn produces_value(node: &Node) -> bool {
997 match node {
998 Node::LetBinding { .. }
999 | Node::VarBinding { .. }
1000 | Node::Assignment { .. }
1001 | Node::ReturnStmt { .. }
1002 | Node::FnDecl { .. }
1003 | Node::ToolDecl { .. }
1004 | Node::SkillDecl { .. }
1005 | Node::ImplBlock { .. }
1006 | Node::StructDecl { .. }
1007 | Node::EnumDecl { .. }
1008 | Node::InterfaceDecl { .. }
1009 | Node::TypeDecl { .. }
1010 | Node::ThrowStmt { .. }
1011 | Node::BreakStmt
1012 | Node::ContinueStmt
1013 | Node::RequireStmt { .. }
1014 | Node::DeferStmt { .. } => false,
1015 Node::TryCatch { .. }
1016 | Node::TryExpr { .. }
1017 | Node::Retry { .. }
1018 | Node::GuardStmt { .. }
1019 | Node::DeadlineBlock { .. }
1020 | Node::MutexBlock { .. }
1021 | Node::Spread(_) => true,
1022 _ => true,
1023 }
1024 }
1025}
1026
1027impl Default for Compiler {
1028 fn default() -> Self {
1029 Self::new()
1030 }
1031}