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::OpenShape { fields, rests } => TypeExpr::OpenShape {
142 fields: fields
143 .iter()
144 .map(|field| ShapeField {
145 name: field.name.clone(),
146 type_expr: self.expand_alias(&field.type_expr),
147 optional: field.optional,
148 })
149 .collect(),
150 rests: rests.iter().map(|r| self.expand_alias(r)).collect(),
151 },
152 TypeExpr::List(inner) => TypeExpr::List(Box::new(self.expand_alias(inner))),
153 TypeExpr::Iter(inner) => TypeExpr::Iter(Box::new(self.expand_alias(inner))),
154 TypeExpr::Generator(inner) => TypeExpr::Generator(Box::new(self.expand_alias(inner))),
155 TypeExpr::Stream(inner) => TypeExpr::Stream(Box::new(self.expand_alias(inner))),
156 TypeExpr::DictType(k, v) => TypeExpr::DictType(
157 Box::new(self.expand_alias(k)),
158 Box::new(self.expand_alias(v)),
159 ),
160 TypeExpr::FnType {
161 params,
162 return_type,
163 } => TypeExpr::FnType {
164 params: params.iter().map(|p| self.expand_alias(p)).collect(),
165 return_type: Box::new(self.expand_alias(return_type)),
166 },
167 TypeExpr::Applied { name, args } => TypeExpr::Applied {
168 name: name.clone(),
169 args: args.iter().map(|a| self.expand_alias(a)).collect(),
170 },
171 TypeExpr::Never => TypeExpr::Never,
172 TypeExpr::LitString(s) => TypeExpr::LitString(s.clone()),
173 TypeExpr::LitInt(v) => TypeExpr::LitInt(*v),
174 TypeExpr::Owned(inner) => TypeExpr::Owned(Box::new(self.expand_alias(inner))),
175 }
176 }
177
178 pub(super) fn schema_value_for_alias(&self, name: &str) -> Option<VmValue> {
181 let ty = self.type_aliases.get(name)?;
182 let expanded = self.expand_alias(ty);
183 Self::type_expr_to_schema_value(&expanded)
184 }
185
186 pub(super) fn is_schema_guard(name: &str) -> bool {
190 matches!(
191 name,
192 "schema_is"
193 | "schema_expect"
194 | "schema_parse"
195 | "schema_check"
196 | "is_type"
197 | "json_validate"
198 )
199 }
200
201 pub(super) fn entry_key_is(key: &SNode, keyword: &str) -> bool {
204 matches!(
205 &key.node,
206 Node::Identifier(name) | Node::StringLiteral(name) | Node::RawStringLiteral(name)
207 if name == keyword
208 )
209 }
210
211 pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
214 Self::collect_enum_names(program, &mut self.enum_names);
217 self.enum_names.insert("Result".to_string());
218 Self::collect_struct_layouts(program, &mut self.struct_layouts);
219 Self::collect_interface_methods(program, &mut self.interface_methods);
220 self.collect_type_aliases(program);
221
222 for sn in program {
223 match &sn.node {
224 Node::ImportDecl { .. } | Node::SelectiveImport { .. } => {
225 self.compile_node(sn)?;
226 }
227 _ => {}
228 }
229 }
230 let main = program
231 .iter()
232 .find(|sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == "default"))
233 .or_else(|| {
234 program
235 .iter()
236 .find(|sn| matches!(peel_node(sn), Node::Pipeline { .. }))
237 });
238
239 let mut pipeline_emits_value = false;
243 if let Some(sn) = main {
244 self.compile_top_level_declarations(program)?;
245 if let Node::Pipeline { body, extends, .. } = peel_node(sn) {
246 if let Some(parent_name) = extends {
247 self.compile_parent_pipeline(program, parent_name)?;
248 }
249 let saved = std::mem::replace(&mut self.module_level, false);
250 self.compile_block(body)?;
251 self.module_level = saved;
252 pipeline_emits_value = true;
253 }
254 } else {
255 let top_level: Vec<&SNode> = program
257 .iter()
258 .filter(|sn| {
259 !matches!(
260 &sn.node,
261 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
262 )
263 })
264 .collect();
265 for sn in &top_level {
266 self.compile_discarded_stmt(sn)?;
267 }
268 if Self::has_top_level_fn_main(program) {
273 let harness_name = self.string_constant("harness");
274 self.chunk.emit_u16(Op::GetVar, harness_name, self.line);
275 self.emit_named_call("main", 1);
276 pipeline_emits_value = true;
277 }
278 }
279
280 self.drain_finallys_to_floor(0)?;
281 if !pipeline_emits_value {
282 self.chunk.emit(Op::Nil, self.line);
283 }
284 self.chunk.emit(Op::Return, self.line);
285 Ok(self.chunk)
286 }
287
288 fn has_top_level_fn_main(program: &[SNode]) -> bool {
292 program
293 .iter()
294 .any(|sn| matches!(peel_node(sn), Node::FnDecl { name, .. } if name == "main"))
295 }
296
297 pub fn compile_named(
299 mut self,
300 program: &[SNode],
301 pipeline_name: &str,
302 ) -> Result<Chunk, CompileError> {
303 Self::collect_enum_names(program, &mut self.enum_names);
304 self.enum_names.insert("Result".to_string());
305 Self::collect_struct_layouts(program, &mut self.struct_layouts);
306 Self::collect_interface_methods(program, &mut self.interface_methods);
307 self.collect_type_aliases(program);
308
309 for sn in program {
310 if matches!(
311 &sn.node,
312 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
313 ) {
314 self.compile_node(sn)?;
315 }
316 }
317 let target = program.iter().find(
318 |sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == pipeline_name),
319 );
320
321 if let Some(sn) = target {
322 self.compile_top_level_declarations(program)?;
323 if let Node::Pipeline { body, extends, .. } = peel_node(sn) {
324 if let Some(parent_name) = extends {
325 self.compile_parent_pipeline(program, parent_name)?;
326 }
327 let saved = std::mem::replace(&mut self.module_level, false);
328 self.compile_block(body)?;
329 self.module_level = saved;
330 }
331 }
332
333 self.drain_finallys_to_floor(0)?;
334 self.chunk.emit(Op::Nil, self.line);
335 self.chunk.emit(Op::Return, self.line);
336 Ok(self.chunk)
337 }
338
339 pub(super) fn compile_parent_pipeline(
341 &mut self,
342 program: &[SNode],
343 parent_name: &str,
344 ) -> Result<(), CompileError> {
345 let parent = program
346 .iter()
347 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == parent_name));
348 if let Some(sn) = parent {
349 if let Node::Pipeline { body, extends, .. } = &sn.node {
350 if let Some(grandparent) = extends {
351 self.compile_parent_pipeline(program, grandparent)?;
352 }
353 for stmt in body {
354 self.compile_discarded_stmt(stmt)?;
355 }
356 }
357 }
358 Ok(())
359 }
360
361 pub(super) fn emit_default_preamble(
366 &mut self,
367 params: &[TypedParam],
368 ) -> Result<(), CompileError> {
369 for (i, param) in params.iter().enumerate() {
370 if let Some(default_expr) = ¶m.default_value {
371 self.chunk.emit(Op::GetArgc, self.line);
372 let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
373 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
374 self.chunk.emit(Op::GreaterEqual, self.line);
375 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
376 self.chunk.emit(Op::Pop, self.line);
378 let masked = self.mask_param_names(¶ms[i..]);
387 let result = self.compile_node(default_expr);
388 self.restore_param_names(masked);
389 result?;
390 self.emit_init_or_define_binding(¶m.name, false);
391 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
392 self.chunk.patch_jump(skip_jump);
393 self.chunk.emit(Op::Pop, self.line);
394 self.chunk.patch_jump(end_jump);
395 }
396 }
397 Ok(())
398 }
399
400 pub(super) fn emit_type_checks(&mut self, params: &[TypedParam]) {
407 for (param_index, param) in params.iter().enumerate() {
408 if let Some(type_expr) = ¶m.type_expr {
409 let check_type = if param.rest {
410 harn_parser::TypeExpr::List(Box::new(type_expr.clone()))
411 } else {
412 type_expr.clone()
413 };
414
415 if let harn_parser::TypeExpr::Named(name) = &check_type {
416 if let Some(methods) = self.interface_methods.get(name).cloned() {
417 let fn_idx = self.string_constant("__assert_interface");
418 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
419 self.emit_get_binding(¶m.name);
420 let name_idx = self.string_constant(¶m.name);
421 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
422 let iface_idx = self.string_constant(name);
423 self.chunk.emit_u16(Op::Constant, iface_idx, self.line);
424 let methods_str = methods.join(",");
425 let methods_idx = self.owned_string_constant(methods_str);
426 self.chunk.emit_u16(Op::Constant, methods_idx, self.line);
427 self.chunk.emit_u8(Op::Call, 4, self.line);
428 self.chunk.emit(Op::Pop, self.line);
429 continue;
430 }
431 }
432
433 if param.default_value.is_some() {
434 if let Some(schema) = Self::type_expr_to_schema_value(&check_type) {
435 self.emit_default_param_schema_check(param_index, param, &schema);
436 }
437 }
438 }
439 }
440 }
441
442 fn emit_default_param_schema_check(
443 &mut self,
444 param_index: usize,
445 param: &TypedParam,
446 schema: &VmValue,
447 ) {
448 self.chunk.emit(Op::GetArgc, self.line);
449 let threshold_idx = self
450 .chunk
451 .add_constant(Constant::Int((param_index + 1) as i64));
452 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
453 self.chunk.emit(Op::GreaterEqual, self.line);
454 let supplied_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
455 self.chunk.emit(Op::Pop, self.line);
456 self.emit_schema_assert_call(param, schema);
457 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
458 self.chunk.patch_jump(supplied_jump);
459 self.chunk.emit(Op::Pop, self.line);
460 self.chunk.patch_jump(end_jump);
461 }
462
463 fn emit_schema_assert_call(&mut self, param: &TypedParam, schema: &VmValue) {
464 let fn_idx = self.string_constant("__assert_schema");
465 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
466 self.emit_get_binding(¶m.name);
467 let name_idx = self.string_constant(¶m.name);
468 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
469 self.emit_vm_value_literal(schema);
470 self.chunk.emit_u8(Op::Call, 3, self.line);
471 self.chunk.emit(Op::Pop, self.line);
472 }
473
474 pub(crate) fn type_expr_to_schema_value(type_expr: &harn_parser::TypeExpr) -> Option<VmValue> {
475 match type_expr {
476 harn_parser::TypeExpr::Named(name) => match name.as_str() {
477 "int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
478 | "closure" | "bytes" => {
479 Some(VmValue::Dict(std::sync::Arc::new(BTreeMap::from([(
480 "type".to_string(),
481 VmValue::String(std::sync::Arc::from(name.as_str())),
482 )]))))
483 }
484 _ => None,
485 },
486 harn_parser::TypeExpr::Shape(fields)
487 | harn_parser::TypeExpr::OpenShape { fields, .. } => {
488 let mut properties = BTreeMap::new();
489 let mut required = Vec::new();
490 for field in fields {
491 let field_schema = Self::type_expr_to_schema_value(&field.type_expr)?;
492 properties.insert(field.name.clone(), field_schema);
493 if !field.optional {
494 required.push(VmValue::String(std::sync::Arc::from(field.name.as_str())));
495 }
496 }
497 let mut out = BTreeMap::new();
498 out.insert(
499 "type".to_string(),
500 VmValue::String(std::sync::Arc::from("dict")),
501 );
502 out.insert(
503 "properties".to_string(),
504 VmValue::Dict(std::sync::Arc::new(properties)),
505 );
506 if !required.is_empty() {
507 out.insert(
508 "required".to_string(),
509 VmValue::List(std::sync::Arc::new(required)),
510 );
511 }
512 Some(VmValue::Dict(std::sync::Arc::new(out)))
513 }
514 harn_parser::TypeExpr::List(inner) => {
515 let mut out = BTreeMap::new();
516 out.insert(
517 "type".to_string(),
518 VmValue::String(std::sync::Arc::from("list")),
519 );
520 if let Some(item_schema) = Self::type_expr_to_schema_value(inner) {
521 out.insert("items".to_string(), item_schema);
522 }
523 Some(VmValue::Dict(std::sync::Arc::new(out)))
524 }
525 harn_parser::TypeExpr::DictType(key, value) => {
526 let mut out = BTreeMap::new();
527 out.insert(
528 "type".to_string(),
529 VmValue::String(std::sync::Arc::from("dict")),
530 );
531 if matches!(key.as_ref(), harn_parser::TypeExpr::Named(name) if name == "string") {
532 if let Some(value_schema) = Self::type_expr_to_schema_value(value) {
533 out.insert("additional_properties".to_string(), value_schema);
534 }
535 }
536 Some(VmValue::Dict(std::sync::Arc::new(out)))
537 }
538 harn_parser::TypeExpr::Union(members) => {
539 if !members.is_empty()
544 && members
545 .iter()
546 .all(|m| matches!(m, harn_parser::TypeExpr::LitString(_)))
547 {
548 let values = members
549 .iter()
550 .map(|m| match m {
551 harn_parser::TypeExpr::LitString(s) => {
552 VmValue::String(std::sync::Arc::from(s.as_str()))
553 }
554 _ => unreachable!(),
555 })
556 .collect::<Vec<_>>();
557 return Some(VmValue::Dict(std::sync::Arc::new(BTreeMap::from([
558 (
559 "type".to_string(),
560 VmValue::String(std::sync::Arc::from("string")),
561 ),
562 (
563 "enum".to_string(),
564 VmValue::List(std::sync::Arc::new(values)),
565 ),
566 ]))));
567 }
568 if !members.is_empty()
569 && members
570 .iter()
571 .all(|m| matches!(m, harn_parser::TypeExpr::LitInt(_)))
572 {
573 let values = members
574 .iter()
575 .map(|m| match m {
576 harn_parser::TypeExpr::LitInt(v) => VmValue::Int(*v),
577 _ => unreachable!(),
578 })
579 .collect::<Vec<_>>();
580 return Some(VmValue::Dict(std::sync::Arc::new(BTreeMap::from([
581 (
582 "type".to_string(),
583 VmValue::String(std::sync::Arc::from("int")),
584 ),
585 (
586 "enum".to_string(),
587 VmValue::List(std::sync::Arc::new(values)),
588 ),
589 ]))));
590 }
591 let branches = members
592 .iter()
593 .filter_map(Self::type_expr_to_schema_value)
594 .collect::<Vec<_>>();
595 if branches.is_empty() {
596 None
597 } else {
598 Some(VmValue::Dict(std::sync::Arc::new(BTreeMap::from([(
599 "union".to_string(),
600 VmValue::List(std::sync::Arc::new(branches)),
601 )]))))
602 }
603 }
604 harn_parser::TypeExpr::Intersection(members) => {
605 let branches = members
609 .iter()
610 .filter_map(Self::type_expr_to_schema_value)
611 .collect::<Vec<_>>();
612 if branches.is_empty() {
613 None
614 } else {
615 Some(VmValue::Dict(std::sync::Arc::new(BTreeMap::from([(
616 "all_of".to_string(),
617 VmValue::List(std::sync::Arc::new(branches)),
618 )]))))
619 }
620 }
621 harn_parser::TypeExpr::FnType { .. } => {
622 Some(VmValue::Dict(std::sync::Arc::new(BTreeMap::from([(
623 "type".to_string(),
624 VmValue::String(std::sync::Arc::from("closure")),
625 )]))))
626 }
627 harn_parser::TypeExpr::Applied { .. } => None,
628 harn_parser::TypeExpr::Iter(_)
629 | harn_parser::TypeExpr::Generator(_)
630 | harn_parser::TypeExpr::Stream(_) => None,
631 harn_parser::TypeExpr::Never => None,
632 harn_parser::TypeExpr::LitString(s) => {
633 Some(VmValue::Dict(std::sync::Arc::new(BTreeMap::from([
634 (
635 "type".to_string(),
636 VmValue::String(std::sync::Arc::from("string")),
637 ),
638 (
639 "const".to_string(),
640 VmValue::String(std::sync::Arc::from(s.as_str())),
641 ),
642 ]))))
643 }
644 harn_parser::TypeExpr::LitInt(v) => {
645 Some(VmValue::Dict(std::sync::Arc::new(BTreeMap::from([
646 (
647 "type".to_string(),
648 VmValue::String(std::sync::Arc::from("int")),
649 ),
650 ("const".to_string(), VmValue::Int(*v)),
651 ]))))
652 }
653 harn_parser::TypeExpr::Owned(inner) => Self::type_expr_to_schema_value(inner),
654 }
655 }
656
657 pub(super) fn emit_vm_value_literal(&mut self, value: &VmValue) {
658 match value {
659 VmValue::String(text) => {
660 let idx = self.string_constant(text);
661 self.chunk.emit_u16(Op::Constant, idx, self.line);
662 }
663 VmValue::Int(number) => {
664 let idx = self.chunk.add_constant(Constant::Int(*number));
665 self.chunk.emit_u16(Op::Constant, idx, self.line);
666 }
667 VmValue::Float(number) => {
668 let idx = self.chunk.add_constant(Constant::Float(*number));
669 self.chunk.emit_u16(Op::Constant, idx, self.line);
670 }
671 VmValue::Bool(value) => {
672 let idx = self.chunk.add_constant(Constant::Bool(*value));
673 self.chunk.emit_u16(Op::Constant, idx, self.line);
674 }
675 VmValue::Nil => self.chunk.emit(Op::Nil, self.line),
676 VmValue::List(items) => {
677 for item in items.iter() {
678 self.emit_vm_value_literal(item);
679 }
680 self.chunk
681 .emit_u16(Op::BuildList, items.len() as u16, self.line);
682 }
683 VmValue::Dict(entries) => {
684 for (key, item) in entries.iter() {
685 let key_idx = self.string_constant(key);
686 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
687 self.emit_vm_value_literal(item);
688 }
689 self.chunk
690 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
691 }
692 _ => {}
693 }
694 }
695
696 pub(super) fn emit_type_name_extra(&mut self, type_name_idx: u16) {
698 let hi = (type_name_idx >> 8) as u8;
699 let lo = type_name_idx as u8;
700 self.chunk.code.push(hi);
701 self.chunk.code.push(lo);
702 self.chunk.lines.push(self.line);
703 self.chunk.columns.push(self.column);
704 self.chunk.lines.push(self.line);
705 self.chunk.columns.push(self.column);
706 }
707
708 pub(super) fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
710 if body.is_empty() {
711 self.chunk.emit(Op::Nil, self.line);
712 } else {
713 self.compile_scoped_block(body)?;
714 }
715 Ok(())
716 }
717
718 pub(super) fn compile_catch_binding(
720 &mut self,
721 error_var: &Option<String>,
722 ) -> Result<(), CompileError> {
723 if let Some(var_name) = error_var {
724 self.emit_define_binding(var_name, false);
725 } else {
726 self.chunk.emit(Op::Pop, self.line);
727 }
728 Ok(())
729 }
730
731 pub(super) fn compile_finally_inline(
738 &mut self,
739 finally_body: &[SNode],
740 ) -> Result<(), CompileError> {
741 if !finally_body.is_empty() {
742 self.compile_scoped_block(finally_body)?;
743 self.chunk.emit(Op::Pop, self.line);
744 }
745 Ok(())
746 }
747
748 pub(super) fn pending_finallys_until_barrier(&self) -> Vec<Vec<SNode>> {
753 let mut out = Vec::new();
754 for entry in self.finally_bodies.iter().rev() {
755 match entry {
756 FinallyEntry::CatchBarrier => break,
757 FinallyEntry::Finally(body) => out.push(body.clone()),
758 }
759 }
760 out
761 }
762
763 pub(super) fn has_pending_finally(&self) -> bool {
765 self.finally_bodies
766 .iter()
767 .any(|e| matches!(e, FinallyEntry::Finally(_)))
768 }
769
770 pub(super) fn compile_plain_rethrow(&mut self) -> Result<(), CompileError> {
779 self.temp_counter += 1;
780 let temp_name = format!("__finally_err_{}__", self.temp_counter);
781 self.emit_define_binding(&temp_name, true);
782 self.emit_get_binding(&temp_name);
783 self.chunk.emit(Op::Throw, self.line);
784 Ok(())
785 }
786
787 pub(super) fn declare_param_slots(&mut self, params: &[TypedParam]) {
788 for param in params {
789 self.define_local_slot(¶m.name, false);
790 }
791 }
792
793 fn mask_param_names(&mut self, params: &[TypedParam]) -> Vec<(String, super::LocalBinding)> {
799 let mut removed = Vec::new();
800 if let Some(scope) = self.local_scopes.last_mut() {
801 for param in params {
802 if let Some(binding) = scope.remove(¶m.name) {
803 removed.push((param.name.clone(), binding));
804 }
805 }
806 }
807 removed
808 }
809
810 fn restore_param_names(&mut self, removed: Vec<(String, super::LocalBinding)>) {
812 if let Some(scope) = self.local_scopes.last_mut() {
813 for (name, binding) in removed {
814 scope.insert(name, binding);
815 }
816 }
817 }
818
819 fn define_local_slot(&mut self, name: &str, mutable: bool) -> Option<u16> {
820 if self.module_level || harn_parser::is_discard_name(name) {
821 return None;
822 }
823 let current = self.local_scopes.last_mut()?;
824 if let Some(existing) = current.get_mut(name) {
825 if existing.mutable || mutable {
826 if mutable {
827 existing.mutable = true;
828 if let Some(info) = self.chunk.local_slots.get_mut(existing.slot as usize) {
829 info.mutable = true;
830 }
831 }
832 return Some(existing.slot);
833 }
834 return None;
835 }
836 let slot = self
837 .chunk
838 .add_local_slot(name.to_string(), mutable, self.scope_depth);
839 current.insert(name.to_string(), super::LocalBinding { slot, mutable });
840 Some(slot)
841 }
842
843 pub(super) fn resolve_local_slot(&self, name: &str) -> Option<super::LocalBinding> {
844 if self.module_level {
845 return None;
846 }
847 self.local_scopes
848 .iter()
849 .rev()
850 .find_map(|scope| scope.get(name).copied())
851 }
852
853 pub(super) fn emit_get_binding(&mut self, name: &str) {
854 if let Some(binding) = self.resolve_local_slot(name) {
855 self.chunk
856 .emit_u16(Op::GetLocalSlot, binding.slot, self.line);
857 } else {
858 let idx = self.string_constant(name);
859 self.chunk.emit_u16(Op::GetVar, idx, self.line);
860 }
861 }
862
863 pub(super) fn emit_define_binding(&mut self, name: &str, mutable: bool) {
864 if let Some(slot) = self.define_local_slot(name, mutable) {
865 self.chunk.emit_u16(Op::DefLocalSlot, slot, self.line);
866 } else {
867 let idx = self.string_constant(name);
868 let op = if mutable { Op::DefVar } else { Op::DefLet };
869 self.chunk.emit_u16(op, idx, self.line);
870 }
871 }
872
873 pub(super) fn emit_init_or_define_binding(&mut self, name: &str, mutable: bool) {
874 if let Some(binding) = self.resolve_local_slot(name) {
875 self.chunk
876 .emit_u16(Op::DefLocalSlot, binding.slot, self.line);
877 } else {
878 self.emit_define_binding(name, mutable);
879 }
880 }
881
882 pub(super) fn emit_set_binding(&mut self, name: &str) {
883 if let Some(binding) = self.resolve_local_slot(name) {
884 let _ = binding.mutable;
885 self.chunk
886 .emit_u16(Op::SetLocalSlot, binding.slot, self.line);
887 } else {
888 let idx = self.string_constant(name);
889 self.chunk.emit_u16(Op::SetVar, idx, self.line);
890 }
891 }
892
893 pub(super) fn begin_scope(&mut self) {
894 self.chunk.emit(Op::PushScope, self.line);
895 self.scope_depth += 1;
896 self.type_scopes.push(std::collections::HashMap::new());
897 self.local_scopes.push(std::collections::HashMap::new());
898 }
899
900 pub(super) fn end_scope(&mut self) {
901 if self.scope_depth > 0 {
902 self.chunk.emit(Op::PopScope, self.line);
903 self.scope_depth -= 1;
904 self.type_scopes.pop();
905 self.local_scopes.pop();
906 }
907 }
908
909 pub(super) fn emit_scope_unwind_to(&mut self, target_depth: usize) {
912 for _ in target_depth..self.scope_depth {
913 self.chunk.emit(Op::PopScope, self.line);
914 }
915 }
916
917 pub(super) fn compile_scoped_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
918 self.begin_scope();
919 let finally_floor = self.finally_bodies.len();
920 if stmts.is_empty() {
921 self.chunk.emit(Op::Nil, self.line);
922 } else {
923 self.compile_block(stmts)?;
924 }
925 self.drain_finallys_to_floor(finally_floor)?;
926 self.end_scope();
927 Ok(())
928 }
929
930 pub(super) fn compile_scoped_statements(
931 &mut self,
932 stmts: &[SNode],
933 ) -> Result<(), CompileError> {
934 self.begin_scope();
935 self.record_monomorphic_var_bindings(stmts);
936 let finally_floor = self.finally_bodies.len();
937 for sn in stmts {
938 self.compile_discarded_stmt(sn)?;
939 }
940 self.drain_finallys_to_floor(finally_floor)?;
941 self.end_scope();
942 Ok(())
943 }
944
945 pub(super) fn drain_finallys_to_floor(&mut self, floor: usize) -> Result<(), CompileError> {
950 while self.finally_bodies.len() > floor {
951 let entry = self.finally_bodies.pop().expect("non-empty by guard");
952 if let FinallyEntry::Finally(body) = entry {
953 self.compile_finally_inline(&body)?;
954 }
955 }
956 Ok(())
957 }
958
959 pub(super) fn run_pending_finallys_for_transfer(
972 &mut self,
973 floor: usize,
974 ) -> Result<(), CompileError> {
975 if self.finally_bodies.len() <= floor {
976 return Ok(());
977 }
978 let saved = self.finally_bodies[floor..].to_vec();
979 let result = self.drain_finallys_to_floor(floor);
980 self.finally_bodies.extend(saved);
981 result
982 }
983
984 pub(super) fn run_pending_finallys_until_barrier(&mut self) -> Result<(), CompileError> {
989 let floor = self
990 .finally_bodies
991 .iter()
992 .rposition(|e| matches!(e, FinallyEntry::CatchBarrier))
993 .map(|i| i + 1)
994 .unwrap_or(0);
995 self.run_pending_finallys_for_transfer(floor)
996 }
997
998 pub(super) fn maybe_register_owned_drop(
1003 &mut self,
1004 pattern: &harn_parser::BindingPattern,
1005 type_ann: Option<&TypeExpr>,
1006 span: harn_lexer::Span,
1007 ) {
1008 let Some(ty) = type_ann else {
1014 return;
1015 };
1016 if !matches!(ty, TypeExpr::Owned(_)) {
1017 return;
1018 }
1019 let harn_parser::BindingPattern::Identifier(name) = pattern else {
1020 return;
1021 };
1022 if harn_parser::is_discard_name(name) {
1023 return;
1024 }
1025 let call = harn_parser::spanned(
1026 Node::FunctionCall {
1027 name: "drop".to_string(),
1028 args: vec![harn_parser::spanned(Node::Identifier(name.clone()), span)],
1029 type_args: Vec::new(),
1030 },
1031 span,
1032 );
1033 self.finally_bodies.push(FinallyEntry::Finally(vec![call]));
1034 }
1035
1036 pub(super) fn compile_discarded_stmt(&mut self, sn: &SNode) -> Result<(), CompileError> {
1052 #[cfg(debug_assertions)]
1053 let probe = self.chunk.balance_probe();
1054 self.compile_node(sn)?;
1055 #[allow(unused_mut)]
1056 let mut produces = Self::produces_value(&sn.node);
1057 #[cfg(test)]
1061 if let Some(forced) = FORCE_DISCARDED_PRODUCES_VALUE.with(std::cell::Cell::get) {
1062 produces = forced;
1063 }
1064 #[cfg(debug_assertions)]
1065 if let Some(delta) = self.chunk.balance_delta_since(probe) {
1066 let expected = i32::from(produces);
1067 debug_assert_eq!(
1068 delta, expected,
1069 "operand-stack imbalance at line {}: produces_value={produces} but the \
1070 node's emitted bytecode netted {delta} (expected {expected}). A \
1071 `produces_value` arm is out of sync with this node's codegen — see #2622.\n\
1072 node: {:?}",
1073 self.line, sn.node,
1074 );
1075 }
1076 if produces {
1077 self.chunk.emit(Op::Pop, self.line);
1078 }
1079 Ok(())
1080 }
1081
1082 pub(super) fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
1083 self.record_monomorphic_var_bindings(stmts);
1084 for (i, snode) in stmts.iter().enumerate() {
1085 if i == stmts.len() - 1 {
1086 self.compile_node(snode)?;
1090 if !Self::produces_value(&snode.node) {
1091 self.chunk.emit(Op::Nil, self.line);
1092 }
1093 } else {
1094 self.compile_discarded_stmt(snode)?;
1095 }
1096 }
1097 Ok(())
1098 }
1099
1100 pub(super) fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1102 self.begin_scope();
1103 let finally_floor = self.finally_bodies.len();
1104 if body.is_empty() {
1105 self.chunk.emit(Op::Nil, self.line);
1106 } else {
1107 self.compile_block(body)?;
1108 if !Self::produces_value(&body.last().unwrap().node) {
1109 self.chunk.emit(Op::Nil, self.line);
1110 }
1111 }
1112 self.drain_finallys_to_floor(finally_floor)?;
1113 self.end_scope();
1114 Ok(())
1115 }
1116
1117 pub(super) fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
1119 match op {
1120 "+" => self.chunk.emit(Op::Add, self.line),
1121 "-" => self.chunk.emit(Op::Sub, self.line),
1122 "*" => self.chunk.emit(Op::Mul, self.line),
1123 "/" => self.chunk.emit(Op::Div, self.line),
1124 "%" => self.chunk.emit(Op::Mod, self.line),
1125 _ => {
1126 return Err(CompileError {
1127 message: format!("Unknown compound operator: {op}"),
1128 line: self.line,
1129 })
1130 }
1131 }
1132 Ok(())
1133 }
1134
1135 pub(super) fn root_var_name(&self, node: &SNode) -> Option<String> {
1137 match &node.node {
1138 Node::Identifier(name) => Some(name.clone()),
1139 Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
1140 self.root_var_name(object)
1141 }
1142 Node::SubscriptAccess { object, .. } | Node::OptionalSubscriptAccess { object, .. } => {
1143 self.root_var_name(object)
1144 }
1145 _ => None,
1146 }
1147 }
1148
1149 pub(super) fn compile_top_level_declarations(
1150 &mut self,
1151 program: &[SNode],
1152 ) -> Result<(), CompileError> {
1153 for sn in program {
1161 if matches!(
1162 &sn.node,
1163 Node::LetBinding { .. } | Node::VarBinding { .. } | Node::ConstBinding { .. }
1164 ) {
1165 self.compile_node(sn)?;
1166 }
1167 }
1168 for sn in program {
1174 let inner_kind = match &sn.node {
1175 Node::AttributedDecl { inner, .. } => &inner.node,
1176 other => other,
1177 };
1178 match inner_kind {
1179 Node::EvalPackDecl {
1180 binding_name,
1181 pack_id,
1182 fields,
1183 body,
1184 summarize,
1185 ..
1186 } => {
1187 self.compile_eval_pack_decl(
1188 binding_name,
1189 pack_id,
1190 fields,
1191 body,
1192 summarize,
1193 false,
1194 )?;
1195 }
1196 Node::FnDecl { .. }
1197 | Node::ToolDecl { .. }
1198 | Node::SkillDecl { .. }
1199 | Node::ImplBlock { .. }
1200 | Node::StructDecl { .. }
1201 | Node::EnumDecl { .. }
1202 | Node::InterfaceDecl { .. }
1203 | Node::TypeDecl { .. } => {
1204 self.compile_node(sn)?;
1205 }
1206 _ => {}
1207 }
1208 }
1209 Ok(())
1210 }
1211
1212 pub(super) fn collect_enum_names(
1214 nodes: &[SNode],
1215 names: &mut std::collections::HashSet<String>,
1216 ) {
1217 for sn in nodes {
1218 match &sn.node {
1219 Node::EnumDecl { name, .. } => {
1220 names.insert(name.clone());
1221 }
1222 Node::Pipeline { body, .. } => {
1223 Self::collect_enum_names(body, names);
1224 }
1225 Node::FnDecl { body, .. } | Node::ToolDecl { body, .. } => {
1226 Self::collect_enum_names(body, names);
1227 }
1228 Node::SkillDecl { fields, .. } => {
1229 for (_k, v) in fields {
1230 Self::collect_enum_names(std::slice::from_ref(v), names);
1231 }
1232 }
1233 Node::EvalPackDecl {
1234 fields,
1235 body,
1236 summarize,
1237 ..
1238 } => {
1239 for (_k, v) in fields {
1240 Self::collect_enum_names(std::slice::from_ref(v), names);
1241 }
1242 Self::collect_enum_names(body, names);
1243 if let Some(summary_body) = summarize {
1244 Self::collect_enum_names(summary_body, names);
1245 }
1246 }
1247 Node::Block(stmts) => {
1248 Self::collect_enum_names(stmts, names);
1249 }
1250 Node::AttributedDecl { inner, .. } => {
1251 Self::collect_enum_names(std::slice::from_ref(inner), names);
1252 }
1253 _ => {}
1254 }
1255 }
1256 }
1257
1258 pub(super) fn collect_struct_layouts(
1259 nodes: &[SNode],
1260 layouts: &mut std::collections::HashMap<String, Vec<String>>,
1261 ) {
1262 for sn in nodes {
1263 match &sn.node {
1264 Node::StructDecl { name, fields, .. } => {
1265 layouts.insert(
1266 name.clone(),
1267 fields.iter().map(|field| field.name.clone()).collect(),
1268 );
1269 }
1270 Node::Pipeline { body, .. }
1271 | Node::FnDecl { body, .. }
1272 | Node::ToolDecl { body, .. } => {
1273 Self::collect_struct_layouts(body, layouts);
1274 }
1275 Node::SkillDecl { fields, .. } => {
1276 for (_k, v) in fields {
1277 Self::collect_struct_layouts(std::slice::from_ref(v), layouts);
1278 }
1279 }
1280 Node::EvalPackDecl {
1281 fields,
1282 body,
1283 summarize,
1284 ..
1285 } => {
1286 for (_k, v) in fields {
1287 Self::collect_struct_layouts(std::slice::from_ref(v), layouts);
1288 }
1289 Self::collect_struct_layouts(body, layouts);
1290 if let Some(summary_body) = summarize {
1291 Self::collect_struct_layouts(summary_body, layouts);
1292 }
1293 }
1294 Node::Block(stmts) => {
1295 Self::collect_struct_layouts(stmts, layouts);
1296 }
1297 Node::AttributedDecl { inner, .. } => {
1298 Self::collect_struct_layouts(std::slice::from_ref(inner), layouts);
1299 }
1300 _ => {}
1301 }
1302 }
1303 }
1304
1305 pub(super) fn collect_interface_methods(
1306 nodes: &[SNode],
1307 interfaces: &mut std::collections::HashMap<String, Vec<String>>,
1308 ) {
1309 for sn in nodes {
1310 match &sn.node {
1311 Node::InterfaceDecl { name, methods, .. } => {
1312 let method_names: Vec<String> =
1313 methods.iter().map(|m| m.name.clone()).collect();
1314 interfaces.insert(name.clone(), method_names);
1315 }
1316 Node::Pipeline { body, .. }
1317 | Node::FnDecl { body, .. }
1318 | Node::ToolDecl { body, .. } => {
1319 Self::collect_interface_methods(body, interfaces);
1320 }
1321 Node::SkillDecl { fields, .. } => {
1322 for (_k, v) in fields {
1323 Self::collect_interface_methods(std::slice::from_ref(v), interfaces);
1324 }
1325 }
1326 Node::EvalPackDecl {
1327 fields,
1328 body,
1329 summarize,
1330 ..
1331 } => {
1332 for (_k, v) in fields {
1333 Self::collect_interface_methods(std::slice::from_ref(v), interfaces);
1334 }
1335 Self::collect_interface_methods(body, interfaces);
1336 if let Some(summary_body) = summarize {
1337 Self::collect_interface_methods(summary_body, interfaces);
1338 }
1339 }
1340 Node::Block(stmts) => {
1341 Self::collect_interface_methods(stmts, interfaces);
1342 }
1343 Node::AttributedDecl { inner, .. } => {
1344 Self::collect_interface_methods(std::slice::from_ref(inner), interfaces);
1345 }
1346 _ => {}
1347 }
1348 }
1349 }
1350
1351 pub fn compile_fn_body(
1364 &mut self,
1365 type_params: &[harn_parser::TypeParam],
1366 params: &[TypedParam],
1367 body: &[SNode],
1368 source_file: Option<String>,
1369 ) -> Result<CompiledFunction, CompileError> {
1370 let mut fn_compiler = self.nested_body();
1371 fn_compiler.enum_names = self.enum_names.clone();
1372 fn_compiler.interface_methods = self.interface_methods.clone();
1373 fn_compiler.type_aliases = self.type_aliases.clone();
1374 fn_compiler.struct_layouts = self.struct_layouts.clone();
1375 fn_compiler.declare_param_slots(params);
1376 fn_compiler.record_param_types(params);
1377 fn_compiler.emit_default_preamble(params)?;
1378 fn_compiler.emit_type_checks(params);
1379 let is_gen = body_contains_yield(body);
1380 fn_compiler.compile_block(body)?;
1381 fn_compiler.chunk.emit(Op::Nil, 0);
1382 fn_compiler.chunk.emit(Op::Return, 0);
1383 fn_compiler.chunk.source_file = source_file;
1384 let param_slots = crate::chunk::ParamSlot::vec_from_typed(params);
1385 let has_runtime_type_checks =
1386 CompiledFunction::has_runtime_type_checks_for_params(¶m_slots);
1387 Ok(CompiledFunction {
1388 name: String::new(),
1389 type_params: type_params.iter().map(|param| param.name.clone()).collect(),
1390 nominal_type_names: fn_compiler.nominal_type_names(),
1391 params: param_slots,
1392 default_start: TypedParam::default_start(params),
1393 chunk: Arc::new(fn_compiler.chunk),
1394 is_generator: is_gen,
1395 is_stream: false,
1396 has_rest_param: false,
1397 has_runtime_type_checks,
1398 })
1399 }
1400
1401 pub(super) fn produces_value(node: &Node) -> bool {
1403 match node {
1404 Node::AttributedDecl { inner, .. } => Self::produces_value(&inner.node),
1411 Node::LetBinding { .. }
1412 | Node::VarBinding { .. }
1413 | Node::ConstBinding { .. }
1414 | Node::Assignment { .. }
1415 | Node::ReturnStmt { .. }
1416 | Node::FnDecl { .. }
1417 | Node::ToolDecl { .. }
1418 | Node::SkillDecl { .. }
1419 | Node::EvalPackDecl { .. }
1420 | Node::ImplBlock { .. }
1421 | Node::StructDecl { .. }
1422 | Node::EnumDecl { .. }
1423 | Node::InterfaceDecl { .. }
1424 | Node::TypeDecl { .. }
1425 | Node::OverrideDecl { .. }
1428 | Node::Pipeline { .. }
1429 | Node::ThrowStmt { .. }
1430 | Node::BreakStmt
1431 | Node::ContinueStmt
1432 | Node::RequireStmt { .. }
1433 | Node::DeferStmt { .. } => false,
1434 Node::TryCatch { has_catch: _, .. }
1435 | Node::TryExpr { .. }
1436 | Node::Retry { .. }
1437 | Node::GuardStmt { .. }
1438 | Node::DeadlineBlock { .. }
1439 | Node::MutexBlock { .. }
1440 | Node::Spread(_) => true,
1441 _ => true,
1442 }
1443 }
1444}
1445
1446impl Default for Compiler {
1447 fn default() -> Self {
1448 Self::new()
1449 }
1450}