Skip to main content

candela/compiler/
compiler.rs

1use crate::cold_path;
2use crate::compiler::compiler_data::InstrSrc;
3use crate::compiler::compiler_data::Source;
4use crate::compiler::compiler_errors::error_cannot_find_dynlib_symbol;
5use crate::compiler::compiler_errors::error_cannot_load_dynlib;
6use crate::compiler::compiler_errors::error_cannot_push_type_to_array;
7use crate::compiler::compiler_errors::error_cannot_read_file;
8use crate::compiler::compiler_errors::error_conditional_expression_without_else;
9use crate::compiler::compiler_errors::error_division_by_zero;
10use crate::compiler::compiler_errors::error_duplicate_map_key;
11use crate::compiler::compiler_errors::error_invalid_index_type;
12use crate::compiler::compiler_errors::error_invalid_type;
13use crate::compiler::compiler_errors::error_map_diff_types;
14use crate::compiler::compiler_errors::error_not_literal_map_key;
15use crate::compiler::compiler_errors::error_range_invalid_type;
16use crate::compiler::compiler_errors::error_type_not_indexable;
17use crate::compiler::compiler_errors::error_unknown_namespace;
18use crate::data::NULL;
19use crate::errors::BLUE;
20use crate::errors::BOLD;
21use crate::errors::RED;
22use crate::errors::RESET;
23use crate::instr::LibFunc;
24use crate::parser;
25use crate::rt::TargetOs;
26use crate::rt::resolve_library_filename;
27use crate::vm::Pool;
28use crate::{data::Data, instr::Instr};
29use compiler_data::Ctx;
30use compiler_data::DynamicLibFn;
31use compiler_data::Dynamiclib;
32use compiler_data::EnumType;
33use compiler_data::EnumVariant;
34use compiler_data::FnSignature;
35use compiler_data::Function;
36use compiler_data::HostFnSig;
37use compiler_data::Pools;
38use compiler_data::State;
39use compiler_data::Struct;
40use compiler_data::Variable;
41use expr::Expr;
42use expr::Span;
43use expr::code_modifies_variable;
44use functions::handle_functions;
45use methods::handle_method_calls;
46use registers::move_reg_to_reg;
47use registers::move_to_id;
48use rustc_hash::FxHashMap;
49use rustc_hash::FxHashSet;
50use smol_strc::SmolStr;
51use smol_strc::ToSmolStr;
52use std::collections::HashMap;
53use std::hash::BuildHasherDefault;
54use std::hint::unreachable_unchecked;
55use std::path::{Path, PathBuf};
56use std::rc::Rc;
57use type_system::DataType;
58use type_system::TypeExpr;
59use type_system::check_if_returns_void;
60use type_system::collect_direct_fn_calls;
61use type_system::struct_field_type_matches;
62
63#[cfg(not(target_arch = "wasm32"))]
64use libloading::Library;
65
66#[cfg(target_arch = "wasm32")]
67use crate::errors::wasm_error;
68pub mod compiler_data;
69mod compiler_errors;
70pub mod type_system;
71
72pub mod expr;
73
74#[path = "functions/functions.rs"]
75mod functions;
76#[path = "functions/methods.rs"]
77mod methods;
78
79mod registers;
80
81pub trait UnwrapId {
82    fn unwrap_id(self) -> u16;
83}
84
85impl UnwrapId for Option<u16> {
86    #[inline(always)]
87    fn unwrap_id(self) -> u16 {
88        debug_assert!(self.is_some());
89        unsafe { self.unwrap_unchecked() }
90    }
91}
92
93/// Fuses the last comparison instruction into a jump instruction (jumps when condition is false)
94fn add_cmp_false(condition_id: u16, len: &mut u16, output: &mut Vec<Instr>, jmp_backwards: bool) {
95    if output.is_empty() {
96        return output.push(Instr::IsFalseJmp(condition_id, *len));
97    }
98    *output.last_mut().unwrap() = match *output.last().unwrap() {
99        Instr::InfFloat(o1, o2, o3) if o3 == condition_id => Instr::SupEqFloatJmp(o1, o2, *len),
100        Instr::InfInt(o1, o2, o3) if o3 == condition_id => Instr::SupEqIntJmp(o1, o2, *len),
101        Instr::InfEqFloat(o1, o2, o3) if o3 == condition_id => Instr::SupFloatJmp(o1, o2, *len),
102        Instr::InfEqInt(o1, o2, o3) if o3 == condition_id => Instr::SupIntJmp(o1, o2, *len),
103        Instr::SupFloat(o1, o2, o3) if o3 == condition_id => Instr::InfEqFloatJmp(o1, o2, *len),
104        Instr::SupInt(o1, o2, o3) if o3 == condition_id => Instr::InfEqIntJmp(o1, o2, *len),
105        Instr::SupEqFloat(o1, o2, o3) if o3 == condition_id => Instr::InfFloatJmp(o1, o2, *len),
106        Instr::SupEqInt(o1, o2, o3) if o3 == condition_id => Instr::InfIntJmp(o1, o2, *len),
107        Instr::Eq(o1, o2, o3) if o3 == condition_id => Instr::NotEqJmp(o1, o2, *len),
108        Instr::ObjEq(o1, o2, o3) if o3 == condition_id => Instr::ObjNotEqJmp(o1, o2, *len),
109        Instr::StrEq(o1, o2, o3) if o3 == condition_id => Instr::StrNotEqJmp(o1, o2, *len),
110        Instr::NotEq(o1, o2, o3) if o3 == condition_id => Instr::EqJmp(o1, o2, *len),
111        Instr::ObjNotEq(o1, o2, o3) if o3 == condition_id => Instr::ObjEqJmp(o1, o2, *len),
112        Instr::StrNotEq(o1, o2, o3) if o3 == condition_id => Instr::StrEqJmp(o1, o2, *len),
113        _ => {
114            output.push(Instr::IsFalseJmp(condition_id, *len));
115            return;
116        }
117    };
118    if jmp_backwards {
119        *len -= 1;
120    }
121}
122
123/// Fuses the last comparison instruction into a jump instruction (jumps when condition is true)
124#[inline(always)]
125fn add_cmp_true(condition_id: u16, output: &mut Vec<Instr>) {
126    if output.is_empty() {
127        return output.push(Instr::IsTrueJmp(condition_id, 0));
128    }
129    let new_instr = match *output.last().unwrap() {
130        Instr::InfFloat(o1, o2, o3) if o3 == condition_id => Instr::InfFloatJmp(o1, o2, 0),
131        Instr::InfInt(o1, o2, o3) if o3 == condition_id => Instr::InfIntJmp(o1, o2, 0),
132        Instr::InfEqFloat(o1, o2, o3) if o3 == condition_id => Instr::InfEqFloatJmp(o1, o2, 0),
133        Instr::InfEqInt(o1, o2, o3) if o3 == condition_id => Instr::InfEqIntJmp(o1, o2, 0),
134        Instr::SupFloat(o1, o2, o3) if o3 == condition_id => Instr::SupFloatJmp(o1, o2, 0),
135        Instr::SupInt(o1, o2, o3) if o3 == condition_id => Instr::SupIntJmp(o1, o2, 0),
136        Instr::SupEqFloat(o1, o2, o3) if o3 == condition_id => Instr::SupEqFloatJmp(o1, o2, 0),
137        Instr::SupEqInt(o1, o2, o3) if o3 == condition_id => Instr::SupEqIntJmp(o1, o2, 0),
138        Instr::Eq(o1, o2, o3) if o3 == condition_id => Instr::EqJmp(o1, o2, 0),
139        Instr::ObjEq(o1, o2, o3) if o3 == condition_id => Instr::ObjEqJmp(o1, o2, 0),
140        Instr::StrEq(o1, o2, o3) if o3 == condition_id => Instr::StrEqJmp(o1, o2, 0),
141        Instr::NotEq(o1, o2, o3) if o3 == condition_id => Instr::NotEqJmp(o1, o2, 0),
142        Instr::ObjNotEq(o1, o2, o3) if o3 == condition_id => Instr::ObjNotEqJmp(o1, o2, 0),
143        Instr::StrNotEq(o1, o2, o3) if o3 == condition_id => Instr::StrNotEqJmp(o1, o2, 0),
144        _ => {
145            output.push(Instr::IsTrueJmp(condition_id, 0));
146            return;
147        }
148    };
149    *output.last_mut().unwrap() = new_instr;
150}
151
152/// Sets the jump size field of a jump instruction
153#[inline(always)]
154const fn set_jmp_size(instr: &mut Instr, size: u16) {
155    match instr {
156        Instr::IsFalseJmp(_, jump_size)
157        | Instr::IsTrueJmp(_, jump_size)
158        | Instr::Jmp(jump_size)
159        | Instr::SupEqFloatJmp(_, _, jump_size)
160        | Instr::SupEqIntJmp(_, _, jump_size)
161        | Instr::SupFloatJmp(_, _, jump_size)
162        | Instr::SupIntJmp(_, _, jump_size)
163        | Instr::InfEqFloatJmp(_, _, jump_size)
164        | Instr::InfEqIntJmp(_, _, jump_size)
165        | Instr::InfFloatJmp(_, _, jump_size)
166        | Instr::InfIntJmp(_, _, jump_size)
167        | Instr::InfIntJmpBack(_, _, jump_size)
168        | Instr::NotEqJmp(_, _, jump_size)
169        | Instr::EqJmp(_, _, jump_size)
170        | Instr::ObjNotEqJmp(_, _, jump_size)
171        | Instr::ObjEqJmp(_, _, jump_size)
172        | Instr::StrNotEqJmp(_, _, jump_size)
173        | Instr::StrEqJmp(_, _, jump_size) => *jump_size = size,
174        _ => unsafe { unreachable_unchecked() },
175    }
176}
177
178/// Compiles short-circuit && and || conditions
179/// bool_or_mode true indicates left side of ||, emits true jumps
180/// bool_or_mode false emits false jumps
181/// Returns (true_jump_idxs, false_jump_idxs)
182#[allow(clippy::too_many_arguments)]
183fn compile_short_circuit_condition(
184    expr: &Expr,
185    v: &mut Vec<Variable>,
186    ctx: Ctx,
187    state: &mut State<'_>,
188    output: &mut Vec<Instr>,
189    bool_or_mode: bool,
190) -> (Vec<usize>, Vec<usize>) {
191    match expr {
192        Expr::BoolOr(left, right, _, _) => {
193            // left side of || always uses true jump mode
194            let (mut true_jumps, left_false) =
195                compile_short_circuit_condition(left, v, ctx, state, output, true);
196            // A false left operand does not settle `||`, so it continues into
197            // the right operand rather than out of the whole expression.
198            let right_start = output.len();
199            for j in left_false {
200                set_jmp_size(&mut output[j], (right_start - j) as u16);
201            }
202            let (right_true, right_false) =
203                compile_short_circuit_condition(right, v, ctx, state, output, bool_or_mode);
204            true_jumps.extend(right_true);
205            (true_jumps, right_false)
206        }
207        Expr::BoolAnd(left, right, _, _) => {
208            if bool_or_mode {
209                // `&&` on the left of `||`, where the caller wants jumps taken
210                // when this conjunction is true. A false left operand settles
211                // the conjunction, so its false jumps skip the right operand
212                // and land on whatever is emitted next, which is exactly where
213                // the enclosing `||` continues.
214                let (_, left_false) =
215                    compile_short_circuit_condition(left, v, ctx, state, output, false);
216                let (right_true, _) =
217                    compile_short_circuit_condition(right, v, ctx, state, output, true);
218                let fallthrough = output.len();
219                for j in left_false {
220                    set_jmp_size(&mut output[j], (fallthrough - j) as u16);
221                }
222                (right_true, Vec::new())
223            } else {
224                // normal && -> if either side is false, jump past the body
225                let (left_true, mut false_jumps) =
226                    compile_short_circuit_condition(left, v, ctx, state, output, false);
227                // A true left operand does not settle `&&`, so it continues
228                // into the right operand. Only the right operand's true jumps
229                // settle the conjunction, and the caller aims those at the body.
230                let right_start = output.len();
231                for j in left_true {
232                    set_jmp_size(&mut output[j], (right_start - j) as u16);
233                }
234                let (right_true, right_false) =
235                    compile_short_circuit_condition(right, v, ctx, state, output, false);
236                false_jumps.extend(right_false);
237                (right_true, false_jumps)
238            }
239        }
240        expr => {
241            let cond_id = expr
242                .compile(v, ctx, state, output, None, false, true)
243                .unwrap_id();
244            if bool_or_mode {
245                add_cmp_true(cond_id, output);
246                state.free_reg(cond_id, v);
247                (vec![output.len() - 1], Vec::new())
248            } else {
249                add_cmp_false(cond_id, &mut 0, output, false);
250                state.free_reg(cond_id, v);
251                (Vec::new(), vec![output.len() - 1])
252            }
253        }
254    }
255}
256
257fn parse_loop_flow_control(
258    loop_code: &mut [Instr],
259    loop_id: u16,
260    code_length: u16,
261    for_loop: bool,
262    indefinite: bool,
263) {
264    loop_code.iter_mut().enumerate().for_each(|(i, x)| {
265        if let Instr::NotEqJmp(break_id, 0, 0) = x
266            && *break_id == loop_id
267        {
268            if for_loop && !indefinite {
269                *x = Instr::Jmp(code_length - i as u16 - 1);
270            } else {
271                *x = Instr::Jmp(code_length - i as u16);
272            }
273        } else if let Instr::EqJmp(continue_id, 0, 0) = x
274            && *continue_id == loop_id
275        {
276            if for_loop {
277                *x = Instr::Jmp(code_length - i as u16 - 3);
278            } else {
279                // loop blocks and while loops only have 1 trailing instruction
280                *x = Instr::Jmp(code_length - i as u16 - 1);
281            }
282        }
283    });
284}
285
286#[inline(always)]
287fn compile_array_literal(
288    array_items: &[Expr],
289    spans: &[Span],
290    v: &mut Vec<Variable>,
291    ctx: Ctx,
292    state: &mut State<'_>,
293    output: &mut Vec<Instr>,
294) -> u16 {
295    if let Some(first) = array_items.first() {
296        let first_type = first.infer_type(v, ctx, state);
297        if let Some(failing_elem_idx) = array_items
298            .iter()
299            .skip(1)
300            .position(|x| x.infer_type(v, ctx, state) != first_type)
301        {
302            let failing_elem_type = array_items[failing_elem_idx + 1].infer_type(v, ctx, state);
303            let failing_elem_span = spans[failing_elem_idx + 2];
304            compiler_errors::error_array_diff_types(
305                ctx.file_idx,
306                state.sources,
307                spans[1],
308                &first_type,
309                failing_elem_span,
310                &failing_elem_type,
311            )
312        }
313    }
314    let array_id = {
315        state.pools.objs.push(Vec::with_capacity(array_items.len()));
316        state.pools.objs.len() - 1
317    };
318    if array_items.is_empty() && !ctx.single_run {
319        let array_reg = {
320            state.registers.push(Data::array(array_id as u32));
321            state.registers.len() - 1
322        } as u16;
323        output.push(Instr::EmptyArray(array_reg));
324        return array_reg;
325    }
326    if ctx.single_run {
327        for elem in array_items {
328            let id = elem
329                .compile(v, ctx, state, output, None, false, true)
330                .unwrap_id();
331            if elem.is_constant_literal() {
332                state
333                    .pools
334                    .objs
335                    .get_mut(array_id)
336                    .push(state.registers[id as usize]);
337            } else {
338                output.push(Instr::ObjElemMov(
339                    id,
340                    array_id as u16,
341                    state.pools.objs[array_id].len() as u16,
342                ));
343                state.pools.objs.get_mut(array_id).push(NULL);
344            }
345        }
346        state.registers.push(Data::array(array_id as u32));
347        (state.registers.len() - 1) as u16
348    } else {
349        // Check if all elements are constant (no instructions emitted)
350        let mut constant_array = true;
351        let mut elem_ids: Vec<u16> = Vec::with_capacity(array_items.len());
352        for elem in array_items {
353            let id = elem
354                .compile(v, ctx, state, output, None, false, true)
355                .unwrap_id();
356            if elem.is_constant_literal() {
357                state
358                    .pools
359                    .objs
360                    .get_mut(array_id)
361                    .push(state.registers[id as usize]);
362            } else {
363                constant_array = false;
364                state.pools.objs.get_mut(array_id).push(NULL);
365            }
366            elem_ids.push(id);
367        }
368
369        if constant_array {
370            // The template array is held by a register to prevent it from being freed by the GC
371            let template_reg = {
372                state.registers.push(Data::array(array_id as u32));
373                (state.registers.len() - 1) as u16
374            };
375            let dest_reg = {
376                state.registers.push(Data::array(0)); // 0 is a placeholder that's overwritten by EmptyArray
377                (state.registers.len() - 1) as u16
378            };
379            output.push(Instr::CloneArray(
380                template_reg,
381                dest_reg,
382                state.pools.objs[array_id].len() as u16,
383            ));
384            dest_reg
385        } else {
386            let dest_reg = {
387                state.registers.push(Data::array(0)); // 0 is a placeholder that's overwritten by EmptyArray
388                (state.registers.len() - 1) as u16
389            };
390            output.push(Instr::EmptyArray(dest_reg));
391            for elem_reg in elem_ids {
392                output.push(Instr::Push(dest_reg, elem_reg));
393            }
394            dest_reg
395        }
396    }
397}
398
399fn compile_struct_literal(
400    namespace: &[SmolStr],
401    fields: &[(SmolStr, Expr, Span, Span)],
402    span: Span,
403    v: &mut Vec<Variable>,
404    ctx: Ctx,
405    state: &mut State<'_>,
406    output: &mut Vec<Instr>,
407) -> u16 {
408    let name = &namespace[namespace.len() - 1];
409    let namespace = &namespace[..(namespace.len() - 1)];
410    let Some(expected_struct_idx) =
411        state
412            .namespace
413            .find_struct(namespace, name, span, ctx.file_idx, state.sources)
414    else {
415        compiler_errors::error_unknown_struct(name, span, state.sources, ctx.file_idx);
416    };
417    let type_id = state.structs[expected_struct_idx].id;
418    let expected_fields_len = state.structs[expected_struct_idx].fields.len();
419    if expected_fields_len < fields.len() {
420        let unexpected_field = &fields[expected_fields_len];
421        compiler_errors::error_struct_no_such_field(
422            ctx.file_idx,
423            name,
424            state.structs[expected_struct_idx].name_span,
425            unexpected_field.2,
426            &unexpected_field.0,
427            state.sources,
428        )
429    }
430    let struct_id = {
431        state.pools.objs.push(Vec::with_capacity(fields.len()));
432        state.pools.objs.len() - 1
433    };
434    if ctx.single_run {
435        for field_idx in 0..expected_fields_len {
436            if let Some((_, field_expr, _, field_value_span)) = fields
437                .iter()
438                .find(|(f, _, _, _)| f == &state.structs[expected_struct_idx].fields[field_idx].0)
439            {
440                let field_type = field_expr.infer_type(v, ctx, state);
441                let field = &state.structs[expected_struct_idx].fields[field_idx];
442                if !struct_field_type_matches(&field.1, &field_type) {
443                    compiler_errors::error_struct_field_invalid_type(
444                        ctx.file_idx,
445                        name,
446                        field.2,
447                        &field.0,
448                        &field.1,
449                        *field_value_span,
450                        &field_type,
451                        state.sources,
452                    );
453                }
454                let id = field_expr
455                    .compile(v, ctx, state, output, None, false, true)
456                    .unwrap_id();
457                if field_expr.is_constant_literal() {
458                    state
459                        .pools
460                        .objs
461                        .get_mut(struct_id)
462                        .push(state.registers[id as usize]);
463                } else {
464                    output.push(Instr::ObjElemMov(
465                        id,
466                        struct_id as u16,
467                        state.pools.objs[struct_id].len() as u16,
468                    ));
469                    state.pools.objs.get_mut(struct_id).push(NULL);
470                }
471            } else {
472                let missing_elems = (0..expected_fields_len)
473                    .into_iter()
474                    .filter(|i| {
475                        !fields.iter().any(|(f, _, _, _)| {
476                            f == &state.structs[expected_struct_idx].fields[*i].0
477                        })
478                    })
479                    .map(|i| &state.structs[struct_id].fields[i].0)
480                    .collect::<Vec<&SmolStr>>();
481                compiler_errors::error_struct_missing_fields(
482                    ctx.file_idx,
483                    state.structs[expected_struct_idx].name_span,
484                    span,
485                    state.sources,
486                    &missing_elems,
487                )
488            }
489        }
490
491        state
492            .registers
493            .push(Data::struct_instance(type_id, struct_id as u32));
494        (state.registers.len() - 1) as u16
495    } else {
496        let mut dynamic: Vec<(u16, u16)> = Vec::with_capacity(expected_fields_len);
497        for field_idx in 0..expected_fields_len {
498            if let Some((_, field_expr, _, field_value_span)) = fields
499                .iter()
500                .find(|(f, _, _, _)| f == &state.structs[expected_struct_idx].fields[field_idx].0)
501            {
502                let field_type = field_expr.infer_type(v, ctx, state);
503                let field = &state.structs[expected_struct_idx].fields[field_idx];
504                if !struct_field_type_matches(&field.1, &field_type) {
505                    compiler_errors::error_struct_field_invalid_type(
506                        ctx.file_idx,
507                        name,
508                        field.2,
509                        &field.0,
510                        &field.1,
511                        *field_value_span,
512                        &field_type,
513                        state.sources,
514                    );
515                }
516                let id = field_expr
517                    .compile(v, ctx, state, output, None, false, true)
518                    .unwrap_id();
519                if field_expr.is_constant_literal() {
520                    state
521                        .pools
522                        .objs
523                        .get_mut(struct_id)
524                        .push(state.registers[id as usize]);
525                } else {
526                    state.pools.objs.get_mut(struct_id).push(NULL);
527                    dynamic.push((id, field_idx as u16));
528                }
529            } else {
530                let missing_elems = (0..expected_fields_len)
531                    .into_iter()
532                    .filter(|i| {
533                        !fields.iter().any(|(f, _, _, _)| {
534                            f == &state.structs[expected_struct_idx].fields[*i].0
535                        })
536                    })
537                    .map(|i| &state.structs[struct_id].fields[i].0)
538                    .collect::<Vec<&SmolStr>>();
539                compiler_errors::error_struct_missing_fields(
540                    ctx.file_idx,
541                    state.structs[expected_struct_idx].name_span,
542                    span,
543                    state.sources,
544                    &missing_elems,
545                );
546            }
547        }
548
549        let template_reg = {
550            state
551                .registers
552                .push(Data::struct_instance(type_id, struct_id as u32));
553            (state.registers.len() - 1) as u16
554        };
555        let dest_reg = {
556            state.registers.push(Data::struct_instance(type_id, 0));
557            (state.registers.len() - 1) as u16
558        };
559        output.push(Instr::CloneStruct(template_reg, dest_reg));
560        for (val_reg, slot) in dynamic {
561            output.push(Instr::SetFieldStruct(dest_reg, val_reg, slot));
562        }
563        dest_reg
564    }
565}
566
567/// Resolves a call/reference path to an enum variant `(enum_id, variant_idx)`,
568/// if it names one. A qualified path (`Color::Red`, `mod::Color::Red`) resolves
569/// the enum by its leading segments and the variant by the last segment; a bare
570/// name (`Some`, `None`) resolves by searching every registered enum for a
571/// variant with that name, first match winning. Never raises a compile error,
572/// so callers use it to intercept otherwise-unknown call/reference paths.
573pub(crate) fn resolve_enum_variant(path: &[SmolStr], state: &State<'_>) -> Option<(u16, u16)> {
574    if path.len() >= 2 {
575        let variant = &path[path.len() - 1];
576        let enum_name = &path[path.len() - 2];
577        let module = &path[..path.len() - 2];
578        let eid = state.namespace.find_enum(module, enum_name)?;
579        let vidx = state.enums[eid]
580            .variants
581            .iter()
582            .position(|vt| &vt.name == variant)?;
583        Some((eid as u16, vidx as u16))
584    } else if let Some(name) = path.first() {
585        for e in state.enums.iter() {
586            if let Some(vidx) = e.variants.iter().position(|vt| &vt.name == name) {
587                return Some((e.id, vidx as u16));
588            }
589        }
590        None
591    } else {
592        None
593    }
594}
595
596/// Lowers an enum-variant construction (`Color::Red`, `Some(x)`) to a fresh
597/// enum value. The object-pool template holds the variant tag at element 0 and
598/// the payload at elements `1..`; constant payloads are baked into the template
599/// and dynamic ones are written after a `CloneEnum` with `SetFieldStruct`,
600/// mirroring how a struct literal is built.
601#[allow(clippy::too_many_arguments)]
602pub(crate) fn compile_enum_construction(
603    enum_id: u16,
604    variant_idx: u16,
605    args: &[Expr],
606    span: Span,
607    args_indexes: &[Span],
608    v: &mut Vec<Variable>,
609    ctx: Ctx,
610    state: &mut State<'_>,
611    output: &mut Vec<Instr>,
612) -> u16 {
613    let variant = &state.enums[enum_id as usize].variants[variant_idx as usize];
614    let variant_name = variant.name.clone();
615    let payload_types = variant.payload.clone();
616    let arity = payload_types.len();
617
618    compiler_errors::check_args(
619        args,
620        arity,
621        &variant_name,
622        span,
623        state.sources,
624        ctx.file_idx,
625    );
626    for (i, expected) in payload_types.iter().enumerate() {
627        // An `any` (Unknown) payload accepts a value of any type.
628        if *expected != DataType::Unknown {
629            functions::check_arg_type(
630                &variant_name,
631                v,
632                ctx,
633                state,
634                args,
635                args_indexes,
636                i,
637                std::slice::from_ref(expected),
638            );
639        }
640    }
641
642    let pool_idx = {
643        state.pools.objs.push(Vec::with_capacity(arity + 1));
644        state.pools.objs.len() - 1
645    };
646    state
647        .pools
648        .objs
649        .get_mut(pool_idx)
650        .push(Data::int(i32::from(variant_idx)));
651
652    let mut dynamic: Vec<(u16, u16)> = Vec::with_capacity(arity);
653    for (i, arg) in args.iter().enumerate() {
654        let id = arg
655            .compile(v, ctx, state, output, None, false, true)
656            .unwrap_id();
657        if arg.is_constant_literal() {
658            let d = state.registers[id as usize];
659            state.pools.objs.get_mut(pool_idx).push(d);
660        } else {
661            state.pools.objs.get_mut(pool_idx).push(NULL);
662            dynamic.push((id, (i + 1) as u16));
663        }
664    }
665
666    let template_reg = {
667        state
668            .registers
669            .push(Data::enum_instance(enum_id, pool_idx as u32));
670        (state.registers.len() - 1) as u16
671    };
672    let dest_reg = {
673        state.registers.push(Data::enum_instance(enum_id, 0));
674        (state.registers.len() - 1) as u16
675    };
676    output.push(Instr::CloneEnum(template_reg, dest_reg));
677    for (val_reg, slot) in dynamic {
678        output.push(Instr::SetFieldStruct(dest_reg, val_reg, slot));
679    }
680    dest_reg
681}
682
683/// Registers a nested `enum` declaration (one inside a function body). Top-level
684/// enums are pre-registered by `parse_toplevel`; this mirrors
685/// `compile_struct_definition` for the nested case.
686fn compile_enum_definition(
687    name: &SmolStr,
688    variants: &[(SmolStr, Box<[TypeExpr]>, Span)],
689    span: Span,
690    ctx: Ctx,
691    state: &mut State<'_>,
692) {
693    let enum_id = state.enums.len() as u16;
694    state.enums.push(EnumType {
695        name: name.clone(),
696        variants: Box::from([]),
697        id: enum_id,
698        name_span: span,
699    });
700    state
701        .namespace
702        .symbols
703        .push((name.clone(), SymbolKind::Enum(enum_id)));
704    let resolved = variants
705        .iter()
706        .map(|(vn, payload, vspan)| EnumVariant {
707            name: vn.clone(),
708            payload: payload
709                .iter()
710                .map(|t| t.to_datatype(ctx.file_idx, state.namespace, state.sources))
711                .collect(),
712            name_span: *vspan,
713        })
714        .collect();
715    state.enums[enum_id as usize].variants = resolved;
716}
717
718/// Extracts a match arm's variant pattern: the variant index within `enum_id`
719/// and the payload binder identifiers (`_` ignores a slot). Raises a compile
720/// error for an unknown variant, a wrong-arity pattern, or a non-identifier
721/// binder.
722pub(crate) fn resolve_variant_pattern(
723    enum_id: u16,
724    pattern: &Expr,
725    fallback_span: Span,
726    ctx: Ctx,
727    state: &State<'_>,
728) -> (u16, Vec<SmolStr>) {
729    let (variant_name, binders, span): (&SmolStr, Vec<SmolStr>, Span) = match pattern {
730        Expr::Var(name, span) => (name, Vec::new(), *span),
731        Expr::NamespacedRef(path, span) => (&path[path.len() - 1], Vec::new(), *span),
732        Expr::FunctionCall(args, namespace, span, _) => {
733            let mut binders = Vec::with_capacity(args.len());
734            for arg in args {
735                if let Expr::Var(binder, _) = arg {
736                    binders.push(binder.clone());
737                } else {
738                    compiler_errors::error_enum(
739                        "Invalid match pattern",
740                        "Enum variant patterns may only bind identifiers, e.g. Circle(r)",
741                        *span,
742                        ctx.file_idx,
743                        state.sources,
744                    );
745                }
746            }
747            (&namespace[namespace.len() - 1], binders, *span)
748        }
749        _ => compiler_errors::error_enum(
750            "Invalid match pattern",
751            "A match on an enum expects variant patterns, e.g. Circle(r) or Unit",
752            fallback_span,
753            ctx.file_idx,
754            state.sources,
755        ),
756    };
757    let e = &state.enums[enum_id as usize];
758    let Some(variant_idx) = e.variants.iter().position(|vt| &vt.name == variant_name) else {
759        compiler_errors::error_enum(
760            "Unknown enum variant",
761            &format!("{} is not a variant of enum {}", variant_name, e.name),
762            span,
763            ctx.file_idx,
764            state.sources,
765        );
766    };
767    let expected_arity = e.variants[variant_idx].payload.len();
768    if binders.len() != expected_arity {
769        compiler_errors::error_enum(
770            "Wrong variant payload arity",
771            &format!(
772                "Variant {} binds {} value(s) but the pattern has {}",
773                variant_name,
774                expected_arity,
775                binders.len()
776            ),
777            span,
778            ctx.file_idx,
779            state.sources,
780        );
781    }
782    (variant_idx as u16, binders)
783}
784
785/// Lowers a `match` on an enum scrutinee to a variant-tag compare chain with
786/// per-arm payload binding, reusing the ordinary conditional-jump machinery.
787#[allow(clippy::too_many_arguments)]
788fn compile_enum_match(
789    enum_id: u16,
790    scrutinee: &Expr,
791    arms: &[(Expr, Box<[Expr]>)],
792    wildcard: Option<&[Expr]>,
793    span: Span,
794    v: &mut Vec<Variable>,
795    ctx: Ctx,
796    state: &mut State<'_>,
797    output: &mut Vec<Instr>,
798) {
799    let scrut_reg = scrutinee
800        .compile(v, ctx, state, output, None, false, true)
801        .unwrap_id();
802    // Root the scrutinee for the whole match so its object-pool payload is not
803    // reclaimed and its register is not reused across arm bodies.
804    let v_base = v.len();
805    v.push(Variable {
806        name: SmolStr::new_static("[MATCH SCRUT]"),
807        register_id: scrut_reg,
808        var_type: DataType::Enum(enum_id),
809    });
810
811    let tag_reg = state.alloc_reg();
812    output.push(Instr::GetFieldStruct(scrut_reg, 0, tag_reg));
813
814    let variant_count = state.enums[enum_id as usize].variants.len();
815    let mut covered = vec![false; variant_count];
816    let mut false_jmps: Vec<usize> = Vec::with_capacity(arms.len());
817    let mut arm_starts: Vec<usize> = Vec::with_capacity(arms.len());
818    let mut end_jmps: Vec<usize> = Vec::with_capacity(arms.len());
819
820    for (pattern, body) in arms {
821        let (variant_idx, binders) = resolve_variant_pattern(enum_id, pattern, span, ctx, state);
822        covered[variant_idx as usize] = true;
823
824        arm_starts.push(output.len());
825        let idx_reg = state.alloc_reg();
826        output.push(Instr::SetInt(idx_reg, i32::from(variant_idx)));
827        false_jmps.push(output.len());
828        output.push(Instr::NotEqJmp(tag_reg, idx_reg, 0));
829        state.free_reg(idx_reg, v);
830
831        // Bind the variant payload into fresh locals for the arm body.
832        let v_arm = v.len();
833        for (i, binder) in binders.iter().enumerate() {
834            if binder.as_str() != "_" {
835                let binder_reg = state.alloc_reg();
836                output.push(Instr::GetFieldStruct(scrut_reg, (i + 1) as u16, binder_reg));
837                let payload_type =
838                    state.enums[enum_id as usize].variants[variant_idx as usize].payload[i].clone();
839                v.push(Variable {
840                    name: binder.clone(),
841                    register_id: binder_reg,
842                    var_type: payload_type,
843                });
844            }
845        }
846
847        let arm_code = compile_expr(body, v, ctx.advance_offset(output.len() as u16), state);
848        output.extend(arm_code);
849        v.truncate(v_arm);
850
851        end_jmps.push(output.len());
852        output.push(Instr::Jmp(0));
853    }
854
855    // Where a non-matching last arm (and the wildcard, if any) begins.
856    let after_arms = output.len();
857    if let Some(w) = wildcard {
858        let wild_code = compile_expr(w, v, ctx.advance_offset(output.len() as u16), state);
859        output.extend(wild_code);
860    }
861    let end = output.len();
862
863    for (k, &j) in false_jmps.iter().enumerate() {
864        let target = if k + 1 < arm_starts.len() {
865            arm_starts[k + 1]
866        } else {
867            after_arms
868        };
869        set_jmp_size(&mut output[j], (target - j) as u16);
870    }
871    for &j in &end_jmps {
872        set_jmp_size(&mut output[j], (end - j) as u16);
873    }
874
875    v.truncate(v_base);
876    state.free_reg(tag_reg, v);
877    state.free_reg(scrut_reg, v);
878
879    if wildcard.is_none() && !covered.iter().all(|&c| c) {
880        let missing: Vec<&str> = state.enums[enum_id as usize]
881            .variants
882            .iter()
883            .enumerate()
884            .filter(|(i, _)| !covered[*i])
885            .map(|(_, vt)| vt.name.as_str())
886            .collect();
887        compiler_errors::error_enum(
888            "Non-exhaustive match",
889            &format!(
890                "match on enum {} does not cover: {}. Add the missing arm(s) or a `_` wildcard",
891                state.enums[enum_id as usize].name,
892                missing.join(", ")
893            ),
894            span,
895            ctx.file_idx,
896            state.sources,
897        );
898    }
899}
900
901/// Compiles a `match`. An enum scrutinee dispatches to variant-pattern matching
902/// with payload binding; any other scrutinee reproduces the equality-chain
903/// lowering (`scrutinee == pattern` per arm) that `match` has always had.
904fn compile_match(
905    scrutinee: &Expr,
906    arms: &[(Expr, Box<[Expr]>)],
907    wildcard: Option<&[Expr]>,
908    span: Span,
909    v: &mut Vec<Variable>,
910    ctx: Ctx,
911    state: &mut State<'_>,
912    output: &mut Vec<Instr>,
913) {
914    if let DataType::Enum(enum_id) = scrutinee.infer_type(v, ctx, state) {
915        compile_enum_match(
916            enum_id, scrutinee, arms, wildcard, span, v, ctx, state, output,
917        );
918    } else {
919        let obj_var = SmolStr::new_static("[MATCH TEMP]");
920        let (first_pat, first_body) = &arms[0];
921        let mut output_code: Vec<Expr> = Vec::with_capacity(arms.len());
922        output_code.extend(first_body.iter().cloned());
923        for (pat, body) in &arms[1..] {
924            output_code.push(Expr::ElseIfBlock(
925                Box::new(Expr::Eq(
926                    Box::new(Expr::Var(obj_var.clone(), span)),
927                    Box::new(pat.clone()),
928                )),
929                body.clone(),
930            ));
931        }
932        if let Some(w) = wildcard {
933            output_code.push(Expr::ElseBlock(Box::from(w)));
934        }
935        let desugared = Expr::EvalBlock(Box::from([
936            Expr::VarDeclare(obj_var.clone(), Box::new(scrutinee.clone())),
937            Expr::Condition(
938                Box::new(Expr::Eq(
939                    Box::new(Expr::Var(obj_var, span)),
940                    Box::new(first_pat.clone()),
941                )),
942                Box::from(output_code),
943                span,
944            ),
945        ]));
946        desugared.compile(v, ctx, state, output, None, false, false);
947    }
948}
949
950fn compile_map_literal(
951    kv_pairs: &[(Expr, Span, Expr, Span)],
952    map_span: Span,
953    v: &mut Vec<Variable>,
954    ctx: Ctx,
955    state: &mut State<'_>,
956    output: &mut Vec<Instr>,
957) -> u16 {
958    let mut global_key_type: DataType = DataType::Unknown;
959    let mut global_val_type: DataType = DataType::Unknown;
960    let map_id = state.pools.maps.len();
961    state.pools.maps.push(HashMap::with_capacity_and_hasher(
962        kv_pairs.len(),
963        BuildHasherDefault::default(),
964    ));
965    if ctx.single_run {
966        for (i, (key, key_span, val, val_span)) in kv_pairs.iter().enumerate() {
967            if let Some((_, repeat_key_span, _, _)) =
968                kv_pairs.iter().skip(i + 1).find(|(k, _, _, _)| k == key)
969            {
970                error_duplicate_map_key(
971                    *key_span,
972                    *repeat_key_span,
973                    map_span,
974                    ctx.file_idx,
975                    state.sources,
976                );
977            }
978            let key_t = key.infer_type(v, ctx, state);
979            let val_t = val.infer_type(v, ctx, state);
980            if i == 0 {
981                global_key_type = key_t;
982                global_val_type = val_t;
983            } else {
984                if key_t != global_key_type {
985                    error_map_diff_types(
986                        ctx.file_idx,
987                        state.sources,
988                        map_span,
989                        &global_key_type,
990                        *key_span,
991                        &key_t,
992                    )
993                }
994                if val_t != global_val_type {
995                    error_map_diff_types(
996                        ctx.file_idx,
997                        state.sources,
998                        map_span,
999                        &global_val_type,
1000                        *val_span,
1001                        &val_t,
1002                    )
1003                }
1004            }
1005            let output_len = output.len();
1006            let key_val_id = key
1007                .compile(v, ctx, state, output, None, false, true)
1008                .unwrap_id();
1009            if !(key.is_constant_literal()
1010                || matches!(key, Expr::Array(_, _)) && output_len == output.len())
1011            {
1012                error_not_literal_map_key(*key_span, map_span, ctx.file_idx, state.sources);
1013            }
1014            let key_val = state.registers[key_val_id as usize];
1015            let id = val
1016                .compile(v, ctx, state, output, None, false, true)
1017                .unwrap_id();
1018            if val.is_constant_literal() {
1019                state.pools.maps[map_id].insert(key_val, state.registers[id as usize]);
1020            } else {
1021                state.pools.maps[map_id].insert(key_val, NULL);
1022                output.push(Instr::MapInsert(
1023                    map_id as u16,
1024                    state.registers.len() as u16,
1025                    id,
1026                ));
1027                state.registers.push(key_val);
1028            }
1029        }
1030        let dest_id = state.registers.len();
1031        state.registers.push(Data::map(map_id as u32));
1032        dest_id as u16
1033    } else {
1034        let mut dynamic: Vec<(Data, u16)> = Vec::with_capacity(kv_pairs.len());
1035        for (i, (key, key_span, val, val_span)) in kv_pairs.iter().enumerate() {
1036            if let Some((_, repeat_key_span, _, _)) =
1037                kv_pairs.iter().skip(i + 1).find(|(k, _, _, _)| k == key)
1038            {
1039                error_duplicate_map_key(
1040                    *key_span,
1041                    *repeat_key_span,
1042                    map_span,
1043                    ctx.file_idx,
1044                    state.sources,
1045                );
1046            }
1047            let key_t = key.infer_type(v, ctx, state);
1048            let val_t = val.infer_type(v, ctx, state);
1049            if i == 0 {
1050                global_key_type = key_t;
1051                global_val_type = val_t;
1052            } else {
1053                if key_t != global_key_type {
1054                    error_map_diff_types(
1055                        ctx.file_idx,
1056                        state.sources,
1057                        map_span,
1058                        &global_key_type,
1059                        *key_span,
1060                        &key_t,
1061                    )
1062                }
1063                if val_t != global_val_type {
1064                    error_map_diff_types(
1065                        ctx.file_idx,
1066                        state.sources,
1067                        map_span,
1068                        &global_val_type,
1069                        *val_span,
1070                        &val_t,
1071                    )
1072                }
1073            }
1074            let output_len = output.len();
1075            let key_val_id = key
1076                .compile(v, ctx, state, output, None, false, true)
1077                .unwrap_id();
1078            if !(key.is_constant_literal()
1079                || matches!(key, Expr::Array(_, _)) && output_len == output.len())
1080            {
1081                error_not_literal_map_key(*key_span, map_span, ctx.file_idx, state.sources);
1082            }
1083            let key_val = state.registers[key_val_id as usize];
1084            let val_id = val
1085                .compile(v, ctx, state, output, None, false, true)
1086                .unwrap_id();
1087            if val.is_constant_literal() {
1088                state.pools.maps[map_id].insert(key_val, state.registers[val_id as usize]);
1089            } else {
1090                state.pools.maps[map_id].insert(key_val, NULL);
1091                dynamic.push((key_val, val_id));
1092            }
1093        }
1094
1095        let template_reg = {
1096            state.registers.push(Data::map(map_id as u32));
1097            (state.registers.len() - 1) as u16
1098        };
1099        let dest_reg = {
1100            state.registers.push(Data::map(0));
1101            (state.registers.len() - 1) as u16
1102        };
1103        output.push(Instr::CloneMap(template_reg, dest_reg));
1104        for (key_val, val_id) in dynamic {
1105            let key_reg = if let Some(&id) = state.const_registers.get(&key_val) {
1106                id
1107            } else {
1108                let id = state.registers.len() as u16;
1109                state.const_registers.insert(key_val, id);
1110                state.registers.push(key_val);
1111                id
1112            };
1113            output.push(Instr::MapInsertReg(dest_reg, key_reg, val_id));
1114        }
1115        dest_reg
1116    }
1117}
1118
1119fn compile_struct_field_access(
1120    struct_expr: &Expr,
1121    field: &SmolStr,
1122    struct_span: Span,
1123    field_span: Span,
1124    v: &mut Vec<Variable>,
1125    ctx: Ctx,
1126    state: &mut State<'_>,
1127    output: &mut Vec<Instr>,
1128) -> u16 {
1129    let t = struct_expr.infer_type(v, ctx, state);
1130    if let DataType::Struct(s_id) = t {
1131        let s = &state.structs[s_id as usize];
1132        let idx = s
1133            .fields
1134            .iter()
1135            .position(|f| &f.0 == field)
1136            .unwrap_or_else(|| {
1137                compiler_errors::error_struct_unknown_field(
1138                    ctx.file_idx,
1139                    field_span,
1140                    field,
1141                    &s.name,
1142                    &s.fields,
1143                    state.sources,
1144                );
1145            });
1146        let id = struct_expr
1147            .compile(v, ctx, state, output, None, false, true)
1148            .unwrap_id();
1149        let dest_reg_id = state.alloc_reg();
1150        output.push(Instr::GetFieldStruct(id, idx as u16, dest_reg_id));
1151        dest_reg_id
1152    } else {
1153        error_invalid_type(
1154            &DataType::Struct(0),
1155            &t,
1156            struct_span,
1157            None,
1158            None,
1159            ctx.file_idx,
1160            state.sources,
1161        );
1162    }
1163}
1164
1165fn compile_array_indexing(
1166    array: &Expr,
1167    index: &Expr,
1168    span: Span,
1169    v: &mut Vec<Variable>,
1170    ctx: Ctx,
1171    state: &mut State<'_>,
1172    output: &mut Vec<Instr>,
1173) -> u16 {
1174    let inferred = array.infer_type(v, ctx, state);
1175    if !inferred.is_indexable() {
1176        error_type_not_indexable(&inferred, span, false, ctx.file_idx, state.sources);
1177    }
1178
1179    let id = array
1180        .compile(v, ctx, state, output, None, false, true)
1181        .unwrap_id();
1182
1183    let index_inferred = index.infer_type(v, ctx, state);
1184    if index_inferred != DataType::Int {
1185        error_invalid_index_type(&index_inferred, span, ctx.file_idx, state.sources);
1186    }
1187    let index_id = index
1188        .compile(v, ctx, state, output, None, false, true)
1189        .unwrap_id();
1190    state.free_reg(index_id, v);
1191    let dest_reg_id = state.alloc_reg();
1192
1193    let to_push = if inferred == DataType::String {
1194        Instr::GetIndexString(id, index_id, dest_reg_id)
1195    } else {
1196        Instr::GetIndexArray(id, index_id, dest_reg_id)
1197    };
1198    output.push(to_push);
1199    state.add_to_src(ctx, output, span);
1200    dest_reg_id
1201}
1202
1203fn compile_array_slice(
1204    array: &Expr,
1205    idx_start: &Expr,
1206    idx_end: &Expr,
1207    span: Span,
1208    v: &mut Vec<Variable>,
1209    ctx: Ctx,
1210    state: &mut State<'_>,
1211    output: &mut Vec<Instr>,
1212) -> u16 {
1213    let inferred = array.infer_type(v, ctx, state);
1214    if !inferred.is_indexable() {
1215        error_type_not_indexable(&inferred, span, false, ctx.file_idx, state.sources);
1216    }
1217    let id = array
1218        .compile(v, ctx, state, output, None, false, true)
1219        .unwrap_id();
1220    let idx_start_inferred = idx_start.infer_type(v, ctx, state);
1221    if idx_start_inferred != DataType::Int {
1222        error_invalid_index_type(&idx_start_inferred, span, ctx.file_idx, state.sources);
1223    }
1224    let idx_start_id = idx_start
1225        .compile(v, ctx, state, output, None, false, true)
1226        .unwrap_id();
1227    let idx_end_inferred = idx_end.infer_type(v, ctx, state);
1228    if idx_end_inferred != DataType::Int {
1229        error_invalid_index_type(&idx_end_inferred, span, ctx.file_idx, state.sources);
1230    }
1231    let idx_end_id = idx_end
1232        .compile(v, ctx, state, output, None, false, true)
1233        .unwrap_id();
1234    output.push(Instr::StoreFuncArg(idx_end_id));
1235    state.free_reg(idx_start_id, v);
1236    state.free_reg(idx_end_id, v);
1237    let dest_reg_id = state.alloc_reg();
1238    let to_push = if inferred == DataType::String {
1239        Instr::GetSliceString(id, idx_start_id, dest_reg_id)
1240    } else {
1241        Instr::GetSliceArray(id, idx_start_id, dest_reg_id)
1242    };
1243    output.push(to_push);
1244    state.add_to_src(ctx, output, span);
1245    dest_reg_id
1246}
1247
1248#[inline]
1249fn uniform_op2(
1250    instr: fn(u16, u16, u16) -> Instr,
1251    t_1: &'static DataType,
1252    instr2: fn(u16, u16, u16) -> Instr,
1253    t_2: &'static DataType,
1254    symbol: &'static str,
1255    l: &Expr,
1256    r: &Expr,
1257    span_l: Span,
1258    span_r: Span,
1259    tgt_id: Option<u16>,
1260    v: &mut Vec<Variable>,
1261    ctx: Ctx,
1262    state: &mut State<'_>,
1263    output: &mut Vec<Instr>,
1264) -> u16 {
1265    let (t_l, t_r) = (l.infer_type(v, ctx, state), r.infer_type(v, ctx, state));
1266    if !((&t_l == t_1 && &t_r == t_1) || (&t_l == t_2 && &t_r == t_2)) {
1267        compiler_errors::error_op(
1268            &t_l,
1269            &t_r,
1270            symbol,
1271            span_l,
1272            span_r,
1273            ctx.file_idx,
1274            state.sources,
1275        );
1276    }
1277    let id_l = l
1278        .compile(v, ctx, state, output, None, false, true)
1279        .unwrap_id();
1280    let id_r = r
1281        .compile(v, ctx, state, output, None, false, true)
1282        .unwrap_id();
1283    state.free_reg(id_l, v);
1284    state.free_reg(id_r, v);
1285    let id = state.alloc_reg_tgt(tgt_id);
1286    output.push(if &t_l == t_1 {
1287        instr(id_l, id_r, id)
1288    } else {
1289        instr2(id_l, id_r, id)
1290    });
1291    id
1292}
1293
1294fn compile_div_op(
1295    l: &Expr,
1296    r: &Expr,
1297    span_l: Span,
1298    span_r: Span,
1299    tgt_id: Option<u16>,
1300    v: &mut Vec<Variable>,
1301    ctx: Ctx,
1302    state: &mut State<'_>,
1303    output: &mut Vec<Instr>,
1304) -> u16 {
1305    // A float left operand next to an `int` zero is a type error, and naming it
1306    // division by zero would report the wrong mistake. Every other left operand
1307    // makes this integer division, which does not divide by zero.
1308    if let Expr::Int(n) = r
1309        && *n == 0
1310        && l.infer_type(v, ctx, state) != DataType::Float
1311    {
1312        error_division_by_zero(false, span_l.extend(span_r), ctx.file_idx, state.sources);
1313    }
1314    let id = uniform_op2(
1315        Instr::DivFloat,
1316        &DataType::Float,
1317        Instr::DivInt,
1318        &DataType::Int,
1319        "/",
1320        l,
1321        r,
1322        span_l,
1323        span_r,
1324        tgt_id,
1325        v,
1326        ctx,
1327        state,
1328        output,
1329    );
1330    if matches!(output.last(), Some(Instr::DivInt(..))) {
1331        state.add_to_src(ctx, output, span_l.extend(span_r));
1332    }
1333    id
1334}
1335
1336fn compile_add_op(
1337    l: &Expr,
1338    r: &Expr,
1339    span_l: Span,
1340    span_r: Span,
1341    tgt_id: Option<u16>,
1342    v: &mut Vec<Variable>,
1343    ctx: Ctx,
1344    state: &mut State<'_>,
1345    output: &mut Vec<Instr>,
1346) -> u16 {
1347    let t_l = l.infer_type(v, ctx, state);
1348    let t_r = r.infer_type(v, ctx, state);
1349    if t_l != t_r
1350        || !matches!(
1351            t_l,
1352            DataType::String | DataType::Array(_) | DataType::Float | DataType::Int
1353        )
1354    {
1355        compiler_errors::error_op(&t_l, &t_r, "+", span_l, span_r, ctx.file_idx, state.sources);
1356    }
1357    // var+1 or 1+var use the dedicated IncInt/IncIntTo instructions
1358    if t_l == DataType::Int
1359        && let Some(Expr::Var(src_name, _)) = {
1360            if matches!(r, Expr::Int(1)) {
1361                Some(l)
1362            } else if matches!(l, Expr::Int(1)) {
1363                Some(r)
1364            } else {
1365                None
1366            }
1367        }
1368        && let Some(src_var) = v.iter().rfind(|x| x.name == *src_name)
1369    {
1370        let src_id = src_var.register_id;
1371        let id = tgt_id.unwrap_or_else(|| state.alloc_reg());
1372        output.push(if src_id == id {
1373            Instr::IncInt(id)
1374        } else {
1375            Instr::IncIntTo(src_id, id)
1376        });
1377        return id;
1378    }
1379    let id_l = l
1380        .compile(v, ctx, state, output, None, false, true)
1381        .unwrap_id();
1382    let id_r = r
1383        .compile(v, ctx, state, output, None, false, true)
1384        .unwrap_id();
1385    state.free_reg(id_l, v);
1386    state.free_reg(id_r, v);
1387    let id = state.alloc_reg_tgt(tgt_id);
1388    if matches!(t_l, DataType::Array(_)) {
1389        output.push(Instr::AddArray(id_l, id_r, id));
1390    } else if t_l == DataType::String {
1391        output.push(Instr::AddStr(id_l, id_r, id));
1392    } else if t_l == DataType::Float {
1393        output.push(Instr::AddFloat(id_l, id_r, id));
1394    } else {
1395        output.push(Instr::AddInt(id_l, id_r, id));
1396    }
1397    id
1398}
1399
1400fn compile_sub_op(
1401    l: &Expr,
1402    r: &Expr,
1403    span_l: Span,
1404    span_r: Span,
1405    tgt_id: Option<u16>,
1406    v: &mut Vec<Variable>,
1407    ctx: Ctx,
1408    state: &mut State<'_>,
1409    output: &mut Vec<Instr>,
1410) -> u16 {
1411    let t_l = l.infer_type(v, ctx, state);
1412    let t_r = r.infer_type(v, ctx, state);
1413    if !((t_l == DataType::Float && t_r == DataType::Float)
1414        || (t_l == DataType::Int && t_r == DataType::Int))
1415    {
1416        compiler_errors::error_op(&t_l, &t_r, "-", span_l, span_r, ctx.file_idx, state.sources);
1417    }
1418    // var-1 uses the dedicated DecInt/DecIntTo instructions
1419    if t_l == DataType::Int
1420        && matches!(r, Expr::Int(1))
1421        && let Expr::Var(src_name, _) = l
1422        && let Some(src_var) = v.iter().rfind(|x| x.name == *src_name)
1423    {
1424        let src_id = src_var.register_id;
1425        let id = tgt_id.unwrap_or_else(|| state.alloc_reg());
1426        output.push(if src_id == id {
1427            Instr::DecInt(id)
1428        } else {
1429            Instr::DecIntTo(src_id, id)
1430        });
1431        return id;
1432    }
1433    let id_l = l
1434        .compile(v, ctx, state, output, None, false, true)
1435        .unwrap_id();
1436    let id_r = r
1437        .compile(v, ctx, state, output, None, false, true)
1438        .unwrap_id();
1439    state.free_reg(id_l, v);
1440    state.free_reg(id_r, v);
1441    let id = state.alloc_reg_tgt(tgt_id);
1442    output.push(if t_l == DataType::Float {
1443        Instr::SubFloat(id_l, id_r, id)
1444    } else {
1445        Instr::SubInt(id_l, id_r, id)
1446    });
1447    id
1448}
1449
1450fn compile_mod_op(
1451    l: &Expr,
1452    r: &Expr,
1453    span_l: Span,
1454    span_r: Span,
1455    tgt_id: Option<u16>,
1456    v: &mut Vec<Variable>,
1457    ctx: Ctx,
1458    state: &mut State<'_>,
1459    output: &mut Vec<Instr>,
1460) -> u16 {
1461    // As in `compile_div_op`: a float left operand makes this a type error
1462    // rather than a remainder by zero.
1463    if let Expr::Int(n) = r
1464        && *n == 0
1465        && l.infer_type(v, ctx, state) != DataType::Float
1466    {
1467        error_division_by_zero(true, span_l.extend(span_r), ctx.file_idx, state.sources);
1468    }
1469    let id = uniform_op2(
1470        Instr::ModFloat,
1471        &DataType::Float,
1472        Instr::ModInt,
1473        &DataType::Int,
1474        "%",
1475        l,
1476        r,
1477        span_l,
1478        span_r,
1479        tgt_id,
1480        v,
1481        ctx,
1482        state,
1483        output,
1484    );
1485    if matches!(output.last(), Some(Instr::ModInt(..))) {
1486        state.add_to_src(ctx, output, span_l.extend(span_r));
1487    }
1488    id
1489}
1490
1491/// Compiles `&&` or `||` where a value is wanted rather than a branch.
1492///
1493/// The left operand is evaluated into the result register and, when it already
1494/// settles the answer, the jump skips the right operand entirely, so an
1495/// expression short-circuits wherever it appears and not only as the condition
1496/// of an `if` or a `while`.
1497///
1498/// The right operand is evaluated into its own register and moved, which keeps
1499/// the last instruction a `Mov`. An enclosing condition fuses the instruction it
1500/// finds at the end of a compiled condition into a jump, and fusing the right
1501/// operand's comparison would strand the short-circuit jump past it.
1502fn compile_short_circuit_value(
1503    l: &Expr,
1504    r: &Expr,
1505    span_l: Span,
1506    span_r: Span,
1507    symbol: &'static str,
1508    tgt_id: Option<u16>,
1509    v: &mut Vec<Variable>,
1510    ctx: Ctx,
1511    state: &mut State<'_>,
1512    output: &mut Vec<Instr>,
1513) -> u16 {
1514    let (t_l, t_r) = (l.infer_type(v, ctx, state), r.infer_type(v, ctx, state));
1515    if t_l != DataType::Bool || t_r != DataType::Bool {
1516        cold_path();
1517        compiler_errors::error_op(
1518            &t_l,
1519            &t_r,
1520            symbol,
1521            span_l,
1522            span_r,
1523            ctx.file_idx,
1524            state.sources,
1525        );
1526    }
1527
1528    let id = state.alloc_reg_tgt(tgt_id);
1529    let left_id = l
1530        .compile(v, ctx, state, output, Some(id), false, true)
1531        .unwrap_id();
1532    if left_id != id {
1533        output.push(Instr::Mov(left_id, id));
1534    }
1535
1536    let skip_idx = output.len();
1537    // `&&` is settled by a false left operand, `||` by a true one. Either way
1538    // the left operand's value is already in the result register.
1539    output.push(if symbol == "&&" {
1540        Instr::IsFalseJmp(id, 0)
1541    } else {
1542        Instr::IsTrueJmp(id, 0)
1543    });
1544
1545    let right_id = r
1546        .compile(v, ctx, state, output, None, false, true)
1547        .unwrap_id();
1548    state.free_reg(right_id, v);
1549    output.push(Instr::Mov(right_id, id));
1550    let skip_size = (output.len() - skip_idx) as u16;
1551    set_jmp_size(&mut output[skip_idx], skip_size);
1552    id
1553}
1554
1555fn compile_eq_op(
1556    l: &Expr,
1557    r: &Expr,
1558    tgt_id: Option<u16>,
1559    v: &mut Vec<Variable>,
1560    ctx: Ctx,
1561    state: &mut State<'_>,
1562    output: &mut Vec<Instr>,
1563) -> u16 {
1564    let l_type = l.infer_type(v, ctx, state);
1565    let r_type = r.infer_type(v, ctx, state);
1566    let is_array = matches!(
1567        l_type,
1568        DataType::Array(_) | DataType::Struct(_) | DataType::Enum(_)
1569    ) && matches!(
1570        r_type,
1571        DataType::Array(_) | DataType::Struct(_) | DataType::Enum(_)
1572    );
1573    let is_string = l_type == DataType::String || r_type == DataType::String;
1574    let id_l = l
1575        .compile(v, ctx, state, output, None, false, true)
1576        .unwrap_id();
1577    let id_r = r
1578        .compile(v, ctx, state, output, None, false, true)
1579        .unwrap_id();
1580    state.free_reg(id_l, v);
1581    state.free_reg(id_r, v);
1582    let id = state.alloc_reg_tgt(tgt_id);
1583    output.push(if is_array {
1584        Instr::ObjEq(id_l, id_r, id)
1585    } else if is_string {
1586        Instr::StrEq(id_l, id_r, id)
1587    } else {
1588        Instr::Eq(id_l, id_r, id)
1589    });
1590    id
1591}
1592
1593fn compile_neq_op(
1594    l: &Expr,
1595    r: &Expr,
1596    tgt_id: Option<u16>,
1597    v: &mut Vec<Variable>,
1598    ctx: Ctx,
1599    state: &mut State<'_>,
1600    output: &mut Vec<Instr>,
1601) -> u16 {
1602    let l_type = l.infer_type(v, ctx, state);
1603    let r_type = r.infer_type(v, ctx, state);
1604    let is_array = matches!(
1605        l_type,
1606        DataType::Array(_) | DataType::Struct(_) | DataType::Enum(_)
1607    ) && matches!(
1608        r_type,
1609        DataType::Array(_) | DataType::Struct(_) | DataType::Enum(_)
1610    );
1611    let is_string = l_type == DataType::String || r_type == DataType::String;
1612    let id_l = l
1613        .compile(v, ctx, state, output, None, false, true)
1614        .unwrap_id();
1615    let id_r = r
1616        .compile(v, ctx, state, output, None, false, true)
1617        .unwrap_id();
1618    state.free_reg(id_l, v);
1619    state.free_reg(id_r, v);
1620    let id = state.alloc_reg_tgt(tgt_id);
1621    if is_array {
1622        output.push(Instr::ObjNotEq(id_l, id_r, id));
1623    } else if is_string {
1624        output.push(Instr::StrNotEq(id_l, id_r, id));
1625    } else {
1626        output.push(Instr::NotEq(id_l, id_r, id));
1627    }
1628    id
1629}
1630
1631fn compile_neg_op(
1632    l: &Expr,
1633    span_l: Span,
1634    span_r: Span,
1635    tgt_id: Option<u16>,
1636    v: &mut Vec<Variable>,
1637    ctx: Ctx,
1638    state: &mut State<'_>,
1639    output: &mut Vec<Instr>,
1640) -> u16 {
1641    let operand_type = l.infer_type(v, ctx, state);
1642    let id_l = l
1643        .compile(v, ctx, state, output, None, false, true)
1644        .unwrap_id();
1645    state.free_reg(id_l, v);
1646    let id = state.alloc_reg_tgt(tgt_id);
1647    if operand_type == DataType::Float {
1648        output.push(Instr::NegFloat(id_l, id));
1649    } else if operand_type == DataType::Int {
1650        output.push(Instr::NegInt(id_l, id));
1651    } else {
1652        compiler_errors::error_op(
1653            &DataType::Null,
1654            &operand_type,
1655            "-",
1656            span_l,
1657            span_r,
1658            ctx.file_idx,
1659            state.sources,
1660        );
1661    }
1662    id
1663}
1664
1665fn compile_bool_neg_op(
1666    l: &Expr,
1667    span_l: Span,
1668    span_r: Span,
1669    tgt_id: Option<u16>,
1670    v: &mut Vec<Variable>,
1671    ctx: Ctx,
1672    state: &mut State<'_>,
1673    output: &mut Vec<Instr>,
1674) -> u16 {
1675    let operand_type = l.infer_type(v, ctx, state);
1676    let id_l = l
1677        .compile(v, ctx, state, output, None, false, true)
1678        .unwrap_id();
1679    state.free_reg(id_l, v);
1680    let id = state.alloc_reg_tgt(tgt_id);
1681    if operand_type != DataType::Bool {
1682        compiler_errors::error_op(
1683            &DataType::Null,
1684            &operand_type,
1685            "!",
1686            span_l,
1687            span_r,
1688            ctx.file_idx,
1689            state.sources,
1690        );
1691    }
1692    output.push(Instr::NegBool(id_l, id));
1693    id
1694}
1695
1696fn compile_inline_condition_branch(
1697    branch: &[Expr],
1698    v: &mut Vec<Variable>,
1699    ctx: Ctx,
1700    state: &mut State<'_>,
1701    output: &mut Vec<Instr>,
1702    tgt_id: u16,
1703) {
1704    let regs_before = state.registers.len() as u16;
1705    let output_len = output.len();
1706    output.extend(compile_expr(
1707        &branch[..branch.len() - 1],
1708        v,
1709        ctx.advance_offset(output.len() as u16),
1710        state,
1711    ));
1712    let val_id = branch[branch.len() - 1]
1713        .compile(
1714            v,
1715            ctx.advance_offset(output.len() as u16),
1716            state,
1717            output,
1718            Some(tgt_id),
1719            false,
1720            true,
1721        )
1722        .unwrap_id();
1723    state.free_scope_registers(regs_before, &output[output_len..], v);
1724    if val_id != tgt_id {
1725        output.push(Instr::Mov(val_id, tgt_id));
1726    }
1727}
1728
1729fn compile_inline_condition(
1730    main_condition: &Expr,
1731    code: &[Expr],
1732    span: Span,
1733    v: &mut Vec<Variable>,
1734    ctx: Ctx,
1735    state: &mut State<'_>,
1736    output: &mut Vec<Instr>,
1737    tgt_id: Option<u16>,
1738) -> u16 {
1739    let return_id = state.alloc_reg_tgt(tgt_id);
1740
1741    // get first code limit (after which there are only else(if) blocks)
1742    let main_code_limit = code
1743        .iter()
1744        .position(|x| matches!(x, Expr::ElseIfBlock(_, _) | Expr::ElseBlock(_)))
1745        .unwrap_or(code.len());
1746
1747    let condition_blocks_count = code.len() - main_code_limit;
1748    let mut cmp_markers: Vec<usize> = Vec::with_capacity(condition_blocks_count);
1749    let mut jmp_markers: Vec<usize> = Vec::with_capacity(condition_blocks_count);
1750    let mut condition_markers: Vec<usize> = Vec::with_capacity(condition_blocks_count);
1751
1752    // parse the main condition
1753    let condition_id = main_condition
1754        .compile(v, ctx, state, output, None, false, true)
1755        .unwrap_id();
1756    add_cmp_false(condition_id, &mut 0, output, false);
1757    cmp_markers.push(output.len() - 1);
1758
1759    compile_inline_condition_branch(&code[..main_code_limit], v, ctx, state, output, return_id);
1760    if main_code_limit != code.len() {
1761        output.push(Instr::Jmp(0));
1762        jmp_markers.push(output.len() - 1);
1763    }
1764
1765    let mut else_exists = false;
1766    for elem in &code[main_code_limit..] {
1767        if let Expr::ElseIfBlock(condition, code) = elem {
1768            condition_markers.push(output.len());
1769            let condition_id = condition
1770                .compile(v, ctx, state, output, None, false, true)
1771                .unwrap_id();
1772            add_cmp_false(condition_id, &mut 0, output, false);
1773            state.free_reg(condition_id, v);
1774            cmp_markers.push(output.len() - 1);
1775            compile_inline_condition_branch(code, v, ctx, state, output, return_id);
1776            output.push(Instr::Jmp(0));
1777            jmp_markers.push(output.len() - 1);
1778        } else if let Expr::ElseBlock(code) = elem {
1779            else_exists = true;
1780            condition_markers.push(output.len());
1781            compile_inline_condition_branch(code, v, ctx, state, output, return_id);
1782        }
1783    }
1784    if !else_exists {
1785        error_conditional_expression_without_else(span, ctx.file_idx, state.sources);
1786    }
1787
1788    for y in jmp_markers {
1789        let diff = output.len() - y;
1790        output[y] = Instr::Jmp(diff as u16);
1791    }
1792    for (i, y) in cmp_markers.iter().enumerate() {
1793        let diff = if i >= condition_markers.len() {
1794            output.len() - 1 - y
1795        } else {
1796            condition_markers[i] - y
1797        };
1798        if let Some(
1799            Instr::IsFalseJmp(_, jump_size)
1800            | Instr::SupEqFloatJmp(_, _, jump_size)
1801            | Instr::SupEqIntJmp(_, _, jump_size)
1802            | Instr::SupFloatJmp(_, _, jump_size)
1803            | Instr::SupIntJmp(_, _, jump_size)
1804            | Instr::InfEqFloatJmp(_, _, jump_size)
1805            | Instr::InfEqIntJmp(_, _, jump_size)
1806            | Instr::InfFloatJmp(_, _, jump_size)
1807            | Instr::InfIntJmp(_, _, jump_size)
1808            | Instr::NotEqJmp(_, _, jump_size)
1809            | Instr::ObjNotEqJmp(_, _, jump_size)
1810            | Instr::EqJmp(_, _, jump_size)
1811            | Instr::ObjEqJmp(_, _, jump_size),
1812        ) = output.get_mut(*y)
1813        {
1814            *jump_size = diff as u16;
1815        }
1816    }
1817    state.free_reg(condition_id, v);
1818    return_id
1819}
1820
1821fn compile_array_index_assignment(
1822    array: &Expr,
1823    index: &Expr,
1824    value: &Expr,
1825    index_span: Span,
1826    elem_span: Span,
1827    v: &mut Vec<Variable>,
1828    ctx: Ctx,
1829    state: &mut State<'_>,
1830    output: &mut Vec<Instr>,
1831) {
1832    let array_type = array.infer_type(v, ctx, state);
1833    if !array_type.is_indexable() {
1834        error_type_not_indexable(&array_type, index_span, false, ctx.file_idx, state.sources);
1835    }
1836    // Get the id of the source array/string (may be a nested GetIndex)
1837    let id = array
1838        .compile(v, ctx, state, output, None, false, true)
1839        .unwrap_id();
1840
1841    let final_id = index
1842        .compile(v, ctx, state, output, None, false, true)
1843        .unwrap_id();
1844
1845    let elem_type = value.infer_type(v, ctx, state);
1846    let elem_id = value
1847        .compile(v, ctx, state, output, None, false, true)
1848        .unwrap_id();
1849    state.free_reg(elem_id, v);
1850    if {
1851        if let DataType::Array(Some(array_type)) = &array_type
1852            && array_type.as_ref() != &elem_type
1853        {
1854            true
1855        } else {
1856            false
1857        }
1858    } || (array_type == DataType::String && elem_type != DataType::String)
1859    {
1860        error_cannot_push_type_to_array(
1861            &array_type,
1862            &elem_type,
1863            index_span,
1864            elem_span,
1865            ctx.file_idx,
1866            state.sources,
1867        );
1868    }
1869
1870    let to_push = if array_type == DataType::String {
1871        Instr::SetElementString(id, elem_id, final_id)
1872    } else {
1873        Instr::SetElementObj(id, elem_id, final_id)
1874    };
1875    output.push(to_push);
1876    state.add_to_src(ctx, output, index_span);
1877    state.free_reg(id, v);
1878}
1879
1880fn compile_struct_field_assignment(
1881    struct_expr: &Expr,
1882    field: &SmolStr,
1883    new_val: &Expr,
1884    struct_span: Span,
1885    field_span: Span,
1886    value_span: Span,
1887    v: &mut Vec<Variable>,
1888    ctx: Ctx,
1889    state: &mut State<'_>,
1890    output: &mut Vec<Instr>,
1891) {
1892    let t = struct_expr.infer_type(v, ctx, state);
1893    let new_val_type = new_val.infer_type(v, ctx, state);
1894    let DataType::Struct(struct_id) = t else {
1895        error_invalid_type(
1896            &DataType::Struct(0),
1897            &t,
1898            struct_span,
1899            None,
1900            None,
1901            ctx.file_idx,
1902            state.sources,
1903        );
1904    };
1905    let mut field_index: Option<u16> = None;
1906    let field_struct = &state.structs[struct_id as usize];
1907    let struct_name = &field_struct.name;
1908    for (i, (expected_field_name, expected_field_type, expected_field_span)) in
1909        field_struct.fields.iter().enumerate()
1910    {
1911        if expected_field_name == field {
1912            if !struct_field_type_matches(expected_field_type, &new_val_type) {
1913                compiler_errors::error_struct_field_invalid_type(
1914                    ctx.file_idx,
1915                    struct_name,
1916                    *expected_field_span,
1917                    expected_field_name,
1918                    expected_field_type,
1919                    value_span,
1920                    &new_val_type,
1921                    state.sources,
1922                );
1923            }
1924            field_index = Some(i as u16);
1925            break;
1926        }
1927    }
1928    let Some(field_index) = field_index else {
1929        compiler_errors::error_struct_unknown_field(
1930            ctx.file_idx,
1931            field_span,
1932            field,
1933            struct_name,
1934            &field_struct.fields,
1935            state.sources,
1936        );
1937    };
1938    let id = struct_expr
1939        .compile(v, ctx, state, output, None, false, true)
1940        .unwrap_id();
1941    let new_elem_reg_id = new_val
1942        .compile(v, ctx, state, output, None, false, true)
1943        .unwrap_id();
1944    output.push(Instr::SetFieldStruct(id, new_elem_reg_id, field_index));
1945}
1946
1947fn compile_condition(
1948    main_condition: &Expr,
1949    code: &[Expr],
1950    v: &mut Vec<Variable>,
1951    ctx: Ctx,
1952    state: &mut State<'_>,
1953    output: &mut Vec<Instr>,
1954) {
1955    // get first code limit (after which there are only else(if) blocks)
1956    let main_code_limit = code
1957        .iter()
1958        .position(|x| matches!(x, Expr::ElseIfBlock(_, _) | Expr::ElseBlock(_)))
1959        .unwrap_or(code.len());
1960
1961    let condition_blocks_count = code.len() - main_code_limit;
1962    // Each entry is the list of false-jump instruction indices for one condition block.
1963    let mut conditional_false_jmp_idxs: Vec<Vec<usize>> =
1964        Vec::with_capacity(condition_blocks_count + 1);
1965    let mut jmp_instr_idx: Vec<usize> = Vec::with_capacity(condition_blocks_count);
1966    let mut condition_markers: Vec<usize> = Vec::with_capacity(condition_blocks_count);
1967
1968    // Compile the main condition
1969    let (true_jump_idxs, false_jump_idxs) =
1970        compile_short_circuit_condition(main_condition, v, ctx, state, output, false);
1971    conditional_false_jmp_idxs.push(false_jump_idxs);
1972
1973    // Modify true jump instructions to point to body_start
1974    let body_start = output.len();
1975    for j in true_jump_idxs {
1976        set_jmp_size(&mut output[j], (body_start - j) as u16);
1977    }
1978
1979    // parse the main code block
1980    let cond_code = compile_expr(
1981        &code[0..main_code_limit],
1982        v,
1983        ctx.advance_offset(output.len() as u16),
1984        state,
1985    );
1986    output.extend(cond_code);
1987    if main_code_limit != code.len() {
1988        output.push(Instr::Jmp(0));
1989        jmp_instr_idx.push(output.len() - 1);
1990    }
1991
1992    for elem in &code[main_code_limit..] {
1993        if let Expr::ElseIfBlock(condition, code) = elem {
1994            condition_markers.push(output.len());
1995            let condition_id = condition
1996                .compile(v, ctx, state, output, None, false, true)
1997                .unwrap_id();
1998            state.free_reg(condition_id, v);
1999            add_cmp_false(condition_id, &mut 0, output, false);
2000            conditional_false_jmp_idxs.push(vec![output.len() - 1]);
2001            let cond_code = compile_expr(code, v, ctx.advance_offset(output.len() as u16), state);
2002            output.extend(cond_code);
2003            output.push(Instr::Jmp(0));
2004            jmp_instr_idx.push(output.len() - 1);
2005        } else if let Expr::ElseBlock(code) = elem {
2006            condition_markers.push(output.len());
2007            let cond_code = compile_expr(code, v, ctx.advance_offset(output.len() as u16), state);
2008            output.extend(cond_code);
2009        }
2010    }
2011
2012    for y in jmp_instr_idx {
2013        let diff = output.len() - y;
2014        output[y] = Instr::Jmp(diff as u16);
2015    }
2016    // Fix all false-jump instructions for each condition block
2017    for (cm_idx, false_idxs) in conditional_false_jmp_idxs.iter().enumerate() {
2018        let target = if cm_idx < condition_markers.len() {
2019            condition_markers[cm_idx]
2020        } else {
2021            output.len()
2022        };
2023        for &y in false_idxs {
2024            set_jmp_size(&mut output[y], (target - y) as u16);
2025        }
2026    }
2027}
2028
2029fn compile_while_loop(
2030    condition: &Expr,
2031    code: &[Expr],
2032    v: &mut Vec<Variable>,
2033    ctx: Ctx,
2034    state: &mut State<'_>,
2035    output: &mut Vec<Instr>,
2036) {
2037    let output_len_before = output.len();
2038
2039    let (true_jump_idxs, false_jump_idxs) =
2040        compile_short_circuit_condition(condition, v, ctx, state, output, false);
2041
2042    let body_start = output.len();
2043    for j in true_jump_idxs {
2044        set_jmp_size(&mut output[j], (body_start - j) as u16);
2045    }
2046
2047    // parse the code block, clone the vars to avoid overriding anything
2048    let loop_id = ctx.block_id + 1;
2049
2050    let mut cond_code = compile_expr(
2051        code,
2052        v,
2053        ctx.no_single_run().advance_offset(output.len() as u16),
2054        state,
2055    );
2056
2057    let exit = output.len() + cond_code.len() + 1;
2058    for j in false_jump_idxs {
2059        set_jmp_size(&mut output[j], (exit - j) as u16);
2060    }
2061
2062    let cond_len = (output.len() - output_len_before) as u16;
2063    let body_len = cond_code.len() as u16;
2064    let len = cond_len + body_len; // full span used by JmpBack
2065    // Break/Continue offsets are relative to cond_code, so pass body_len+1 (body remaining + JmpBack)
2066    parse_loop_flow_control(&mut cond_code, loop_id, body_len + 1, false, false);
2067    output.extend(cond_code);
2068    output.push(Instr::JmpBack(len));
2069}
2070
2071fn compile_for_loop(
2072    var_name: &SmolStr,
2073    array: &Expr,
2074    code: &[Expr],
2075    span: Span,
2076    v: &mut Vec<Variable>,
2077    ctx: Ctx,
2078    state: &mut State<'_>,
2079    output: &mut Vec<Instr>,
2080) {
2081    let real_var = var_name.as_str() != "_";
2082
2083    // parse the array, get its id (the target array is the first Expr in array_code)
2084    let array_type = array.infer_type(v, ctx, state);
2085    let mut array = array
2086        .compile(v, ctx, state, output, None, false, true)
2087        .unwrap_id();
2088
2089    // Iterating a map walks its keys: materialize the key array and iterate that
2090    // with the ordinary array machinery. The loop variable binds each key.
2091    if matches!(array_type, DataType::Map(_)) {
2092        let keys_reg = state.alloc_reg();
2093        output.push(Instr::CallLibFunc(LibFunc::Keys, array, keys_reg));
2094        array = keys_reg;
2095    }
2096
2097    let array_len_id = state.alloc_reg();
2098
2099    output.push(Instr::CallLibFunc(LibFunc::Len, array, array_len_id));
2100
2101    // set up the id of the index variable (0..len)
2102    let index_id = if ctx.single_run {
2103        state.registers.push(0.into());
2104        (state.registers.len() - 1) as u16
2105    } else {
2106        let id = state.alloc_reg();
2107        output.push(Instr::SetInt(id, 0));
2108        id
2109    };
2110
2111    // do the 'i < len' condition, set up the condition's id (true/false)
2112    let condition_id = state.alloc_reg();
2113
2114    output.push(Instr::InfInt(index_id, array_len_id, condition_id));
2115
2116    // set up the variable for the current element (for current_element_id in ... {}) => current_element_id = array[index]
2117    let current_element_id = if real_var { state.alloc_reg() } else { 0 };
2118
2119    let v_len = v.len();
2120
2121    let is_str = array_type == DataType::String;
2122
2123    if real_var {
2124        v.push(Variable {
2125            name: var_name.clone(),
2126            register_id: current_element_id,
2127            var_type: match array_type {
2128                DataType::String => DataType::String,
2129                DataType::Array(a_type) => a_type.map_or(DataType::Null, |t| *t),
2130                // A map iterates its keys; the loop variable is a key.
2131                DataType::Map(m) => m.0.map_or(DataType::Unknown, |t| t),
2132                t => {
2133                    error_type_not_indexable(&t, span, true, ctx.file_idx, state.sources);
2134                }
2135            },
2136        });
2137    }
2138    let loop_id = ctx.block_id + 1;
2139
2140    // accounts for the GetIndexArray/GetIndexString instruction
2141    let pending = real_var as u16;
2142
2143    let regs_before = state.registers.len() as u16;
2144    let mut cond_code = compile_expr(
2145        code,
2146        v,
2147        ctx.no_single_run()
2148            .advance_offset(output.len() as u16 + pending),
2149        state,
2150    );
2151    // Clean up variables
2152    v.truncate(v_len);
2153    state.free_loop_scope_registers(regs_before, &cond_code, v);
2154
2155    // add the condition ('i < len') jumping logic
2156    let mut len = (cond_code.len() + 3) as u16 + pending;
2157    add_cmp_false(condition_id, &mut len, output, true);
2158
2159    // load the element's value into the current_element_id register
2160    if real_var {
2161        if is_str {
2162            output.push(Instr::GetIndexString(array, index_id, current_element_id));
2163        } else {
2164            output.push(Instr::GetIndexArray(array, index_id, current_element_id));
2165        }
2166    }
2167    parse_loop_flow_control(&mut cond_code, loop_id, len, true, false);
2168    // then add the condition code
2169    output.extend(cond_code);
2170    // add 1 to the index (i+=1) so that the next loop iteration will have the next element in the array
2171    output.push(Instr::IncInt(index_id));
2172
2173    // jump back to the loop if still inside of it
2174    output.push(Instr::JmpBack(len));
2175
2176    if ctx.single_run {
2177        state.free_reg(array_len_id, v);
2178        state.free_reg(index_id, v);
2179        state.free_reg(condition_id, v);
2180        if real_var {
2181            state.free_reg(current_element_id, v);
2182        }
2183    }
2184}
2185
2186fn compile_int_for_loop(
2187    var_name: &SmolStr,
2188    start_elem: &Expr,
2189    end_elem: &Expr,
2190    code: &[Expr],
2191    span1: Span,
2192    span2: Span,
2193    v: &mut Vec<Variable>,
2194    ctx: Ctx,
2195    state: &mut State<'_>,
2196    output: &mut Vec<Instr>,
2197) {
2198    // IntForLoop is compiled to:
2199    // ----
2200    // (1) if i >= end_elem jump out
2201    // (2) loop_body
2202    // (3) i += 1
2203    // (4) if i < end_elem jump back to body
2204    // ----
2205    //
2206    //
2207    // Check start and elem type
2208    let t1 = start_elem.infer_type(v, ctx, state);
2209    let t2 = end_elem.infer_type(v, ctx, state);
2210    if t1 != DataType::Int {
2211        error_range_invalid_type(span1, &t1, ctx.file_idx, state.sources);
2212    }
2213    if t2 != DataType::Int {
2214        error_range_invalid_type(span2, &t2, ctx.file_idx, state.sources);
2215    }
2216    let elem_id = if ctx.single_run {
2217        start_elem
2218            .compile(v, ctx, state, output, None, false, true)
2219            .unwrap_id()
2220    } else {
2221        let start_elem_id = start_elem
2222            .compile(v, ctx, state, output, None, false, true)
2223            .unwrap_id();
2224        let start_val = state.registers[start_elem_id as usize];
2225        let elem_id = state.alloc_reg();
2226        if state.const_registers.values().any(|&v| v == start_elem_id) && start_val.is_int() {
2227            output.push(Instr::SetInt(elem_id, start_val.as_int()));
2228        } else {
2229            output.push(Instr::Mov(start_elem_id, elem_id));
2230        }
2231        elem_id
2232    };
2233    let end_elem_id = end_elem
2234        .compile(v, ctx, state, output, None, false, true)
2235        .unwrap_id();
2236
2237    // elem_id is a fresh mutable register -> remove from const_registers just in case
2238    state.const_registers.retain(|_, &mut v| v != elem_id);
2239
2240    let v_len = v.len();
2241    v.push(Variable {
2242        name: var_name.clone(),
2243        register_id: elem_id,
2244        var_type: DataType::Int,
2245    });
2246    let loop_id = ctx.block_id + 1;
2247
2248    // (1) if i >= end_elem jump out -> push placeholder first so that compile_expr sees the correct offset
2249    let jmp_idx = output.len();
2250    output.push(Instr::SupEqIntJmp(elem_id, end_elem_id, 0));
2251
2252    let regs_before = state.registers.len() as u16;
2253    let compiled_loop_code = compile_expr(
2254        code,
2255        v,
2256        ctx.no_single_run().advance_offset(output.len() as u16),
2257        state,
2258    );
2259    state.free_loop_scope_registers(regs_before, &compiled_loop_code, v);
2260    let compiled_loop_code_len = compiled_loop_code.len() as u16;
2261
2262    // (2) loop_body
2263    output.extend(compiled_loop_code);
2264
2265    // (3) i+= 1
2266    output.push(Instr::IncInt(elem_id));
2267
2268    // (4) if i < end_elem jump back to body
2269    output.push(Instr::InfIntJmpBack(
2270        elem_id,
2271        end_elem_id,
2272        compiled_loop_code_len + 1,
2273    ));
2274
2275    let exit_size = (output.len() - jmp_idx) as u16;
2276    output[jmp_idx] = Instr::SupEqIntJmp(elem_id, end_elem_id, exit_size);
2277
2278    parse_loop_flow_control(&mut output[jmp_idx + 1..], loop_id, exit_size, true, false);
2279    v.truncate(v_len);
2280
2281    if ctx.single_run {
2282        state.free_reg(end_elem_id, v);
2283        state.free_reg(elem_id, v);
2284    }
2285}
2286
2287fn compile_loop_block(
2288    code: &[Expr],
2289    v: &mut Vec<Variable>,
2290    ctx: Ctx,
2291    state: &mut State<'_>,
2292    output: &mut Vec<Instr>,
2293) {
2294    let loop_id = ctx.block_id + 1;
2295    let regs_before = state.registers.len() as u16;
2296    let mut compiled = compile_expr(
2297        code,
2298        v,
2299        ctx.no_single_run().advance_offset(output.len() as u16),
2300        state,
2301    );
2302    state.free_loop_scope_registers(regs_before, &compiled, v);
2303    let code_length = compiled.len() as u16;
2304    parse_loop_flow_control(&mut compiled, loop_id, code_length + 1, false, true);
2305    output.extend(compiled);
2306    output.push(Instr::JmpBack(code_length));
2307}
2308
2309fn compile_try_catch_block(
2310    e: &[Expr],
2311    err_var: &SmolStr,
2312    catch_code: &[Expr],
2313    v: &mut Vec<Variable>,
2314    ctx: Ctx,
2315    state: &mut State<'_>,
2316    output: &mut Vec<Instr>,
2317) {
2318    output.push(Instr::StartErrorCatch(0, 0)); // patched later on
2319    let err_catch_instr = output.len() - 1;
2320    // A function body is compiled inline at its first call site and records its
2321    // absolute entry address as `ctx.offset + output.len()`, so both blocks are
2322    // compiled at the offset they occupy. Compiling them at the
2323    // enclosing offset makes every call inside a `try` jump short by the length
2324    // of the code already emitted before it.
2325    let main_code = compile_expr(e, v, ctx.advance_offset(output.len() as u16), state);
2326    output.extend(main_code);
2327    output.push(Instr::StopErrorCatch);
2328    output.push(Instr::Jmp(0)); // jumps over the catch handler if no error arises
2329    let jmp_catch_instr = output.len() - 1;
2330
2331    let v_len = v.len();
2332    let err_reg_id = state.alloc_reg();
2333    v.push(Variable {
2334        name: err_var.clone(),
2335        register_id: err_reg_id,
2336        var_type: DataType::String,
2337    });
2338    output[err_catch_instr] =
2339        Instr::StartErrorCatch((output.len() - err_catch_instr) as u16, err_reg_id);
2340    let catch_code = compile_expr(
2341        catch_code,
2342        v,
2343        ctx.advance_offset(output.len() as u16),
2344        state,
2345    );
2346    v.truncate(v_len);
2347    output.extend(catch_code);
2348    output[jmp_catch_instr] = Instr::Jmp((output.len() - jmp_catch_instr) as u16);
2349    state.free_reg(err_reg_id, v);
2350}
2351
2352fn compile_var_declaration(
2353    name: &SmolStr,
2354    value: &Expr,
2355    remaining_code: &[Expr],
2356    v: &mut Vec<Variable>,
2357    ctx: Ctx,
2358    state: &mut State<'_>,
2359    output: &mut Vec<Instr>,
2360) {
2361    let var_type = value.infer_type(v, ctx, state);
2362
2363    let var_id = if ctx.single_run {
2364        value
2365            .compile(v, ctx, state, output, None, true, true)
2366            .unwrap_id()
2367    } else {
2368        let src_id = value
2369            .compile(v, ctx, state, output, None, false, true)
2370            .unwrap_id();
2371        if code_modifies_variable(name, remaining_code) {
2372            let mutable_id = state.alloc_reg();
2373            move_reg_to_reg(output, src_id, mutable_id, state.registers[src_id as usize]);
2374            mutable_id
2375        } else {
2376            src_id
2377        }
2378    };
2379
2380    if let DataType::Fn(fn_id) = &var_type {
2381        state
2382            .namespace
2383            .symbols
2384            .push((name.clone(), SymbolKind::Fn(*fn_id)));
2385    }
2386    v.push(Variable {
2387        name: name.clone(),
2388        register_id: var_id,
2389        var_type,
2390    });
2391}
2392
2393fn compile_var_assignment(
2394    name: &SmolStr,
2395    value: &Expr,
2396    span: Span,
2397    v: &mut Vec<Variable>,
2398    ctx: Ctx,
2399    state: &mut State<'_>,
2400    output: &mut Vec<Instr>,
2401) {
2402    let var_type = value.infer_type(v, ctx, state);
2403    let var_pos = v.iter().rposition(|x| x.name == *name).unwrap_or_else(|| {
2404        compiler_errors::error_unknown_variable(name, span, v, ctx.file_idx, state.sources);
2405    });
2406    let id = v[var_pos].register_id;
2407
2408    if var_type == DataType::Int {
2409        // (is_inc, src_var_name)
2410        let inc_dec: Option<(bool, &str)> = match value {
2411            // var+1/1+var use the dedicated IncInt/IncIntTo instructions
2412            Expr::Add(l, r, _, _) => {
2413                let src = if matches!(r.as_ref(), Expr::Int(1)) {
2414                    Some(l.as_ref())
2415                } else if matches!(l.as_ref(), Expr::Int(1)) {
2416                    Some(r.as_ref())
2417                } else {
2418                    None
2419                };
2420                src.and_then(|e| {
2421                    if let Expr::Var(src_name, _) = e {
2422                        v.iter()
2423                            .rfind(|x| x.name == *src_name)
2424                            .filter(|x| x.var_type == DataType::Int)
2425                            .map(|_| (true, src_name.as_str()))
2426                    } else {
2427                        None
2428                    }
2429                })
2430            }
2431            // var-1 uses the dedicated DecInt/DecIntTo instructions
2432            Expr::Sub(l, r, _, _) => {
2433                if matches!(r.as_ref(), Expr::Int(1)) {
2434                    if let Expr::Var(src_name, _) = l.as_ref() {
2435                        v.iter()
2436                            .rfind(|x| x.name == *src_name)
2437                            .filter(|x| x.var_type == DataType::Int)
2438                            .map(|_| (false, src_name.as_str()))
2439                    } else {
2440                        None
2441                    }
2442                } else {
2443                    None
2444                }
2445            }
2446            _ => None,
2447        };
2448        if let Some((is_inc, src_name)) = inc_dec {
2449            let src_id = v.iter().rfind(|x| x.name == src_name).unwrap().register_id;
2450            output.push(if src_id == id {
2451                if is_inc {
2452                    Instr::IncInt(id)
2453                } else {
2454                    Instr::DecInt(id)
2455                }
2456            } else {
2457                if is_inc {
2458                    Instr::IncIntTo(src_id, id)
2459                } else {
2460                    Instr::DecIntTo(src_id, id)
2461                }
2462            });
2463            return;
2464        }
2465    }
2466
2467    let output_len = output.len();
2468    let obj_id = value
2469        .compile(v, ctx, state, output, Some(id), false, true)
2470        .unwrap_id();
2471    if output.len() != output_len {
2472        if !move_to_id(output, id) {
2473            output.push(Instr::Mov(obj_id, id));
2474        }
2475    } else if state.const_registers.values().any(|&v| v == obj_id) {
2476        move_reg_to_reg(output, obj_id, id, state.registers[obj_id as usize]);
2477    } else {
2478        output.push(Instr::Mov(obj_id, id));
2479    }
2480    if !v
2481        .iter()
2482        .any(|var| &var.name != name && var.register_id == obj_id)
2483    {
2484        state.free_reg(obj_id, v);
2485    }
2486    v[var_pos].var_type = var_type;
2487}
2488
2489fn compile_struct_definition(
2490    name: &SmolStr,
2491    fields: &[(SmolStr, TypeExpr, Span)],
2492    span: Span,
2493    ctx: Ctx,
2494    state: &mut State<'_>,
2495    _output: &mut Vec<Instr>,
2496) {
2497    let struct_id = state.structs.len() as u16;
2498    state.structs.push(Struct {
2499        // pushing it first allows structs to be recursive
2500        name: name.clone(),
2501        fields: Box::from([]),
2502        id: struct_id,
2503        name_span: span,
2504    });
2505    state.namespace.symbols.push((
2506        name.clone(),
2507        SymbolKind::Struct((state.structs.len() - 1) as u16),
2508    ));
2509    let parsed_fields = fields
2510        .iter()
2511        .map(|(f, f_t, f_span)| {
2512            (
2513                f.clone(),
2514                f_t.to_datatype(ctx.file_idx, state.namespace, state.sources),
2515                *f_span,
2516            )
2517        })
2518        .collect();
2519    state.structs[struct_id as usize].fields = parsed_fields;
2520}
2521
2522fn compile_function_definition(
2523    fn_name: &SmolStr,
2524    fn_args: &[(SmolStr, Option<TypeExpr>)],
2525    fn_code: &Rc<[Expr]>,
2526    span: Span,
2527    declared_return_type: Option<&(TypeExpr, Span)>,
2528    _v: &mut Vec<Variable>,
2529    ctx: Ctx,
2530    state: &mut State<'_>,
2531    _output: &mut Vec<Instr>,
2532) {
2533    if let Some(func) = state.fns.iter().find(|func| &func.name == fn_name) {
2534        compiler_errors::error_function_already_defined(func, span, ctx.file_idx, state.sources);
2535    }
2536    let mut callees = Vec::new();
2537    collect_direct_fn_calls(fn_code, &mut callees);
2538    state
2539        .namespace
2540        .symbols
2541        .push((fn_name.clone(), SymbolKind::Fn(state.fns.len() as u16)));
2542    state.fns.push(Function {
2543        name: fn_name.clone(),
2544        args: Box::from(fn_args.iter().map(|(a, t)| {
2545            (
2546                a.clone(),
2547                t.clone()
2548                    .map(|t_e| t_e.to_datatype(ctx.file_idx, state.namespace, state.sources)),
2549            )
2550        }))
2551        .collect(),
2552        code: fn_code.clone(),
2553        impls: Vec::new(),
2554        is_recursive: None,
2555        returns_null: check_if_returns_void(fn_code),
2556        src_file: ctx.file_idx,
2557        return_type_cache: Vec::new(),
2558        direct_calls: callees.into_boxed_slice(),
2559        name_span: span,
2560        return_type: declared_return_type.map(|(t_e, t_span)| {
2561            (
2562                t_e.to_datatype(ctx.file_idx, state.namespace, state.sources),
2563                *t_span,
2564            )
2565        }),
2566    });
2567    state.fn_registers.push(Vec::new());
2568}
2569
2570fn compile_return(
2571    return_value: Option<&Expr>,
2572    v: &mut Vec<Variable>,
2573    ctx: Ctx,
2574    state: &mut State<'_>,
2575    output: &mut Vec<Instr>,
2576) {
2577    if let Some(x) = return_value {
2578        let id = x
2579            .compile(v, ctx, state, output, None, false, true)
2580            .unwrap_id();
2581        if ctx.is_compiling_recursive {
2582            output.push(Instr::RecursiveReturn(id));
2583        } else {
2584            output.push(Instr::Return(id));
2585        }
2586    }
2587}
2588
2589#[inline]
2590fn compile_loop_break(ctx: Ctx, output: &mut Vec<Instr>) {
2591    output.push(Instr::NotEqJmp(ctx.block_id + 1, 0, 0));
2592}
2593
2594#[inline]
2595fn compile_loop_continue(ctx: Ctx, output: &mut Vec<Instr>) {
2596    output.push(Instr::EqJmp(ctx.block_id + 1, 0, 0));
2597}
2598
2599#[inline]
2600fn compile_eval_block(
2601    code: &[Expr],
2602    v: &mut Vec<Variable>,
2603    ctx: Ctx,
2604    state: &mut State<'_>,
2605    output: &mut Vec<Instr>,
2606) {
2607    output.extend(compile_expr(
2608        code,
2609        v,
2610        ctx.set_offset(output.len() as u16),
2611        state,
2612    ));
2613}
2614
2615pub fn compile_expr(
2616    input: &[Expr],
2617    v: &mut Vec<Variable>,
2618    ctx: Ctx,
2619    state: &mut State<'_>,
2620) -> Vec<Instr> {
2621    let v_len = v.len();
2622    let fn_len = state.fns.len();
2623    let symbols_len = state.namespace.symbols.len();
2624    let mut output: Vec<Instr> = Vec::with_capacity(input.len());
2625    for (idx, x) in input.iter().enumerate() {
2626        if let Some(id) = x.compile_with_code_context(
2627            v,
2628            ctx,
2629            state,
2630            &mut output,
2631            None,
2632            false,
2633            &input[idx + 1..],
2634            false,
2635        ) {
2636            state.free_reg(id, v);
2637        }
2638    }
2639    v.truncate(v_len);
2640    state.fns.truncate(fn_len);
2641    state.namespace.symbols.truncate(symbols_len);
2642    output
2643}
2644
2645impl Expr {
2646    #[must_use]
2647    pub const fn is_constant_literal(&self) -> bool {
2648        matches!(
2649            self,
2650            Self::Int(_) | Self::Float(_) | Self::String(_) | Self::Bool(_) | Self::Null
2651        )
2652    }
2653    #[inline(always)]
2654    pub fn compile(
2655        &self,
2656        v: &mut Vec<Variable>,
2657        ctx: Ctx,
2658        state: &mut State<'_>,
2659        output: &mut Vec<Instr>,
2660        tgt_id: Option<u16>,
2661        var_assignment: bool,
2662        uses_id: bool,
2663    ) -> Option<u16> {
2664        self.compile_with_code_context(v, ctx, state, output, tgt_id, var_assignment, &[], uses_id)
2665    }
2666    pub fn compile_with_code_context(
2667        &self,
2668        v: &mut Vec<Variable>,
2669        ctx: Ctx,
2670        state: &mut State<'_>,
2671        output: &mut Vec<Instr>,
2672        tgt_id: Option<u16>,
2673        var_assignment: bool,
2674        remaining_code: &[Self],
2675        uses_id: bool,
2676    ) -> Option<u16> {
2677        match self {
2678            Self::Int(num) => {
2679                debug_assert!(uses_id);
2680                if var_assignment {
2681                    state.registers.push((*num).into());
2682                    return Some((state.registers.len() - 1) as u16);
2683                }
2684                let data = (*num).into();
2685                if let Some(&id) = state.const_registers.get(&data) {
2686                    Some(id)
2687                } else {
2688                    let id = state.registers.len() as u16;
2689                    state.const_registers.insert(data, id);
2690                    state.registers.push(data);
2691                    Some(id)
2692                }
2693            }
2694            Self::Float(num) => {
2695                debug_assert!(uses_id);
2696                if var_assignment {
2697                    state.registers.push((*num).into());
2698                    return Some((state.registers.len() - 1) as u16);
2699                }
2700                let data = (*num).into();
2701                if let Some(&id) = state.const_registers.get(&data) {
2702                    Some(id)
2703                } else {
2704                    state.registers.push(data);
2705                    let id = (state.registers.len() - 1) as u16;
2706                    state.const_registers.insert(data, id);
2707                    Some(id)
2708                }
2709            }
2710            Self::String(str) => {
2711                debug_assert!(uses_id);
2712                if var_assignment {
2713                    state
2714                        .registers
2715                        .push(Data::p_str(str, &mut state.pools.strings));
2716                    return Some((state.registers.len() - 1) as u16);
2717                }
2718                let data = Data::p_str(str, &mut state.pools.strings);
2719                if let Some(&id) = state.const_registers.get(&data) {
2720                    Some(id)
2721                } else {
2722                    let id = state.registers.len() as u16;
2723                    state.const_registers.insert(data, id);
2724                    state.registers.push(data);
2725                    Some(id)
2726                }
2727            }
2728            Self::Null => {
2729                debug_assert!(uses_id);
2730                if var_assignment {
2731                    state.registers.push(NULL);
2732                    return Some((state.registers.len() - 1) as u16);
2733                }
2734                if let Some(&id) = state.const_registers.get(&NULL) {
2735                    Some(id)
2736                } else {
2737                    let id = state.registers.len() as u16;
2738                    state.const_registers.insert(NULL, id);
2739                    state.registers.push(NULL);
2740                    Some(id)
2741                }
2742            }
2743            Self::Bool(bool) => {
2744                debug_assert!(uses_id);
2745                if var_assignment {
2746                    state.registers.push((*bool).into());
2747                    return Some((state.registers.len() - 1) as u16);
2748                }
2749                let data: Data = (*bool).into();
2750                if let Some(&id) = state.const_registers.get(&data) {
2751                    Some(id)
2752                } else {
2753                    let id = state.registers.len() as u16;
2754                    state.const_registers.insert(data, id);
2755                    state.registers.push(data);
2756                    Some(id)
2757                }
2758            }
2759            Self::Var(name, span) => {
2760                debug_assert!(uses_id);
2761                if let Some(Variable {
2762                    name: _,
2763                    register_id,
2764                    var_type: _,
2765                }) = v.iter().rfind(|v_temp| *name == v_temp.name)
2766                {
2767                    Some(*register_id)
2768                } else if let Some((enum_id, variant_idx)) =
2769                    resolve_enum_variant(std::slice::from_ref(name), state)
2770                {
2771                    Some(compile_enum_construction(
2772                        enum_id,
2773                        variant_idx,
2774                        &[],
2775                        *span,
2776                        &[],
2777                        v,
2778                        ctx,
2779                        state,
2780                        output,
2781                    ))
2782                } else {
2783                    compiler_errors::error_unknown_variable(
2784                        name,
2785                        *span,
2786                        v,
2787                        ctx.file_idx,
2788                        state.sources,
2789                    );
2790                }
2791            }
2792            Self::Array(array_items, spans) => {
2793                debug_assert!(uses_id);
2794                Some(compile_array_literal(
2795                    array_items,
2796                    spans,
2797                    v,
2798                    ctx,
2799                    state,
2800                    output,
2801                ))
2802            }
2803            Self::Struct(namespace, fields, span) => {
2804                debug_assert!(uses_id);
2805                Some(compile_struct_literal(
2806                    namespace, fields, *span, v, ctx, state, output,
2807                ))
2808            }
2809            Self::Map(kv_pairs, span) => {
2810                debug_assert!(uses_id);
2811                Some(compile_map_literal(kv_pairs, *span, v, ctx, state, output))
2812            }
2813            Self::GetStructField(struct_expr, field, struct_span, field_span) => {
2814                debug_assert!(uses_id);
2815                Some(compile_struct_field_access(
2816                    struct_expr,
2817                    field,
2818                    *struct_span,
2819                    *field_span,
2820                    v,
2821                    ctx,
2822                    state,
2823                    output,
2824                ))
2825            }
2826            // array[index]
2827            Self::ArrayGetIndex(array, index, span) => {
2828                debug_assert!(uses_id);
2829                Some(compile_array_indexing(
2830                    array, index, *span, v, ctx, state, output,
2831                ))
2832            }
2833            // array[start..end]
2834            Self::ArrayGetSlice(array, idx_start, idx_end, span) => {
2835                debug_assert!(uses_id);
2836                Some(compile_array_slice(
2837                    array, idx_start, idx_end, *span, v, ctx, state, output,
2838                ))
2839            }
2840            Self::Mul(l, r, span1, span2) => {
2841                debug_assert!(uses_id);
2842                Some(uniform_op2(
2843                    Instr::MulFloat,
2844                    &DataType::Float,
2845                    Instr::MulInt,
2846                    &DataType::Int,
2847                    "*",
2848                    l,
2849                    r,
2850                    *span1,
2851                    *span2,
2852                    tgt_id,
2853                    v,
2854                    ctx,
2855                    state,
2856                    output,
2857                ))
2858            }
2859            Self::Div(l, r, span1, span2) => {
2860                debug_assert!(uses_id);
2861                Some(compile_div_op(
2862                    l, r, *span1, *span2, tgt_id, v, ctx, state, output,
2863                ))
2864            }
2865            Self::Add(l, r, span1, span2) => {
2866                debug_assert!(uses_id);
2867                Some(compile_add_op(
2868                    l, r, *span1, *span2, tgt_id, v, ctx, state, output,
2869                ))
2870            }
2871            Self::Sub(l, r, span1, span2) => {
2872                debug_assert!(uses_id);
2873                Some(compile_sub_op(
2874                    l, r, *span1, *span2, tgt_id, v, ctx, state, output,
2875                ))
2876            }
2877            Self::Mod(l, r, span1, span2) => {
2878                debug_assert!(uses_id);
2879                Some(compile_mod_op(
2880                    l, r, *span1, *span2, tgt_id, v, ctx, state, output,
2881                ))
2882            }
2883            Self::Pow(l, r, span1, span2) => {
2884                debug_assert!(uses_id);
2885                Some(uniform_op2(
2886                    Instr::PowFloat,
2887                    &DataType::Float,
2888                    Instr::PowInt,
2889                    &DataType::Int,
2890                    "^",
2891                    l,
2892                    r,
2893                    *span1,
2894                    *span2,
2895                    tgt_id,
2896                    v,
2897                    ctx,
2898                    state,
2899                    output,
2900                ))
2901            }
2902            Self::Eq(l, r) => {
2903                debug_assert!(uses_id);
2904                Some(compile_eq_op(l, r, tgt_id, v, ctx, state, output))
2905            }
2906            Self::NotEq(l, r) => {
2907                debug_assert!(uses_id);
2908                Some(compile_neq_op(l, r, tgt_id, v, ctx, state, output))
2909            }
2910            Self::Sup(l, r, span1, span2) => {
2911                debug_assert!(uses_id);
2912                Some(uniform_op2(
2913                    Instr::SupFloat,
2914                    &DataType::Float,
2915                    Instr::SupInt,
2916                    &DataType::Int,
2917                    ">",
2918                    l,
2919                    r,
2920                    *span1,
2921                    *span2,
2922                    tgt_id,
2923                    v,
2924                    ctx,
2925                    state,
2926                    output,
2927                ))
2928            }
2929            Self::SupEq(l, r, span1, span2) => {
2930                debug_assert!(uses_id);
2931                Some(uniform_op2(
2932                    Instr::SupEqFloat,
2933                    &DataType::Float,
2934                    Instr::SupEqInt,
2935                    &DataType::Int,
2936                    ">=",
2937                    l,
2938                    r,
2939                    *span1,
2940                    *span2,
2941                    tgt_id,
2942                    v,
2943                    ctx,
2944                    state,
2945                    output,
2946                ))
2947            }
2948            Self::Inf(l, r, span1, span2) => {
2949                debug_assert!(uses_id);
2950                Some(uniform_op2(
2951                    Instr::InfFloat,
2952                    &DataType::Float,
2953                    Instr::InfInt,
2954                    &DataType::Int,
2955                    "<",
2956                    l,
2957                    r,
2958                    *span1,
2959                    *span2,
2960                    tgt_id,
2961                    v,
2962                    ctx,
2963                    state,
2964                    output,
2965                ))
2966            }
2967            Self::InfEq(l, r, span1, span2) => {
2968                debug_assert!(uses_id);
2969                Some(uniform_op2(
2970                    Instr::InfEqFloat,
2971                    &DataType::Float,
2972                    Instr::InfEqInt,
2973                    &DataType::Int,
2974                    "<=",
2975                    l,
2976                    r,
2977                    *span1,
2978                    *span2,
2979                    tgt_id,
2980                    v,
2981                    ctx,
2982                    state,
2983                    output,
2984                ))
2985            }
2986            Self::BoolAnd(l, r, span1, span2) => {
2987                debug_assert!(uses_id);
2988                Some(compile_short_circuit_value(
2989                    l, r, *span1, *span2, "&&", tgt_id, v, ctx, state, output,
2990                ))
2991            }
2992            Self::BoolOr(l, r, span1, span2) => {
2993                debug_assert!(uses_id);
2994                Some(compile_short_circuit_value(
2995                    l, r, *span1, *span2, "||", tgt_id, v, ctx, state, output,
2996                ))
2997            }
2998            Self::Neg(l, span1, span2) => {
2999                debug_assert!(uses_id);
3000                Some(compile_neg_op(
3001                    l, *span1, *span2, tgt_id, v, ctx, state, output,
3002                ))
3003            }
3004            Self::BoolNeg(l, span1, span2) => {
3005                debug_assert!(uses_id);
3006                Some(compile_bool_neg_op(
3007                    l, *span1, *span2, tgt_id, v, ctx, state, output,
3008                ))
3009            }
3010            Self::InlineCondition(main_condition, code, span) => {
3011                debug_assert!(uses_id);
3012                Some(compile_inline_condition(
3013                    main_condition,
3014                    code,
3015                    *span,
3016                    v,
3017                    ctx,
3018                    state,
3019                    output,
3020                    tgt_id,
3021                ))
3022            }
3023            Self::FunctionCall(args, namespace, markers, args_indexes) if uses_id => Some(
3024                handle_functions(
3025                    output,
3026                    v,
3027                    ctx,
3028                    state,
3029                    tgt_id,
3030                    args,
3031                    namespace,
3032                    *markers,
3033                    args_indexes,
3034                )
3035                .unwrap_or_else(|| {
3036                    if let Some(&id) = state.const_registers.get(&NULL) {
3037                        id
3038                    } else {
3039                        let id = state.registers.len() as u16;
3040                        state.const_registers.insert(NULL, id);
3041                        state.registers.push(NULL);
3042                        id
3043                    }
3044                }),
3045            ),
3046            Self::AnonymousFunction(_, _, _) => {
3047                debug_assert!(uses_id);
3048                if let Some(&id) = state.const_registers.get(&NULL) {
3049                    Some(id)
3050                } else {
3051                    let id = state.registers.len() as u16;
3052                    state.const_registers.insert(NULL, id);
3053                    state.registers.push(NULL);
3054                    Some(id)
3055                }
3056            }
3057
3058            // ------------------
3059            // --- STATEMENTS ---
3060            // ------------------
3061
3062            // x[y] = z;
3063            Self::ArrayModify(array, index, value, index_markers, elem_markers) => {
3064                debug_assert!(!uses_id);
3065                compile_array_index_assignment(
3066                    array,
3067                    index,
3068                    value,
3069                    *index_markers,
3070                    *elem_markers,
3071                    v,
3072                    ctx,
3073                    state,
3074                    output,
3075                );
3076                None
3077            }
3078            Self::SetStructField(
3079                struct_expr,
3080                field,
3081                new_val,
3082                struct_span,
3083                field_span,
3084                value_span,
3085            ) => {
3086                debug_assert!(!uses_id);
3087                compile_struct_field_assignment(
3088                    struct_expr,
3089                    field,
3090                    new_val,
3091                    *struct_span,
3092                    *field_span,
3093                    *value_span,
3094                    v,
3095                    ctx,
3096                    state,
3097                    output,
3098                );
3099                None
3100            }
3101            Self::Condition(main_condition, code, _) => {
3102                debug_assert!(!uses_id);
3103                compile_condition(main_condition, code, v, ctx, state, output);
3104                None
3105            }
3106            Self::WhileBlock(condition, code) => {
3107                debug_assert!(!uses_id);
3108                compile_while_loop(condition, code, v, ctx, state, output);
3109                None
3110            }
3111            Self::ForLoop(var_name, array, code, span) => {
3112                debug_assert!(!uses_id);
3113                compile_for_loop(var_name, array, code, *span, v, ctx, state, output);
3114                None
3115            }
3116            Self::IntForLoop(var_name, start_elem, end_elem, code, span1, span2) => {
3117                debug_assert!(!uses_id);
3118                compile_int_for_loop(
3119                    var_name, start_elem, end_elem, code, *span1, *span2, v, ctx, state, output,
3120                );
3121                None
3122            }
3123            Self::LoopBlock(code) => {
3124                debug_assert!(!uses_id);
3125                compile_loop_block(code, v, ctx, state, output);
3126                None
3127            }
3128            Self::TryCatchBlock(e, err_var, catch_code) => {
3129                debug_assert!(!uses_id);
3130                compile_try_catch_block(e, err_var, catch_code, v, ctx, state, output);
3131                None
3132            }
3133            Self::VarDeclare(name, value) => {
3134                debug_assert!(!uses_id);
3135                compile_var_declaration(name, value, remaining_code, v, ctx, state, output);
3136                None
3137            }
3138            Self::VarAssign(name, value, span) => {
3139                debug_assert!(!uses_id);
3140                compile_var_assignment(name, value, *span, v, ctx, state, output);
3141                None
3142            }
3143            Self::StructDeclare(name, fields, span) => {
3144                debug_assert!(!uses_id);
3145                compile_struct_definition(name, fields, *span, ctx, state, output);
3146                None
3147            }
3148            Self::EnumDeclare(name, variants, span) => {
3149                debug_assert!(!uses_id);
3150                compile_enum_definition(name, variants, *span, ctx, state);
3151                None
3152            }
3153            Self::Match(scrutinee, arms, wildcard, span) => {
3154                debug_assert!(!uses_id);
3155                compile_match(
3156                    scrutinee,
3157                    arms,
3158                    wildcard.as_deref(),
3159                    *span,
3160                    v,
3161                    ctx,
3162                    state,
3163                    output,
3164                );
3165                None
3166            }
3167            Self::NamespacedRef(path, span) => {
3168                debug_assert!(uses_id);
3169                if let Some((enum_id, variant_idx)) = resolve_enum_variant(path, state) {
3170                    Some(compile_enum_construction(
3171                        enum_id,
3172                        variant_idx,
3173                        &[],
3174                        *span,
3175                        &[],
3176                        v,
3177                        ctx,
3178                        state,
3179                        output,
3180                    ))
3181                } else {
3182                    compiler_errors::error_enum(
3183                        "Unknown enum variant",
3184                        &format!("{} does not name an enum variant", path.join("::")),
3185                        *span,
3186                        ctx.file_idx,
3187                        state.sources,
3188                    );
3189                }
3190            }
3191            Self::FunctionCall(args, namespace, markers, args_indexes) if !uses_id => {
3192                let output_id = handle_functions(
3193                    output,
3194                    v,
3195                    ctx,
3196                    state,
3197                    tgt_id,
3198                    args,
3199                    namespace,
3200                    *markers,
3201                    args_indexes,
3202                );
3203                if let Some(id) = output_id {
3204                    state.free_reg(id, v);
3205                }
3206                None
3207            }
3208            Self::ObjFunctionCall(obj, args, namespace, obj_span, fn_span, args_indexes)
3209                if !uses_id =>
3210            {
3211                let output_id = handle_method_calls(
3212                    output,
3213                    v,
3214                    ctx,
3215                    state,
3216                    tgt_id,
3217                    obj,
3218                    args,
3219                    namespace,
3220                    *obj_span,
3221                    *fn_span,
3222                    args_indexes,
3223                );
3224                if let Some(id) = output_id {
3225                    state.free_reg(id, v);
3226                }
3227                None
3228            }
3229            Self::ObjFunctionCall(obj, args, namespace, obj_span, fn_span, args_indexes)
3230                if uses_id =>
3231            {
3232                Some(
3233                    handle_method_calls(
3234                        output,
3235                        v,
3236                        ctx,
3237                        state,
3238                        tgt_id,
3239                        obj,
3240                        args,
3241                        namespace,
3242                        *obj_span,
3243                        *fn_span,
3244                        args_indexes,
3245                    )
3246                    .unwrap_or_else(|| {
3247                        if let Some(&id) = state.const_registers.get(&NULL) {
3248                            id
3249                        } else {
3250                            let id = state.registers.len() as u16;
3251                            state.const_registers.insert(NULL, id);
3252                            state.registers.push(NULL);
3253                            id
3254                        }
3255                    }),
3256                )
3257            }
3258            Self::FunctionDecl(fn_name, fn_args, fn_code, span, return_type) => {
3259                debug_assert!(!uses_id);
3260                compile_function_definition(
3261                    fn_name,
3262                    fn_args,
3263                    fn_code,
3264                    *span,
3265                    return_type.as_ref(),
3266                    v,
3267                    ctx,
3268                    state,
3269                    output,
3270                );
3271                None
3272            }
3273            Self::ReturnVal(return_value) => {
3274                debug_assert!(!uses_id);
3275                compile_return(return_value.as_ref().as_ref(), v, ctx, state, output);
3276                None
3277            }
3278            Self::Break => {
3279                debug_assert!(!uses_id);
3280                compile_loop_break(ctx, output);
3281                None
3282            }
3283            Self::Continue => {
3284                debug_assert!(!uses_id);
3285                compile_loop_continue(ctx, output);
3286                None
3287            }
3288            Self::EvalBlock(code) => {
3289                debug_assert!(!uses_id);
3290                compile_eval_block(code, v, ctx, state, output);
3291                None
3292            }
3293            _ => unsafe { unreachable_unchecked() },
3294        }
3295    }
3296}
3297
3298#[cfg(target_arch = "aarch64")]
3299const ARCH_SUFFIX: &str = "-aarch64";
3300#[cfg(target_arch = "x86_64")]
3301const ARCH_SUFFIX: &str = "-x86_64";
3302#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
3303const ARCH_SUFFIX: &str = "";
3304
3305#[derive(Debug, Copy, Clone)]
3306pub enum SymbolKind {
3307    Fn(u16),
3308    Struct(u16),
3309    Enum(u16),
3310}
3311
3312/// Whether two symbols are the same underlying definition (same kind, same
3313/// id in the global fn/struct/enum tables).
3314const fn symbol_ids_equal(a: SymbolKind, b: SymbolKind) -> bool {
3315    match (a, b) {
3316        (SymbolKind::Fn(x), SymbolKind::Fn(y))
3317        | (SymbolKind::Struct(x), SymbolKind::Struct(y))
3318        | (SymbolKind::Enum(x), SymbolKind::Enum(y)) => x == y,
3319        _ => false,
3320    }
3321}
3322
3323#[derive(Debug, Clone, Default)]
3324pub struct Namespace {
3325    pub symbols: Vec<(SmolStr, SymbolKind)>,
3326    pub children: Vec<(SmolStr, Self)>,
3327}
3328
3329impl Namespace {
3330    pub fn fns(&self) -> impl Iterator<Item = &(SmolStr, SymbolKind)> {
3331        self.symbols
3332            .iter()
3333            .filter(|(_, kind)| matches!(kind, SymbolKind::Fn(_)))
3334    }
3335    pub fn structs(&self) -> impl Iterator<Item = &(SmolStr, SymbolKind)> {
3336        self.symbols
3337            .iter()
3338            .filter(|(_, kind)| matches!(kind, SymbolKind::Struct(_)))
3339    }
3340    #[must_use]
3341    pub fn find_function(
3342        &self,
3343        path: &[SmolStr],
3344        function_name: &str,
3345        span: Span,
3346        file_idx: u16,
3347        sources: &[Source],
3348    ) -> Option<usize> {
3349        self.walk_to_namespace(path, span, file_idx, sources)
3350            .symbols
3351            .iter()
3352            .find_map(|(name, kind)| {
3353                if name.as_str() == function_name
3354                    && let SymbolKind::Fn(fn_id) = kind
3355                {
3356                    Some(*fn_id as usize)
3357                } else {
3358                    None
3359                }
3360            })
3361    }
3362    #[must_use]
3363    pub fn find_struct(
3364        &self,
3365        path: &[SmolStr],
3366        struct_name: &str,
3367        span: Span,
3368        file_idx: u16,
3369        sources: &[Source],
3370    ) -> Option<usize> {
3371        self.walk_to_namespace(path, span, file_idx, sources)
3372            .symbols
3373            .iter()
3374            .find_map(|(name, kind)| {
3375                if name.as_str() == struct_name
3376                    && let SymbolKind::Struct(struct_id) = kind
3377                {
3378                    Some(*struct_id as usize)
3379                } else {
3380                    None
3381                }
3382            })
3383    }
3384    /// Resolves an enum type id by name (with an optional module path). Returns
3385    /// `None` when no enum by that name exists in the resolved namespace; never
3386    /// raises a compile error itself so it can be used for speculative
3387    /// enum-variant resolution against otherwise-unknown call/reference paths.
3388    #[must_use]
3389    pub fn find_enum(&self, path: &[SmolStr], enum_name: &str) -> Option<usize> {
3390        let mut current = self;
3391        for sub in path {
3392            current = &current.children.iter().find(|(name, _)| name == sub)?.1;
3393        }
3394        current.symbols.iter().find_map(|(name, kind)| {
3395            if name.as_str() == enum_name
3396                && let SymbolKind::Enum(enum_id) = kind
3397            {
3398                Some(*enum_id as usize)
3399            } else {
3400                None
3401            }
3402        })
3403    }
3404    /// Resolves a namespaced function without raising a compile error when the
3405    /// namespace or function is absent. Used by the array-method auto-prelude,
3406    /// which routes `arr.map(f)` to `list::map` only when that module resolved.
3407    #[must_use]
3408    pub fn try_find_function(&self, path: &[SmolStr], function_name: &str) -> Option<usize> {
3409        let mut current = self;
3410        for sub in path {
3411            current = &current.children.iter().find(|(name, _)| name == sub)?.1;
3412        }
3413        current.symbols.iter().find_map(|(name, kind)| {
3414            if name.as_str() == function_name
3415                && let SymbolKind::Fn(fn_id) = kind
3416            {
3417                Some(*fn_id as usize)
3418            } else {
3419                None
3420            }
3421        })
3422    }
3423    #[must_use]
3424    pub fn walk_to_namespace(
3425        &self,
3426        path: &[SmolStr],
3427        span: Span,
3428        file_idx: u16,
3429        sources: &[Source],
3430    ) -> &Self {
3431        let mut current = self;
3432        for sub in path {
3433            current = if let Some((_, child_namespace)) =
3434                current.children.iter().find(|(name, _)| name == sub)
3435            {
3436                child_namespace
3437            } else {
3438                error_unknown_namespace(path, span, file_idx, sources);
3439            };
3440        }
3441        current
3442    }
3443}
3444
3445/// Loads the `std/list` module as an implicit `list` child namespace so its
3446/// higher-order helpers work as array methods (`arr.map(f)`) with no explicit
3447/// import. Resolution mirrors the library-import path (`CANDELA_LIB_PATH` or
3448/// `libs/` beside the executable); a missing library directory is not an error,
3449/// the prelude is absent.
3450#[cfg(not(target_arch = "wasm32"))]
3451fn load_auto_prelude(
3452    fns: &mut Vec<Function>,
3453    structs: &mut Vec<Struct>,
3454    enums: &mut Vec<EnumType>,
3455    fn_registers: &mut Vec<Vec<u16>>,
3456    dynamic_libs: &mut Vec<Dynamiclib>,
3457    sources: &mut Vec<Source>,
3458    namespace: &mut Namespace,
3459    files: &mut FxHashMap<PathBuf, Namespace>,
3460    file_namespaces: &mut FxHashMap<u16, Namespace>,
3461    pending_structs: &mut Vec<(u16, u16, Box<[(SmolStr, TypeExpr, Span)]>)>,
3462    pending_enums: &mut PendingEnums,
3463    pending_fns: &mut Vec<(
3464        u16,
3465        u16,
3466        Box<[(SmolStr, Option<TypeExpr>)]>,
3467        Option<(TypeExpr, Span)>,
3468    )>,
3469    pending_dylibs: &mut Vec<(
3470        u16,
3471        u16,
3472        Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>,
3473        Rc<Library>,
3474        SmolStr,
3475        Span,
3476    )>,
3477    pending_host: &mut Vec<(
3478        u16,
3479        u16,
3480        Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>,
3481        SmolStr,
3482        Span,
3483    )>,
3484) {
3485    const PRELUDE_REL: &str = "std/list.cdl";
3486    const PRELUDE_CHILD: &str = "list";
3487
3488    if namespace
3489        .children
3490        .iter()
3491        .any(|(name, _)| name.as_str() == PRELUDE_CHILD)
3492    {
3493        return;
3494    }
3495
3496    let path = if let Some(base) = std::env::var_os("CANDELA_LIB_PATH") {
3497        PathBuf::from(base).join(PRELUDE_REL)
3498    } else if let Ok(exe) = std::env::current_exe() {
3499        exe.canonicalize()
3500            .unwrap_or(exe)
3501            .parent()
3502            .unwrap_or_else(|| Path::new("."))
3503            .join("libs")
3504            .join(PRELUDE_REL)
3505    } else {
3506        return;
3507    };
3508
3509    if let Some(cached) = files.get(&path) {
3510        namespace
3511            .children
3512            .push((PRELUDE_CHILD.into(), cached.clone()));
3513        return;
3514    }
3515
3516    let Ok(contents) = std::fs::read_to_string(&path) else {
3517        return;
3518    };
3519
3520    let child_src_idx = sources.len() as u16;
3521    let file_name: SmolStr = path.to_str().unwrap_or(PRELUDE_REL).into();
3522    sources.push(Source {
3523        filename: file_name,
3524        contents,
3525    });
3526    let file_code = parser::parse(&sources.last().unwrap().contents, sources.last().unwrap());
3527
3528    let mut child_namespace = Namespace {
3529        symbols: Vec::new(),
3530        children: Vec::new(),
3531    };
3532    parse_toplevel(
3533        file_code,
3534        &path,
3535        child_src_idx,
3536        fns,
3537        structs,
3538        enums,
3539        fn_registers,
3540        dynamic_libs,
3541        sources,
3542        &mut child_namespace,
3543        files,
3544        file_namespaces,
3545        pending_structs,
3546        pending_enums,
3547        pending_fns,
3548        pending_dylibs,
3549        pending_host,
3550    );
3551    files.insert(path, child_namespace.clone());
3552    namespace
3553        .children
3554        .push((PRELUDE_CHILD.into(), child_namespace));
3555}
3556
3557/// Opens the dynamic library a `dylib` import resolved to.
3558///
3559/// `filename` is the OS-mapped name (`resolve_library_filename`'s output) for a
3560/// logical import, or the already-resolved explicit path otherwise.
3561///
3562/// A bare filename handed to the OS loader (`dlopen` on Linux/macOS) is
3563/// resolved only through the system search path (the run-path,
3564/// `LD_LIBRARY_PATH`, `ld.so.cache`, `/lib`, `/usr/lib`), never the current
3565/// directory or the importing file's directory. Windows' `LoadLibraryA`
3566/// differs: it also searches the application directory and the current
3567/// directory by default. A library built to sit next to the `.cdl` file
3568/// (rather than installed as a system library) therefore loads on Windows but
3569/// silently fails on Linux/macOS.
3570///
3571/// To match Windows' default search order, a logical import is tried, in
3572/// order: next to the importing file, then in the current directory, and only
3573/// then handed to the OS loader bare, so genuine system libraries (`z`, `m`,
3574/// `sqlite3`) still resolve exactly as before.
3575#[cfg(not(target_arch = "wasm32"))]
3576fn open_dylib(file_path: &Path, filename: &str, is_logical: bool) -> Option<libloading::Library> {
3577    if is_logical {
3578        let file_dir = file_path.parent().unwrap_or_else(|| Path::new("."));
3579        let mut candidates = vec![file_dir.join(filename)];
3580        let cwd_candidate = Path::new(".").join(filename);
3581        if !candidates.contains(&cwd_candidate) {
3582            candidates.push(cwd_candidate);
3583        }
3584        for candidate in &candidates {
3585            if let Ok(lib) = unsafe { libloading::Library::new(candidate) } {
3586                return Some(lib);
3587            }
3588        }
3589    }
3590    unsafe { libloading::Library::new(filename) }.ok()
3591}
3592
3593/// Recursively collects functions, dyn libs, and imported files
3594/// Deferred enum-payload resolution: `(enum_id, src_file_idx, variants)`, filled
3595/// in `resolve_types` after every type name is registered, so an enum payload
3596/// may reference a type declared later.
3597type PendingEnums = Vec<(u16, u16, Box<[(SmolStr, Box<[TypeExpr]>, Span)]>)>;
3598
3599fn parse_toplevel(
3600    code: Vec<Expr>,
3601    file_path: &Path,
3602    src_file_idx: u16,
3603    fns: &mut Vec<Function>,
3604    structs: &mut Vec<Struct>,
3605    enums: &mut Vec<EnumType>,
3606    fn_registers: &mut Vec<Vec<u16>>,
3607    dynamic_libs: &mut Vec<Dynamiclib>,
3608    sources: &mut Vec<Source>,
3609    namespace: &mut Namespace,
3610    files: &mut FxHashMap<PathBuf, Namespace>,
3611    file_namespaces: &mut FxHashMap<u16, Namespace>,
3612    pending_structs: &mut Vec<(u16, u16, Box<[(SmolStr, TypeExpr, Span)]>)>,
3613    pending_enums: &mut PendingEnums,
3614    pending_fns: &mut Vec<(
3615        u16,
3616        u16,
3617        Box<[(SmolStr, Option<TypeExpr>)]>,
3618        Option<(TypeExpr, Span)>,
3619    )>,
3620    #[cfg(not(target_arch = "wasm32"))] pending_dylibs: &mut Vec<(
3621        u16,
3622        u16,
3623        Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>,
3624        Rc<Library>,
3625        // Library spec exactly as written in the source (logical name or path),
3626        // carried to the artifact recipe so a `.cdlb` re-resolves it by name.
3627        SmolStr,
3628        Span,
3629    )>,
3630    pending_host: &mut Vec<(
3631        u16,
3632        u16,
3633        Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>,
3634        SmolStr,
3635        Span,
3636    )>,
3637) {
3638    let mut imports = Vec::new();
3639    for expr in code {
3640        match expr {
3641            Expr::FunctionDecl(fn_name, fn_args, fn_code, span, fn_return_type) => {
3642                if let Some((_, SymbolKind::Fn(func_id))) =
3643                    namespace.symbols.iter().rfind(|(f, _)| f == &fn_name)
3644                {
3645                    let func = &fns[*func_id as usize];
3646                    compiler_errors::error_function_already_defined(
3647                        func,
3648                        span,
3649                        src_file_idx,
3650                        sources,
3651                    );
3652                }
3653                fn_registers.push(Vec::new());
3654                let returns_void = check_if_returns_void(&fn_code);
3655                let mut callees = Vec::new();
3656                collect_direct_fn_calls(&fn_code, &mut callees);
3657
3658                let fn_id = fns.len() as u16;
3659                fns.push(Function {
3660                    name: fn_name.clone(),
3661                    args: Box::new([]),
3662                    code: fn_code,
3663                    impls: Vec::new(),
3664                    is_recursive: None,
3665                    returns_null: returns_void,
3666                    src_file: src_file_idx,
3667                    return_type_cache: Vec::new(),
3668                    direct_calls: callees.into_boxed_slice(),
3669                    name_span: span,
3670                    // Resolved with the argument types once every file's
3671                    // namespace is known; see the `pending_fns` drain.
3672                    return_type: None,
3673                });
3674                pending_fns.push((fn_id, src_file_idx, fn_args, fn_return_type));
3675                namespace.symbols.push((fn_name, SymbolKind::Fn(fn_id)));
3676            }
3677            Expr::StructDeclare(name, fields, span) => {
3678                let struct_id = structs.len() as u16;
3679                structs.push(Struct {
3680                    name: name.clone(),
3681                    fields: Box::from([]),
3682                    id: struct_id,
3683                    name_span: span,
3684                });
3685                namespace
3686                    .symbols
3687                    .push((name, SymbolKind::Struct(struct_id)));
3688                pending_structs.push((struct_id, src_file_idx, fields));
3689            }
3690            Expr::EnumDeclare(name, variants, span) => {
3691                let enum_id = enums.len() as u16;
3692                enums.push(EnumType {
3693                    name: name.clone(),
3694                    variants: Box::from([]),
3695                    id: enum_id,
3696                    name_span: span,
3697                });
3698                namespace.symbols.push((name, SymbolKind::Enum(enum_id)));
3699                pending_enums.push((enum_id, src_file_idx, variants));
3700            }
3701            #[cfg(target_arch = "wasm32")]
3702            Expr::ImportDylib(..) => wasm_error("WASM does not support loading dynamic libraries"),
3703            #[cfg(target_arch = "wasm32")]
3704            Expr::ImportFile(..) => wasm_error("WASM does not support importing files"),
3705            import @ (Expr::ImportFile(..) | Expr::ImportDylib(..) | Expr::HostBlock(..)) => {
3706                imports.push(import);
3707            }
3708            _ => {}
3709        }
3710    }
3711
3712    files.insert(file_path.to_path_buf(), namespace.clone());
3713
3714    // Auto-prelude: make the std::list array methods (map/filter/reduce and
3715    // friends) callable as methods on arrays without an explicit import. This is
3716    // best-effort: if the shipped library directory is not present (for
3717    // example an embedding host with no `libs/` tree), the prelude is skipped and
3718    // array methods resolve as they did before.
3719    #[cfg(not(target_arch = "wasm32"))]
3720    if src_file_idx == 0 {
3721        load_auto_prelude(
3722            fns,
3723            structs,
3724            enums,
3725            fn_registers,
3726            dynamic_libs,
3727            sources,
3728            namespace,
3729            files,
3730            file_namespaces,
3731            pending_structs,
3732            pending_enums,
3733            pending_fns,
3734            pending_dylibs,
3735            pending_host,
3736        );
3737    }
3738
3739    // Names merged into this file's scope by bare imports, with the module
3740    // each came from; consulted to report both sources on a collision.
3741    let mut merged_symbol_origins: Vec<(SmolStr, SmolStr)> = Vec::new();
3742
3743    for import in imports {
3744        match import {
3745            #[cfg(not(target_arch = "wasm32"))]
3746            Expr::ImportDylib(path, fn_signatures, span) => {
3747                // The spec exactly as written; recorded in the artifact recipe so
3748                // a `.cdlb` re-resolves the library by name (per-OS) at load.
3749                let spec = path.clone();
3750                // A bare logical name (no path separator, not absolute, no
3751                // extension, e.g. `z`, `sqlite3`) names a system library the
3752                // OS loader searches for. Anything with a separator or extension
3753                // is an explicit path resolved relative to the importing file.
3754                let is_logical = !spec.contains('/')
3755                    && !spec.contains('\\')
3756                    && !Path::new(spec.as_str()).is_absolute()
3757                    && Path::new(spec.as_str()).extension().is_none();
3758
3759                let (open_target, dylib_name): (SmolStr, SmolStr) = if is_logical {
3760                    // e.g. `z` -> `libz.so` / `libz.dylib` / `z.dll`. Resolution
3761                    // of where that file is found happens in `open_dylib` below.
3762                    (
3763                        resolve_library_filename(spec.as_str(), TargetOs::CURRENT).into(),
3764                        spec.clone(),
3765                    )
3766                } else {
3767                    let base_path = if Path::new(spec.as_str()).is_relative() {
3768                        file_path
3769                            .parent()
3770                            .unwrap_or_else(|| Path::new("."))
3771                            .join(spec.as_str())
3772                            .to_string_lossy()
3773                            .to_smolstr()
3774                    } else {
3775                        spec.clone()
3776                    };
3777                    let dylib_name = PathBuf::from(base_path.as_str())
3778                        .file_prefix()
3779                        .and_then(|s| s.to_str())
3780                        .unwrap_or(base_path.as_str())
3781                        .to_smolstr();
3782                    // When the extension is omitted, prefer an arch-specific
3783                    // build if one is present next to the base path, else fall
3784                    // back to the per-OS filename convention.
3785                    let resolved = if Path::new(base_path.as_str()).extension().is_none() {
3786                        let arch_path = format!(
3787                            "{base_path}{ARCH_SUFFIX}.{}",
3788                            TargetOs::CURRENT.dynamic_lib_extension()
3789                        );
3790                        if Path::new(&arch_path).exists() {
3791                            SmolStr::from(arch_path)
3792                        } else {
3793                            resolve_library_filename(base_path.as_str(), TargetOs::CURRENT).into()
3794                        }
3795                    } else {
3796                        base_path
3797                    };
3798                    (resolved, dylib_name)
3799                };
3800
3801                let lib = Rc::new(
3802                    open_dylib(file_path, open_target.as_str(), is_logical)
3803                        .unwrap_or_else(|| error_cannot_load_dynlib(span, src_file_idx, sources)),
3804                );
3805                pending_dylibs.push((
3806                    src_file_idx,
3807                    dynamic_libs.len() as u16,
3808                    fn_signatures,
3809                    lib,
3810                    spec,
3811                    span,
3812                ));
3813                dynamic_libs.push(Dynamiclib {
3814                    name: dylib_name,
3815                    fns: Box::new([]),
3816                    is_host: false,
3817                });
3818            }
3819            Expr::HostBlock(host_namespace, fn_signatures, span) => {
3820                pending_host.push((
3821                    src_file_idx,
3822                    dynamic_libs.len() as u16,
3823                    fn_signatures,
3824                    host_namespace.clone(),
3825                    span,
3826                ));
3827                dynamic_libs.push(Dynamiclib {
3828                    name: host_namespace,
3829                    fns: Box::new([]),
3830                    is_host: true,
3831                });
3832            }
3833            Expr::ImportFile(path, alias, is_logical, span) => {
3834                // The shipped library directory: `CANDELA_LIB_PATH` overrides its
3835                // location (it names the `libs/` dir that holds `std/` and, for the
3836                // C-backed modules, `std_src/`); otherwise it is `libs/` beside the
3837                // running executable, which is where the toolchain installs it. This
3838                // is the single source of truth for the default std location.
3839                let shipped_lib = |path: &SmolStr| -> Option<PathBuf> {
3840                    if let Some(base) = std::env::var_os("CANDELA_LIB_PATH") {
3841                        return Some(PathBuf::from(base).join(path.as_str()));
3842                    }
3843                    std::env::current_exe().ok().map(|p| {
3844                        p.canonicalize()
3845                            .unwrap_or(p)
3846                            .parent()
3847                            .unwrap_or_else(|| Path::new("."))
3848                            .join("libs")
3849                            .join(path.as_str())
3850                    })
3851                };
3852
3853                let file_path = if is_logical {
3854                    // A library import (`import "std/string";`, extensionless)
3855                    // resolves against the shipped library directory only, never
3856                    // source-relative, so it works from any working directory
3857                    // with nothing set.
3858                    shipped_lib(&path).unwrap_or_else(|| {
3859                        error_cannot_read_file(span, src_file_idx, sources);
3860                    })
3861                } else {
3862                    // A `.cdl` file import resolves next to the importing file first,
3863                    // then falls back to the shipped library directory.
3864                    file_path
3865                        .parent()
3866                        .unwrap_or_else(|| Path::new("."))
3867                        .join(path.as_str())
3868                        .canonicalize()
3869                        .unwrap_or_else(|_| {
3870                            shipped_lib(&path).unwrap_or_else(|| {
3871                                error_cannot_read_file(span, src_file_idx, sources);
3872                            })
3873                        })
3874                };
3875
3876                let child_namespace = if let Some(cached) = files.get(&file_path) {
3877                    cached.clone()
3878                } else {
3879                    let file_contents = std::fs::read_to_string(&file_path).unwrap_or_else(|_| {
3880                        error_cannot_read_file(span, src_file_idx, sources);
3881                    });
3882                    let file_name: SmolStr = file_path.to_str().unwrap_or(path.as_str()).into();
3883
3884                    let child_src_idx = sources.len() as u16;
3885
3886                    sources.push(Source {
3887                        filename: file_name.clone(),
3888                        contents: file_contents,
3889                    });
3890
3891                    // Parse the imported file's contents
3892                    let file_code =
3893                        parser::parse(&sources.last().unwrap().contents, sources.last().unwrap());
3894
3895                    let mut child_namespace = Namespace {
3896                        symbols: Vec::new(),
3897                        children: Vec::new(),
3898                    };
3899
3900                    parse_toplevel(
3901                        file_code,
3902                        &file_path,
3903                        child_src_idx,
3904                        fns,
3905                        structs,
3906                        enums,
3907                        fn_registers,
3908                        dynamic_libs,
3909                        sources,
3910                        &mut child_namespace,
3911                        files,
3912                        file_namespaces,
3913                        pending_structs,
3914                        pending_enums,
3915                        pending_fns,
3916                        #[cfg(not(target_arch = "wasm32"))]
3917                        pending_dylibs,
3918                        pending_host,
3919                    );
3920                    files.insert(file_path.clone(), child_namespace.clone());
3921                    child_namespace
3922                };
3923
3924                if let Some(alias) = alias {
3925                    // `import "..." as name;` binds the module under a
3926                    // namespace: its symbols are reachable as `name::symbol`.
3927                    namespace.children.push((alias, child_namespace));
3928                } else {
3929                    // A bare import merges the module's symbols into this
3930                    // file's own scope. The module path as written, used to
3931                    // name the source in a collision error.
3932                    let module_display: SmolStr = if is_logical {
3933                        path.strip_suffix(".cdl").unwrap_or(path.as_str()).into()
3934                    } else {
3935                        path.clone()
3936                    };
3937                    for (name, kind) in child_namespace.symbols {
3938                        if let Some((_, existing)) =
3939                            namespace.symbols.iter().find(|(n, _)| n == &name)
3940                        {
3941                            // The same underlying symbol arriving through two
3942                            // routes (for example two modules that both import
3943                            // a third) is not a conflict.
3944                            if symbol_ids_equal(*existing, kind) {
3945                                continue;
3946                            }
3947                            let existing_origin = merged_symbol_origins
3948                                .iter()
3949                                .find(|(n, _)| n == &name)
3950                                .map_or_else(
3951                                    || String::from("defined in this file"),
3952                                    |(_, module)| format!("imported from \"{module}\""),
3953                                );
3954                            compiler_errors::error_import_symbol_collision(
3955                                &name,
3956                                &existing_origin,
3957                                &module_display,
3958                                span,
3959                                src_file_idx,
3960                                sources,
3961                            );
3962                        }
3963                        merged_symbol_origins.push((name.clone(), module_display.clone()));
3964                        namespace.symbols.push((name, kind));
3965                    }
3966                }
3967            }
3968            _ => unsafe { unreachable_unchecked() },
3969        }
3970    }
3971    file_namespaces.insert(src_file_idx, namespace.clone());
3972}
3973
3974fn resolve_types(
3975    structs: &mut [Struct],
3976    enums: &mut [EnumType],
3977    fns: &mut [Function],
3978    pending_structs: Vec<(u16, u16, Box<[(SmolStr, TypeExpr, Span)]>)>,
3979    pending_enums: PendingEnums,
3980    pending_fns: Vec<(
3981        u16,
3982        u16,
3983        Box<[(SmolStr, Option<TypeExpr>)]>,
3984        Option<(TypeExpr, Span)>,
3985    )>,
3986    #[cfg(not(target_arch = "wasm32"))] pending_dylibs: Vec<(
3987        u16,
3988        u16,
3989        Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>,
3990        Rc<Library>,
3991        // Library spec exactly as written in the source (logical name or path),
3992        // carried to the artifact recipe so a `.cdlb` re-resolves it by name.
3993        SmolStr,
3994        Span,
3995    )>,
3996    pending_host: Vec<(
3997        u16,
3998        u16,
3999        Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>,
4000        SmolStr,
4001        Span,
4002    )>,
4003    file_namespaces: &FxHashMap<u16, Namespace>,
4004    dynamic_libs_fns: &mut Vec<DynamicLibFn>,
4005    host_fns: &mut Vec<HostFnSig>,
4006    dynamic_libs: &mut [Dynamiclib],
4007    sources: &[Source],
4008) {
4009    for (struct_id, src_file_idx, fields) in pending_structs {
4010        let resolved_fields = fields
4011            .iter()
4012            .map(|(field_name, field_type, field_span)| {
4013                (
4014                    field_name.clone(),
4015                    field_type.to_datatype(src_file_idx, &file_namespaces[&src_file_idx], sources),
4016                    *field_span,
4017                )
4018            })
4019            .collect();
4020        structs[struct_id as usize].fields = resolved_fields;
4021    }
4022    for (enum_id, src_file_idx, variants) in pending_enums {
4023        let resolved_variants = variants
4024            .iter()
4025            .map(|(variant_name, payload, name_span)| EnumVariant {
4026                name: variant_name.clone(),
4027                payload: payload
4028                    .iter()
4029                    .map(|t| t.to_datatype(src_file_idx, &file_namespaces[&src_file_idx], sources))
4030                    .collect(),
4031                name_span: *name_span,
4032            })
4033            .collect();
4034        enums[enum_id as usize].variants = resolved_variants;
4035    }
4036    for (fn_id, src_file_idx, args, return_type) in pending_fns {
4037        let resolved_args = args
4038            .iter()
4039            .map(|(arg_name, arg_type)| {
4040                (
4041                    arg_name.clone(),
4042                    arg_type.clone().map(|t_e| {
4043                        t_e.to_datatype(src_file_idx, &file_namespaces[&src_file_idx], sources)
4044                    }),
4045                )
4046            })
4047            .collect();
4048        fns[fn_id as usize].args = resolved_args;
4049        fns[fn_id as usize].return_type = return_type.map(|(t_e, t_span)| {
4050            (
4051                t_e.to_datatype(src_file_idx, &file_namespaces[&src_file_idx], sources),
4052                t_span,
4053            )
4054        });
4055    }
4056    #[cfg(not(target_arch = "wasm32"))]
4057    for (src_file_idx, dynlib_id, fn_signatures, lib, library_spec, span) in pending_dylibs {
4058        let namespace = &file_namespaces[&src_file_idx];
4059        let fns = fn_signatures
4060            .iter()
4061            .map(|(fn_name, fn_args, fn_return_type, fn_name_span)| {
4062                let fn_args = fn_args
4063                    .iter()
4064                    .map(|t| t.to_datatype(src_file_idx, namespace, sources))
4065                    .collect::<Vec<DataType>>()
4066                    .into_boxed_slice();
4067                let fn_return_type = fn_return_type.to_datatype(src_file_idx, namespace, sources);
4068                let return_val = FnSignature {
4069                    name: fn_name.clone(),
4070                    args: fn_args.clone(),
4071                    return_type: fn_return_type.clone(),
4072                    id: dynamic_libs_fns.len() as u16,
4073                    variadic: false,
4074                };
4075                let arg_types: Vec<_> = fn_args.iter().map(|t| t.to_c_type(structs)).collect();
4076                let return_type = fn_return_type.to_c_type(structs);
4077                let cif = libffi::middle::Cif::new(arg_types, return_type);
4078                let ptr = unsafe {
4079                    libffi::middle::CodePtr(
4080                        lib.get::<*const ()>(fn_name.as_bytes())
4081                            .unwrap_or_else(|_| {
4082                                error_cannot_find_dynlib_symbol(
4083                                    fn_name,
4084                                    *fn_name_span,
4085                                    span,
4086                                    src_file_idx,
4087                                    sources,
4088                                );
4089                            })
4090                            .try_as_raw_ptr()
4091                            .unwrap_unchecked(),
4092                    )
4093                };
4094
4095                let mut types = vec![fn_return_type];
4096                types.extend(fn_args);
4097
4098                dynamic_libs_fns.push(DynamicLibFn {
4099                    types: Box::from(types),
4100                    library: library_spec.clone(),
4101                    symbol: fn_name.clone(),
4102                    _lib: Rc::clone(&lib),
4103                    ptr,
4104                    cif,
4105                });
4106                return_val
4107            })
4108            .collect();
4109        dynamic_libs[dynlib_id as usize].fns = fns;
4110    }
4111
4112    // Resolve `host "..." { ... }` blocks. Unlike dylibs there is no shared
4113    // object to load or FFI CIF to build: each signature is type-checked and
4114    // recorded as a `HostFnSig` whose `id` the VM later uses to dispatch to the
4115    // Rust closure the embedding `Engine` bound to `(namespace, name)`.
4116    for (src_file_idx, dynlib_id, fn_signatures, host_namespace, _span) in pending_host {
4117        let namespace = &file_namespaces[&src_file_idx];
4118        let fns = fn_signatures
4119            .iter()
4120            .map(|(fn_name, fn_args, fn_return_type, _fn_name_span)| {
4121                // A lone `...` sentinel argument marks a variadic host fn: it
4122                // takes no fixed argument types, and the call site forwards
4123                // every supplied argument to the registered closure.
4124                let variadic = fn_args.len() == 1
4125                    && matches!(&fn_args[0], TypeExpr::Identifier(s, _) if s.as_str() == "...");
4126                let fn_args = if variadic {
4127                    Box::from([])
4128                } else {
4129                    fn_args
4130                        .iter()
4131                        .map(|t| t.to_datatype(src_file_idx, namespace, sources))
4132                        .collect::<Vec<DataType>>()
4133                        .into_boxed_slice()
4134                };
4135                let fn_return_type = fn_return_type.to_datatype(src_file_idx, namespace, sources);
4136                let return_val = FnSignature {
4137                    name: fn_name.clone(),
4138                    args: fn_args.clone(),
4139                    return_type: fn_return_type.clone(),
4140                    id: host_fns.len() as u16,
4141                    variadic,
4142                };
4143
4144                let mut types = vec![fn_return_type];
4145                types.extend(fn_args);
4146                host_fns.push(HostFnSig {
4147                    types: types.into_boxed_slice(),
4148                    namespace: host_namespace.clone(),
4149                    name: fn_name.clone(),
4150                    variadic,
4151                });
4152                return_val
4153            })
4154            .collect();
4155        dynamic_libs[dynlib_id as usize].fns = fns;
4156    }
4157}
4158
4159/// The complete result of compiling a candela program.
4160///
4161/// In addition to the fields the CLI/VM needs to run `main`, this carries the
4162/// compiler-side tables (`functions`, `dyn_libs`, `namespace`, register
4163/// bookkeeping) that the embedding `Program` keeps alive so it can compile
4164/// additional function specializations on demand for `Program::call`, plus the
4165/// resolved `host_fns` signature table used to dispatch `host` calls.
4166pub struct CompileOutput {
4167    pub instructions: Vec<Instr>,
4168    pub registers: Vec<Data>,
4169    pub pools: Pools,
4170    pub instr_src: Vec<InstrSrc>,
4171    pub fn_registers: Vec<Vec<u16>>,
4172    pub dyn_lib_fns: Vec<DynamicLibFn>,
4173    pub host_fns: Vec<HostFnSig>,
4174    pub allocated_arg_count: usize,
4175    pub allocated_call_depth: usize,
4176    pub sources: Vec<Source>,
4177    pub structs: Vec<Struct>,
4178    pub enums: Vec<EnumType>,
4179    pub functions: Vec<Function>,
4180    pub dyn_libs: Vec<Dynamiclib>,
4181    pub namespace: Namespace,
4182    pub const_registers: FxHashMap<Data, u16>,
4183    pub free_registers: Vec<u16>,
4184}
4185
4186#[must_use]
4187pub fn compile(contents: String, filename: &str, debug: bool) -> CompileOutput {
4188    #[cfg(not(target_arch = "wasm32"))]
4189    let now = std::time::Instant::now();
4190
4191    // A previous compilation on this thread may have been aborted mid-inference
4192    // by an error unwind; make sure its bookkeeping doesn't leak into this one.
4193    type_system::reset_inference_state();
4194
4195    let main_src = Source {
4196        filename: SmolStr::from(filename),
4197        contents,
4198    };
4199
4200    let code = parser::parse(&main_src.contents, &main_src);
4201
4202    #[cfg(not(target_arch = "wasm32"))]
4203    if debug {
4204        println!("PARSING TIME: {:.2?}", now.elapsed());
4205    }
4206
4207    let mut variables: Vec<Variable> = Vec::new();
4208    let mut registers: Vec<Data> = Vec::new();
4209    let mut pools: Pools = Pools {
4210        objs: Pool::with_capacity(10),
4211        maps: Pool::with_capacity(2),
4212        strings: Pool::with_capacity(10),
4213    };
4214    let mut instr_src: Vec<InstrSrc> = Vec::new();
4215    let mut fn_registers: Vec<Vec<u16>> = Vec::new();
4216    let mut functions: Vec<Function> = Vec::new();
4217    let mut structs: Vec<Struct> = Vec::new();
4218    let mut enums: Vec<EnumType> = Vec::new();
4219    let mut dyn_libs: Vec<Dynamiclib> = Vec::new();
4220    let mut dyn_lib_fns: Vec<DynamicLibFn> = Vec::new();
4221    let mut host_fns: Vec<HostFnSig> = Vec::new();
4222    let mut allocated_arg_count = 0;
4223    let mut allocated_call_depth = 0;
4224    let mut const_registers: FxHashMap<Data, u16> = FxHashMap::default();
4225    let mut free_registers = Vec::new();
4226
4227    let mut sources: Vec<Source> = vec![main_src];
4228    let main_path = PathBuf::from(filename)
4229        .canonicalize()
4230        .unwrap_or_else(|_| PathBuf::from(filename));
4231    let mut namespace = Namespace::default();
4232
4233    let mut files: FxHashMap<PathBuf, Namespace> = FxHashMap::default();
4234    let mut file_namespaces: FxHashMap<u16, Namespace> = FxHashMap::default();
4235    let mut pending_structs: Vec<(u16, u16, Box<[(SmolStr, TypeExpr, Span)]>)> = Vec::new();
4236    let mut pending_enums: PendingEnums = Vec::new();
4237    let mut pending_fns: Vec<(
4238        u16,
4239        u16,
4240        Box<[(SmolStr, Option<TypeExpr>)]>,
4241        Option<(TypeExpr, Span)>,
4242    )> = Vec::with_capacity(2);
4243    #[cfg(not(target_arch = "wasm32"))]
4244    let mut pending_dylibs: Vec<(
4245        u16,
4246        u16,
4247        Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>,
4248        Rc<Library>,
4249        SmolStr,
4250        Span,
4251    )> = Vec::new();
4252    let mut pending_host: Vec<(
4253        u16,
4254        u16,
4255        Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>,
4256        SmolStr,
4257        Span,
4258    )> = Vec::new();
4259
4260    parse_toplevel(
4261        code,
4262        &main_path,
4263        0,
4264        &mut functions,
4265        &mut structs,
4266        &mut enums,
4267        &mut fn_registers,
4268        &mut dyn_libs,
4269        &mut sources,
4270        &mut namespace,
4271        &mut files,
4272        &mut file_namespaces,
4273        &mut pending_structs,
4274        &mut pending_enums,
4275        &mut pending_fns,
4276        #[cfg(not(target_arch = "wasm32"))]
4277        &mut pending_dylibs,
4278        &mut pending_host,
4279    );
4280    resolve_types(
4281        &mut structs,
4282        &mut enums,
4283        &mut functions,
4284        pending_structs,
4285        pending_enums,
4286        pending_fns,
4287        #[cfg(not(target_arch = "wasm32"))]
4288        pending_dylibs,
4289        pending_host,
4290        &file_namespaces,
4291        &mut dyn_lib_fns,
4292        &mut host_fns,
4293        &mut dyn_libs,
4294        &sources,
4295    );
4296
4297    let ctx = Ctx {
4298        block_id: 0,
4299        is_compiling_recursive: false,
4300        file_idx: 0,
4301        single_run: true,
4302        offset: 0,
4303    };
4304    let mut state = State {
4305        registers: &mut registers,
4306        fns: &mut functions,
4307        structs: &mut structs,
4308        enums: &mut enums,
4309        pools: &mut pools,
4310        instr_src: &mut instr_src,
4311        fn_registers: &mut fn_registers,
4312        dyn_libs: &mut dyn_libs,
4313        allocated_arg_count: &mut allocated_arg_count,
4314        allocated_call_depth: &mut allocated_call_depth,
4315        const_registers: &mut const_registers,
4316        free_registers: &mut free_registers,
4317        sources: &mut sources,
4318        reserved_registers: FxHashSet::default(),
4319        namespace: &mut namespace,
4320    };
4321    let mut instructions = compile_expr(
4322        &state.fns
4323            .iter()
4324            .find(|func| func.name == "main" && func.src_file == 0)
4325            .unwrap_or_else(|| {
4326                #[cfg(target_arch = "wasm32")]
4327                wasm_error("Cannot find main function");
4328
4329                if crate::errors::diagnostics_enabled() {
4330                    crate::errors::emit_diagnostic(
4331                        state.sources[0].filename.as_str(),
4332                        0..0,
4333                        String::from("Cannot find main function"),
4334                        "no_main_function",
4335                    );
4336                }
4337                eprintln!(
4338                    "--------------\n{RED}CANDELA RUNTIME ERROR:{RESET}\nCannot find {BLUE}{BOLD}main{RESET} function\n--------------",
4339                );
4340                std::process::exit(1);
4341            })
4342            .code
4343            .clone(),
4344        &mut variables,
4345        ctx,
4346        &mut state,
4347    );
4348    instructions.push(Instr::Halt(0));
4349
4350    #[cfg(debug_assertions)]
4351    if debug {
4352        println!("---- DEBUG ----");
4353        if !pools.objs.is_empty() {
4354            println!("---  ARRAYS  ---");
4355            for (i, data) in pools.objs.iter().enumerate() {
4356                println!(" {i} {data:?}");
4357            }
4358        }
4359        println!("-- REGISTERS --");
4360        for (i, data) in registers.iter().enumerate() {
4361            println!(
4362                " [{i}] {}",
4363                data.format(
4364                    &pools.objs,
4365                    &pools.strings,
4366                    &pools.maps,
4367                    &structs,
4368                    &enums,
4369                    true
4370                )
4371            );
4372        }
4373        if !instructions.is_empty() {
4374            println!("-- INSTRUCTIONS --");
4375            for (i, instr) in instructions.iter().enumerate() {
4376                println!(" {i}: {instr:?}");
4377            }
4378        }
4379        println!("------------------");
4380    }
4381
4382    CompileOutput {
4383        instructions,
4384        registers,
4385        pools,
4386        instr_src,
4387        fn_registers,
4388        dyn_lib_fns,
4389        host_fns,
4390        allocated_arg_count,
4391        allocated_call_depth,
4392        sources,
4393        structs,
4394        enums,
4395        functions,
4396        dyn_libs,
4397        namespace,
4398        const_registers,
4399        free_registers,
4400    }
4401}