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