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 for fb in self.all_pending_finallys() {
269 self.compile_finally_inline(&fb)?;
270 }
271 if !pipeline_emits_value {
272 self.chunk.emit(Op::Nil, self.line);
273 }
274 self.chunk.emit(Op::Return, self.line);
275 Ok(self.chunk)
276 }
277
278 fn has_top_level_fn_main(program: &[SNode]) -> bool {
282 program
283 .iter()
284 .any(|sn| matches!(peel_node(sn), Node::FnDecl { name, .. } if name == "main"))
285 }
286
287 pub fn compile_named(
289 mut self,
290 program: &[SNode],
291 pipeline_name: &str,
292 ) -> Result<Chunk, CompileError> {
293 Self::collect_enum_names(program, &mut self.enum_names);
294 self.enum_names.insert("Result".to_string());
295 Self::collect_struct_layouts(program, &mut self.struct_layouts);
296 Self::collect_interface_methods(program, &mut self.interface_methods);
297 self.collect_type_aliases(program);
298
299 for sn in program {
300 if matches!(
301 &sn.node,
302 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
303 ) {
304 self.compile_node(sn)?;
305 }
306 }
307 let target = program.iter().find(
308 |sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == pipeline_name),
309 );
310
311 if let Some(sn) = target {
312 self.compile_top_level_declarations(program)?;
313 if let Node::Pipeline { body, extends, .. } = peel_node(sn) {
314 if let Some(parent_name) = extends {
315 self.compile_parent_pipeline(program, parent_name)?;
316 }
317 let saved = std::mem::replace(&mut self.module_level, false);
318 self.compile_block(body)?;
319 self.module_level = saved;
320 }
321 }
322
323 for fb in self.all_pending_finallys() {
324 self.compile_finally_inline(&fb)?;
325 }
326 self.chunk.emit(Op::Nil, self.line);
327 self.chunk.emit(Op::Return, self.line);
328 Ok(self.chunk)
329 }
330
331 pub(super) fn compile_parent_pipeline(
333 &mut self,
334 program: &[SNode],
335 parent_name: &str,
336 ) -> Result<(), CompileError> {
337 let parent = program
338 .iter()
339 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == parent_name));
340 if let Some(sn) = parent {
341 if let Node::Pipeline { body, extends, .. } = &sn.node {
342 if let Some(grandparent) = extends {
343 self.compile_parent_pipeline(program, grandparent)?;
344 }
345 for stmt in body {
346 self.compile_discarded_stmt(stmt)?;
347 }
348 }
349 }
350 Ok(())
351 }
352
353 pub(super) fn emit_default_preamble(
358 &mut self,
359 params: &[TypedParam],
360 ) -> Result<(), CompileError> {
361 for (i, param) in params.iter().enumerate() {
362 if let Some(default_expr) = ¶m.default_value {
363 self.chunk.emit(Op::GetArgc, self.line);
364 let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
365 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
366 self.chunk.emit(Op::GreaterEqual, self.line);
367 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
368 self.chunk.emit(Op::Pop, self.line);
370 self.compile_node(default_expr)?;
371 self.emit_init_or_define_binding(¶m.name, false);
372 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
373 self.chunk.patch_jump(skip_jump);
374 self.chunk.emit(Op::Pop, self.line);
375 self.chunk.patch_jump(end_jump);
376 }
377 }
378 Ok(())
379 }
380
381 pub(super) fn emit_type_checks(&mut self, params: &[TypedParam]) {
388 for (param_index, param) in params.iter().enumerate() {
389 if let Some(type_expr) = ¶m.type_expr {
390 let check_type = if param.rest {
391 harn_parser::TypeExpr::List(Box::new(type_expr.clone()))
392 } else {
393 type_expr.clone()
394 };
395
396 if let harn_parser::TypeExpr::Named(name) = &check_type {
397 if let Some(methods) = self.interface_methods.get(name).cloned() {
398 let fn_idx = self.string_constant("__assert_interface");
399 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
400 self.emit_get_binding(¶m.name);
401 let name_idx = self.string_constant(¶m.name);
402 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
403 let iface_idx = self.string_constant(name);
404 self.chunk.emit_u16(Op::Constant, iface_idx, self.line);
405 let methods_str = methods.join(",");
406 let methods_idx = self.owned_string_constant(methods_str);
407 self.chunk.emit_u16(Op::Constant, methods_idx, self.line);
408 self.chunk.emit_u8(Op::Call, 4, self.line);
409 self.chunk.emit(Op::Pop, self.line);
410 continue;
411 }
412 }
413
414 if param.default_value.is_some() {
415 if let Some(schema) = Self::type_expr_to_schema_value(&check_type) {
416 self.emit_default_param_schema_check(param_index, param, &schema);
417 }
418 }
419 }
420 }
421 }
422
423 fn emit_default_param_schema_check(
424 &mut self,
425 param_index: usize,
426 param: &TypedParam,
427 schema: &VmValue,
428 ) {
429 self.chunk.emit(Op::GetArgc, self.line);
430 let threshold_idx = self
431 .chunk
432 .add_constant(Constant::Int((param_index + 1) as i64));
433 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
434 self.chunk.emit(Op::GreaterEqual, self.line);
435 let supplied_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
436 self.chunk.emit(Op::Pop, self.line);
437 self.emit_schema_assert_call(param, schema);
438 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
439 self.chunk.patch_jump(supplied_jump);
440 self.chunk.emit(Op::Pop, self.line);
441 self.chunk.patch_jump(end_jump);
442 }
443
444 fn emit_schema_assert_call(&mut self, param: &TypedParam, schema: &VmValue) {
445 let fn_idx = self.string_constant("__assert_schema");
446 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
447 self.emit_get_binding(¶m.name);
448 let name_idx = self.string_constant(¶m.name);
449 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
450 self.emit_vm_value_literal(schema);
451 self.chunk.emit_u8(Op::Call, 3, self.line);
452 self.chunk.emit(Op::Pop, self.line);
453 }
454
455 pub(crate) fn type_expr_to_schema_value(type_expr: &harn_parser::TypeExpr) -> Option<VmValue> {
456 match type_expr {
457 harn_parser::TypeExpr::Named(name) => match name.as_str() {
458 "int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
459 | "closure" | "bytes" => {
460 Some(VmValue::Dict(std::sync::Arc::new(BTreeMap::from([(
461 "type".to_string(),
462 VmValue::String(std::sync::Arc::from(name.as_str())),
463 )]))))
464 }
465 _ => None,
466 },
467 harn_parser::TypeExpr::Shape(fields) => {
468 let mut properties = BTreeMap::new();
469 let mut required = Vec::new();
470 for field in fields {
471 let field_schema = Self::type_expr_to_schema_value(&field.type_expr)?;
472 properties.insert(field.name.clone(), field_schema);
473 if !field.optional {
474 required.push(VmValue::String(std::sync::Arc::from(field.name.as_str())));
475 }
476 }
477 let mut out = BTreeMap::new();
478 out.insert(
479 "type".to_string(),
480 VmValue::String(std::sync::Arc::from("dict")),
481 );
482 out.insert(
483 "properties".to_string(),
484 VmValue::Dict(std::sync::Arc::new(properties)),
485 );
486 if !required.is_empty() {
487 out.insert(
488 "required".to_string(),
489 VmValue::List(std::sync::Arc::new(required)),
490 );
491 }
492 Some(VmValue::Dict(std::sync::Arc::new(out)))
493 }
494 harn_parser::TypeExpr::List(inner) => {
495 let mut out = BTreeMap::new();
496 out.insert(
497 "type".to_string(),
498 VmValue::String(std::sync::Arc::from("list")),
499 );
500 if let Some(item_schema) = Self::type_expr_to_schema_value(inner) {
501 out.insert("items".to_string(), item_schema);
502 }
503 Some(VmValue::Dict(std::sync::Arc::new(out)))
504 }
505 harn_parser::TypeExpr::DictType(key, value) => {
506 let mut out = BTreeMap::new();
507 out.insert(
508 "type".to_string(),
509 VmValue::String(std::sync::Arc::from("dict")),
510 );
511 if matches!(key.as_ref(), harn_parser::TypeExpr::Named(name) if name == "string") {
512 if let Some(value_schema) = Self::type_expr_to_schema_value(value) {
513 out.insert("additional_properties".to_string(), value_schema);
514 }
515 }
516 Some(VmValue::Dict(std::sync::Arc::new(out)))
517 }
518 harn_parser::TypeExpr::Union(members) => {
519 if !members.is_empty()
524 && members
525 .iter()
526 .all(|m| matches!(m, harn_parser::TypeExpr::LitString(_)))
527 {
528 let values = members
529 .iter()
530 .map(|m| match m {
531 harn_parser::TypeExpr::LitString(s) => {
532 VmValue::String(std::sync::Arc::from(s.as_str()))
533 }
534 _ => unreachable!(),
535 })
536 .collect::<Vec<_>>();
537 return Some(VmValue::Dict(std::sync::Arc::new(BTreeMap::from([
538 (
539 "type".to_string(),
540 VmValue::String(std::sync::Arc::from("string")),
541 ),
542 (
543 "enum".to_string(),
544 VmValue::List(std::sync::Arc::new(values)),
545 ),
546 ]))));
547 }
548 if !members.is_empty()
549 && members
550 .iter()
551 .all(|m| matches!(m, harn_parser::TypeExpr::LitInt(_)))
552 {
553 let values = members
554 .iter()
555 .map(|m| match m {
556 harn_parser::TypeExpr::LitInt(v) => VmValue::Int(*v),
557 _ => unreachable!(),
558 })
559 .collect::<Vec<_>>();
560 return Some(VmValue::Dict(std::sync::Arc::new(BTreeMap::from([
561 (
562 "type".to_string(),
563 VmValue::String(std::sync::Arc::from("int")),
564 ),
565 (
566 "enum".to_string(),
567 VmValue::List(std::sync::Arc::new(values)),
568 ),
569 ]))));
570 }
571 let branches = members
572 .iter()
573 .filter_map(Self::type_expr_to_schema_value)
574 .collect::<Vec<_>>();
575 if branches.is_empty() {
576 None
577 } else {
578 Some(VmValue::Dict(std::sync::Arc::new(BTreeMap::from([(
579 "union".to_string(),
580 VmValue::List(std::sync::Arc::new(branches)),
581 )]))))
582 }
583 }
584 harn_parser::TypeExpr::Intersection(members) => {
585 let branches = members
589 .iter()
590 .filter_map(Self::type_expr_to_schema_value)
591 .collect::<Vec<_>>();
592 if branches.is_empty() {
593 None
594 } else {
595 Some(VmValue::Dict(std::sync::Arc::new(BTreeMap::from([(
596 "all_of".to_string(),
597 VmValue::List(std::sync::Arc::new(branches)),
598 )]))))
599 }
600 }
601 harn_parser::TypeExpr::FnType { .. } => {
602 Some(VmValue::Dict(std::sync::Arc::new(BTreeMap::from([(
603 "type".to_string(),
604 VmValue::String(std::sync::Arc::from("closure")),
605 )]))))
606 }
607 harn_parser::TypeExpr::Applied { .. } => None,
608 harn_parser::TypeExpr::Iter(_)
609 | harn_parser::TypeExpr::Generator(_)
610 | harn_parser::TypeExpr::Stream(_) => None,
611 harn_parser::TypeExpr::Never => None,
612 harn_parser::TypeExpr::LitString(s) => {
613 Some(VmValue::Dict(std::sync::Arc::new(BTreeMap::from([
614 (
615 "type".to_string(),
616 VmValue::String(std::sync::Arc::from("string")),
617 ),
618 (
619 "const".to_string(),
620 VmValue::String(std::sync::Arc::from(s.as_str())),
621 ),
622 ]))))
623 }
624 harn_parser::TypeExpr::LitInt(v) => {
625 Some(VmValue::Dict(std::sync::Arc::new(BTreeMap::from([
626 (
627 "type".to_string(),
628 VmValue::String(std::sync::Arc::from("int")),
629 ),
630 ("const".to_string(), VmValue::Int(*v)),
631 ]))))
632 }
633 harn_parser::TypeExpr::Owned(inner) => Self::type_expr_to_schema_value(inner),
634 }
635 }
636
637 pub(super) fn emit_vm_value_literal(&mut self, value: &VmValue) {
638 match value {
639 VmValue::String(text) => {
640 let idx = self.string_constant(text);
641 self.chunk.emit_u16(Op::Constant, idx, self.line);
642 }
643 VmValue::Int(number) => {
644 let idx = self.chunk.add_constant(Constant::Int(*number));
645 self.chunk.emit_u16(Op::Constant, idx, self.line);
646 }
647 VmValue::Float(number) => {
648 let idx = self.chunk.add_constant(Constant::Float(*number));
649 self.chunk.emit_u16(Op::Constant, idx, self.line);
650 }
651 VmValue::Bool(value) => {
652 let idx = self.chunk.add_constant(Constant::Bool(*value));
653 self.chunk.emit_u16(Op::Constant, idx, self.line);
654 }
655 VmValue::Nil => self.chunk.emit(Op::Nil, self.line),
656 VmValue::List(items) => {
657 for item in items.iter() {
658 self.emit_vm_value_literal(item);
659 }
660 self.chunk
661 .emit_u16(Op::BuildList, items.len() as u16, self.line);
662 }
663 VmValue::Dict(entries) => {
664 for (key, item) in entries.iter() {
665 let key_idx = self.string_constant(key);
666 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
667 self.emit_vm_value_literal(item);
668 }
669 self.chunk
670 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
671 }
672 _ => {}
673 }
674 }
675
676 pub(super) fn emit_type_name_extra(&mut self, type_name_idx: u16) {
678 let hi = (type_name_idx >> 8) as u8;
679 let lo = type_name_idx as u8;
680 self.chunk.code.push(hi);
681 self.chunk.code.push(lo);
682 self.chunk.lines.push(self.line);
683 self.chunk.columns.push(self.column);
684 self.chunk.lines.push(self.line);
685 self.chunk.columns.push(self.column);
686 }
687
688 pub(super) fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
690 if body.is_empty() {
691 self.chunk.emit(Op::Nil, self.line);
692 } else {
693 self.compile_scoped_block(body)?;
694 }
695 Ok(())
696 }
697
698 pub(super) fn compile_catch_binding(
700 &mut self,
701 error_var: &Option<String>,
702 ) -> Result<(), CompileError> {
703 if let Some(var_name) = error_var {
704 self.emit_define_binding(var_name, false);
705 } else {
706 self.chunk.emit(Op::Pop, self.line);
707 }
708 Ok(())
709 }
710
711 pub(super) fn compile_finally_inline(
718 &mut self,
719 finally_body: &[SNode],
720 ) -> Result<(), CompileError> {
721 if !finally_body.is_empty() {
722 self.compile_scoped_block(finally_body)?;
723 self.chunk.emit(Op::Pop, self.line);
724 }
725 Ok(())
726 }
727
728 pub(super) fn pending_finallys_until_barrier(&self) -> Vec<Vec<SNode>> {
733 let mut out = Vec::new();
734 for entry in self.finally_bodies.iter().rev() {
735 match entry {
736 FinallyEntry::CatchBarrier => break,
737 FinallyEntry::Finally(body) => out.push(body.clone()),
738 }
739 }
740 out
741 }
742
743 pub(super) fn pending_finallys_down_to(&self, floor: usize) -> Vec<Vec<SNode>> {
749 let mut out = Vec::new();
750 for entry in self.finally_bodies[floor..].iter().rev() {
751 if let FinallyEntry::Finally(body) = entry {
752 out.push(body.clone());
753 }
754 }
755 out
756 }
757
758 pub(super) fn all_pending_finallys(&self) -> Vec<Vec<SNode>> {
760 self.pending_finallys_down_to(0)
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 define_local_slot(&mut self, name: &str, mutable: bool) -> Option<u16> {
794 if self.module_level || harn_parser::is_discard_name(name) {
795 return None;
796 }
797 let current = self.local_scopes.last_mut()?;
798 if let Some(existing) = current.get_mut(name) {
799 if existing.mutable || mutable {
800 if mutable {
801 existing.mutable = true;
802 if let Some(info) = self.chunk.local_slots.get_mut(existing.slot as usize) {
803 info.mutable = true;
804 }
805 }
806 return Some(existing.slot);
807 }
808 return None;
809 }
810 let slot = self
811 .chunk
812 .add_local_slot(name.to_string(), mutable, self.scope_depth);
813 current.insert(name.to_string(), super::LocalBinding { slot, mutable });
814 Some(slot)
815 }
816
817 pub(super) fn resolve_local_slot(&self, name: &str) -> Option<super::LocalBinding> {
818 if self.module_level {
819 return None;
820 }
821 self.local_scopes
822 .iter()
823 .rev()
824 .find_map(|scope| scope.get(name).copied())
825 }
826
827 pub(super) fn emit_get_binding(&mut self, name: &str) {
828 if let Some(binding) = self.resolve_local_slot(name) {
829 self.chunk
830 .emit_u16(Op::GetLocalSlot, binding.slot, self.line);
831 } else {
832 let idx = self.string_constant(name);
833 self.chunk.emit_u16(Op::GetVar, idx, self.line);
834 }
835 }
836
837 pub(super) fn emit_define_binding(&mut self, name: &str, mutable: bool) {
838 if let Some(slot) = self.define_local_slot(name, mutable) {
839 self.chunk.emit_u16(Op::DefLocalSlot, slot, self.line);
840 } else {
841 let idx = self.string_constant(name);
842 let op = if mutable { Op::DefVar } else { Op::DefLet };
843 self.chunk.emit_u16(op, idx, self.line);
844 }
845 }
846
847 pub(super) fn emit_init_or_define_binding(&mut self, name: &str, mutable: bool) {
848 if let Some(binding) = self.resolve_local_slot(name) {
849 self.chunk
850 .emit_u16(Op::DefLocalSlot, binding.slot, self.line);
851 } else {
852 self.emit_define_binding(name, mutable);
853 }
854 }
855
856 pub(super) fn emit_set_binding(&mut self, name: &str) {
857 if let Some(binding) = self.resolve_local_slot(name) {
858 let _ = binding.mutable;
859 self.chunk
860 .emit_u16(Op::SetLocalSlot, binding.slot, self.line);
861 } else {
862 let idx = self.string_constant(name);
863 self.chunk.emit_u16(Op::SetVar, idx, self.line);
864 }
865 }
866
867 pub(super) fn begin_scope(&mut self) {
868 self.chunk.emit(Op::PushScope, self.line);
869 self.scope_depth += 1;
870 self.type_scopes.push(std::collections::HashMap::new());
871 self.local_scopes.push(std::collections::HashMap::new());
872 }
873
874 pub(super) fn end_scope(&mut self) {
875 if self.scope_depth > 0 {
876 self.chunk.emit(Op::PopScope, self.line);
877 self.scope_depth -= 1;
878 self.type_scopes.pop();
879 self.local_scopes.pop();
880 }
881 }
882
883 pub(super) fn unwind_scopes_to(&mut self, target_depth: usize) {
884 while self.scope_depth > target_depth {
885 self.chunk.emit(Op::PopScope, self.line);
886 self.scope_depth -= 1;
887 self.type_scopes.pop();
888 self.local_scopes.pop();
889 }
890 }
891
892 pub(super) fn compile_scoped_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
893 self.begin_scope();
894 let finally_floor = self.finally_bodies.len();
895 if stmts.is_empty() {
896 self.chunk.emit(Op::Nil, self.line);
897 } else {
898 self.compile_block(stmts)?;
899 }
900 self.drain_finallys_to_floor(finally_floor)?;
901 self.end_scope();
902 Ok(())
903 }
904
905 pub(super) fn compile_scoped_statements(
906 &mut self,
907 stmts: &[SNode],
908 ) -> Result<(), CompileError> {
909 self.begin_scope();
910 let finally_floor = self.finally_bodies.len();
911 for sn in stmts {
912 self.compile_discarded_stmt(sn)?;
913 }
914 self.drain_finallys_to_floor(finally_floor)?;
915 self.end_scope();
916 Ok(())
917 }
918
919 pub(super) fn drain_finallys_to_floor(&mut self, floor: usize) -> Result<(), CompileError> {
924 while self.finally_bodies.len() > floor {
925 let entry = self.finally_bodies.pop().expect("non-empty by guard");
926 if let FinallyEntry::Finally(body) = entry {
927 self.compile_finally_inline(&body)?;
928 }
929 }
930 Ok(())
931 }
932
933 pub(super) fn maybe_register_owned_drop(
938 &mut self,
939 pattern: &harn_parser::BindingPattern,
940 type_ann: Option<&TypeExpr>,
941 span: harn_lexer::Span,
942 ) {
943 let Some(ty) = type_ann else {
949 return;
950 };
951 if !matches!(ty, TypeExpr::Owned(_)) {
952 return;
953 }
954 let harn_parser::BindingPattern::Identifier(name) = pattern else {
955 return;
956 };
957 if harn_parser::is_discard_name(name) {
958 return;
959 }
960 let call = harn_parser::spanned(
961 Node::FunctionCall {
962 name: "drop".to_string(),
963 args: vec![harn_parser::spanned(Node::Identifier(name.clone()), span)],
964 type_args: Vec::new(),
965 },
966 span,
967 );
968 self.finally_bodies.push(FinallyEntry::Finally(vec![call]));
969 }
970
971 pub(super) fn compile_discarded_stmt(&mut self, sn: &SNode) -> Result<(), CompileError> {
987 #[cfg(debug_assertions)]
988 let probe = self.chunk.balance_probe();
989 self.compile_node(sn)?;
990 #[allow(unused_mut)]
991 let mut produces = Self::produces_value(&sn.node);
992 #[cfg(test)]
996 if let Some(forced) = FORCE_DISCARDED_PRODUCES_VALUE.with(std::cell::Cell::get) {
997 produces = forced;
998 }
999 #[cfg(debug_assertions)]
1000 if let Some(delta) = self.chunk.balance_delta_since(probe) {
1001 let expected = i32::from(produces);
1002 debug_assert_eq!(
1003 delta, expected,
1004 "operand-stack imbalance at line {}: produces_value={produces} but the \
1005 node's emitted bytecode netted {delta} (expected {expected}). A \
1006 `produces_value` arm is out of sync with this node's codegen — see #2622.\n\
1007 node: {:?}",
1008 self.line, sn.node,
1009 );
1010 }
1011 if produces {
1012 self.chunk.emit(Op::Pop, self.line);
1013 }
1014 Ok(())
1015 }
1016
1017 pub(super) fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
1018 for (i, snode) in stmts.iter().enumerate() {
1019 if i == stmts.len() - 1 {
1020 self.compile_node(snode)?;
1024 if !Self::produces_value(&snode.node) {
1025 self.chunk.emit(Op::Nil, self.line);
1026 }
1027 } else {
1028 self.compile_discarded_stmt(snode)?;
1029 }
1030 }
1031 Ok(())
1032 }
1033
1034 pub(super) fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1036 self.begin_scope();
1037 let finally_floor = self.finally_bodies.len();
1038 if body.is_empty() {
1039 self.chunk.emit(Op::Nil, self.line);
1040 } else {
1041 self.compile_block(body)?;
1042 if !Self::produces_value(&body.last().unwrap().node) {
1043 self.chunk.emit(Op::Nil, self.line);
1044 }
1045 }
1046 self.drain_finallys_to_floor(finally_floor)?;
1047 self.end_scope();
1048 Ok(())
1049 }
1050
1051 pub(super) fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
1053 match op {
1054 "+" => self.chunk.emit(Op::Add, self.line),
1055 "-" => self.chunk.emit(Op::Sub, self.line),
1056 "*" => self.chunk.emit(Op::Mul, self.line),
1057 "/" => self.chunk.emit(Op::Div, self.line),
1058 "%" => self.chunk.emit(Op::Mod, self.line),
1059 _ => {
1060 return Err(CompileError {
1061 message: format!("Unknown compound operator: {op}"),
1062 line: self.line,
1063 })
1064 }
1065 }
1066 Ok(())
1067 }
1068
1069 pub(super) fn root_var_name(&self, node: &SNode) -> Option<String> {
1071 match &node.node {
1072 Node::Identifier(name) => Some(name.clone()),
1073 Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
1074 self.root_var_name(object)
1075 }
1076 Node::SubscriptAccess { object, .. } | Node::OptionalSubscriptAccess { object, .. } => {
1077 self.root_var_name(object)
1078 }
1079 _ => None,
1080 }
1081 }
1082
1083 pub(super) fn compile_top_level_declarations(
1084 &mut self,
1085 program: &[SNode],
1086 ) -> Result<(), CompileError> {
1087 for sn in program {
1095 if matches!(
1096 &sn.node,
1097 Node::LetBinding { .. } | Node::VarBinding { .. } | Node::ConstBinding { .. }
1098 ) {
1099 self.compile_node(sn)?;
1100 }
1101 }
1102 for sn in program {
1108 let inner_kind = match &sn.node {
1109 Node::AttributedDecl { inner, .. } => &inner.node,
1110 other => other,
1111 };
1112 match inner_kind {
1113 Node::EvalPackDecl {
1114 binding_name,
1115 pack_id,
1116 fields,
1117 body,
1118 summarize,
1119 ..
1120 } => {
1121 self.compile_eval_pack_decl(
1122 binding_name,
1123 pack_id,
1124 fields,
1125 body,
1126 summarize,
1127 false,
1128 )?;
1129 }
1130 Node::FnDecl { .. }
1131 | Node::ToolDecl { .. }
1132 | Node::SkillDecl { .. }
1133 | Node::ImplBlock { .. }
1134 | Node::StructDecl { .. }
1135 | Node::EnumDecl { .. }
1136 | Node::InterfaceDecl { .. }
1137 | Node::TypeDecl { .. } => {
1138 self.compile_node(sn)?;
1139 }
1140 _ => {}
1141 }
1142 }
1143 Ok(())
1144 }
1145
1146 pub(super) fn collect_enum_names(
1148 nodes: &[SNode],
1149 names: &mut std::collections::HashSet<String>,
1150 ) {
1151 for sn in nodes {
1152 match &sn.node {
1153 Node::EnumDecl { name, .. } => {
1154 names.insert(name.clone());
1155 }
1156 Node::Pipeline { body, .. } => {
1157 Self::collect_enum_names(body, names);
1158 }
1159 Node::FnDecl { body, .. } | Node::ToolDecl { body, .. } => {
1160 Self::collect_enum_names(body, names);
1161 }
1162 Node::SkillDecl { fields, .. } => {
1163 for (_k, v) in fields {
1164 Self::collect_enum_names(std::slice::from_ref(v), names);
1165 }
1166 }
1167 Node::EvalPackDecl {
1168 fields,
1169 body,
1170 summarize,
1171 ..
1172 } => {
1173 for (_k, v) in fields {
1174 Self::collect_enum_names(std::slice::from_ref(v), names);
1175 }
1176 Self::collect_enum_names(body, names);
1177 if let Some(summary_body) = summarize {
1178 Self::collect_enum_names(summary_body, names);
1179 }
1180 }
1181 Node::Block(stmts) => {
1182 Self::collect_enum_names(stmts, names);
1183 }
1184 Node::AttributedDecl { inner, .. } => {
1185 Self::collect_enum_names(std::slice::from_ref(inner), names);
1186 }
1187 _ => {}
1188 }
1189 }
1190 }
1191
1192 pub(super) fn collect_struct_layouts(
1193 nodes: &[SNode],
1194 layouts: &mut std::collections::HashMap<String, Vec<String>>,
1195 ) {
1196 for sn in nodes {
1197 match &sn.node {
1198 Node::StructDecl { name, fields, .. } => {
1199 layouts.insert(
1200 name.clone(),
1201 fields.iter().map(|field| field.name.clone()).collect(),
1202 );
1203 }
1204 Node::Pipeline { body, .. }
1205 | Node::FnDecl { body, .. }
1206 | Node::ToolDecl { body, .. } => {
1207 Self::collect_struct_layouts(body, layouts);
1208 }
1209 Node::SkillDecl { fields, .. } => {
1210 for (_k, v) in fields {
1211 Self::collect_struct_layouts(std::slice::from_ref(v), layouts);
1212 }
1213 }
1214 Node::EvalPackDecl {
1215 fields,
1216 body,
1217 summarize,
1218 ..
1219 } => {
1220 for (_k, v) in fields {
1221 Self::collect_struct_layouts(std::slice::from_ref(v), layouts);
1222 }
1223 Self::collect_struct_layouts(body, layouts);
1224 if let Some(summary_body) = summarize {
1225 Self::collect_struct_layouts(summary_body, layouts);
1226 }
1227 }
1228 Node::Block(stmts) => {
1229 Self::collect_struct_layouts(stmts, layouts);
1230 }
1231 Node::AttributedDecl { inner, .. } => {
1232 Self::collect_struct_layouts(std::slice::from_ref(inner), layouts);
1233 }
1234 _ => {}
1235 }
1236 }
1237 }
1238
1239 pub(super) fn collect_interface_methods(
1240 nodes: &[SNode],
1241 interfaces: &mut std::collections::HashMap<String, Vec<String>>,
1242 ) {
1243 for sn in nodes {
1244 match &sn.node {
1245 Node::InterfaceDecl { name, methods, .. } => {
1246 let method_names: Vec<String> =
1247 methods.iter().map(|m| m.name.clone()).collect();
1248 interfaces.insert(name.clone(), method_names);
1249 }
1250 Node::Pipeline { body, .. }
1251 | Node::FnDecl { body, .. }
1252 | Node::ToolDecl { body, .. } => {
1253 Self::collect_interface_methods(body, interfaces);
1254 }
1255 Node::SkillDecl { fields, .. } => {
1256 for (_k, v) in fields {
1257 Self::collect_interface_methods(std::slice::from_ref(v), interfaces);
1258 }
1259 }
1260 Node::EvalPackDecl {
1261 fields,
1262 body,
1263 summarize,
1264 ..
1265 } => {
1266 for (_k, v) in fields {
1267 Self::collect_interface_methods(std::slice::from_ref(v), interfaces);
1268 }
1269 Self::collect_interface_methods(body, interfaces);
1270 if let Some(summary_body) = summarize {
1271 Self::collect_interface_methods(summary_body, interfaces);
1272 }
1273 }
1274 Node::Block(stmts) => {
1275 Self::collect_interface_methods(stmts, interfaces);
1276 }
1277 Node::AttributedDecl { inner, .. } => {
1278 Self::collect_interface_methods(std::slice::from_ref(inner), interfaces);
1279 }
1280 _ => {}
1281 }
1282 }
1283 }
1284
1285 pub fn compile_fn_body(
1298 &mut self,
1299 type_params: &[harn_parser::TypeParam],
1300 params: &[TypedParam],
1301 body: &[SNode],
1302 source_file: Option<String>,
1303 ) -> Result<CompiledFunction, CompileError> {
1304 let mut fn_compiler = self.nested_body();
1305 fn_compiler.enum_names = self.enum_names.clone();
1306 fn_compiler.interface_methods = self.interface_methods.clone();
1307 fn_compiler.type_aliases = self.type_aliases.clone();
1308 fn_compiler.struct_layouts = self.struct_layouts.clone();
1309 fn_compiler.declare_param_slots(params);
1310 fn_compiler.record_param_types(params);
1311 fn_compiler.emit_default_preamble(params)?;
1312 fn_compiler.emit_type_checks(params);
1313 let is_gen = body_contains_yield(body);
1314 fn_compiler.compile_block(body)?;
1315 fn_compiler.chunk.emit(Op::Nil, 0);
1316 fn_compiler.chunk.emit(Op::Return, 0);
1317 fn_compiler.chunk.source_file = source_file;
1318 let param_slots = crate::chunk::ParamSlot::vec_from_typed(params);
1319 let has_runtime_type_checks =
1320 CompiledFunction::has_runtime_type_checks_for_params(¶m_slots);
1321 Ok(CompiledFunction {
1322 name: String::new(),
1323 type_params: type_params.iter().map(|param| param.name.clone()).collect(),
1324 nominal_type_names: fn_compiler.nominal_type_names(),
1325 params: param_slots,
1326 default_start: TypedParam::default_start(params),
1327 chunk: Arc::new(fn_compiler.chunk),
1328 is_generator: is_gen,
1329 is_stream: false,
1330 has_rest_param: false,
1331 has_runtime_type_checks,
1332 })
1333 }
1334
1335 pub(super) fn produces_value(node: &Node) -> bool {
1337 match node {
1338 Node::AttributedDecl { inner, .. } => Self::produces_value(&inner.node),
1345 Node::LetBinding { .. }
1346 | Node::VarBinding { .. }
1347 | Node::ConstBinding { .. }
1348 | Node::Assignment { .. }
1349 | Node::ReturnStmt { .. }
1350 | Node::FnDecl { .. }
1351 | Node::ToolDecl { .. }
1352 | Node::SkillDecl { .. }
1353 | Node::EvalPackDecl { .. }
1354 | Node::ImplBlock { .. }
1355 | Node::StructDecl { .. }
1356 | Node::EnumDecl { .. }
1357 | Node::InterfaceDecl { .. }
1358 | Node::TypeDecl { .. }
1359 | Node::OverrideDecl { .. }
1362 | Node::Pipeline { .. }
1363 | Node::ThrowStmt { .. }
1364 | Node::BreakStmt
1365 | Node::ContinueStmt
1366 | Node::RequireStmt { .. }
1367 | Node::DeferStmt { .. } => false,
1368 Node::TryCatch { has_catch: _, .. }
1369 | Node::TryExpr { .. }
1370 | Node::Retry { .. }
1371 | Node::GuardStmt { .. }
1372 | Node::DeadlineBlock { .. }
1373 | Node::MutexBlock { .. }
1374 | Node::Spread(_) => true,
1375 _ => true,
1376 }
1377 }
1378}
1379
1380impl Default for Compiler {
1381 fn default() -> Self {
1382 Self::new()
1383 }
1384}