1use crate::value::VmDictExt;
2use std::collections::BTreeMap;
3use std::sync::Arc;
4
5use harn_parser::{Node, SNode, ShapeField, TypeExpr, TypedParam};
6
7use crate::chunk::{Chunk, CompiledFunction, Constant, Op};
8use crate::value::VmValue;
9
10use super::error::CompileError;
11use super::yield_scan::body_contains_yield;
12use super::{peel_node, Compiler, CompilerOptions, FinallyEntry};
13
14#[cfg(test)]
15thread_local! {
16 pub(super) static FORCE_DISCARDED_PRODUCES_VALUE: std::cell::Cell<Option<bool>> =
23 const { std::cell::Cell::new(None) };
24}
25
26impl Compiler {
27 pub fn new() -> Self {
28 Self::with_options(CompilerOptions::from_env())
29 }
30
31 pub fn with_options(options: CompilerOptions) -> Self {
32 Self {
33 options,
34 chunk: Chunk::new(),
35 line: 1,
36 column: 1,
37 enum_names: std::collections::HashSet::new(),
38 struct_layouts: std::collections::HashMap::new(),
39 interface_methods: std::collections::HashMap::new(),
40 loop_stack: Vec::new(),
41 handler_depth: 0,
42 finally_bodies: Vec::new(),
43 temp_counter: 0,
44 scope_depth: 0,
45 type_aliases: std::collections::HashMap::new(),
46 type_scopes: vec![std::collections::HashMap::new()],
47 monomorphic_bindings: std::collections::HashSet::new(),
48 string_constants: std::collections::HashMap::new(),
49 local_scopes: vec![std::collections::HashMap::new()],
50 module_level: true,
51 }
52 }
53
54 pub(super) fn for_nested_body(options: CompilerOptions) -> Self {
58 let mut c = Self::with_options(options);
59 c.module_level = false;
60 c
61 }
62
63 pub(super) fn nested_body(&self) -> Self {
64 Self::for_nested_body(self.options)
65 }
66
67 pub(super) fn nominal_type_names(&self) -> Vec<String> {
68 let mut names: Vec<String> = self
69 .struct_layouts
70 .keys()
71 .chain(self.enum_names.iter())
72 .cloned()
73 .collect();
74 names.sort();
75 names.dedup();
76 names
77 }
78
79 pub(super) fn string_constant(&mut self, value: &str) -> u16 {
80 if let Some(idx) = self.string_constants.get(value) {
81 return *idx;
82 }
83 let owned = value.to_string();
84 let idx = self.chunk.add_constant(Constant::String(owned.clone()));
85 self.string_constants.insert(owned, idx);
86 idx
87 }
88
89 pub(super) fn owned_string_constant(&mut self, value: String) -> u16 {
90 if let Some(idx) = self.string_constants.get(value.as_str()) {
91 return *idx;
92 }
93 let idx = self.chunk.add_constant(Constant::String(value.clone()));
94 self.string_constants.insert(value, idx);
95 idx
96 }
97
98 pub(super) fn collect_type_aliases(&mut self, program: &[SNode]) {
102 for sn in program {
103 if let Node::TypeDecl {
104 name,
105 type_expr,
106 type_params: _,
107 } = &sn.node
108 {
109 self.type_aliases.insert(name.clone(), type_expr.clone());
110 }
111 }
112 }
113
114 pub(super) fn expand_alias(&self, ty: &TypeExpr) -> TypeExpr {
118 match ty {
119 TypeExpr::Named(name) => {
120 if let Some(target) = self.type_aliases.get(name) {
121 self.expand_alias(target)
122 } else {
123 TypeExpr::Named(name.clone())
124 }
125 }
126 TypeExpr::Union(types) => {
127 TypeExpr::Union(types.iter().map(|t| self.expand_alias(t)).collect())
128 }
129 TypeExpr::Intersection(types) => {
130 TypeExpr::Intersection(types.iter().map(|t| self.expand_alias(t)).collect())
131 }
132 TypeExpr::Shape(fields) => TypeExpr::Shape(
133 fields
134 .iter()
135 .map(|field| ShapeField {
136 name: field.name.clone(),
137 type_expr: self.expand_alias(&field.type_expr),
138 optional: field.optional,
139 })
140 .collect(),
141 ),
142 TypeExpr::OpenShape { fields, rests } => TypeExpr::OpenShape {
143 fields: fields
144 .iter()
145 .map(|field| ShapeField {
146 name: field.name.clone(),
147 type_expr: self.expand_alias(&field.type_expr),
148 optional: field.optional,
149 })
150 .collect(),
151 rests: rests.iter().map(|r| self.expand_alias(r)).collect(),
152 },
153 TypeExpr::List(inner) => TypeExpr::List(Box::new(self.expand_alias(inner))),
154 TypeExpr::Iter(inner) => TypeExpr::Iter(Box::new(self.expand_alias(inner))),
155 TypeExpr::Generator(inner) => TypeExpr::Generator(Box::new(self.expand_alias(inner))),
156 TypeExpr::Stream(inner) => TypeExpr::Stream(Box::new(self.expand_alias(inner))),
157 TypeExpr::DictType(k, v) => TypeExpr::DictType(
158 Box::new(self.expand_alias(k)),
159 Box::new(self.expand_alias(v)),
160 ),
161 TypeExpr::FnType {
162 params,
163 return_type,
164 } => TypeExpr::FnType {
165 params: params.iter().map(|p| self.expand_alias(p)).collect(),
166 return_type: Box::new(self.expand_alias(return_type)),
167 },
168 TypeExpr::Applied { name, args } => TypeExpr::Applied {
169 name: name.clone(),
170 args: args.iter().map(|a| self.expand_alias(a)).collect(),
171 },
172 TypeExpr::Never => TypeExpr::Never,
173 TypeExpr::LitString(s) => TypeExpr::LitString(s.clone()),
174 TypeExpr::LitInt(v) => TypeExpr::LitInt(*v),
175 TypeExpr::Owned(inner) => TypeExpr::Owned(Box::new(self.expand_alias(inner))),
176 }
177 }
178
179 pub(super) fn schema_value_for_alias(&self, name: &str) -> Option<VmValue> {
182 let ty = self.type_aliases.get(name)?;
183 let expanded = self.expand_alias(ty);
184 Self::type_expr_to_schema_value(&expanded)
185 }
186
187 pub(super) fn is_schema_guard(name: &str) -> bool {
191 matches!(
192 name,
193 "schema_is"
194 | "schema_expect"
195 | "schema_parse"
196 | "schema_check"
197 | "schema_report"
198 | "is_type"
199 | "json_validate"
200 )
201 }
202
203 pub(super) fn entry_key_is(key: &SNode, keyword: &str) -> bool {
206 matches!(
207 &key.node,
208 Node::Identifier(name) | Node::StringLiteral(name) | Node::RawStringLiteral(name)
209 if name == keyword
210 )
211 }
212
213 pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
216 Self::collect_enum_names(program, &mut self.enum_names);
219 self.enum_names.insert("Result".to_string());
220 Self::collect_struct_layouts(program, &mut self.struct_layouts);
221 Self::collect_interface_methods(program, &mut self.interface_methods);
222 self.collect_type_aliases(program);
223
224 for sn in program {
225 match &sn.node {
226 Node::ImportDecl { .. } | Node::SelectiveImport { .. } => {
227 self.compile_node(sn)?;
228 }
229 _ => {}
230 }
231 }
232 let main = program
233 .iter()
234 .find(|sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == "default"))
235 .or_else(|| {
236 program
237 .iter()
238 .find(|sn| matches!(peel_node(sn), Node::Pipeline { .. }))
239 });
240
241 let mut pipeline_emits_value = false;
245 if let Some(sn) = main {
246 self.compile_top_level_declarations(program)?;
247 if let Node::Pipeline { body, extends, .. } = peel_node(sn) {
248 if let Some(parent_name) = extends {
249 self.compile_parent_pipeline(program, parent_name)?;
250 }
251 let saved = std::mem::replace(&mut self.module_level, false);
252 self.compile_block(body)?;
253 self.module_level = saved;
254 pipeline_emits_value = true;
255 }
256 } else {
257 let top_level: Vec<&SNode> = program
259 .iter()
260 .filter(|sn| {
261 !matches!(
262 &sn.node,
263 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
264 )
265 })
266 .collect();
267 for sn in &top_level {
268 self.compile_discarded_stmt(sn)?;
269 }
270 if Self::has_top_level_fn_main(program) {
275 let harness_name = self.string_constant("harness");
276 self.chunk.emit_u16(Op::GetVar, harness_name, self.line);
277 self.emit_named_call("main", 1);
278 pipeline_emits_value = true;
279 }
280 }
281
282 self.drain_finallys_to_floor(0)?;
283 if !pipeline_emits_value {
284 self.chunk.emit(Op::Nil, self.line);
285 }
286 self.chunk.emit(Op::Return, self.line);
287 super::ensure_chunk_addressable(&self.chunk, "the program body", self.line)?;
288 Ok(self.chunk)
289 }
290
291 fn has_top_level_fn_main(program: &[SNode]) -> bool {
295 program
296 .iter()
297 .any(|sn| matches!(peel_node(sn), Node::FnDecl { name, .. } if name == "main"))
298 }
299
300 pub fn compile_named(
302 mut self,
303 program: &[SNode],
304 pipeline_name: &str,
305 ) -> Result<Chunk, CompileError> {
306 Self::collect_enum_names(program, &mut self.enum_names);
307 self.enum_names.insert("Result".to_string());
308 Self::collect_struct_layouts(program, &mut self.struct_layouts);
309 Self::collect_interface_methods(program, &mut self.interface_methods);
310 self.collect_type_aliases(program);
311
312 for sn in program {
313 if matches!(
314 &sn.node,
315 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
316 ) {
317 self.compile_node(sn)?;
318 }
319 }
320 let target = program.iter().find(
321 |sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == pipeline_name),
322 );
323
324 if let Some(sn) = target {
325 self.compile_top_level_declarations(program)?;
326 if let Node::Pipeline { body, extends, .. } = peel_node(sn) {
327 if let Some(parent_name) = extends {
328 self.compile_parent_pipeline(program, parent_name)?;
329 }
330 let saved = std::mem::replace(&mut self.module_level, false);
331 self.compile_block(body)?;
332 self.module_level = saved;
333 }
334 }
335
336 self.drain_finallys_to_floor(0)?;
337 self.chunk.emit(Op::Nil, self.line);
338 self.chunk.emit(Op::Return, self.line);
339 super::ensure_chunk_addressable(&self.chunk, "the pipeline body", self.line)?;
340 Ok(self.chunk)
341 }
342
343 pub(super) fn compile_parent_pipeline(
345 &mut self,
346 program: &[SNode],
347 parent_name: &str,
348 ) -> Result<(), CompileError> {
349 let parent = program
350 .iter()
351 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == parent_name));
352 if let Some(sn) = parent {
353 if let Node::Pipeline { body, extends, .. } = &sn.node {
354 if let Some(grandparent) = extends {
355 self.compile_parent_pipeline(program, grandparent)?;
356 }
357 for stmt in body {
358 self.compile_discarded_stmt(stmt)?;
359 }
360 }
361 }
362 Ok(())
363 }
364
365 pub(super) fn emit_default_preamble(
370 &mut self,
371 params: &[TypedParam],
372 ) -> Result<(), CompileError> {
373 for (i, param) in params.iter().enumerate() {
374 if let Some(default_expr) = ¶m.default_value {
375 self.chunk.emit(Op::GetArgc, self.line);
376 let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
377 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
378 self.chunk.emit(Op::GreaterEqual, self.line);
379 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
380 self.chunk.emit(Op::Pop, self.line);
382 let masked = self.mask_param_names(¶ms[i..]);
391 let result = self.compile_node(default_expr);
392 self.restore_param_names(masked);
393 result?;
394 self.emit_init_or_define_binding(¶m.name, false);
395 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
396 self.chunk.patch_jump(skip_jump);
397 self.chunk.emit(Op::Pop, self.line);
398 self.chunk.patch_jump(end_jump);
399 }
400 }
401 Ok(())
402 }
403
404 pub(super) fn emit_type_checks(&mut self, params: &[TypedParam]) {
411 for (param_index, param) in params.iter().enumerate() {
412 if let Some(type_expr) = ¶m.type_expr {
413 let check_type = if param.rest {
414 harn_parser::TypeExpr::List(Box::new(type_expr.clone()))
415 } else {
416 type_expr.clone()
417 };
418
419 if let harn_parser::TypeExpr::Named(name) = &check_type {
420 if let Some(methods) = self.interface_methods.get(name).cloned() {
421 let fn_idx = self.string_constant("__assert_interface");
422 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
423 self.emit_get_binding(¶m.name);
424 let name_idx = self.string_constant(¶m.name);
425 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
426 let iface_idx = self.string_constant(name);
427 self.chunk.emit_u16(Op::Constant, iface_idx, self.line);
428 let methods_str = methods.join(",");
429 let methods_idx = self.owned_string_constant(methods_str);
430 self.chunk.emit_u16(Op::Constant, methods_idx, self.line);
431 self.chunk.emit_u8(Op::Call, 4, self.line);
432 self.chunk.emit(Op::Pop, self.line);
433 continue;
434 }
435 }
436
437 if param.default_value.is_some() {
438 if let Some(schema) = Self::type_expr_to_schema_value(&check_type) {
439 self.emit_default_param_schema_check(param_index, param, &schema);
440 }
441 }
442 }
443 }
444 }
445
446 fn emit_default_param_schema_check(
447 &mut self,
448 param_index: usize,
449 param: &TypedParam,
450 schema: &VmValue,
451 ) {
452 self.chunk.emit(Op::GetArgc, self.line);
453 let threshold_idx = self
454 .chunk
455 .add_constant(Constant::Int((param_index + 1) as i64));
456 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
457 self.chunk.emit(Op::GreaterEqual, self.line);
458 let supplied_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
459 self.chunk.emit(Op::Pop, self.line);
460 self.emit_schema_assert_call(param, schema);
461 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
462 self.chunk.patch_jump(supplied_jump);
463 self.chunk.emit(Op::Pop, self.line);
464 self.chunk.patch_jump(end_jump);
465 }
466
467 fn emit_schema_assert_call(&mut self, param: &TypedParam, schema: &VmValue) {
468 let fn_idx = self.string_constant("__assert_schema");
469 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
470 self.emit_get_binding(¶m.name);
471 let name_idx = self.string_constant(¶m.name);
472 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
473 self.emit_vm_value_literal(schema);
474 self.chunk.emit_u8(Op::Call, 3, self.line);
475 self.chunk.emit(Op::Pop, self.line);
476 }
477
478 pub(crate) fn type_expr_to_schema_value(type_expr: &harn_parser::TypeExpr) -> Option<VmValue> {
479 match type_expr {
480 harn_parser::TypeExpr::Named(name) => match name.as_str() {
481 "int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
482 | "closure" | "bytes" => Some(VmValue::dict(BTreeMap::from([(
483 "type".to_string(),
484 VmValue::String(arcstr::ArcStr::from(name.as_str())),
485 )]))),
486 _ => None,
487 },
488 harn_parser::TypeExpr::Shape(fields)
489 | harn_parser::TypeExpr::OpenShape { fields, .. } => {
490 let mut properties = BTreeMap::new();
491 let mut required = Vec::new();
492 for field in fields {
493 let field_schema = Self::type_expr_to_schema_value(&field.type_expr)?;
494 properties.insert(field.name.clone(), field_schema);
495 if !field.optional {
496 required.push(VmValue::String(arcstr::ArcStr::from(field.name.as_str())));
497 }
498 }
499 let mut out = BTreeMap::new();
500 out.put_str("type", "dict");
501 out.insert("properties".to_string(), VmValue::dict(properties));
502 if !required.is_empty() {
503 out.insert(
504 "required".to_string(),
505 VmValue::List(std::sync::Arc::new(required)),
506 );
507 }
508 Some(VmValue::dict(out))
509 }
510 harn_parser::TypeExpr::List(inner) => {
511 let mut out = BTreeMap::new();
512 out.put_str("type", "list");
513 if let Some(item_schema) = Self::type_expr_to_schema_value(inner) {
514 out.insert("items".to_string(), item_schema);
515 }
516 Some(VmValue::dict(out))
517 }
518 harn_parser::TypeExpr::DictType(key, value) => {
519 let mut out = BTreeMap::new();
520 out.put_str("type", "dict");
521 if matches!(key.as_ref(), harn_parser::TypeExpr::Named(name) if name == "string") {
522 if let Some(value_schema) = Self::type_expr_to_schema_value(value) {
523 out.insert("additional_properties".to_string(), value_schema);
524 }
525 }
526 Some(VmValue::dict(out))
527 }
528 harn_parser::TypeExpr::Union(members) => {
529 if !members.is_empty()
534 && members
535 .iter()
536 .all(|m| matches!(m, harn_parser::TypeExpr::LitString(_)))
537 {
538 let values = members
539 .iter()
540 .map(|m| match m {
541 harn_parser::TypeExpr::LitString(s) => {
542 VmValue::String(arcstr::ArcStr::from(s.as_str()))
543 }
544 _ => unreachable!(),
545 })
546 .collect::<Vec<_>>();
547 return Some(VmValue::dict(BTreeMap::from([
548 (
549 "type".to_string(),
550 VmValue::String(arcstr::ArcStr::from("string")),
551 ),
552 (
553 "enum".to_string(),
554 VmValue::List(std::sync::Arc::new(values)),
555 ),
556 ])));
557 }
558 if !members.is_empty()
559 && members
560 .iter()
561 .all(|m| matches!(m, harn_parser::TypeExpr::LitInt(_)))
562 {
563 let values = members
564 .iter()
565 .map(|m| match m {
566 harn_parser::TypeExpr::LitInt(v) => VmValue::Int(*v),
567 _ => unreachable!(),
568 })
569 .collect::<Vec<_>>();
570 return Some(VmValue::dict(BTreeMap::from([
571 (
572 "type".to_string(),
573 VmValue::String(arcstr::ArcStr::from("int")),
574 ),
575 (
576 "enum".to_string(),
577 VmValue::List(std::sync::Arc::new(values)),
578 ),
579 ])));
580 }
581 let branches = members
582 .iter()
583 .filter_map(Self::type_expr_to_schema_value)
584 .collect::<Vec<_>>();
585 if branches.is_empty() {
586 None
587 } else {
588 Some(VmValue::dict(BTreeMap::from([(
589 "union".to_string(),
590 VmValue::List(std::sync::Arc::new(branches)),
591 )])))
592 }
593 }
594 harn_parser::TypeExpr::Intersection(members) => {
595 let branches = members
599 .iter()
600 .filter_map(Self::type_expr_to_schema_value)
601 .collect::<Vec<_>>();
602 if branches.is_empty() {
603 None
604 } else {
605 Some(VmValue::dict(BTreeMap::from([(
606 "all_of".to_string(),
607 VmValue::List(std::sync::Arc::new(branches)),
608 )])))
609 }
610 }
611 harn_parser::TypeExpr::FnType { .. } => Some(VmValue::dict(BTreeMap::from([(
612 "type".to_string(),
613 VmValue::String(arcstr::ArcStr::from("closure")),
614 )]))),
615 harn_parser::TypeExpr::Applied { .. } => None,
616 harn_parser::TypeExpr::Iter(_)
617 | harn_parser::TypeExpr::Generator(_)
618 | harn_parser::TypeExpr::Stream(_) => None,
619 harn_parser::TypeExpr::Never => None,
620 harn_parser::TypeExpr::LitString(s) => Some(VmValue::dict(BTreeMap::from([
621 (
622 "type".to_string(),
623 VmValue::String(arcstr::ArcStr::from("string")),
624 ),
625 (
626 "const".to_string(),
627 VmValue::String(arcstr::ArcStr::from(s.as_str())),
628 ),
629 ]))),
630 harn_parser::TypeExpr::LitInt(v) => Some(VmValue::dict(BTreeMap::from([
631 (
632 "type".to_string(),
633 VmValue::String(arcstr::ArcStr::from("int")),
634 ),
635 ("const".to_string(), VmValue::Int(*v)),
636 ]))),
637 harn_parser::TypeExpr::Owned(inner) => Self::type_expr_to_schema_value(inner),
638 }
639 }
640
641 pub(super) fn emit_vm_value_literal(&mut self, value: &VmValue) {
642 match value {
643 VmValue::String(text) => {
644 let idx = self.string_constant(text);
645 self.chunk.emit_u16(Op::Constant, idx, self.line);
646 }
647 VmValue::Int(number) => {
648 let idx = self.chunk.add_constant(Constant::Int(*number));
649 self.chunk.emit_u16(Op::Constant, idx, self.line);
650 }
651 VmValue::Float(number) => {
652 let idx = self.chunk.add_constant(Constant::Float(*number));
653 self.chunk.emit_u16(Op::Constant, idx, self.line);
654 }
655 VmValue::Bool(value) => {
656 let idx = self.chunk.add_constant(Constant::Bool(*value));
657 self.chunk.emit_u16(Op::Constant, idx, self.line);
658 }
659 VmValue::Nil => self.chunk.emit(Op::Nil, self.line),
660 VmValue::List(items) => {
661 for item in items.iter() {
662 self.emit_vm_value_literal(item);
663 }
664 self.chunk
665 .emit_u16(Op::BuildList, items.len() as u16, self.line);
666 }
667 VmValue::Dict(entries) => {
668 for (key, item) in entries.iter() {
669 let key_idx = self.string_constant(key);
670 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
671 self.emit_vm_value_literal(item);
672 }
673 self.chunk
674 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
675 }
676 _ => {}
677 }
678 }
679
680 pub(super) fn emit_type_name_extra(&mut self, type_name_idx: u16) {
682 let hi = (type_name_idx >> 8) as u8;
683 let lo = type_name_idx as u8;
684 self.chunk.code.push(hi);
685 self.chunk.code.push(lo);
686 self.chunk.lines.push(self.line);
687 self.chunk.columns.push(self.column);
688 self.chunk.lines.push(self.line);
689 self.chunk.columns.push(self.column);
690 }
691
692 pub(super) fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
694 if body.is_empty() {
695 self.chunk.emit(Op::Nil, self.line);
696 } else {
697 self.compile_scoped_block(body)?;
698 }
699 Ok(())
700 }
701
702 pub(super) fn compile_catch_binding(
704 &mut self,
705 error_var: &Option<String>,
706 ) -> Result<(), CompileError> {
707 if let Some(var_name) = error_var {
708 self.emit_define_binding(var_name, false);
709 } else {
710 self.chunk.emit(Op::Pop, self.line);
711 }
712 Ok(())
713 }
714
715 pub(super) fn compile_finally_inline(
722 &mut self,
723 finally_body: &[SNode],
724 ) -> Result<(), CompileError> {
725 if !finally_body.is_empty() {
726 self.compile_scoped_block(finally_body)?;
727 self.chunk.emit(Op::Pop, self.line);
728 }
729 Ok(())
730 }
731
732 pub(super) fn pending_finallys_until_barrier(&self) -> Vec<Vec<SNode>> {
737 let mut out = Vec::new();
738 for entry in self.finally_bodies.iter().rev() {
739 match entry {
740 FinallyEntry::CatchBarrier => break,
741 FinallyEntry::Finally(body) => out.push(body.clone()),
742 }
743 }
744 out
745 }
746
747 pub(super) fn has_pending_finally(&self) -> bool {
749 self.finally_bodies
750 .iter()
751 .any(|e| matches!(e, FinallyEntry::Finally(_)))
752 }
753
754 pub(super) fn compile_plain_rethrow(&mut self) -> Result<(), CompileError> {
763 self.temp_counter += 1;
764 let temp_name = format!("__finally_err_{}__", self.temp_counter);
765 self.emit_define_binding(&temp_name, true);
766 self.emit_get_binding(&temp_name);
767 self.chunk.emit(Op::Throw, self.line);
768 Ok(())
769 }
770
771 pub(super) fn declare_param_slots(&mut self, params: &[TypedParam]) {
772 for param in params {
773 self.define_local_slot(¶m.name, false);
774 }
775 }
776
777 fn mask_param_names(&mut self, params: &[TypedParam]) -> Vec<(String, super::LocalBinding)> {
783 let mut removed = Vec::new();
784 if let Some(scope) = self.local_scopes.last_mut() {
785 for param in params {
786 if let Some(binding) = scope.remove(¶m.name) {
787 removed.push((param.name.clone(), binding));
788 }
789 }
790 }
791 removed
792 }
793
794 fn restore_param_names(&mut self, removed: Vec<(String, super::LocalBinding)>) {
796 if let Some(scope) = self.local_scopes.last_mut() {
797 for (name, binding) in removed {
798 scope.insert(name, binding);
799 }
800 }
801 }
802
803 fn define_local_slot(&mut self, name: &str, mutable: bool) -> Option<u16> {
804 if self.module_level || harn_parser::is_discard_name(name) {
805 return None;
806 }
807 let current = self.local_scopes.last_mut()?;
808 if let Some(existing) = current.get_mut(name) {
809 if existing.mutable || mutable {
810 if mutable {
811 existing.mutable = true;
812 if let Some(info) = self.chunk.local_slots.get_mut(existing.slot as usize) {
813 info.mutable = true;
814 }
815 }
816 return Some(existing.slot);
817 }
818 return None;
819 }
820 let slot = self
821 .chunk
822 .add_local_slot(name.to_string(), mutable, self.scope_depth);
823 current.insert(name.to_string(), super::LocalBinding { slot, mutable });
824 Some(slot)
825 }
826
827 pub(super) fn resolve_local_slot(&self, name: &str) -> Option<super::LocalBinding> {
828 if self.module_level {
829 return None;
830 }
831 self.local_scopes
832 .iter()
833 .rev()
834 .find_map(|scope| scope.get(name).copied())
835 }
836
837 pub(super) fn emit_get_binding(&mut self, name: &str) {
838 if let Some(binding) = self.resolve_local_slot(name) {
839 self.chunk
840 .emit_u16(Op::GetLocalSlot, binding.slot, self.line);
841 } else {
842 let idx = self.string_constant(name);
843 self.chunk.emit_u16(Op::GetVar, idx, self.line);
844 }
845 }
846
847 pub(super) fn emit_define_binding(&mut self, name: &str, mutable: bool) {
848 if let Some(slot) = self.define_local_slot(name, mutable) {
849 self.chunk.emit_u16(Op::DefLocalSlot, slot, self.line);
850 } else {
851 let idx = self.string_constant(name);
852 let op = if mutable { Op::DefVar } else { Op::DefLet };
853 self.chunk.emit_u16(op, idx, self.line);
854 }
855 }
856
857 pub(super) fn emit_init_or_define_binding(&mut self, name: &str, mutable: bool) {
858 if let Some(binding) = self.resolve_local_slot(name) {
859 self.chunk
860 .emit_u16(Op::DefLocalSlot, binding.slot, self.line);
861 } else {
862 self.emit_define_binding(name, mutable);
863 }
864 }
865
866 pub(super) fn emit_set_binding(&mut self, name: &str) {
867 if let Some(binding) = self.resolve_local_slot(name) {
868 let _ = binding.mutable;
869 self.chunk
870 .emit_u16(Op::SetLocalSlot, binding.slot, self.line);
871 } else {
872 let idx = self.string_constant(name);
873 self.chunk.emit_u16(Op::SetVar, idx, self.line);
874 }
875 }
876
877 pub(super) fn begin_scope(&mut self) {
878 self.chunk.emit(Op::PushScope, self.line);
879 self.scope_depth += 1;
880 self.type_scopes.push(std::collections::HashMap::new());
881 self.local_scopes.push(std::collections::HashMap::new());
882 }
883
884 pub(super) fn end_scope(&mut self) {
885 if self.scope_depth > 0 {
886 self.chunk.emit(Op::PopScope, self.line);
887 self.scope_depth -= 1;
888 self.type_scopes.pop();
889 self.local_scopes.pop();
890 }
891 }
892
893 pub(super) fn emit_scope_unwind_to(&mut self, target_depth: usize) {
896 for _ in target_depth..self.scope_depth {
897 self.chunk.emit(Op::PopScope, self.line);
898 }
899 }
900
901 pub(super) fn compile_scoped_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
902 self.begin_scope();
903 let finally_floor = self.finally_bodies.len();
904 if stmts.is_empty() {
905 self.chunk.emit(Op::Nil, self.line);
906 } else {
907 self.compile_block(stmts)?;
908 }
909 self.drain_finallys_to_floor(finally_floor)?;
910 self.end_scope();
911 Ok(())
912 }
913
914 pub(super) fn compile_scoped_statements(
915 &mut self,
916 stmts: &[SNode],
917 ) -> Result<(), CompileError> {
918 self.begin_scope();
919 self.record_monomorphic_var_bindings(stmts);
920 let finally_floor = self.finally_bodies.len();
921 for sn in stmts {
922 self.compile_discarded_stmt(sn)?;
923 }
924 self.drain_finallys_to_floor(finally_floor)?;
925 self.end_scope();
926 Ok(())
927 }
928
929 pub(super) fn drain_finallys_to_floor(&mut self, floor: usize) -> Result<(), CompileError> {
934 while self.finally_bodies.len() > floor {
935 let entry = self.finally_bodies.pop().expect("non-empty by guard");
936 if let FinallyEntry::Finally(body) = entry {
937 self.compile_finally_inline(&body)?;
938 }
939 }
940 Ok(())
941 }
942
943 pub(super) fn run_pending_finallys_for_transfer(
956 &mut self,
957 floor: usize,
958 ) -> Result<(), CompileError> {
959 if self.finally_bodies.len() <= floor {
960 return Ok(());
961 }
962 let saved = self.finally_bodies[floor..].to_vec();
963 let result = self.drain_finallys_to_floor(floor);
964 self.finally_bodies.extend(saved);
965 result
966 }
967
968 pub(super) fn run_pending_finallys_until_barrier(&mut self) -> Result<(), CompileError> {
973 let floor = self
974 .finally_bodies
975 .iter()
976 .rposition(|e| matches!(e, FinallyEntry::CatchBarrier))
977 .map(|i| i + 1)
978 .unwrap_or(0);
979 self.run_pending_finallys_for_transfer(floor)
980 }
981
982 pub(super) fn maybe_register_owned_drop(
987 &mut self,
988 pattern: &harn_parser::BindingPattern,
989 type_ann: Option<&TypeExpr>,
990 span: harn_lexer::Span,
991 ) {
992 let Some(ty) = type_ann else {
998 return;
999 };
1000 if !matches!(ty, TypeExpr::Owned(_)) {
1001 return;
1002 }
1003 let harn_parser::BindingPattern::Identifier(name) = pattern else {
1004 return;
1005 };
1006 if harn_parser::is_discard_name(name) {
1007 return;
1008 }
1009 let call = harn_parser::spanned(
1010 Node::FunctionCall {
1011 name: "drop".to_string(),
1012 args: vec![harn_parser::spanned(Node::Identifier(name.clone()), span)],
1013 type_args: Vec::new(),
1014 },
1015 span,
1016 );
1017 self.finally_bodies.push(FinallyEntry::Finally(vec![call]));
1018 }
1019
1020 pub(super) fn compile_discarded_stmt(&mut self, sn: &SNode) -> Result<(), CompileError> {
1036 #[cfg(debug_assertions)]
1037 let probe = self.chunk.balance_probe();
1038 self.compile_node(sn)?;
1039 #[allow(unused_mut)]
1040 let mut produces = Self::produces_value(&sn.node);
1041 #[cfg(test)]
1045 if let Some(forced) = FORCE_DISCARDED_PRODUCES_VALUE.with(std::cell::Cell::get) {
1046 produces = forced;
1047 }
1048 #[cfg(debug_assertions)]
1049 if let Some(delta) = self.chunk.balance_delta_since(probe) {
1050 let expected = i32::from(produces);
1051 debug_assert_eq!(
1052 delta, expected,
1053 "operand-stack imbalance at line {}: produces_value={produces} but the \
1054 node's emitted bytecode netted {delta} (expected {expected}). A \
1055 `produces_value` arm is out of sync with this node's codegen — see #2622.\n\
1056 node: {:?}",
1057 self.line, sn.node,
1058 );
1059 }
1060 if produces {
1061 self.chunk.emit(Op::Pop, self.line);
1062 }
1063 Ok(())
1064 }
1065
1066 pub(super) fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
1067 self.record_monomorphic_var_bindings(stmts);
1068 for (i, snode) in stmts.iter().enumerate() {
1069 if i == stmts.len() - 1 {
1070 self.compile_node(snode)?;
1074 if !Self::produces_value(&snode.node) {
1075 self.chunk.emit(Op::Nil, self.line);
1076 }
1077 } else {
1078 self.compile_discarded_stmt(snode)?;
1079 }
1080 }
1081 Ok(())
1082 }
1083
1084 pub(super) fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1086 self.begin_scope();
1087 let finally_floor = self.finally_bodies.len();
1088 if body.is_empty() {
1089 self.chunk.emit(Op::Nil, self.line);
1090 } else {
1091 self.compile_block(body)?;
1092 if !Self::produces_value(&body.last().unwrap().node) {
1093 self.chunk.emit(Op::Nil, self.line);
1094 }
1095 }
1096 self.drain_finallys_to_floor(finally_floor)?;
1097 self.end_scope();
1098 Ok(())
1099 }
1100
1101 pub(super) fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
1103 match op {
1104 "+" => self.chunk.emit(Op::Add, self.line),
1105 "-" => self.chunk.emit(Op::Sub, self.line),
1106 "*" => self.chunk.emit(Op::Mul, self.line),
1107 "/" => self.chunk.emit(Op::Div, self.line),
1108 "%" => self.chunk.emit(Op::Mod, self.line),
1109 _ => {
1110 return Err(CompileError {
1111 message: format!("Unknown compound operator: {op}"),
1112 line: self.line,
1113 })
1114 }
1115 }
1116 Ok(())
1117 }
1118
1119 pub(super) fn compile_top_level_declarations(
1120 &mut self,
1121 program: &[SNode],
1122 ) -> Result<(), CompileError> {
1123 for sn in program {
1135 let handled_elsewhere = matches!(
1136 peel_node(sn),
1137 Node::Pipeline { .. }
1138 | Node::ImportDecl { .. }
1139 | Node::SelectiveImport { .. }
1140 | Node::OverrideDecl { .. }
1141 | Node::EvalPackDecl { .. }
1142 | Node::FnDecl { .. }
1143 | Node::ToolDecl { .. }
1144 | Node::SkillDecl { .. }
1145 | Node::ImplBlock { .. }
1146 | Node::StructDecl { .. }
1147 | Node::EnumDecl { .. }
1148 | Node::InterfaceDecl { .. }
1149 | Node::TypeDecl { .. }
1150 );
1151 if !handled_elsewhere {
1152 self.compile_discarded_stmt(sn)?;
1153 }
1154 }
1155 for sn in program {
1161 let inner_kind = match &sn.node {
1162 Node::AttributedDecl { inner, .. } => &inner.node,
1163 other => other,
1164 };
1165 match inner_kind {
1166 Node::EvalPackDecl {
1167 binding_name,
1168 pack_id,
1169 fields,
1170 body,
1171 summarize,
1172 ..
1173 } => {
1174 self.compile_eval_pack_decl(
1175 binding_name,
1176 pack_id,
1177 fields,
1178 body,
1179 summarize,
1180 false,
1181 )?;
1182 }
1183 Node::FnDecl { .. }
1184 | Node::ToolDecl { .. }
1185 | Node::SkillDecl { .. }
1186 | Node::ImplBlock { .. }
1187 | Node::StructDecl { .. }
1188 | Node::EnumDecl { .. }
1189 | Node::InterfaceDecl { .. }
1190 | Node::TypeDecl { .. } => {
1191 self.compile_node(sn)?;
1192 }
1193 _ => {}
1194 }
1195 }
1196 Ok(())
1197 }
1198
1199 pub(super) fn collect_enum_names(
1201 nodes: &[SNode],
1202 names: &mut std::collections::HashSet<String>,
1203 ) {
1204 for sn in nodes {
1205 match &sn.node {
1206 Node::EnumDecl { name, .. } => {
1207 names.insert(name.clone());
1208 }
1209 Node::Pipeline { body, .. } => {
1210 Self::collect_enum_names(body, names);
1211 }
1212 Node::FnDecl { body, .. } | Node::ToolDecl { body, .. } => {
1213 Self::collect_enum_names(body, names);
1214 }
1215 Node::SkillDecl { fields, .. } => {
1216 for (_k, v) in fields {
1217 Self::collect_enum_names(std::slice::from_ref(v), names);
1218 }
1219 }
1220 Node::EvalPackDecl {
1221 fields,
1222 body,
1223 summarize,
1224 ..
1225 } => {
1226 for (_k, v) in fields {
1227 Self::collect_enum_names(std::slice::from_ref(v), names);
1228 }
1229 Self::collect_enum_names(body, names);
1230 if let Some(summary_body) = summarize {
1231 Self::collect_enum_names(summary_body, names);
1232 }
1233 }
1234 Node::Block(stmts) => {
1235 Self::collect_enum_names(stmts, names);
1236 }
1237 Node::AttributedDecl { inner, .. } => {
1238 Self::collect_enum_names(std::slice::from_ref(inner), names);
1239 }
1240 _ => {}
1241 }
1242 }
1243 }
1244
1245 pub(super) fn collect_struct_layouts(
1246 nodes: &[SNode],
1247 layouts: &mut std::collections::HashMap<String, Vec<String>>,
1248 ) {
1249 for sn in nodes {
1250 match &sn.node {
1251 Node::StructDecl { name, fields, .. } => {
1252 layouts.insert(
1253 name.clone(),
1254 fields.iter().map(|field| field.name.clone()).collect(),
1255 );
1256 }
1257 Node::Pipeline { body, .. }
1258 | Node::FnDecl { body, .. }
1259 | Node::ToolDecl { body, .. } => {
1260 Self::collect_struct_layouts(body, layouts);
1261 }
1262 Node::SkillDecl { fields, .. } => {
1263 for (_k, v) in fields {
1264 Self::collect_struct_layouts(std::slice::from_ref(v), layouts);
1265 }
1266 }
1267 Node::EvalPackDecl {
1268 fields,
1269 body,
1270 summarize,
1271 ..
1272 } => {
1273 for (_k, v) in fields {
1274 Self::collect_struct_layouts(std::slice::from_ref(v), layouts);
1275 }
1276 Self::collect_struct_layouts(body, layouts);
1277 if let Some(summary_body) = summarize {
1278 Self::collect_struct_layouts(summary_body, layouts);
1279 }
1280 }
1281 Node::Block(stmts) => {
1282 Self::collect_struct_layouts(stmts, layouts);
1283 }
1284 Node::AttributedDecl { inner, .. } => {
1285 Self::collect_struct_layouts(std::slice::from_ref(inner), layouts);
1286 }
1287 _ => {}
1288 }
1289 }
1290 }
1291
1292 pub(super) fn collect_interface_methods(
1293 nodes: &[SNode],
1294 interfaces: &mut std::collections::HashMap<String, Vec<String>>,
1295 ) {
1296 for sn in nodes {
1297 match &sn.node {
1298 Node::InterfaceDecl { name, methods, .. } => {
1299 let method_names: Vec<String> =
1300 methods.iter().map(|m| m.name.clone()).collect();
1301 interfaces.insert(name.clone(), method_names);
1302 }
1303 Node::Pipeline { body, .. }
1304 | Node::FnDecl { body, .. }
1305 | Node::ToolDecl { body, .. } => {
1306 Self::collect_interface_methods(body, interfaces);
1307 }
1308 Node::SkillDecl { fields, .. } => {
1309 for (_k, v) in fields {
1310 Self::collect_interface_methods(std::slice::from_ref(v), interfaces);
1311 }
1312 }
1313 Node::EvalPackDecl {
1314 fields,
1315 body,
1316 summarize,
1317 ..
1318 } => {
1319 for (_k, v) in fields {
1320 Self::collect_interface_methods(std::slice::from_ref(v), interfaces);
1321 }
1322 Self::collect_interface_methods(body, interfaces);
1323 if let Some(summary_body) = summarize {
1324 Self::collect_interface_methods(summary_body, interfaces);
1325 }
1326 }
1327 Node::Block(stmts) => {
1328 Self::collect_interface_methods(stmts, interfaces);
1329 }
1330 Node::AttributedDecl { inner, .. } => {
1331 Self::collect_interface_methods(std::slice::from_ref(inner), interfaces);
1332 }
1333 _ => {}
1334 }
1335 }
1336 }
1337
1338 pub fn compile_fn_body(
1351 &mut self,
1352 type_params: &[harn_parser::TypeParam],
1353 params: &[TypedParam],
1354 body: &[SNode],
1355 source_file: Option<String>,
1356 ) -> Result<CompiledFunction, CompileError> {
1357 let mut fn_compiler = self.nested_body();
1358 fn_compiler.enum_names = self.enum_names.clone();
1359 fn_compiler.interface_methods = self.interface_methods.clone();
1360 fn_compiler.type_aliases = self.type_aliases.clone();
1361 fn_compiler.struct_layouts = self.struct_layouts.clone();
1362 fn_compiler.declare_param_slots(params);
1363 fn_compiler.record_param_types(params);
1364 fn_compiler.emit_default_preamble(params)?;
1365 fn_compiler.emit_type_checks(params);
1366 let is_gen = body_contains_yield(body);
1367 fn_compiler.compile_block(body)?;
1368 fn_compiler.chunk.emit(Op::Nil, 0);
1369 fn_compiler.chunk.emit(Op::Return, 0);
1370 fn_compiler.chunk.source_file = source_file;
1371 let param_slots = crate::chunk::ParamSlot::vec_from_typed(params);
1372 let has_runtime_type_checks =
1373 CompiledFunction::has_runtime_type_checks_for_params(¶m_slots);
1374 super::ensure_chunk_addressable(&fn_compiler.chunk, "function body", self.line)?;
1375 Ok(CompiledFunction {
1376 name: String::new(),
1377 type_params: type_params.iter().map(|param| param.name.clone()).collect(),
1378 nominal_type_names: fn_compiler.nominal_type_names(),
1379 params: param_slots,
1380 default_start: TypedParam::default_start(params),
1381 chunk: Arc::new(fn_compiler.chunk),
1382 is_generator: is_gen,
1383 is_stream: false,
1384 has_rest_param: false,
1385 has_runtime_type_checks,
1386 })
1387 }
1388
1389 pub(super) fn produces_value(node: &Node) -> bool {
1391 match node {
1392 Node::AttributedDecl { inner, .. } => Self::produces_value(&inner.node),
1399 Node::LetBinding { .. }
1400 | Node::VarBinding { .. }
1401 | Node::ConstBinding { .. }
1402 | Node::Assignment { .. }
1403 | Node::ReturnStmt { .. }
1404 | Node::FnDecl { .. }
1405 | Node::ToolDecl { .. }
1406 | Node::SkillDecl { .. }
1407 | Node::EvalPackDecl { .. }
1408 | Node::ImplBlock { .. }
1409 | Node::StructDecl { .. }
1410 | Node::EnumDecl { .. }
1411 | Node::InterfaceDecl { .. }
1412 | Node::TypeDecl { .. }
1413 | Node::OverrideDecl { .. }
1416 | Node::Pipeline { .. }
1417 | Node::ThrowStmt { .. }
1418 | Node::BreakStmt
1419 | Node::ContinueStmt
1420 | Node::RequireStmt { .. }
1421 | Node::DeferStmt { .. } => false,
1422 Node::TryCatch { has_catch: _, .. }
1423 | Node::TryExpr { .. }
1424 | Node::Retry { .. }
1425 | Node::GuardStmt { .. }
1426 | Node::DeadlineBlock { .. }
1427 | Node::MutexBlock { .. }
1428 | Node::Spread(_) => true,
1429 _ => true,
1430 }
1431 }
1432}
1433
1434impl Default for Compiler {
1435 fn default() -> Self {
1436 Self::new()
1437 }
1438}