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