Skip to main content

hara_native/vm/
compiler.rs

1//! Compiler: `Form` trees (with parser spans) to a validated `Program`.
2//!
3//! Supports the milestone-4 synchronous subset: literals, lexical locals,
4//! the ten shared primitives, `if`, `do`, `let`, `loop`/`recur`, `fn`
5//! closures with capture-by-value upvalues (including variadic
6//! parameters), direct calls, exceptions, and the registry-direct global
7//! forms — `def`, `defn` (single- and multi-arity, interning real
8//! late-bound vars), `var`, `set!`, `declare`, `field`, and `instance?`
9//! (issue #223; see
10//! `specs/01-lang/010-bytecode/draft/hal-bytecode-vm.edn` `:vm/namespaces`).
11//! Anything else is a typed [`CompileError`] with source context; the
12//! compiler never emits fallback calls into the tree-walking evaluator.
13//!
14//! Structure: shared state (constants, finished prototypes) plus a stack
15//! of function contexts. Each context owns a code buffer, scope stack,
16//! loop stack, and capture list. Slot layout per function: parameters at
17//! `0..arity-1`, captures at `arity..arity+capture_count-1`, body locals
18//! above. Captures are discovered by a free-variable pre-pass over the
19//! body, so their slots are reserved (and pre-declared in the function's
20//! base scope) before any body-local slot is allocated.
21
22use crate::core::{IntrinsicOp, Value};
23use crate::kernel::{Form, Position, Span, SpannedForm};
24use crate::lang::data::List as PList;
25use std::collections::{HashMap, HashSet};
26
27use super::error::{CompileError, CompileErrorKind};
28use super::opcode::Instruction;
29use super::program::{
30    FunctionPrototype, Program, TryEntry, MAX_CONSTANTS, MAX_PRIMITIVE_ARGUMENTS,
31};
32use super::source_map::SourceMap;
33use super::validate::{self, stack_heights};
34
35#[path = "compiler/bindings.rs"]
36mod bindings;
37#[path = "compiler/calls.rs"]
38mod calls;
39#[path = "compiler/coroutines.rs"]
40mod coroutines;
41#[path = "compiler/destructure.rs"]
42mod destructure;
43#[path = "compiler/exceptions.rs"]
44mod exceptions;
45#[path = "compiler/functions.rs"]
46mod functions;
47#[path = "compiler/literals.rs"]
48mod literals;
49#[path = "compiler/scope.rs"]
50mod scope;
51use exceptions::TryContext;
52#[path = "compiler/globals.rs"]
53mod globals;
54#[path = "compiler/recur.rs"]
55mod recur;
56use recur::LoopContext;
57use scope::ScopeStack;
58
59/// Operators that name language forms the VM does not implement. In
60/// operator position they report as unsupported rather than as unbound
61/// symbols; everything else unbound reports as an unbound symbol,
62/// matching the evaluator.
63const UNSUPPORTED_OPERATORS: &[&str] = &["in-ns", "await"];
64
65/// Compiles source text into a validated program. Multiple top-level
66/// forms compile as an implicit `do`. Without a namespace registry the
67/// program must be closed: only the names it declares itself are
68/// visible as globals (issue #223).
69pub fn compile_source(source: &str) -> Result<Program, CompileError> {
70    let forms = crate::kernel::read_forms(source)?;
71    compile_spanned_forms(&forms)
72}
73
74fn compile_spanned_forms(forms: &[SpannedForm]) -> Result<Program, CompileError> {
75    prepare_top_level_namespaces(forms)?;
76    compile_spanned_forms_without_namespace_preparation(forms, HashSet::new(), false)
77}
78
79fn compile_spanned_forms_without_namespace_preparation(
80    forms: &[SpannedForm],
81    excluded_foundation_libraries: HashSet<String>,
82    allow_unbound_globals: bool,
83) -> Result<Program, CompileError> {
84    let mut compiler = Compiler::new(excluded_foundation_libraries, allow_unbound_globals);
85    compiler.predeclare_top_level(forms);
86    let children = compiler.children(forms);
87    compiler.compile_sequence(&children, true)?;
88    compiler.finish()
89}
90
91fn prepare_top_level_namespaces(forms: &[SpannedForm]) -> Result<(), CompileError> {
92    fn prepare(form: &Form, position: Position) -> Result<(), CompileError> {
93        let Form::List(items) = crate::core::form_without_metadata(form) else {
94            return Ok(());
95        };
96        match items.first() {
97            Some(Form::Symbol(operator)) if operator == "ns" || operator == "ns+" => {
98                crate::core::prepare_namespace_form(form).map_err(|message| {
99                    CompileError::new(CompileErrorKind::UnsupportedForm, message, Some(position))
100                })
101            }
102            Some(Form::Symbol(operator)) if operator == "do" => {
103                for child in items.iter().skip(1) {
104                    prepare(child, position)?;
105                }
106                Ok(())
107            }
108            _ => Ok(()),
109        }
110    }
111
112    for form in forms {
113        prepare(&form.form, form.span.start)?;
114    }
115    Ok(())
116}
117
118/// Compiles against a caller's namespace registry: registry vars
119/// (std.foundation and anything already interned) are visible to the
120/// two-phase global check, exactly as they will resolve at execution
121/// time through `execute_program_with_globals` (issue #223).
122pub fn compile_source_with(
123    source: &str,
124    registry: &crate::kernel::NamespaceRegistry<crate::core::Value>,
125) -> Result<Program, CompileError> {
126    let forms = crate::kernel::read_forms(source)?;
127    let config = source_namespace_config(&forms)?;
128    compile_spanned_forms_with_config(&forms, registry, config, true)
129}
130
131/// Variant of [`compile_source_with`] for direct runtime evaluation. It keeps
132/// source namespace configuration intact while allowing late-bound globals
133/// which a preceding dynamic form may define.
134pub fn compile_source_with_allow_unbound_globals(
135    source: &str,
136    registry: &crate::kernel::NamespaceRegistry<crate::core::Value>,
137) -> Result<Program, CompileError> {
138    let forms = crate::kernel::read_forms(source)?;
139    let config = source_namespace_config(&forms)?;
140    compile_spanned_forms_with_config_options(&forms, registry, config, true, true)
141}
142
143/// Compiles source against a caller-owned registry and namespace configuration.
144/// The configuration is applied to parsed forms before bytecode lowering so
145/// aliases remain source-positioned and the VM does not need an evaluator or
146/// text round-trip fallback. Runtime callers use this when their namespace
147/// declaration has already been loaded and its complete config is available.
148pub fn compile_source_with_config(
149    source: &str,
150    registry: &crate::kernel::NamespaceRegistry<crate::core::Value>,
151    config: crate::kernel::GeneratedNamespaceConfig,
152) -> Result<Program, CompileError> {
153    let forms = crate::kernel::read_forms(source)?;
154    compile_spanned_forms_with_config(&forms, registry, config, true)
155}
156
157/// Compiles source for a direct-native runtime escape hatch. Dynamic Hara
158/// evaluation may define a Var before a later form in the same native frame
159/// reads it, so unresolved names are emitted as late-bound global reads. The
160/// direct runtime still fails at the read if the Var was not actually
161/// materialized; no evaluator fallback is introduced.
162pub fn compile_source_with_config_allow_unbound_globals(
163    source: &str,
164    registry: &crate::kernel::NamespaceRegistry<crate::core::Value>,
165    config: crate::kernel::GeneratedNamespaceConfig,
166) -> Result<Program, CompileError> {
167    let forms = crate::kernel::read_forms(source)?;
168    compile_spanned_forms_with_config_options(&forms, registry, config, true, true)
169}
170
171/// Compiles an already-read form without printing it back to source first.
172/// This is used by the native runtime's ordinary form boundary so metadata
173/// such as `^:async` remains part of the bytecode contract.
174pub fn compile_form_with_config_allow_unbound_globals(
175    form: Form,
176    registry: &crate::kernel::NamespaceRegistry<crate::core::Value>,
177    config: crate::kernel::GeneratedNamespaceConfig,
178) -> Result<Program, CompileError> {
179    let form = synthetic_spanned_form(form);
180    compile_spanned_form_with_config_allow_unbound_globals(form, registry, config)
181}
182
183/// Compiles already-read source forms without discarding their parser spans.
184/// Runtime entry points use this boundary so direct-native execution reports
185/// the source location of nested exception creation and throw instructions
186/// while preserving one program for each contiguous ordinary-form batch.
187pub fn compile_spanned_forms_with_config_allow_unbound_globals(
188    forms: &[SpannedForm],
189    registry: &crate::kernel::NamespaceRegistry<crate::core::Value>,
190    config: crate::kernel::GeneratedNamespaceConfig,
191) -> Result<Program, CompileError> {
192    compile_spanned_forms_with_config_options(forms, registry, config, false, true)
193}
194
195/// Compiles an already-read source form without discarding its parser span.
196/// Prefer [`compile_spanned_forms_with_config_allow_unbound_globals`] when a
197/// runtime entry point has more than one contiguous ordinary source form.
198pub fn compile_spanned_form_with_config_allow_unbound_globals(
199    form: SpannedForm,
200    registry: &crate::kernel::NamespaceRegistry<crate::core::Value>,
201    config: crate::kernel::GeneratedNamespaceConfig,
202) -> Result<Program, CompileError> {
203    compile_spanned_forms_with_config_allow_unbound_globals(&[form], registry, config)
204}
205
206/// Reports whether source contains a language-level dynamic evaluation
207/// boundary. Direct-native callers use this to permit late-bound reads which
208/// may be materialized by `Runtime/eval`, `load-string`, or `defonce` during
209/// execution. The scan is deliberately structural so a string containing the
210/// word `eval` does not change compilation policy.
211pub fn source_uses_dynamic_evaluation(source: &str) -> Result<bool, CompileError> {
212    let forms = crate::kernel::read_forms(source)?;
213
214    fn dynamic_symbol(name: &str) -> bool {
215        matches!(
216            name.rsplit_once('/').map_or(name, |(_, local)| local),
217            "defonce" | "eval" | "eval-in" | "eval-in-ns" | "load-string" | "with-ns"
218        )
219    }
220
221    fn contains(form: &Form) -> bool {
222        match crate::core::form_without_metadata(form) {
223            Form::List(values) => {
224                values.first().is_some_and(
225                    |value| matches!(value, Form::Symbol(name) if dynamic_symbol(name)),
226                ) || values.iter().any(contains)
227            }
228            Form::Vector(values) | Form::Set(values) => values.iter().any(contains),
229            Form::Map(entries) => entries
230                .iter()
231                .any(|(key, value)| contains(key) || contains(value)),
232            Form::Tagged(_, value) => contains(value),
233            _ => false,
234        }
235    }
236
237    Ok(forms.iter().any(|form| contains(&form.form)))
238}
239
240fn compile_spanned_forms_with_config(
241    forms: &[SpannedForm],
242    registry: &crate::kernel::NamespaceRegistry<crate::core::Value>,
243    config: crate::kernel::GeneratedNamespaceConfig,
244    prepare_namespaces: bool,
245) -> Result<Program, CompileError> {
246    compile_spanned_forms_with_config_options(forms, registry, config, prepare_namespaces, false)
247}
248
249fn compile_spanned_forms_with_config_options(
250    forms: &[SpannedForm],
251    registry: &crate::kernel::NamespaceRegistry<crate::core::Value>,
252    mut config: crate::kernel::GeneratedNamespaceConfig,
253    prepare_namespaces: bool,
254    allow_unbound_globals: bool,
255) -> Result<Program, CompileError> {
256    crate::core::with_namespace_registry(registry, || {
257        if prepare_namespaces {
258            prepare_top_level_namespaces(forms)?;
259        }
260        sync_registry_global_aliases(&mut config, registry);
261        let rewritten = forms
262            .iter()
263            .map(|form| rewrite_spanned_form(form, &config))
264            .collect::<Vec<_>>();
265        compile_spanned_forms_without_namespace_preparation(
266            &rewritten,
267            config.excluded_foundation_libraries().clone(),
268            allow_unbound_globals,
269        )
270    })
271}
272
273pub fn source_namespace_config(
274    forms: &[SpannedForm],
275) -> Result<crate::kernel::GeneratedNamespaceConfig, CompileError> {
276    let mut selected = None;
277    for form in forms {
278        let crate::kernel::Form::List(items) = crate::core::form_without_metadata(&form.form)
279        else {
280            continue;
281        };
282        let Some(crate::kernel::Form::Symbol(operator)) = items.first() else {
283            continue;
284        };
285        let clause_start = match operator.as_str() {
286            "ns" => 2,
287            "ns+" => 1,
288            _ => continue,
289        };
290        if items.len() < clause_start {
291            return Err(CompileError::new(
292                CompileErrorKind::UnsupportedForm,
293                "namespace declaration is missing its clauses",
294                Some(form.span.start),
295            ));
296        }
297        if operator == "ns" || items.len() > clause_start {
298            selected = Some(
299                crate::kernel::GeneratedNamespaceConfig::configure_with(
300                    &items[clause_start..],
301                    |_| true,
302                )
303                .map_err(|message| {
304                    CompileError::new(
305                        CompileErrorKind::UnsupportedForm,
306                        message,
307                        Some(form.span.start),
308                    )
309                })?,
310            );
311        }
312    }
313    Ok(selected.unwrap_or_else(crate::kernel::GeneratedNamespaceConfig::defaults))
314}
315
316fn sync_registry_global_aliases(
317    config: &mut crate::kernel::GeneratedNamespaceConfig,
318    registry: &crate::kernel::NamespaceRegistry<crate::core::Value>,
319) {
320    let excluded_foundation_libraries = config.excluded_foundation_libraries().clone();
321    let excluded_foundation = config.excluded_foundation().clone();
322    let current = registry.current();
323    for library in &excluded_foundation_libraries {
324        if let Some(alias) = crate::kernel::generated::foundation_library_alias(library) {
325            current.unalias(alias);
326        }
327    }
328    for (alias, target) in current.aliases() {
329        let library = target.name().as_str().strip_prefix("std.foundation.");
330        if library.is_some_and(|library| excluded_foundation_libraries.contains(library)) {
331            current.unalias(alias.as_str());
332        }
333    }
334    for (alias, target) in current.lazy_aliases() {
335        let library = target.as_str().strip_prefix("std.foundation.");
336        if library.is_some_and(|library| excluded_foundation_libraries.contains(library)) {
337            current.unalias(alias.as_str());
338        }
339    }
340    config.set_global_aliases(
341        registry
342            .global_aliases()
343            .into_iter()
344            .filter(|(_, namespace)| {
345                let library = namespace
346                    .as_str()
347                    .strip_prefix("std.foundation.")
348                    .unwrap_or_default();
349                !excluded_foundation_libraries.contains(library)
350                    && !excluded_foundation.contains(library)
351            })
352            .map(|(alias, namespace)| (alias.as_str().to_owned(), namespace.as_str().to_owned())),
353    );
354}
355
356pub(crate) fn rewrite_spanned_form(
357    form: &SpannedForm,
358    config: &crate::kernel::GeneratedNamespaceConfig,
359) -> SpannedForm {
360    SpannedForm {
361        form: config.rewrite(form.form.clone()),
362        span: form.span.clone(),
363        children: form
364            .children
365            .iter()
366            .map(|child| rewrite_spanned_form(child, config))
367            .collect(),
368    }
369}
370
371/// Lowers decoded HALC directly into bytecode while preserving its canonical
372/// schema graph in the resulting program. The module's `ns` declaration is
373/// loader configuration rather than executable code and is omitted here.
374pub fn compile_halc_module(
375    module: &crate::kernel::halc::HalcModule,
376    registry: &crate::kernel::NamespaceRegistry<crate::core::Value>,
377) -> Result<Program, CompileError> {
378    let previous = registry.current().name().as_str().to_owned();
379    registry.set_current(&module.namespace);
380    let forms = module
381        .forms
382        .iter()
383        .filter(|form| !top_level_operator(form, "ns"))
384        .cloned()
385        .map(synthetic_spanned_form)
386        .collect::<Vec<_>>();
387    let result = compile_spanned_forms_with_config(
388        &forms,
389        registry,
390        crate::kernel::GeneratedNamespaceConfig::defaults(),
391        false,
392    );
393    registry.set_current(previous);
394    let mut program = result?;
395    program.namespace = Some(module.namespace.clone());
396    program.schema_types = module.schemas.definition_types.clone();
397    program.function_types = module.schemas.function_types.clone();
398    program.inferred_function_types = crate::kernel::schema::infer_function_types(
399        &module.namespace,
400        &module.forms,
401        &program.function_types,
402        &program.schema_types,
403    );
404    validate_declared_function_arities(&program)?;
405    Ok(program)
406}
407
408fn validate_declared_function_arities(program: &Program) -> Result<(), CompileError> {
409    for (index, prototype) in program.functions.iter().enumerate() {
410        let Some(crate::kernel::SchemaType::Function(arities)) =
411            program.function_schema(index as u16)
412        else {
413            continue;
414        };
415        let compatible = arities.iter().any(|schema| {
416            schema.fixed.len() == prototype.arity as usize
417                && schema.rest.is_some() == prototype.variadic
418        });
419        if !compatible {
420            return Err(CompileError::new(
421                CompileErrorKind::Arity,
422                format!(
423                    "function schema for {} has no {}-argument arity{}",
424                    prototype.name.as_deref().unwrap_or("<anonymous>"),
425                    prototype.arity,
426                    if prototype.variadic {
427                        " with rest arguments"
428                    } else {
429                        ""
430                    }
431                ),
432                None,
433            ));
434        }
435    }
436    Ok(())
437}
438
439fn top_level_operator(form: &Form, expected: &str) -> bool {
440    matches!(
441        crate::core::form_without_metadata(form),
442        Form::List(items)
443            if matches!(items.first(), Some(Form::Symbol(operator)) if operator == expected)
444    )
445}
446
447pub(crate) fn synthetic_spanned_form(form: Form) -> SpannedForm {
448    let position = Position {
449        offset: 0,
450        line: 1,
451        column: 1,
452    };
453    let children = match &form {
454        Form::List(values) | Form::Vector(values) | Form::Set(values) => {
455            values.iter().cloned().map(synthetic_spanned_form).collect()
456        }
457        Form::Map(entries) => entries
458            .iter()
459            .flat_map(|(key, value)| [key.clone(), value.clone()])
460            .map(synthetic_spanned_form)
461            .collect(),
462        Form::Tagged(_, value) | Form::Metadata(_, value) => {
463            vec![synthetic_spanned_form(value.as_ref().clone())]
464        }
465        _ => Vec::new(),
466    };
467    SpannedForm {
468        form,
469        span: Span {
470            start: position,
471            end: position,
472        },
473        children,
474    }
475}
476
477/// A form paired with its span and (when the parser provided matching
478/// children) the spans of its elements.
479#[derive(Clone, Copy)]
480struct Child<'a> {
481    form: &'a Form,
482    span: &'a Span,
483    children: Option<&'a [SpannedForm]>,
484}
485
486/// One in-progress function body: code, scopes, loops, and captures.
487/// The entry function is context 0 with arity and captures 0.
488struct FnContext {
489    /// Reserved index into `Compiler::functions`.
490    proto_id: usize,
491    name: Option<String>,
492    /// Fixed parameter count; params occupy slots `0..params-1`.
493    params: u16,
494    /// Whether the function has a `& rest` parameter (occupying the slot
495    /// directly above the fixed params, below the captures).
496    variadic: bool,
497    /// Whether `std.native.Coroutine/await` may be emitted in this
498    /// function. Every function context resets this flag.
499    suspend_allowed: bool,
500    /// Whether calls to this prototype return a result promise.
501    async_function: bool,
502    /// Captured free variables in slot order (slots `params..`); each
503    /// entry carries the first-occurrence position for diagnostics.
504    captures: Vec<(String, Option<Position>)>,
505    code: Vec<Instruction>,
506    source_map: SourceMap,
507    scopes: ScopeStack,
508    loops: Vec<LoopContext>,
509    tries: Vec<TryContext>,
510    /// Finished handler table entries for this function; entry depths are
511    /// patched in after stack analysis in `finish`.
512    handlers: Vec<TryEntry>,
513    /// Whether control can reach the next emitted instruction. `recur`
514    /// clears it; the compiler emits no dead code.
515    fallthrough: bool,
516}
517
518struct Compiler {
519    /// Namespace selected when compilation began. Macro expansion may load
520    /// other modules, but global binding must remain relative to this owner.
521    namespace: String,
522    constants: Vec<Value>,
523    constant_index: HashMap<Value, u32>,
524    functions: Vec<FunctionPrototype>,
525    contexts: Vec<FnContext>,
526    /// Names this program defines (`def`/`defn`/`declare`/`defstruct`):
527    /// visible to global references compiled after their defining form
528    /// (issue #223 two-phase visibility).
529    globals: Vec<String>,
530    /// Foundation child libraries explicitly removed by the source namespace config.
531    /// This is checked before the process-wide global alias registry so an
532    /// excluded `str/` (or equivalent) cannot be resurrected by lookup.
533    excluded_foundation_libraries: HashSet<String>,
534    /// Direct runtime evaluation can materialize a global after the current
535    /// source has already been compiled. Such names use the same late-bound
536    /// `GetGlobal` instruction but are allowed through the compile-time check.
537    allow_unbound_globals: bool,
538    /// Source-level forwarding shims opted into call-site lowering with
539    /// `^{:inline target/name}`.
540    inline_globals: HashMap<String, String>,
541    /// Var metadata table indexed by `DefGlobal` operands.
542    var_metadata: Vec<std::rc::Rc<crate::lang::data::Metadata>>,
543    /// True while compiling a direct child of the top-level sequence;
544    /// `defn` and `declare` are only legal there.
545    top_level: bool,
546    next_destructure_id: u64,
547}
548
549/// The reservation placed in `functions` while a body is compiled: the
550/// prototype index, arity, and capture count are known up front, the
551/// code is filled in when the context closes.
552fn placeholder(
553    name: Option<String>,
554    arity: u16,
555    capture_count: u16,
556    variadic: bool,
557    async_function: bool,
558) -> FunctionPrototype {
559    FunctionPrototype {
560        name,
561        async_function,
562        arity,
563        variadic,
564        capture_count,
565        local_count: 0,
566        max_stack: 0,
567        code: Vec::new(),
568        source_map: SourceMap::default(),
569        handlers: Vec::new(),
570    }
571}
572
573impl Compiler {
574    fn predeclare_top_level(&mut self, forms: &[SpannedForm]) {
575        for spanned in forms {
576            let Form::List(items) = crate::core::form_without_metadata(&spanned.form) else {
577                continue;
578            };
579            let Some(Form::Symbol(operator)) = items.first() else {
580                continue;
581            };
582            if operator == "declare" {
583                for item in items.iter().skip(1) {
584                    if let Form::Symbol(name) = crate::core::form_without_metadata(item) {
585                        if !name.contains('/') {
586                            self.declare_program_global(name);
587                        }
588                    }
589                }
590                continue;
591            }
592        }
593    }
594
595    fn new(
596        excluded_foundation_libraries: HashSet<String>,
597        allow_unbound_globals: bool,
598    ) -> Compiler {
599        let mut scopes = ScopeStack::new();
600        scopes.push_scope();
601        Compiler {
602            namespace: crate::core::namespace_registry()
603                .map(|registry| registry.current().name().as_str().to_owned())
604                .unwrap_or_else(|_| "user".into()),
605            constants: Vec::new(),
606            constant_index: HashMap::new(),
607            functions: vec![placeholder(None, 0, 0, false, false)],
608            contexts: vec![FnContext {
609                proto_id: 0,
610                name: None,
611                params: 0,
612                variadic: false,
613                suspend_allowed: false,
614                async_function: false,
615                captures: Vec::new(),
616                code: Vec::new(),
617                source_map: SourceMap::default(),
618                scopes,
619                loops: Vec::new(),
620                tries: Vec::new(),
621                handlers: Vec::new(),
622                fallthrough: true,
623            }],
624            globals: Vec::new(),
625            excluded_foundation_libraries,
626            allow_unbound_globals,
627            inline_globals: HashMap::new(),
628            var_metadata: Vec::new(),
629            top_level: true,
630            next_destructure_id: 0,
631        }
632    }
633
634    fn ctx(&self) -> &FnContext {
635        self.contexts.last().expect("function context is open")
636    }
637
638    fn ctx_mut(&mut self) -> &mut FnContext {
639        self.contexts.last_mut().expect("function context is open")
640    }
641
642    /// Resolves coroutine forms by canonical Var identity so aliases and
643    /// referred names behave exactly like fully-qualified source.
644    fn is_coroutine_var(&self, name: &str, member: &str) -> bool {
645        let canonical = format!("std.native.Coroutine/{member}");
646        let legacy = format!("std.foundation.coroutine/{member}");
647        if name == canonical || name == legacy {
648            return true;
649        }
650        crate::core::namespace_registry()
651            .ok()
652            .is_some_and(|registry| {
653                let Some(source) = registry.resolve(&crate::lang::data::Symbol::parse(name)) else {
654                    return false;
655                };
656                [canonical, legacy].into_iter().any(|target| {
657                    registry
658                        .resolve(&crate::lang::data::Symbol::parse(&target))
659                        .is_some_and(|target| source.same_identity(&target))
660                })
661            })
662    }
663
664    fn is_host_call_var(&self, name: &str) -> bool {
665        let canonical = "std.native.Host/call";
666        if name == canonical {
667            return true;
668        }
669        crate::core::namespace_registry()
670            .ok()
671            .and_then(|registry| {
672                let source = registry.resolve(&crate::lang::data::Symbol::parse(name))?;
673                let target = registry.resolve(&crate::lang::data::Symbol::parse(canonical))?;
674                Some(source.same_identity(&target))
675            })
676            .unwrap_or(false)
677    }
678
679    fn compile_host_call(
680        &mut self,
681        children: &[Child<'_>],
682        span: &Span,
683    ) -> Result<(), CompileError> {
684        if children.len() != 4 {
685            return Err(CompileError::new(
686                CompileErrorKind::Arity,
687                "std.native.Host/call expects service, method, and an argument vector",
688                Some(span.start),
689            ));
690        }
691        self.compile_call_arguments(children, span)?;
692        if self.ctx().fallthrough {
693            self.emit(Instruction::HostCall, Some(span.start));
694        }
695        Ok(())
696    }
697
698    /// Pairs parsed forms with their spans. When a node's children do not
699    /// match its element count (reader macros expand to synthetic lists),
700    /// elements inherit the parent span.
701    fn children<'a>(&self, nodes: &'a [SpannedForm]) -> Vec<Child<'a>> {
702        nodes
703            .iter()
704            .map(|node| Child {
705                form: &node.form,
706                span: &node.span,
707                children: Some(&node.children),
708            })
709            .collect()
710    }
711
712    fn list_children<'a>(
713        &self,
714        elements: &'a [Form],
715        span: &'a Span,
716        children: Option<&'a [SpannedForm]>,
717    ) -> Vec<Child<'a>> {
718        let usable = children.filter(|nodes| nodes.len() == elements.len());
719        elements
720            .iter()
721            .enumerate()
722            .map(
723                |(index, form)| match usable.and_then(|nodes| nodes.get(index)) {
724                    Some(node) => Child {
725                        form: &node.form,
726                        span: &node.span,
727                        children: Some(&node.children),
728                    },
729                    None => Child {
730                        form,
731                        span,
732                        children: None,
733                    },
734                },
735            )
736            .collect()
737    }
738
739    fn emit(&mut self, instruction: Instruction, position: Option<Position>) -> usize {
740        let context = self.ctx_mut();
741        debug_assert!(context.fallthrough, "no emission after control terminates");
742        let index = context.code.len();
743        context.code.push(instruction);
744        context.source_map.record(position);
745        index
746    }
747
748    fn patch_jump(&mut self, at: usize, target: usize) {
749        let target = target as u32;
750        match &mut self.ctx_mut().code[at] {
751            Instruction::Jump(operand) | Instruction::JumpIfFalse(operand) => *operand = target,
752            other => unreachable!("patching non-jump instruction: {other:?}"),
753        }
754    }
755
756    fn constant(&mut self, value: Value, span: &Span) -> Result<(), CompileError> {
757        let index = self.constant_index_of(value, span)?;
758        self.emit(Instruction::Constant(index), Some(span.start));
759        Ok(())
760    }
761
762    fn unique_constant(&mut self, value: Value, span: &Span) -> Result<(), CompileError> {
763        if self.constants.len() >= MAX_CONSTANTS {
764            return Err(CompileError::new(
765                CompileErrorKind::Limit,
766                format!("constant pool exceeds limit of {MAX_CONSTANTS}"),
767                Some(span.start),
768            ));
769        }
770        let index = self.constants.len() as u32;
771        self.constants.push(value);
772        self.emit(Instruction::Constant(index), Some(span.start));
773        Ok(())
774    }
775
776    /// The pool index for a constant, interning it if new. Used directly
777    /// for instruction operands (global names, struct fields); `constant`
778    /// additionally emits the load.
779    fn constant_index_of(&mut self, value: Value, span: &Span) -> Result<u32, CompileError> {
780        match self.constant_index.get(&value) {
781            Some(index) => Ok(*index),
782            None => {
783                if self.constants.len() >= MAX_CONSTANTS {
784                    return Err(CompileError::new(
785                        CompileErrorKind::Limit,
786                        format!("constant pool exceeds limit of {MAX_CONSTANTS}"),
787                        Some(span.start),
788                    ));
789                }
790                let index = self.constants.len() as u32;
791                self.constants.push(value.clone());
792                self.constant_index.insert(value, index);
793                Ok(index)
794            }
795        }
796    }
797
798    fn unsupported(&self, form: &Form, span: &Span) -> CompileError {
799        let message = match form {
800            Form::List(elements) => match elements.first() {
801                Some(Form::Symbol(name)) => format!("unsupported operator: {name}"),
802                _ => format!("unsupported form: {form}"),
803            },
804            _ => format!("unsupported form: {form}"),
805        };
806        CompileError::new(CompileErrorKind::UnsupportedForm, message, Some(span.start))
807    }
808
809    /// Compiles a sequence of forms as an implicit `do`: every non-final
810    /// result is popped. Dead forms after a terminating `recur` are not
811    /// analyzed, matching the evaluator, which never reaches them.
812    fn compile_sequence(&mut self, children: &[Child<'_>], tail: bool) -> Result<(), CompileError> {
813        if children.is_empty() {
814            self.emit(Instruction::Nil, None);
815            return Ok(());
816        }
817        let top = self.top_level && self.contexts.len() == 1;
818        let last = children.len() - 1;
819        for (index, child) in children.iter().enumerate() {
820            if !self.ctx().fallthrough {
821                break;
822            }
823            self.top_level = top;
824            self.compile_form(
825                child.form,
826                child.span,
827                child.children,
828                tail && index == last,
829            )?;
830            if index != last && self.ctx().fallthrough {
831                self.emit(Instruction::Pop, Some(child.span.start));
832            }
833        }
834        Ok(())
835    }
836
837    fn compile_form(
838        &mut self,
839        form: &Form,
840        span: &Span,
841        children: Option<&[SpannedForm]>,
842        tail: bool,
843    ) -> Result<(), CompileError> {
844        let top = self.top_level;
845        self.top_level = false;
846        if !self.ctx().fallthrough {
847            // Dead code (e.g. after a nested infinite loop): not analyzed,
848            // matching the evaluator, which never reaches it.
849            return Ok(());
850        }
851        if let Some(expanded) =
852            destructure::expand(form, &mut self.next_destructure_id).map_err(|message| {
853                CompileError::new(CompileErrorKind::UnsupportedForm, message, Some(span.start))
854            })?
855        {
856            self.top_level = top;
857            return self.compile_form(&expanded, span, None, tail);
858        }
859        if let Form::List(values) = crate::core::form_without_metadata(form) {
860            let protected = matches!(
861                values.first(),
862                Some(Form::Symbol(name))
863                    if name == "quote" || name == "syntax-quote" || name == "comment"
864            );
865            if !protected {
866                let expanded = crate::core::vm_macroexpand(form).map_err(|message| {
867                    CompileError::new(CompileErrorKind::UnsupportedForm, message, Some(span.start))
868                })?;
869                if expanded != *form {
870                    self.top_level = top;
871                    return self.compile_form(&expanded, span, None, tail);
872                }
873            }
874        }
875        match form {
876            Form::Nil => {
877                self.emit(Instruction::Nil, Some(span.start));
878                Ok(())
879            }
880            Form::Bool(true) => {
881                self.emit(Instruction::True, Some(span.start));
882                Ok(())
883            }
884            Form::Bool(false) => {
885                self.emit(Instruction::False, Some(span.start));
886                Ok(())
887            }
888            Form::Number(value) => self.constant(Value::Number(*value), span),
889            Form::Float(value) => {
890                let value = crate::numeric::finite_float(*value).map_err(|error| {
891                    CompileError::new(CompileErrorKind::Parse, error, Some(span.start))
892                })?;
893                self.constant(Value::Float(value), span)
894            }
895            Form::String(value) => self.constant(Value::String(value.clone()), span),
896            Form::Keyword(value) => self.constant(Value::Keyword(value.clone().into()), span),
897            Form::Character(value) => self.constant(Value::Character(*value), span),
898            Form::BigInteger(value) => {
899                self.constant(crate::numeric::compact_integer(value.clone()), span)
900            }
901            Form::Regex(value) => self.constant(Value::Regex(value.clone()), span),
902            Form::Tagged(tag, value) if tag == "uuid" => {
903                let Form::String(text) = value.as_ref() else {
904                    return Err(CompileError::new(
905                        CompileErrorKind::UnsupportedForm,
906                        "#uuid expects a UUID string literal",
907                        Some(span.start),
908                    ));
909                };
910                let value = crate::core::uuid_tag_value(Value::String(text.clone())).map_err(
911                    |message| CompileError::new(CompileErrorKind::Parse, message, Some(span.start)),
912                )?;
913                self.constant(value, span)
914            }
915            Form::Tagged(tag, value) if tag == "arr" => {
916                let Form::Vector(values) = value.as_ref() else {
917                    return Err(CompileError::new(
918                        CompileErrorKind::UnsupportedForm,
919                        "#arr expects a vector literal",
920                        Some(span.start),
921                    ));
922                };
923                if values.len() > MAX_PRIMITIVE_ARGUMENTS {
924                    return Err(CompileError::new(
925                        CompileErrorKind::Limit,
926                        format!(
927                            "#arr supports at most {MAX_PRIMITIVE_ARGUMENTS} elements"
928                        ),
929                        Some(span.start),
930                    ));
931                }
932                self.compile_collection_values(values.iter(), span)?;
933                let target = self.name_constant("std.native.Arr/new", span)?;
934                self.emit(
935                    Instruction::IntrinsicCall {
936                        target,
937                        argc: values.len() as u8,
938                    },
939                    Some(span.start),
940                );
941                Ok(())
942            }
943            Form::Tagged(tag, value) if tag == "obj" => {
944                let Form::Map(entries) = value.as_ref() else {
945                    return Err(CompileError::new(
946                        CompileErrorKind::UnsupportedForm,
947                        "#obj expects a map literal",
948                        Some(span.start),
949                    ));
950                };
951                let arguments = entries.len().saturating_mul(2);
952                if arguments > MAX_PRIMITIVE_ARGUMENTS {
953                    return Err(CompileError::new(
954                        CompileErrorKind::Limit,
955                        format!(
956                            "#obj supports at most {} entries",
957                            MAX_PRIMITIVE_ARGUMENTS / 2
958                        ),
959                        Some(span.start),
960                    ));
961                }
962                for (key, value) in entries {
963                    self.compile_form(key, span, None, false)?;
964                    self.compile_form(value, span, None, false)?;
965                }
966                let target = self.name_constant("std.native.Obj/new", span)?;
967                self.emit(
968                    Instruction::IntrinsicCall {
969                        target,
970                        argc: arguments as u8,
971                    },
972                    Some(span.start),
973                );
974                Ok(())
975            }
976            // Collection identity is observable even when language equality
977            // is structural across concrete sequential/map/set types.  Do
978            // not intern collection literals in the Value-keyed constant
979            // pool: `[]` may otherwise alias an earlier `()`, and the HTA
980            // constant codec canonicalizes collection values. Literal
981            // vectors use a non-interned constant; dynamic collections are
982            // built in bytecode so concrete type and order remain intact.
983            Form::Vector(values) => {
984                if values.iter().all(literal_collection_form) {
985                    let value = crate::core::form_to_value(form).map_err(|message| {
986                        CompileError::new(
987                            CompileErrorKind::UnsupportedForm,
988                            message,
989                            Some(span.start),
990                        )
991                    })?;
992                    return self.unique_constant(value, span);
993                }
994                self.compile_collection_values(values.iter(), span)?;
995                self.emit(
996                    Instruction::BuildVector(self.collection_count(values.len(), span)?),
997                    Some(span.start),
998                );
999                Ok(())
1000            }
1001            Form::Map(entries) => {
1002                // HTA canonicalization sorts hash-map keys. Map literals stay
1003                // out of the HBC constant pool so runtime construction remains
1004                // identical across Rust and Truffle runtimes.
1005                if entries.len() > usize::from(u16::MAX) {
1006                    return Err(CompileError::new(
1007                        CompileErrorKind::Limit,
1008                        "map literal exceeds 65535 entries",
1009                        Some(span.start),
1010                    ));
1011                }
1012                for (key, value) in entries {
1013                    self.compile_form(key, span, None, false)?;
1014                    self.compile_form(value, span, None, false)?;
1015                }
1016                self.emit(
1017                    Instruction::BuildMap(entries.len() as u16),
1018                    Some(span.start),
1019                );
1020                Ok(())
1021            }
1022            Form::Set(values) => {
1023                self.compile_collection_values(values.iter(), span)?;
1024                self.emit(
1025                    Instruction::BuildSet(self.collection_count(values.len(), span)?),
1026                    Some(span.start),
1027                );
1028                Ok(())
1029            }
1030            Form::Metadata(_, value) => {
1031                // Metadata wraps the form without changing its lexical or
1032                // top-level position.  Restore the position after the
1033                // dispatch prologue above; otherwise a top-level `^{...}
1034                // (defn ...)` is mistaken for a nested definition.
1035                self.top_level = top;
1036                self.compile_form(value, span, None, tail)
1037            }
1038            Form::Symbol(name) => match self.ctx().scopes.resolve(name) {
1039                Some(slot) => {
1040                    self.emit(Instruction::LoadLocal(slot), Some(span.start));
1041                    Ok(())
1042                }
1043                None if self.visible_global(name) => self.emit_get_global(name, span),
1044                None if IntrinsicOp::from_symbol(name).is_some() => {
1045                    let target = self.name_constant(name, span)?;
1046                    self.emit(Instruction::IntrinsicValue(target), Some(span.start));
1047                    Ok(())
1048                }
1049                None if self.visible_bytecode_callable(name) => {
1050                    let index = self.name_constant(name, span)?;
1051                    self.emit(Instruction::BuiltinValue(index), Some(span.start));
1052                    Ok(())
1053                }
1054                None if self.visible_namespace(name) => {
1055                    let index = self.name_constant(name, span)?;
1056                    self.emit(Instruction::NamespaceValue(index), Some(span.start));
1057                    Ok(())
1058                }
1059                None => Err(CompileError::new(
1060                    CompileErrorKind::UnboundSymbol,
1061                    format!("unbound symbol: {name}"),
1062                    Some(span.start),
1063                )),
1064            },
1065            Form::List(elements) if elements.is_empty() => {
1066                self.constant(Value::List(PList::new()), span)
1067            }
1068            Form::List(elements) => {
1069                let children = self.list_children(elements, span, children);
1070                match &elements[0] {
1071                    Form::Symbol(name) if self.is_coroutine_var(name, "await") => {
1072                        self.compile_await(&children, span)
1073                    }
1074                    Form::Symbol(name) if self.is_coroutine_var(name, "yield") => {
1075                        self.compile_yield(&children, span)
1076                    }
1077                    Form::Symbol(name) if self.is_host_call_var(name) => {
1078                        self.compile_host_call(&children, span)
1079                    }
1080                    Form::Symbol(name) if name == "." => {
1081                        if elements.len() != 3 {
1082                            return Err(CompileError::new(
1083                                CompileErrorKind::UnsupportedForm,
1084                                "dot expects a receiver and method",
1085                                Some(span.start),
1086                            ));
1087                        }
1088                        let Form::List(method_form) = &elements[2] else {
1089                            return Err(CompileError::new(
1090                                CompileErrorKind::UnsupportedForm,
1091                                "dot call expects a method list",
1092                                Some(span.start),
1093                            ));
1094                        };
1095                        let Some(Form::Symbol(method_name)) = method_form.first() else {
1096                            return Err(CompileError::new(
1097                                CompileErrorKind::UnsupportedForm,
1098                                "dot method must be a symbol",
1099                                Some(span.start),
1100                            ));
1101                        };
1102                        self.compile_form(&elements[1], span, None, false)?;
1103                        for argument in &method_form[1..] {
1104                            self.compile_form(argument, span, None, false)?;
1105                        }
1106                        let method = self.name_constant(method_name, span)?;
1107                        self.emit(
1108                            Instruction::DotCall {
1109                                method,
1110                                argc: (method_form.len() - 1) as u8,
1111                            },
1112                            Some(span.start),
1113                        );
1114                        Ok(())
1115                    }
1116                    Form::Symbol(name) if name == "if" => self.compile_if(&children, span, tail),
1117                    Form::Symbol(name) if name == "and" => self.compile_and(&children, span, tail),
1118                    Form::Symbol(name) if name == "or" => self.compile_or(&children, span, tail),
1119                    Form::Symbol(name) if name == "cond" => {
1120                        self.compile_cond(&children, span, tail)
1121                    }
1122                    Form::Symbol(name) if name == "quote" => self.compile_quote(&children, span),
1123                    Form::Symbol(name) if name == "comment" => {
1124                        self.emit(Instruction::Nil, Some(span.start));
1125                        Ok(())
1126                    }
1127                    Form::Symbol(name) if name == "syntax-quote" => {
1128                        self.compile_syntax_quote(&children, span)
1129                    }
1130                    Form::Symbol(name) if name == "do" => {
1131                        // A top-level `do` is transparent: its statements
1132                        // keep top-level position, so `defn` lowering works
1133                        // inside `(do (defn ...) ...)`.
1134                        self.top_level = top;
1135                        self.compile_sequence(&children[1..], tail)
1136                    }
1137                    Form::Symbol(name) if name == "let" => self.compile_let(&children, span, tail),
1138                    Form::Symbol(name) if name == "loop" => {
1139                        self.compile_loop(&children, span, tail)
1140                    }
1141                    Form::Symbol(name) if name == "recur" => {
1142                        self.compile_recur(&children, span, tail)
1143                    }
1144                    Form::Symbol(name) if name == "fn" => self.compile_fn_form(&children, span),
1145                    Form::Symbol(name) if name == "def" => self.compile_def(&children, span),
1146                    Form::Symbol(name) if name == "defn" => self.compile_defn(&children, span, top),
1147                    Form::Symbol(name) if name == "defmacro" => {
1148                        self.compile_defmacro(&children, span, top)
1149                    }
1150                    Form::Symbol(name) if name == "declare" => {
1151                        self.compile_declare(&children, span, top)
1152                    }
1153                    Form::Symbol(name) if name == "var" => self.compile_var(&children, span),
1154                    Form::Symbol(name) if name == "set!" => self.compile_set(&children, span),
1155                    Form::Symbol(name)
1156                        if name == "require" || (matches!(name.as_str(), "ns" | "ns+") && !top) =>
1157                    {
1158                        let value = crate::core::form_to_value(form).map_err(|message| {
1159                            CompileError::new(
1160                                CompileErrorKind::UnsupportedForm,
1161                                message,
1162                                Some(span.start),
1163                            )
1164                        })?;
1165                        let index = self.constant_index_of(value, span)?;
1166                        self.emit(Instruction::NamespaceOperation(index), Some(span.start));
1167                        Ok(())
1168                    }
1169                    Form::Symbol(name) if name == "ns" || name == "ns+" => {
1170                        if !top {
1171                            return Err(self.unsupported(form, span));
1172                        }
1173                        self.emit(Instruction::Nil, Some(span.start));
1174                        Ok(())
1175                    }
1176                    Form::Symbol(name) if name == "field" => self.compile_field(&children, span),
1177                    Form::Symbol(name) if name == "instance?" => {
1178                        self.compile_instance_of(&children, span)
1179                    }
1180                    Form::Symbol(name) if name == "try" => self.compile_try(&children, span, tail),
1181                    Form::Symbol(name) if name == "__dynamic-bind" => {
1182                        self.compile_dynamic_binding(&children, span, true)
1183                    }
1184                    Form::Symbol(name) if name == "__dynamic-unbind" => {
1185                        self.compile_dynamic_binding(&children, span, false)
1186                    }
1187                    Form::Symbol(name) if name == "throw" => self.compile_throw(&children, span),
1188                    Form::Symbol(name)
1189                        if UNSUPPORTED_OPERATORS.contains(&name.as_str())
1190                            && self.ctx().scopes.resolve(name).is_none()
1191                            && !self.visible_global(name) =>
1192                    {
1193                        Err(self.unsupported(form, span))
1194                    }
1195                    Form::Symbol(name)
1196                        if (name.starts_with("std.native.Arr/")
1197                            || name.starts_with("std.native.Obj/"))
1198                            && IntrinsicOp::from_symbol(name).is_some() =>
1199                    {
1200                        self.compile_primitive(
1201                            &children,
1202                            span,
1203                            IntrinsicOp::from_symbol(name).expect("intrinsic was checked"),
1204                        )
1205                    }
1206                    // Precedence mirrors the evaluator (core.rs operator
1207                    // dispatch): a bound var wins over the structural
1208                    // builtin arms, so a program-declared or registry
1209                    // global compiles to GetGlobal+Call even when it names
1210                    // a primitive; only otherwise-unbound operator names
1211                    // lower to intrinsic instructions (issue #223).
1212                    Form::Symbol(name)
1213                        if self.ctx().scopes.resolve(name).is_some()
1214                            || self.visible_global(name) =>
1215                    {
1216                        self.compile_named_call(name, &children, span)
1217                    }
1218                    Form::Symbol(name) => match IntrinsicOp::from_symbol(name) {
1219                        Some(op) => self.compile_primitive(&children, span, op),
1220                        None => self.compile_named_call(name, &children, span),
1221                    },
1222                    _ => self.compile_expression_call(&children, span),
1223                }
1224            }
1225            _ => Err(self.unsupported(form, span)),
1226        }
1227    }
1228
1229    fn collection_count(&self, count: usize, span: &Span) -> Result<u16, CompileError> {
1230        u16::try_from(count).map_err(|_| {
1231            CompileError::new(
1232                CompileErrorKind::Limit,
1233                "collection literal exceeds 65535 items",
1234                Some(span.start),
1235            )
1236        })
1237    }
1238
1239    fn compile_collection_values<'a>(
1240        &mut self,
1241        values: impl Iterator<Item = &'a Form>,
1242        span: &Span,
1243    ) -> Result<(), CompileError> {
1244        for value in values {
1245            self.compile_form(value, span, None, false)?;
1246        }
1247        Ok(())
1248    }
1249
1250    /// Compiles the argument forms of a call (callee already compiled).
1251    fn compile_call_arguments(
1252        &mut self,
1253        children: &[Child<'_>],
1254        span: &Span,
1255    ) -> Result<(), CompileError> {
1256        let argc = children.len() - 1;
1257        if argc > MAX_PRIMITIVE_ARGUMENTS {
1258            return Err(CompileError::new(
1259                CompileErrorKind::Limit,
1260                format!("calls support at most {MAX_PRIMITIVE_ARGUMENTS} arguments"),
1261                Some(span.start),
1262            ));
1263        }
1264        for argument in &children[1..] {
1265            self.compile_form(argument.form, argument.span, argument.children, false)?;
1266        }
1267        Ok(())
1268    }
1269
1270    fn compile_if(
1271        &mut self,
1272        children: &[Child<'_>],
1273        span: &Span,
1274        tail: bool,
1275    ) -> Result<(), CompileError> {
1276        // The condition is never a tail position; the branches inherit the
1277        // `if`'s own tail context.
1278        if children.len() != 3 && children.len() != 4 {
1279            return Err(CompileError::new(
1280                CompileErrorKind::Arity,
1281                "if expects 2 or 3 arguments",
1282                Some(span.start),
1283            ));
1284        }
1285        let condition = &children[1];
1286        self.compile_form(condition.form, condition.span, condition.children, false)?;
1287        if !self.ctx().fallthrough {
1288            // The condition cannot produce a value (e.g. an infinite inner
1289            // loop); the branches are dead code.
1290            return Ok(());
1291        }
1292        let jump_else = self.emit(Instruction::JumpIfFalse(0), Some(condition.span.start));
1293        let then = &children[2];
1294        self.compile_form(then.form, then.span, then.children, tail)?;
1295        let then_fell = self.ctx().fallthrough;
1296        let jump_end = if then_fell {
1297            Some(self.emit(Instruction::Jump(0), Some(then.span.start)))
1298        } else {
1299            None
1300        };
1301        // The else branch starts fresh at its label.
1302        self.ctx_mut().fallthrough = true;
1303        let else_target = self.ctx().code.len();
1304        if let Some(else_form) = children.get(3) {
1305            self.compile_form(else_form.form, else_form.span, else_form.children, tail)?;
1306        } else {
1307            self.emit(Instruction::Nil, Some(span.start));
1308        }
1309        let else_fell = self.ctx().fallthrough;
1310        let end = self.ctx().code.len();
1311        self.patch_jump(jump_else, else_target);
1312        if let Some(jump_end) = jump_end {
1313            self.patch_jump(jump_end, end);
1314        }
1315        self.ctx_mut().fallthrough = then_fell || else_fell;
1316        Ok(())
1317    }
1318
1319    fn compile_and(
1320        &mut self,
1321        children: &[Child<'_>],
1322        span: &Span,
1323        tail: bool,
1324    ) -> Result<(), CompileError> {
1325        if children.len() == 1 {
1326            self.emit(Instruction::True, Some(span.start));
1327            return Ok(());
1328        }
1329        let mut false_jumps = Vec::new();
1330        for child in &children[1..children.len() - 1] {
1331            self.compile_form(child.form, child.span, child.children, false)?;
1332            if !self.ctx().fallthrough {
1333                break;
1334            }
1335            self.emit(Instruction::Dup, Some(child.span.start));
1336            false_jumps.push(self.emit(Instruction::JumpIfFalse(0), Some(child.span.start)));
1337            self.emit(Instruction::Pop, Some(child.span.start));
1338        }
1339        if self.ctx().fallthrough {
1340            let last = children.last().expect("and has an argument");
1341            self.compile_form(last.form, last.span, last.children, tail)?;
1342        }
1343        let last_fell = self.ctx().fallthrough;
1344        let end = self.ctx().code.len();
1345        let short_circuits = !false_jumps.is_empty();
1346        for jump in false_jumps {
1347            self.patch_jump(jump, end);
1348        }
1349        self.ctx_mut().fallthrough = last_fell || short_circuits;
1350        Ok(())
1351    }
1352
1353    fn compile_or(
1354        &mut self,
1355        children: &[Child<'_>],
1356        span: &Span,
1357        tail: bool,
1358    ) -> Result<(), CompileError> {
1359        if children.len() == 1 {
1360            self.emit(Instruction::Nil, Some(span.start));
1361            return Ok(());
1362        }
1363        let mut end_jumps = Vec::new();
1364        for child in &children[1..children.len() - 1] {
1365            self.compile_form(child.form, child.span, child.children, false)?;
1366            if !self.ctx().fallthrough {
1367                break;
1368            }
1369            self.emit(Instruction::Dup, Some(child.span.start));
1370            let false_jump = self.emit(Instruction::JumpIfFalse(0), Some(child.span.start));
1371            end_jumps.push(self.emit(Instruction::Jump(0), Some(child.span.start)));
1372            let next = self.ctx().code.len();
1373            self.patch_jump(false_jump, next);
1374            self.emit(Instruction::Pop, Some(child.span.start));
1375        }
1376        if self.ctx().fallthrough {
1377            let last = children.last().expect("or has an argument");
1378            self.compile_form(last.form, last.span, last.children, tail)?;
1379        }
1380        let last_fell = self.ctx().fallthrough;
1381        let end = self.ctx().code.len();
1382        let short_circuits = !end_jumps.is_empty();
1383        for jump in end_jumps {
1384            self.patch_jump(jump, end);
1385        }
1386        self.ctx_mut().fallthrough = last_fell || short_circuits;
1387        Ok(())
1388    }
1389
1390    fn compile_cond(
1391        &mut self,
1392        children: &[Child<'_>],
1393        span: &Span,
1394        tail: bool,
1395    ) -> Result<(), CompileError> {
1396        let clauses = &children[1..];
1397        if clauses.len() % 2 != 0 {
1398            return Err(CompileError::new(
1399                CompileErrorKind::Arity,
1400                "cond expects test/expression pairs",
1401                Some(span.start),
1402            ));
1403        }
1404        if clauses.is_empty() {
1405            self.emit(Instruction::Nil, Some(span.start));
1406            return Ok(());
1407        }
1408        let mut end_jumps = Vec::new();
1409        for pair in clauses.chunks(2) {
1410            self.compile_form(pair[0].form, pair[0].span, pair[0].children, false)?;
1411            if !self.ctx().fallthrough {
1412                let end = self.ctx().code.len();
1413                for jump in end_jumps {
1414                    self.patch_jump(jump, end);
1415                }
1416                return Ok(());
1417            }
1418            let next_jump = self.emit(Instruction::JumpIfFalse(0), Some(pair[0].span.start));
1419            self.compile_form(pair[1].form, pair[1].span, pair[1].children, tail)?;
1420            if self.ctx().fallthrough {
1421                end_jumps.push(self.emit(Instruction::Jump(0), Some(pair[1].span.start)));
1422            }
1423            // A false test reaches the next clause even when the preceding
1424            // expression terminated with recur, throw, or return.
1425            self.ctx_mut().fallthrough = true;
1426            let next = self.ctx().code.len();
1427            self.patch_jump(next_jump, next);
1428        }
1429        self.emit(Instruction::Nil, Some(span.start));
1430        let end = self.ctx().code.len();
1431        for jump in end_jumps {
1432            self.patch_jump(jump, end);
1433        }
1434        Ok(())
1435    }
1436
1437    fn compile_dynamic_binding(
1438        &mut self,
1439        children: &[Child<'_>],
1440        span: &Span,
1441        bind: bool,
1442    ) -> Result<(), CompileError> {
1443        let expected = if bind { 3 } else { 2 };
1444        if children.len() != expected {
1445            return Err(CompileError::new(
1446                CompileErrorKind::Arity,
1447                if bind {
1448                    "dynamic bind expects a Var and value"
1449                } else {
1450                    "dynamic unbind expects a Var"
1451                },
1452                Some(span.start),
1453            ));
1454        }
1455        let Form::Symbol(name) = children[1].form else {
1456            return Err(CompileError::new(
1457                CompileErrorKind::UnsupportedForm,
1458                "dynamic binding target must be a symbol",
1459                Some(children[1].span.start),
1460            ));
1461        };
1462        let name = self.global_name_constant(name, children[1].span)?;
1463        if bind {
1464            self.compile_form(
1465                children[2].form,
1466                children[2].span,
1467                children[2].children,
1468                false,
1469            )?;
1470            if self.ctx().fallthrough {
1471                self.emit(Instruction::DynamicBind(name), Some(span.start));
1472            }
1473        } else {
1474            self.emit(Instruction::DynamicUnbind(name), Some(span.start));
1475        }
1476        Ok(())
1477    }
1478
1479    /// Compiles `let`-style ordered bindings into fresh slots, returns the
1480    /// bound slots, and leaves the scope open for the body.
1481    fn compile_bindings(
1482        &mut self,
1483        children: &[Child<'_>],
1484        form_name: &str,
1485    ) -> Result<Vec<u16>, CompileError> {
1486        let bindings = &children[1];
1487        let pairs: &[Form] = match bindings.form {
1488            Form::List(values) | Form::Vector(values) => values,
1489            _ => {
1490                return Err(CompileError::new(
1491                    CompileErrorKind::Arity,
1492                    format!("{form_name} expects a binding list or vector"),
1493                    Some(bindings.span.start),
1494                ))
1495            }
1496        };
1497        if pairs.len() % 2 != 0 {
1498            return Err(CompileError::new(
1499                CompileErrorKind::Arity,
1500                format!("{form_name} bindings require name/value pairs"),
1501                Some(bindings.span.start),
1502            ));
1503        }
1504        // Binding-pair children keep their own spans when available.
1505        let pair_children = self.list_children(pairs, bindings.span, bindings.children);
1506        let mut slots = Vec::with_capacity(pairs.len() / 2);
1507        for pair in pair_children.chunks(2) {
1508            let (name, initializer) = (&pair[0], &pair[1]);
1509            // Binding names are structural: validate before compiling the
1510            // initializer so destructuring reports on the name.
1511            let Form::Symbol(symbol) = name.form else {
1512                return Err(CompileError::new(
1513                    CompileErrorKind::UnsupportedForm,
1514                    format!("{form_name} destructuring is not supported"),
1515                    Some(name.span.start),
1516                ));
1517            };
1518            self.compile_form(
1519                initializer.form,
1520                initializer.span,
1521                initializer.children,
1522                false,
1523            )?;
1524            if !self.ctx().fallthrough {
1525                return Ok(slots);
1526            }
1527            let slot = self.ctx_mut().scopes.declare(symbol).map_err(|error| {
1528                CompileError::new(error.kind(), error.message(), Some(name.span.start))
1529            })?;
1530            self.emit(Instruction::StoreLocal(slot), Some(name.span.start));
1531            slots.push(slot);
1532        }
1533        Ok(slots)
1534    }
1535
1536    fn compile_let(
1537        &mut self,
1538        children: &[Child<'_>],
1539        span: &Span,
1540        tail: bool,
1541    ) -> Result<(), CompileError> {
1542        if children.len() < 3 {
1543            return Err(CompileError::new(
1544                CompileErrorKind::Arity,
1545                "let expects bindings and a body",
1546                Some(span.start),
1547            ));
1548        }
1549        self.ctx_mut().scopes.push_scope();
1550        let result = self
1551            .compile_bindings(children, "let")
1552            .and_then(|_| self.compile_sequence(&children[2..], tail));
1553        self.ctx_mut().scopes.pop_scope();
1554        result
1555    }
1556
1557    fn compile_loop(
1558        &mut self,
1559        children: &[Child<'_>],
1560        span: &Span,
1561        _tail: bool,
1562    ) -> Result<(), CompileError> {
1563        if children.len() < 3 {
1564            return Err(CompileError::new(
1565                CompileErrorKind::Arity,
1566                "loop expects bindings and a body",
1567                Some(span.start),
1568            ));
1569        }
1570        self.ctx_mut().scopes.push_scope();
1571        let result = self.compile_bindings(children, "loop").and_then(|slots| {
1572            let header = self.ctx().code.len();
1573            self.ctx_mut().loops.push(LoopContext { header, slots });
1574            // Multiple body forms sequence like `do`; the last one is
1575            // the loop's tail (recur) position.
1576            let result = self.compile_sequence(&children[2..], true);
1577            self.ctx_mut().loops.pop();
1578            result
1579        });
1580        self.ctx_mut().scopes.pop_scope();
1581        result
1582    }
1583
1584    fn finish(mut self) -> Result<Program, CompileError> {
1585        if self.ctx().fallthrough {
1586            self.emit(Instruction::Return, None);
1587        }
1588        self.close_context();
1589        let mut program = Program {
1590            namespace: None,
1591            var_metadata: self.var_metadata,
1592            schema_types: HashMap::new(),
1593            function_types: HashMap::new(),
1594            inferred_function_types: HashMap::new(),
1595            constants: self.constants,
1596            functions: self.functions,
1597            entry: 0,
1598        };
1599        // The shared analysis computes each operand-stack high-water mark;
1600        // full validation then runs over the whole program before it is
1601        // returned. Handler entry depths are patched from the same pass.
1602        for index in 0..program.functions.len() {
1603            let heights = stack_heights(&program, &program.functions[index])
1604                .map_err(|error| internal(error.to_string()))?;
1605            program.functions[index].max_stack = heights.iter().copied().max().unwrap_or(0);
1606            for entry_index in 0..program.functions[index].handlers.len() {
1607                let start = program.functions[index].handlers[entry_index].start as usize;
1608                program.functions[index].handlers[entry_index].depth = heights[start];
1609            }
1610        }
1611        validate::validate(&program).map_err(|error| internal(error.to_string()))?;
1612        Ok(program)
1613    }
1614}
1615
1616fn literal_collection_form(form: &Form) -> bool {
1617    match form {
1618        Form::Nil
1619        | Form::Bool(_)
1620        | Form::Number(_)
1621        | Form::Float(_)
1622        | Form::String(_)
1623        | Form::Keyword(_)
1624        | Form::Character(_)
1625        | Form::BigInteger(_)
1626        | Form::Regex(_) => true,
1627        Form::Vector(values) | Form::Set(values) => values.iter().all(literal_collection_form),
1628        Form::Map(entries) => entries
1629            .iter()
1630            .all(|(key, value)| literal_collection_form(key) && literal_collection_form(value)),
1631        _ => false,
1632    }
1633}
1634
1635fn constant_form(form: &Form) -> bool {
1636    match form {
1637        Form::Nil
1638        | Form::Bool(_)
1639        | Form::Number(_)
1640        | Form::Float(_)
1641        | Form::BigInteger(_)
1642        | Form::Character(_)
1643        | Form::Regex(_)
1644        | Form::Keyword(_)
1645        | Form::String(_) => true,
1646        Form::Tagged(_, value) | Form::Metadata(_, value) => constant_form(value),
1647        Form::Vector(values) | Form::Set(values) => values.iter().all(constant_form),
1648        Form::Map(entries) => entries
1649            .iter()
1650            .all(|(key, value)| constant_form(key) && constant_form(value)),
1651        Form::Symbol(_) | Form::List(_) => false,
1652    }
1653}
1654
1655fn unquote_argument(form: &Form, operator: &str) -> Option<Result<Form, String>> {
1656    let Form::List(parts) = crate::core::form_without_metadata(form) else {
1657        return None;
1658    };
1659    if !matches!(parts.first(), Some(Form::Symbol(name)) if name == operator) {
1660        return None;
1661    }
1662    Some(if parts.len() == 2 {
1663        Ok(parts[1].clone())
1664    } else {
1665        Err(format!("{operator} expects one argument"))
1666    })
1667}
1668
1669fn internal(message: String) -> CompileError {
1670    CompileError::new(CompileErrorKind::Internal, message, None)
1671}
1672
1673#[cfg(test)]
1674#[path = "compiler/tests.rs"]
1675mod tests;