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