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 enum_variant_owners: std::collections::HashMap::new(),
39 predeclared_enum_declarations: std::collections::HashSet::new(),
40 enum_catalog_scopes: Vec::new(),
41 struct_layouts: std::collections::HashMap::new(),
42 interface_methods: std::collections::HashMap::new(),
43 loop_stack: Vec::new(),
44 handler_depth: 0,
45 finally_bodies: Vec::new(),
46 temp_counter: 0,
47 scope_depth: 0,
48 type_aliases: std::collections::HashMap::new(),
49 type_scopes: vec![std::collections::HashMap::new()],
50 monomorphic_bindings: std::collections::HashSet::new(),
51 string_constants: std::collections::HashMap::new(),
52 local_scopes: vec![std::collections::HashMap::new()],
53 module_level: true,
54 captured_bindings: std::collections::HashSet::new(),
55 }
56 }
57
58 pub(super) fn for_nested_body(options: CompilerOptions) -> Self {
62 let mut c = Self::with_options(options);
63 c.module_level = false;
64 c
65 }
66
67 pub(super) fn nested_body(&self) -> Self {
68 Self::for_nested_body(self.options)
69 }
70
71 pub(super) fn nominal_type_names(&self) -> Vec<String> {
72 let mut names: Vec<String> = self
73 .struct_layouts
74 .keys()
75 .chain(self.enum_names.iter())
76 .cloned()
77 .collect();
78 names.sort();
79 names.dedup();
80 names
81 }
82
83 pub(super) fn string_constant(&mut self, value: &str) -> u16 {
84 if let Some(idx) = self.string_constants.get(value) {
85 return *idx;
86 }
87 let owned = value.to_string();
88 let idx = self.chunk.add_constant(Constant::String(owned.clone()));
89 self.string_constants.insert(owned, idx);
90 idx
91 }
92
93 pub(super) fn owned_string_constant(&mut self, value: String) -> u16 {
94 if let Some(idx) = self.string_constants.get(value.as_str()) {
95 return *idx;
96 }
97 let idx = self.chunk.add_constant(Constant::String(value.clone()));
98 self.string_constants.insert(value, idx);
99 idx
100 }
101
102 pub(crate) fn collect_type_aliases(&mut self, program: &[SNode]) {
106 for sn in program {
107 if let Node::TypeDecl {
108 name,
109 type_expr,
110 type_params: _,
111 is_pub: _,
112 } = peel_node(sn)
113 {
114 self.type_aliases.insert(name.clone(), type_expr.clone());
115 }
116 }
117 }
118
119 pub(crate) fn expand_alias(&self, ty: &TypeExpr) -> TypeExpr {
129 let mut visiting = std::collections::HashSet::new();
130 self.expand_alias_inner(ty, &mut visiting)
131 }
132
133 fn expand_alias_inner(
134 &self,
135 ty: &TypeExpr,
136 visiting: &mut std::collections::HashSet<String>,
137 ) -> TypeExpr {
138 match ty {
139 TypeExpr::Named(name) => {
140 if let Some(target) = self.type_aliases.get(name) {
141 if !visiting.insert(name.clone()) {
142 return TypeExpr::Named(name.clone());
143 }
144 let resolved = self.expand_alias_inner(target, visiting);
145 visiting.remove(name);
146 resolved
147 } else {
148 TypeExpr::Named(name.clone())
149 }
150 }
151 TypeExpr::Union(types) => TypeExpr::Union(
152 types
153 .iter()
154 .map(|t| self.expand_alias_inner(t, visiting))
155 .collect(),
156 ),
157 TypeExpr::Intersection(types) => TypeExpr::Intersection(
158 types
159 .iter()
160 .map(|t| self.expand_alias_inner(t, visiting))
161 .collect(),
162 ),
163 TypeExpr::Shape(fields) => TypeExpr::Shape(
164 fields
165 .iter()
166 .map(|field| ShapeField {
167 type_expr: self.expand_alias_inner(&field.type_expr, visiting),
168 ..field.clone()
169 })
170 .collect(),
171 ),
172 TypeExpr::OpenShape { fields, rests } => TypeExpr::OpenShape {
173 fields: fields
174 .iter()
175 .map(|field| ShapeField {
176 type_expr: self.expand_alias_inner(&field.type_expr, visiting),
177 ..field.clone()
178 })
179 .collect(),
180 rests: rests
181 .iter()
182 .map(|r| self.expand_alias_inner(r, visiting))
183 .collect(),
184 },
185 TypeExpr::List(inner) => {
186 TypeExpr::List(Box::new(self.expand_alias_inner(inner, visiting)))
187 }
188 TypeExpr::Iter(inner) => {
189 TypeExpr::Iter(Box::new(self.expand_alias_inner(inner, visiting)))
190 }
191 TypeExpr::Generator(inner) => {
192 TypeExpr::Generator(Box::new(self.expand_alias_inner(inner, visiting)))
193 }
194 TypeExpr::Stream(inner) => {
195 TypeExpr::Stream(Box::new(self.expand_alias_inner(inner, visiting)))
196 }
197 TypeExpr::DictType(k, v) => TypeExpr::DictType(
198 Box::new(self.expand_alias_inner(k, visiting)),
199 Box::new(self.expand_alias_inner(v, visiting)),
200 ),
201 TypeExpr::FnType {
202 params,
203 return_type,
204 } => TypeExpr::FnType {
205 params: params
206 .iter()
207 .map(|p| self.expand_alias_inner(p, visiting))
208 .collect(),
209 return_type: Box::new(self.expand_alias_inner(return_type, visiting)),
210 },
211 TypeExpr::Applied { name, args } => TypeExpr::Applied {
212 name: name.clone(),
213 args: args
214 .iter()
215 .map(|a| self.expand_alias_inner(a, visiting))
216 .collect(),
217 },
218 TypeExpr::Never => TypeExpr::Never,
219 TypeExpr::LitString(s) => TypeExpr::LitString(s.clone()),
220 TypeExpr::LitInt(v) => TypeExpr::LitInt(*v),
221 TypeExpr::Owned(inner) => {
222 TypeExpr::Owned(Box::new(self.expand_alias_inner(inner, visiting)))
223 }
224 }
225 }
226
227 pub(super) fn schema_value_for_alias(&self, name: &str) -> Option<VmValue> {
230 let ty = self.type_aliases.get(name)?;
231 let expanded = self.expand_alias(ty);
232 Self::type_expr_to_schema_value(&expanded)
233 }
234
235 pub fn lower_public_type_schemas(
242 program: &[SNode],
243 ) -> std::collections::BTreeMap<String, VmValue> {
244 let mut compiler = Compiler::new();
245 compiler.collect_type_aliases(program);
246 let mut schemas = std::collections::BTreeMap::new();
247 for sn in program {
248 let inner = peel_node(sn);
249 if let Node::TypeDecl {
250 name, is_pub: true, ..
251 } = inner
252 {
253 if let Some(schema) = compiler.schema_value_for_alias(name) {
254 schemas.insert(name.clone(), schema);
255 }
256 }
257 }
258 schemas
259 }
260
261 pub(super) fn is_schema_guard(name: &str) -> bool {
265 matches!(
266 name,
267 "schema_is"
268 | "schema_expect"
269 | "schema_parse"
270 | "schema_check"
271 | "schema_report"
272 | "is_type"
273 | "json_validate"
274 )
275 }
276
277 pub(super) fn entry_key_is(key: &SNode, keyword: &str) -> bool {
280 matches!(
281 &key.node,
282 Node::Identifier(name) | Node::StringLiteral(name) | Node::RawStringLiteral(name)
283 if name == keyword
284 )
285 }
286
287 pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
290 self.collect_module_enum_catalog(program);
293 if self.enum_names.insert("Result".to_string()) {
294 Self::seed_builtin_variant_owners(&mut self.enum_variant_owners);
295 }
296 Self::collect_struct_layouts(program, &mut self.struct_layouts);
297 Self::collect_interface_methods(program, &mut self.interface_methods);
298 self.collect_type_aliases(program);
299 self.seed_module_captured_idents(program);
304
305 for sn in program {
306 match &sn.node {
307 Node::ImportDecl { .. } | Node::SelectiveImport { .. } => {
308 self.compile_node(sn)?;
309 }
310 _ => {}
311 }
312 }
313 let main = program
314 .iter()
315 .find(|sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == "default"))
316 .or_else(|| {
317 program
318 .iter()
319 .find(|sn| matches!(peel_node(sn), Node::Pipeline { .. }))
320 });
321
322 let mut pipeline_emits_value = false;
326 if let Some(sn) = main {
327 self.compile_top_level_declarations(program)?;
328 if let Node::Pipeline { body, extends, .. } = peel_node(sn) {
329 self.compile_with_pipeline_captures(
330 program,
331 body,
332 extends.as_deref(),
333 |compiler| {
334 if let Some(parent_name) = extends {
335 compiler.compile_parent_pipeline(program, parent_name)?;
336 }
337 let saved = std::mem::replace(&mut compiler.module_level, false);
338 let result = compiler.compile_block(body);
339 compiler.module_level = saved;
340 result
341 },
342 )?;
343 pipeline_emits_value = true;
344 }
345 } else {
346 let top_level: Vec<&SNode> = program
348 .iter()
349 .filter(|sn| {
350 !matches!(
351 &sn.node,
352 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
353 )
354 })
355 .collect();
356 for sn in &top_level {
357 self.compile_discarded_stmt(sn)?;
358 }
359 if Self::has_top_level_fn_main(program) {
364 let harness_name = self.string_constant("harness");
365 self.chunk.emit_u16(Op::GetVar, harness_name, self.line);
366 self.emit_named_call("main", 1);
367 pipeline_emits_value = true;
368 }
369 }
370
371 self.drain_finallys_to_floor(0)?;
372 if !pipeline_emits_value {
373 self.chunk.emit(Op::Nil, self.line);
374 }
375 self.chunk.emit(Op::Return, self.line);
376 super::ensure_chunk_addressable(&self.chunk, "the program body", self.line)?;
377 Ok(self.chunk)
378 }
379
380 fn has_top_level_fn_main(program: &[SNode]) -> bool {
384 program
385 .iter()
386 .any(|sn| matches!(peel_node(sn), Node::FnDecl { name, .. } if name == "main"))
387 }
388
389 pub fn compile_named(
391 self,
392 program: &[SNode],
393 pipeline_name: &str,
394 ) -> Result<Chunk, CompileError> {
395 self.compile_named_inner(program, pipeline_name, false)
396 }
397
398 pub fn compile_named_with_param_globals(
405 self,
406 program: &[SNode],
407 pipeline_name: &str,
408 ) -> Result<Chunk, CompileError> {
409 self.compile_named_inner(program, pipeline_name, true)
410 }
411
412 fn compile_named_inner(
413 mut self,
414 program: &[SNode],
415 pipeline_name: &str,
416 bind_params_from_globals: bool,
417 ) -> Result<Chunk, CompileError> {
418 self.collect_module_enum_catalog(program);
419 if self.enum_names.insert("Result".to_string()) {
420 Self::seed_builtin_variant_owners(&mut self.enum_variant_owners);
421 }
422 Self::collect_struct_layouts(program, &mut self.struct_layouts);
423 Self::collect_interface_methods(program, &mut self.interface_methods);
424 self.collect_type_aliases(program);
425 self.seed_module_captured_idents(program);
430
431 for sn in program {
432 if matches!(
433 &sn.node,
434 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
435 ) {
436 self.compile_node(sn)?;
437 }
438 }
439 let target = program.iter().find(
440 |sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == pipeline_name),
441 );
442
443 let mut pipeline_emits_value = false;
444 if let Some(sn) = target {
445 self.compile_top_level_declarations(program)?;
446 if let Node::Pipeline {
447 name,
448 body,
449 extends,
450 params,
451 ..
452 } = peel_node(sn)
453 {
454 if bind_params_from_globals {
455 let callable = self.compile_pipeline_callable(
456 program,
457 name,
458 params,
459 body,
460 extends.as_deref(),
461 )?;
462 let function_index = self.chunk.functions.len();
463 self.chunk.functions.push(Arc::new(callable));
464 self.chunk
465 .emit_u16(Op::Closure, function_index as u16, self.line);
466 for param in params {
467 let index = self.string_constant(¶m.name);
468 self.chunk.emit_u16(Op::GetVar, index, self.line);
469 }
470 self.chunk.emit_u8(Op::Call, params.len() as u8, self.line);
471 pipeline_emits_value = true;
472 } else {
473 self.compile_with_pipeline_captures(
474 program,
475 body,
476 extends.as_deref(),
477 |compiler| {
478 if let Some(parent_name) = extends {
479 compiler.compile_parent_pipeline(program, parent_name)?;
480 }
481 let saved = std::mem::replace(&mut compiler.module_level, false);
482 let result = compiler.compile_block(body);
483 compiler.module_level = saved;
484 result
485 },
486 )?;
487 }
488 }
489 }
490
491 self.drain_finallys_to_floor(0)?;
492 if !pipeline_emits_value {
493 self.chunk.emit(Op::Nil, self.line);
494 }
495 self.chunk.emit(Op::Return, self.line);
496 super::ensure_chunk_addressable(&self.chunk, "the pipeline body", self.line)?;
497 Ok(self.chunk)
498 }
499
500 pub(super) fn emit_default_preamble(
505 &mut self,
506 params: &[TypedParam],
507 ) -> Result<(), CompileError> {
508 for (i, param) in params.iter().enumerate() {
509 if let Some(default_expr) = ¶m.default_value {
510 self.chunk.emit(Op::GetArgc, self.line);
511 let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
512 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
513 self.chunk.emit(Op::GreaterEqual, self.line);
514 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
515 self.chunk.emit(Op::Pop, self.line);
517 let masked = self.mask_param_names(¶ms[i..]);
526 let result = self.compile_node(default_expr);
527 self.restore_param_names(masked);
528 result?;
529 self.emit_init_or_define_binding(¶m.name, false);
530 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
531 self.chunk.patch_jump(skip_jump);
532 self.chunk.emit(Op::Pop, self.line);
533 self.chunk.patch_jump(end_jump);
534 }
535 }
536 Ok(())
537 }
538
539 pub(super) fn emit_type_checks(&mut self, params: &[TypedParam]) {
546 for (param_index, param) in params.iter().enumerate() {
547 if let Some(type_expr) = ¶m.type_expr {
548 let check_type = if param.rest {
549 harn_parser::TypeExpr::List(Box::new(type_expr.clone()))
550 } else {
551 type_expr.clone()
552 };
553
554 if let harn_parser::TypeExpr::Named(name) = &check_type {
555 if let Some(methods) = self.interface_methods.get(name).cloned() {
556 let fn_idx = self.string_constant("__assert_interface");
557 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
558 self.emit_get_binding(¶m.name);
559 let name_idx = self.string_constant(¶m.name);
560 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
561 let iface_idx = self.string_constant(name);
562 self.chunk.emit_u16(Op::Constant, iface_idx, self.line);
563 let methods_str = methods.join(",");
564 let methods_idx = self.owned_string_constant(methods_str);
565 self.chunk.emit_u16(Op::Constant, methods_idx, self.line);
566 self.chunk.emit_u8(Op::Call, 4, self.line);
567 self.chunk.emit(Op::Pop, self.line);
568 continue;
569 }
570 }
571
572 if param.default_value.is_some() {
573 if let Some(schema) = Self::type_expr_to_schema_value(&check_type) {
574 self.emit_default_param_schema_check(param_index, param, &schema);
575 }
576 }
577 }
578 }
579 }
580
581 fn emit_default_param_schema_check(
582 &mut self,
583 param_index: usize,
584 param: &TypedParam,
585 schema: &VmValue,
586 ) {
587 self.chunk.emit(Op::GetArgc, self.line);
588 let threshold_idx = self
589 .chunk
590 .add_constant(Constant::Int((param_index + 1) as i64));
591 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
592 self.chunk.emit(Op::GreaterEqual, self.line);
593 let supplied_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
594 self.chunk.emit(Op::Pop, self.line);
595 self.emit_schema_assert_call(param, schema);
596 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
597 self.chunk.patch_jump(supplied_jump);
598 self.chunk.emit(Op::Pop, self.line);
599 self.chunk.patch_jump(end_jump);
600 }
601
602 fn emit_schema_assert_call(&mut self, param: &TypedParam, schema: &VmValue) {
603 let fn_idx = self.string_constant("__assert_schema");
604 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
605 self.emit_get_binding(¶m.name);
606 let name_idx = self.string_constant(¶m.name);
607 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
608 self.emit_vm_value_literal(schema);
609 self.chunk.emit_u8(Op::Call, 3, self.line);
610 self.chunk.emit(Op::Pop, self.line);
611 }
612
613 pub(crate) fn type_expr_to_schema_value(type_expr: &harn_parser::TypeExpr) -> Option<VmValue> {
614 match type_expr {
615 harn_parser::TypeExpr::Named(name) => match name.as_str() {
616 "int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
617 | "closure" | "bytes" => Some(VmValue::dict(BTreeMap::from([(
618 "type".to_string(),
619 VmValue::String(arcstr::ArcStr::from(name.as_str())),
620 )]))),
621 _ => None,
622 },
623 harn_parser::TypeExpr::Shape(fields)
624 | harn_parser::TypeExpr::OpenShape { fields, .. } => {
625 let mut properties = BTreeMap::new();
626 let mut required = Vec::new();
627 for field in fields {
628 let mut field_schema = Self::type_expr_to_schema_value(&field.type_expr)?;
629 if field.optional {
630 field_schema = VmValue::dict(BTreeMap::from([(
631 "union".to_string(),
632 VmValue::List(std::sync::Arc::new(vec![
633 field_schema,
634 VmValue::dict(BTreeMap::from([(
635 "type".to_string(),
636 VmValue::String(arcstr::ArcStr::from("nil")),
637 )])),
638 ])),
639 )]));
640 }
641 properties.insert(field.name.clone(), field_schema);
642 if !field.optional {
643 required.push(VmValue::String(arcstr::ArcStr::from(field.name.as_str())));
644 }
645 }
646 let mut out = BTreeMap::new();
647 out.put_str("type", "dict");
648 out.insert("properties".to_string(), VmValue::dict(properties));
649 if !required.is_empty() {
650 out.insert(
651 "required".to_string(),
652 VmValue::List(std::sync::Arc::new(required)),
653 );
654 }
655 Some(VmValue::dict(out))
656 }
657 harn_parser::TypeExpr::List(inner) => {
658 let mut out = BTreeMap::new();
659 out.put_str("type", "list");
660 if let Some(item_schema) = Self::type_expr_to_schema_value(inner) {
661 out.insert("items".to_string(), item_schema);
662 }
663 Some(VmValue::dict(out))
664 }
665 harn_parser::TypeExpr::DictType(key, value) => {
666 let mut out = BTreeMap::new();
667 out.put_str("type", "dict");
668 if matches!(key.as_ref(), harn_parser::TypeExpr::Named(name) if name == "string") {
669 if let Some(value_schema) = Self::type_expr_to_schema_value(value) {
670 out.insert("additional_properties".to_string(), value_schema);
671 }
672 }
673 Some(VmValue::dict(out))
674 }
675 harn_parser::TypeExpr::Union(members) => {
676 if !members.is_empty()
681 && members
682 .iter()
683 .all(|m| matches!(m, harn_parser::TypeExpr::LitString(_)))
684 {
685 let values = members
686 .iter()
687 .map(|m| match m {
688 harn_parser::TypeExpr::LitString(s) => {
689 VmValue::String(arcstr::ArcStr::from(s.as_str()))
690 }
691 _ => unreachable!(),
692 })
693 .collect::<Vec<_>>();
694 return Some(VmValue::dict(BTreeMap::from([
695 (
696 "type".to_string(),
697 VmValue::String(arcstr::ArcStr::from("string")),
698 ),
699 (
700 "enum".to_string(),
701 VmValue::List(std::sync::Arc::new(values)),
702 ),
703 ])));
704 }
705 if !members.is_empty()
706 && members
707 .iter()
708 .all(|m| matches!(m, harn_parser::TypeExpr::LitInt(_)))
709 {
710 let values = members
711 .iter()
712 .map(|m| match m {
713 harn_parser::TypeExpr::LitInt(v) => VmValue::Int(*v),
714 _ => unreachable!(),
715 })
716 .collect::<Vec<_>>();
717 return Some(VmValue::dict(BTreeMap::from([
718 (
719 "type".to_string(),
720 VmValue::String(arcstr::ArcStr::from("int")),
721 ),
722 (
723 "enum".to_string(),
724 VmValue::List(std::sync::Arc::new(values)),
725 ),
726 ])));
727 }
728 let branches = members
729 .iter()
730 .map(Self::type_expr_to_schema_value)
731 .collect::<Option<Vec<_>>>()?;
732 if branches.is_empty() {
733 None
734 } else {
735 Some(VmValue::dict(BTreeMap::from([(
736 "union".to_string(),
737 VmValue::List(std::sync::Arc::new(branches)),
738 )])))
739 }
740 }
741 harn_parser::TypeExpr::Intersection(members) => {
742 let branches = members
746 .iter()
747 .map(Self::type_expr_to_schema_value)
748 .collect::<Option<Vec<_>>>()?;
749 if branches.is_empty() {
750 None
751 } else {
752 Some(VmValue::dict(BTreeMap::from([(
753 "all_of".to_string(),
754 VmValue::List(std::sync::Arc::new(branches)),
755 )])))
756 }
757 }
758 harn_parser::TypeExpr::FnType { .. } => Some(VmValue::dict(BTreeMap::from([(
759 "type".to_string(),
760 VmValue::String(arcstr::ArcStr::from("closure")),
761 )]))),
762 harn_parser::TypeExpr::Applied { .. } => None,
763 harn_parser::TypeExpr::Iter(_)
764 | harn_parser::TypeExpr::Generator(_)
765 | harn_parser::TypeExpr::Stream(_) => None,
766 harn_parser::TypeExpr::Never => None,
767 harn_parser::TypeExpr::LitString(s) => Some(VmValue::dict(BTreeMap::from([
768 (
769 "type".to_string(),
770 VmValue::String(arcstr::ArcStr::from("string")),
771 ),
772 (
773 "const".to_string(),
774 VmValue::String(arcstr::ArcStr::from(s.as_str())),
775 ),
776 ]))),
777 harn_parser::TypeExpr::LitInt(v) => Some(VmValue::dict(BTreeMap::from([
778 (
779 "type".to_string(),
780 VmValue::String(arcstr::ArcStr::from("int")),
781 ),
782 ("const".to_string(), VmValue::Int(*v)),
783 ]))),
784 harn_parser::TypeExpr::Owned(inner) => Self::type_expr_to_schema_value(inner),
785 }
786 }
787
788 pub(super) fn emit_vm_value_literal(&mut self, value: &VmValue) {
789 match value {
790 VmValue::String(text) => {
791 let idx = self.string_constant(text);
792 self.chunk.emit_u16(Op::Constant, idx, self.line);
793 }
794 VmValue::Int(number) => {
795 let idx = self.chunk.add_constant(Constant::Int(*number));
796 self.chunk.emit_u16(Op::Constant, idx, self.line);
797 }
798 VmValue::Float(number) => {
799 let idx = self.chunk.add_constant(Constant::Float(*number));
800 self.chunk.emit_u16(Op::Constant, idx, self.line);
801 }
802 VmValue::Bool(value) => {
803 let idx = self.chunk.add_constant(Constant::Bool(*value));
804 self.chunk.emit_u16(Op::Constant, idx, self.line);
805 }
806 VmValue::Nil => self.chunk.emit(Op::Nil, self.line),
807 VmValue::List(items) => {
808 for item in items.iter() {
809 self.emit_vm_value_literal(item);
810 }
811 self.chunk
812 .emit_u16(Op::BuildList, items.len() as u16, self.line);
813 }
814 VmValue::Dict(entries) => {
815 for (key, item) in entries.iter() {
816 let key_idx = self.string_constant(key);
817 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
818 self.emit_vm_value_literal(item);
819 }
820 self.chunk
821 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
822 }
823 _ => {}
824 }
825 }
826
827 pub(super) fn emit_type_name_extra(&mut self, type_name_idx: u16) {
829 let hi = (type_name_idx >> 8) as u8;
830 let lo = type_name_idx as u8;
831 self.chunk.code.push(hi);
832 self.chunk.code.push(lo);
833 self.chunk.lines.push(self.line);
834 self.chunk.columns.push(self.column);
835 self.chunk.lines.push(self.line);
836 self.chunk.columns.push(self.column);
837 }
838
839 pub(super) fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
841 if body.is_empty() {
842 self.chunk.emit(Op::Nil, self.line);
843 } else {
844 self.compile_scoped_block(body)?;
845 }
846 Ok(())
847 }
848
849 pub(super) fn compile_catch_binding(
851 &mut self,
852 error_var: &Option<String>,
853 ) -> Result<(), CompileError> {
854 if let Some(var_name) = error_var {
855 self.emit_define_binding(var_name, false);
856 } else {
857 self.chunk.emit(Op::Pop, self.line);
858 }
859 Ok(())
860 }
861
862 pub(super) fn compile_finally_inline(
869 &mut self,
870 finally_body: &[SNode],
871 ) -> Result<(), CompileError> {
872 if !finally_body.is_empty() {
873 self.compile_scoped_block(finally_body)?;
874 self.chunk.emit(Op::Pop, self.line);
875 }
876 Ok(())
877 }
878
879 pub(super) fn has_pending_finally_until_barrier(&self) -> bool {
883 self.finally_bodies
884 .iter()
885 .rev()
886 .take_while(|entry| !matches!(entry, FinallyEntry::CatchBarrier))
887 .any(|entry| matches!(entry, FinallyEntry::Finally(_)))
888 }
889
890 pub(super) fn has_pending_finally(&self) -> bool {
892 self.finally_bodies
893 .iter()
894 .any(|e| matches!(e, FinallyEntry::Finally(_)))
895 }
896
897 pub(super) fn compile_plain_rethrow(&mut self) -> Result<(), CompileError> {
906 self.temp_counter += 1;
907 let temp_name = format!("__finally_err_{}__", self.temp_counter);
908 self.emit_define_binding(&temp_name, true);
909 self.emit_get_binding(&temp_name);
910 self.chunk.emit(Op::Throw, self.line);
911 Ok(())
912 }
913
914 pub(super) fn declare_param_slots(&mut self, params: &[TypedParam]) {
915 for param in params {
916 self.define_local_slot(¶m.name, false);
917 }
918 }
919
920 fn mask_param_names(&mut self, params: &[TypedParam]) -> Vec<(String, super::LocalBinding)> {
926 let mut removed = Vec::new();
927 if let Some(scope) = self.local_scopes.last_mut() {
928 for param in params {
929 if let Some(binding) = scope.remove(¶m.name) {
930 removed.push((param.name.clone(), binding));
931 }
932 }
933 }
934 removed
935 }
936
937 fn restore_param_names(&mut self, removed: Vec<(String, super::LocalBinding)>) {
939 if let Some(scope) = self.local_scopes.last_mut() {
940 for (name, binding) in removed {
941 scope.insert(name, binding);
942 }
943 }
944 }
945
946 pub(super) fn seed_captured_idents(&mut self, body: &[SNode]) {
951 let match_patterns = self.lexical_match_pattern_catalog();
952 self.captured_bindings =
953 harn_parser::lexical::captured_bindings_in_nested_callables(body, &match_patterns);
954 }
955
956 fn seed_module_captured_idents(&mut self, body: &[SNode]) {
957 let match_patterns = self.lexical_match_pattern_catalog();
958 self.captured_bindings =
959 harn_parser::lexical::captured_bindings_in_compiled_module(body, &match_patterns);
960 }
961
962 pub(super) fn lexical_match_pattern_catalog(
963 &self,
964 ) -> harn_parser::lexical::MatchPatternCatalog {
965 harn_parser::lexical::MatchPatternCatalog::new(&self.enum_names, &self.enum_variant_owners)
966 }
967
968 pub(super) fn begin_scope(&mut self) {
969 self.chunk.emit(Op::PushScope, self.line);
970 self.scope_depth += 1;
971 let enum_catalog = self.enum_catalog_snapshot();
972 self.enum_catalog_scopes.push(enum_catalog);
973 self.type_scopes.push(std::collections::HashMap::new());
974 self.local_scopes.push(std::collections::HashMap::new());
975 }
976
977 pub(super) fn end_scope(&mut self) {
978 if self.scope_depth > 0 {
979 self.chunk.emit(Op::PopScope, self.line);
980 self.scope_depth -= 1;
981 if let Some(snapshot) = self.enum_catalog_scopes.pop() {
982 self.restore_enum_catalog(snapshot);
983 }
984 self.type_scopes.pop();
985 self.local_scopes.pop();
986 }
987 }
988
989 pub(super) fn emit_scope_unwind_to(&mut self, target_depth: usize) {
992 for _ in target_depth..self.scope_depth {
993 self.chunk.emit(Op::PopScope, self.line);
994 }
995 }
996
997 pub(super) fn compile_scoped_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
998 self.begin_scope();
999 let finally_floor = self.finally_bodies.len();
1000 if stmts.is_empty() {
1001 self.chunk.emit(Op::Nil, self.line);
1002 } else {
1003 self.compile_block(stmts)?;
1004 }
1005 self.drain_finallys_to_floor(finally_floor)?;
1006 self.end_scope();
1007 Ok(())
1008 }
1009
1010 pub(super) fn compile_scoped_statements(
1011 &mut self,
1012 stmts: &[SNode],
1013 ) -> Result<(), CompileError> {
1014 self.begin_scope();
1015 self.record_monomorphic_var_bindings(stmts);
1016 let finally_floor = self.finally_bodies.len();
1017 for sn in stmts {
1018 self.compile_discarded_stmt(sn)?;
1019 }
1020 self.drain_finallys_to_floor(finally_floor)?;
1021 self.end_scope();
1022 Ok(())
1023 }
1024
1025 pub(super) fn drain_finallys_to_floor(&mut self, floor: usize) -> Result<(), CompileError> {
1030 while self.finally_bodies.len() > floor {
1031 let entry = self.finally_bodies.pop().expect("non-empty by guard");
1032 if let FinallyEntry::Finally(body) = entry {
1033 self.compile_finally_inline(&body)?;
1034 }
1035 }
1036 Ok(())
1037 }
1038
1039 pub(super) fn run_pending_finallys_for_transfer(
1052 &mut self,
1053 floor: usize,
1054 ) -> Result<(), CompileError> {
1055 if self.finally_bodies.len() <= floor {
1056 return Ok(());
1057 }
1058 let saved = self.finally_bodies[floor..].to_vec();
1059 let result = self.drain_finallys_to_floor(floor);
1060 self.finally_bodies.extend(saved);
1061 result
1062 }
1063
1064 pub(super) fn run_pending_finallys_until_barrier(&mut self) -> Result<(), CompileError> {
1069 let floor = self
1070 .finally_bodies
1071 .iter()
1072 .rposition(|e| matches!(e, FinallyEntry::CatchBarrier))
1073 .map(|i| i + 1)
1074 .unwrap_or(0);
1075 self.run_pending_finallys_for_transfer(floor)
1076 }
1077
1078 pub(super) fn maybe_register_owned_drop(
1083 &mut self,
1084 pattern: &harn_parser::BindingPattern,
1085 type_ann: Option<&TypeExpr>,
1086 span: harn_lexer::Span,
1087 ) {
1088 let Some(ty) = type_ann else {
1094 return;
1095 };
1096 if !matches!(ty, TypeExpr::Owned(_)) {
1097 return;
1098 }
1099 let harn_parser::BindingPattern::Identifier(name) = pattern else {
1100 return;
1101 };
1102 if harn_parser::is_discard_name(name) {
1103 return;
1104 }
1105 let call = harn_parser::spanned(
1106 Node::FunctionCall {
1107 name: "drop".to_string(),
1108 args: vec![harn_parser::spanned(Node::Identifier(name.clone()), span)],
1109 type_args: Vec::new(),
1110 },
1111 span,
1112 );
1113 self.finally_bodies.push(FinallyEntry::Finally(vec![call]));
1114 }
1115
1116 pub(super) fn compile_discarded_stmt(&mut self, sn: &SNode) -> Result<(), CompileError> {
1132 #[cfg(debug_assertions)]
1133 let probe = self.chunk.balance_probe();
1134 self.compile_node(sn)?;
1135 #[allow(unused_mut)]
1136 let mut produces = Self::produces_value(&sn.node);
1137 #[cfg(test)]
1141 if let Some(forced) = FORCE_DISCARDED_PRODUCES_VALUE.with(std::cell::Cell::get) {
1142 produces = forced;
1143 }
1144 #[cfg(debug_assertions)]
1145 if let Some(delta) = self.chunk.balance_delta_since(probe) {
1146 let expected = i32::from(produces);
1147 debug_assert_eq!(
1148 delta, expected,
1149 "operand-stack imbalance at line {}: produces_value={produces} but the \
1150 node's emitted bytecode netted {delta} (expected {expected}). A \
1151 `produces_value` arm is out of sync with this node's codegen — see #2622.\n\
1152 node: {:?}",
1153 self.line, sn.node,
1154 );
1155 }
1156 if produces {
1157 self.chunk.emit(Op::Pop, self.line);
1158 }
1159 Ok(())
1160 }
1161
1162 pub(super) fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
1163 self.record_monomorphic_var_bindings(stmts);
1164 for (i, snode) in stmts.iter().enumerate() {
1165 if i == stmts.len() - 1 {
1166 self.compile_node(snode)?;
1170 if !Self::produces_value(&snode.node) {
1171 self.chunk.emit(Op::Nil, self.line);
1172 }
1173 } else {
1174 self.compile_discarded_stmt(snode)?;
1175 }
1176 }
1177 Ok(())
1178 }
1179
1180 pub(super) fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1182 self.begin_scope();
1183 let finally_floor = self.finally_bodies.len();
1184 if body.is_empty() {
1185 self.chunk.emit(Op::Nil, self.line);
1186 } else {
1187 self.compile_block(body)?;
1188 if !Self::produces_value(&body.last().unwrap().node) {
1189 self.chunk.emit(Op::Nil, self.line);
1190 }
1191 }
1192 self.drain_finallys_to_floor(finally_floor)?;
1193 self.end_scope();
1194 Ok(())
1195 }
1196
1197 pub(super) fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
1199 match op {
1200 "+" => self.chunk.emit(Op::Add, self.line),
1201 "-" => self.chunk.emit(Op::Sub, self.line),
1202 "*" => self.chunk.emit(Op::Mul, self.line),
1203 "/" => self.chunk.emit(Op::Div, self.line),
1204 "%" => self.chunk.emit(Op::Mod, self.line),
1205 _ => {
1206 return Err(CompileError {
1207 message: format!("Unknown compound operator: {op}"),
1208 line: self.line,
1209 })
1210 }
1211 }
1212 Ok(())
1213 }
1214
1215 pub(super) fn compile_top_level_declarations(
1216 &mut self,
1217 program: &[SNode],
1218 ) -> Result<(), CompileError> {
1219 for sn in program {
1231 if !harn_parser::lexical::is_deferred_module_declaration(sn) {
1232 self.compile_discarded_stmt(sn)?;
1233 }
1234 }
1235 for sn in program {
1241 let inner_kind = match &sn.node {
1242 Node::AttributedDecl { inner, .. } => &inner.node,
1243 other => other,
1244 };
1245 match inner_kind {
1246 Node::EvalPackDecl {
1247 binding_name,
1248 pack_id,
1249 fields,
1250 body,
1251 summarize,
1252 ..
1253 } => {
1254 self.compile_eval_pack_decl(
1255 binding_name,
1256 pack_id,
1257 fields,
1258 body,
1259 summarize,
1260 false,
1261 )?;
1262 }
1263 Node::FnDecl { .. }
1264 | Node::ToolDecl { .. }
1265 | Node::SkillDecl { .. }
1266 | Node::ImplBlock { .. }
1267 | Node::StructDecl { .. }
1268 | Node::EnumDecl { .. }
1269 | Node::InterfaceDecl { .. } => {
1270 self.compile_node(sn)?;
1271 }
1272 Node::TypeDecl { .. } => {}
1273 _ => {}
1274 }
1275 }
1276 Ok(())
1277 }
1278
1279 pub fn compile_fn_body(
1292 &mut self,
1293 type_params: &[harn_parser::TypeParam],
1294 params: &[TypedParam],
1295 body: &[SNode],
1296 source_file: Option<String>,
1297 ) -> Result<CompiledFunction, CompileError> {
1298 let mut fn_compiler = self.nested_body();
1299 fn_compiler.enum_names = self.enum_names.clone();
1300 fn_compiler.enum_variant_owners = self.enum_variant_owners.clone();
1301 fn_compiler.interface_methods = self.interface_methods.clone();
1302 fn_compiler.type_aliases = self.type_aliases.clone();
1303 fn_compiler.struct_layouts = self.struct_layouts.clone();
1304 fn_compiler.declare_param_slots(params);
1305 fn_compiler.record_param_types(params);
1306 fn_compiler.emit_default_preamble(params)?;
1307 fn_compiler.emit_type_checks(params);
1308 let is_gen = body_contains_yield(body);
1309 fn_compiler.seed_captured_idents(body);
1310 fn_compiler.compile_block(body)?;
1311 fn_compiler.chunk.emit(Op::Nil, 0);
1312 fn_compiler.chunk.emit(Op::Return, 0);
1313 fn_compiler.chunk.source_file = source_file;
1314 let param_slots = fn_compiler.compile_param_slots(params);
1315 let has_runtime_type_checks =
1316 CompiledFunction::has_runtime_type_checks_for_params(¶m_slots);
1317 super::ensure_chunk_addressable(&fn_compiler.chunk, "function body", self.line)?;
1318 Ok(CompiledFunction {
1319 name: String::new(),
1320 type_params: type_params.iter().map(|param| param.name.clone()).collect(),
1321 nominal_type_names: fn_compiler.nominal_type_names(),
1322 params: param_slots,
1323 default_start: TypedParam::default_start(params),
1324 chunk: Arc::new(fn_compiler.chunk),
1325 is_generator: is_gen,
1326 is_stream: false,
1327 has_rest_param: false,
1328 has_runtime_type_checks,
1329 })
1330 }
1331
1332 pub(super) fn produces_value(node: &Node) -> bool {
1334 match node {
1335 Node::AttributedDecl { inner, .. } => Self::produces_value(&inner.node),
1342 Node::LetBinding { .. }
1343 | Node::ConstBinding { .. }
1344 | Node::Assignment { .. }
1345 | Node::ReturnStmt { .. }
1346 | Node::FnDecl { .. }
1347 | Node::ToolDecl { .. }
1348 | Node::SkillDecl { .. }
1349 | Node::EvalPackDecl { .. }
1350 | Node::ImplBlock { .. }
1351 | Node::StructDecl { .. }
1352 | Node::EnumDecl { .. }
1353 | Node::InterfaceDecl { .. }
1354 | Node::TypeDecl { .. }
1355 | Node::OverrideDecl { .. }
1358 | Node::Pipeline { .. }
1359 | Node::ThrowStmt { .. }
1360 | Node::BreakStmt
1361 | Node::ContinueStmt
1362 | Node::RequireStmt { .. }
1363 | Node::DeferStmt { .. } => false,
1364 Node::TryCatch { has_catch: _, .. }
1365 | Node::TryExpr { .. }
1366 | Node::Retry { .. }
1367 | Node::GuardStmt { .. }
1368 | Node::DeadlineBlock { .. }
1369 | Node::MutexBlock { .. }
1370 | Node::Spread(_) => true,
1371 _ => true,
1372 }
1373 }
1374}
1375
1376impl Default for Compiler {
1377 fn default() -> Self {
1378 Self::new()
1379 }
1380}