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