Skip to main content

ling_codegen/
bytecode.rs

1use crate::CodegenBackend;
2use ling_ast::ast::BinOp;
3use ling_ast::ast::UnOp;
4use ling_mir::ir::*;
5use std::collections::HashMap;
6
7// ─── Opcodes ─────────────────────────────────────────────────────────────────
8
9const OP_NOP: u8 = 0;
10const OP_PUSHI64: u8 = 1;
11const OP_PUSHF64: u8 = 2;
12const OP_PUSHSTR: u8 = 3;
13const OP_PUSHBOOL: u8 = 4;
14const OP_PUSHNONE: u8 = 5;
15const OP_POP: u8 = 6;
16const OP_LOADLOCAL: u8 = 7;
17const OP_STORELOCAL: u8 = 8;
18const OP_ADD: u8 = 9;
19const OP_SUB: u8 = 10;
20const OP_MUL: u8 = 11;
21const OP_DIV: u8 = 12;
22const OP_REM: u8 = 13;
23const OP_EQ: u8 = 14;
24const OP_NE: u8 = 15;
25const OP_LT: u8 = 16;
26const OP_LE: u8 = 17;
27const OP_GT: u8 = 18;
28const OP_GE: u8 = 19;
29const OP_AND: u8 = 20;
30const OP_OR: u8 = 21;
31const OP_NOT: u8 = 22;
32const OP_NEG: u8 = 23;
33const OP_CALL: u8 = 24;
34const OP_CALLBUILTIN: u8 = 25;
35const OP_RET: u8 = 26;
36const OP_JUMP: u8 = 27;
37const OP_JUMPIFFALSE: u8 = 28;
38const OP_MAKELIST: u8 = 29;
39const OP_GETINDEX: u8 = 30;
40const OP_HALT: u8 = 0xFF;
41
42// ─── Values ──────────────────────────────────────────────────────────────────
43
44#[derive(Debug, Clone)]
45pub enum Value {
46    Number(f64),
47    Bool(bool),
48    Str(String),
49    None,
50    List(Vec<Value>),
51}
52
53impl Value {
54    fn is_truthy(&self) -> bool {
55        match self {
56            Value::Bool(b) => *b,
57            Value::Number(n) => *n != 0.0,
58            Value::Str(s) => !s.is_empty(),
59            Value::List(l) => !l.is_empty(),
60            Value::None => false,
61        }
62    }
63
64    fn display(&self) -> String {
65        match self {
66            Value::Number(n) => {
67                if n.fract() == 0.0 && n.is_finite() && n.abs() < 1e15 {
68                    format!("{:.0}", n)
69                } else {
70                    format!("{}", n)
71                }
72            },
73            Value::Bool(b) => b.to_string(),
74            Value::Str(s) => s.clone(),
75            Value::None => "()".to_string(),
76            Value::List(l) => {
77                let items: Vec<String> = l.iter().map(|v| v.display()).collect();
78                format!("[{}]", items.join(", "))
79            },
80        }
81    }
82}
83
84fn values_equal(a: &Value, b: &Value) -> bool {
85    match (a, b) {
86        (Value::Number(a), Value::Number(b)) => a.to_bits() == b.to_bits(),
87        (Value::Bool(a), Value::Bool(b)) => a == b,
88        (Value::Str(a), Value::Str(b)) => a == b,
89        (Value::None, Value::None) => true,
90        (Value::List(a), Value::List(b)) => {
91            a.len() == b.len() && a.iter().zip(b).all(|(x, y)| values_equal(x, y))
92        },
93        _ => false,
94    }
95}
96
97fn num_cmp<F: Fn(f64, f64) -> bool>(a: &Value, b: &Value, cmp: F) -> bool {
98    match (a, b) {
99        (Value::Number(a), Value::Number(b)) => cmp(*a, *b),
100        _ => false,
101    }
102}
103
104fn resolve_builtin(name: &str) -> Result<(&'static str, usize), String> {
105    match name {
106        "print" | "println" | "พิมพ์" | "印" | "打印" | "印刷" => Ok(("print", 1)),
107        "format" | "รูปแบบ" | "格式" | "フォーマット" | "서식" => {
108            Ok(("format", 0))
109        },
110        "len" | "str_len" | "ความยาว" | "长度" | "長さ" | "길이" => {
111            Ok(("len", 1))
112        },
113        "to_str" | "str" | "แปลงสตริง" => Ok(("to_str", 1)),
114        "sin" => Ok(("sin", 1)),
115        "cos" => Ok(("cos", 1)),
116        _ => Err(format!("unknown builtin: {}", name)),
117    }
118}
119
120// ─── Chunk ───────────────────────────────────────────────────────────────────
121
122#[derive(Debug, Default)]
123pub struct Chunk {
124    pub code: Vec<u8>,
125    pub floats: Vec<f64>,
126    pub strings: Vec<String>,
127    pub local_count: u16,
128}
129
130impl Chunk {
131    fn w(&mut self, b: u8) {
132        self.code.push(b);
133    }
134    fn w2(&mut self, v: u16) {
135        self.code.extend_from_slice(&v.to_le_bytes());
136    }
137    fn w8(&mut self, v: i64) {
138        self.code.extend_from_slice(&v.to_le_bytes());
139    }
140    fn add_float(&mut self, v: f64) -> u16 {
141        let i = self.floats.len();
142        self.floats.push(v);
143        i as u16
144    }
145    fn add_str(&mut self, s: &str) -> u16 {
146        let i = self.strings.len();
147        self.strings.push(s.to_string());
148        i as u16
149    }
150}
151
152// ─── Compiled program ────────────────────────────────────────────────────────
153
154#[derive(Debug)]
155pub struct CompiledFunction {
156    pub name: String,
157    pub chunk: Chunk,
158    pub arg_count: u16,
159}
160
161#[derive(Debug)]
162pub struct VmProgram {
163    pub functions: Vec<CompiledFunction>,
164    pub main_index: usize,
165}
166
167// ─── Compiler ────────────────────────────────────────────────────────────────
168
169struct Ctx {
170    chunk: Chunk,
171    local_map: HashMap<Local, u16>,
172    local_next: u16,
173    bb_start: HashMap<usize, usize>,
174    patches: Vec<(usize, usize)>,
175}
176
177pub fn compile_mir_program(mir: &MirProgram) -> VmProgram {
178    let fn_names: Vec<String> = mir.functions.iter().map(|f| f.name.clone()).collect();
179    let mut functions = Vec::new();
180    let mut main_index = 0;
181
182    for (i, mir_fn) in mir.functions.iter().enumerate() {
183        let chunk = compile_fn(mir_fn, &fn_names);
184        if mir_fn.name == "__main__" || mir_fn.name == "start" || mir_fn.name == "เริ่ม" {
185            main_index = i;
186        }
187        functions.push(CompiledFunction {
188            name: mir_fn.name.clone(),
189            chunk,
190            arg_count: mir_fn.arg_count as u16,
191        });
192    }
193
194    if functions.is_empty() {
195        functions.push(CompiledFunction {
196            name: "__main__".into(),
197            chunk: Chunk { code: vec![OP_PUSHNONE, OP_RET], ..Default::default() },
198            arg_count: 0,
199        });
200    }
201
202    VmProgram { functions, main_index }
203}
204
205fn compile_fn(mir_fn: &MirFunction, fn_names: &[String]) -> Chunk {
206    let mut ctx = Ctx {
207        chunk: Chunk::default(),
208        local_map: HashMap::new(),
209        local_next: 0,
210        bb_start: HashMap::new(),
211        patches: Vec::new(),
212    };
213
214    // Local(0) = return slot at slot 0
215    ctx.local_map.insert(Local(0), 0);
216
217    // Args: Local(1)..Local(arg_count) get sequential slots
218    for i in 0..mir_fn.arg_count {
219        let local = Local(i + 1);
220        ctx.local_map.entry(local).or_insert_with(|| {
221            let s = ctx.local_next;
222            ctx.local_next += 1;
223            s
224        });
225    }
226
227    // Explicit locals get slots after args
228    for li in 0..mir_fn.locals.len() {
229        let local = Local(mir_fn.arg_count + 1 + li);
230        ctx.local_map.entry(local).or_insert_with(|| {
231            let s = ctx.local_next;
232            ctx.local_next += 1;
233            s
234        });
235    }
236
237    ctx.chunk.local_count = ctx.local_next;
238
239    for bb_idx in 0..mir_fn.basic_blocks.len() {
240        ctx.bb_start.insert(bb_idx, ctx.chunk.code.len());
241        let bb = &mir_fn.basic_blocks[bb_idx];
242        for stmt in &bb.statements {
243            compile_stmt(stmt, fn_names, &mut ctx);
244        }
245        if let Some(term) = &bb.terminator {
246            compile_term(term, fn_names, &mut ctx);
247        }
248    }
249
250    for &(pos, target) in &ctx.patches {
251        let target_offset = ctx.bb_start.get(&target).copied().unwrap_or(0);
252        ctx.chunk.code[pos..pos + 2].copy_from_slice(&(target_offset as u16).to_le_bytes());
253    }
254
255    ctx.chunk
256}
257
258fn compile_stmt(stmt: &Statement, fn_names: &[String], ctx: &mut Ctx) {
259    match &stmt.kind {
260        StatementKind::Assign(local, rvalue) => {
261            compile_rval(rvalue, fn_names, ctx);
262            let slot = *ctx.local_map.entry(*local).or_insert_with(|| {
263                let s = ctx.local_next;
264                ctx.local_next += 1;
265                s
266            });
267            ctx.chunk.w(OP_STORELOCAL);
268            ctx.chunk.w2(slot);
269        },
270        _ => {},
271    }
272}
273
274fn compile_rval(rv: &Rvalue, fn_names: &[String], ctx: &mut Ctx) {
275    match rv {
276        Rvalue::Use(op) => compile_op(op, fn_names, ctx),
277        Rvalue::BinaryOp(op, l, r) => {
278            compile_op(l, fn_names, ctx);
279            compile_op(r, fn_names, ctx);
280            ctx.chunk.w(match op {
281                BinOp::Add => OP_ADD,
282                BinOp::Sub => OP_SUB,
283                BinOp::Mul => OP_MUL,
284                BinOp::Div => OP_DIV,
285                BinOp::Rem => OP_REM,
286                BinOp::Eq => OP_EQ,
287                BinOp::Ne => OP_NE,
288                BinOp::Lt => OP_LT,
289                BinOp::Le => OP_LE,
290                BinOp::Gt => OP_GT,
291                BinOp::Ge => OP_GE,
292                BinOp::And => OP_AND,
293                BinOp::Or => OP_OR,
294                _ => OP_NOP, // unsupported ops become nop
295            });
296        },
297        Rvalue::UnaryOp(op, o) => {
298            compile_op(o, fn_names, ctx);
299            ctx.chunk.w(match op {
300                UnOp::Not => OP_NOT,
301                UnOp::Neg => OP_NEG,
302                _ => OP_NOP,
303            });
304        },
305        Rvalue::Call { func, args } => {
306            for arg in args {
307                compile_op(arg, fn_names, ctx);
308            }
309            match func {
310                Operand::Constant(Constant::Function(name)) => {
311                    if let Some(idx) = fn_names.iter().position(|n| n == name) {
312                        ctx.chunk.w(OP_CALL);
313                        ctx.chunk.w2(idx as u16);
314                    } else {
315                        let sidx = ctx.chunk.add_str(name);
316                        ctx.chunk.w(OP_CALLBUILTIN);
317                        ctx.chunk.w2(sidx);
318                        ctx.chunk.w2(args.len() as u16);
319                    }
320                },
321                _ => {
322                    compile_op(func, fn_names, ctx);
323                    let sidx = ctx.chunk.add_str("__indirect");
324                    ctx.chunk.w(OP_CALLBUILTIN);
325                    ctx.chunk.w2(sidx);
326                    ctx.chunk.w2(args.len() as u16);
327                },
328            }
329        },
330        Rvalue::Aggregate(_kind, ops) => {
331            for op in ops {
332                compile_op(op, fn_names, ctx);
333            }
334            ctx.chunk.w(OP_MAKELIST);
335            ctx.chunk.w2(ops.len() as u16);
336        },
337        Rvalue::GetIndex(obj, idx) => {
338            compile_op(obj, fn_names, ctx);
339            compile_op(idx, fn_names, ctx);
340            ctx.chunk.w(OP_GETINDEX);
341        },
342        _ => {
343            ctx.chunk.w(OP_PUSHNONE);
344        },
345    }
346}
347
348fn compile_op(op: &Operand, _fn_names: &[String], ctx: &mut Ctx) {
349    match op {
350        Operand::Copy(l) | Operand::Move(l) => {
351            let slot = ctx.local_map.get(l).copied().unwrap_or(0);
352            ctx.chunk.w(OP_LOADLOCAL);
353            ctx.chunk.w2(slot);
354        },
355        Operand::Constant(c) => match c {
356            Constant::I64(v) => {
357                ctx.chunk.w(OP_PUSHI64);
358                ctx.chunk.w8(*v);
359            },
360            Constant::F64(bits) => {
361                let idx = ctx.chunk.add_float(f64::from_bits(*bits));
362                ctx.chunk.w(OP_PUSHF64);
363                ctx.chunk.w2(idx);
364            },
365            Constant::Str(s) => {
366                let idx = ctx.chunk.add_str(s);
367                ctx.chunk.w(OP_PUSHSTR);
368                ctx.chunk.w2(idx);
369            },
370            Constant::Bool(b) => {
371                ctx.chunk.w(OP_PUSHBOOL);
372                ctx.chunk.w(if *b { 1 } else { 0 });
373            },
374            _ => {
375                ctx.chunk.w(OP_PUSHNONE);
376            },
377        },
378    }
379}
380
381fn compile_term(term: &Terminator, fn_names: &[String], ctx: &mut Ctx) {
382    match &term.kind {
383        TerminatorKind::Return => {
384            ctx.chunk.w(OP_LOADLOCAL);
385            ctx.chunk.w2(0);
386            ctx.chunk.w(OP_RET);
387        },
388        TerminatorKind::Goto { target } => {
389            ctx.chunk.w(OP_JUMP);
390            let pos = ctx.chunk.code.len();
391            ctx.chunk.w2(0);
392            ctx.patches.push((pos, target.0));
393        },
394        TerminatorKind::SwitchInt { discr, targets, otherwise } => {
395            compile_op(discr, fn_names, ctx);
396            if let Some((val, target)) = targets.first() {
397                ctx.chunk.w(OP_PUSHI64);
398                ctx.chunk.w8(*val);
399                ctx.chunk.w(OP_EQ);
400                ctx.chunk.w(OP_JUMPIFFALSE);
401                let pos = ctx.chunk.code.len();
402                ctx.chunk.w2(0);
403                ctx.patches.push((pos, target.0));
404            }
405            ctx.chunk.w(OP_JUMP);
406            let pos = ctx.chunk.code.len();
407            ctx.chunk.w2(0);
408            ctx.patches.push((pos, otherwise.0));
409        },
410        TerminatorKind::Unreachable => {
411            ctx.chunk.w(OP_HALT);
412        },
413    }
414}
415
416fn read_u8(code: &[u8], ip: usize) -> u8 {
417    code[ip]
418}
419
420fn read_u16(code: &[u8], ip: usize) -> u16 {
421    let mut bytes = [0u8; 2];
422    bytes.copy_from_slice(&code[ip..ip + 2]);
423    u16::from_le_bytes(bytes)
424}
425
426fn read_i64(code: &[u8], ip: usize) -> i64 {
427    let mut bytes = [0u8; 8];
428    bytes.copy_from_slice(&code[ip..ip + 8]);
429    i64::from_le_bytes(bytes)
430}
431
432// ─── VM ──────────────────────────────────────────────────────────────────────
433
434struct Frame {
435    fn_index: usize,
436    ip: usize,
437    base: usize,
438    local_count: usize,
439}
440
441pub struct Vm {
442    stack: Vec<Value>,
443    program: Option<VmProgram>,
444    call_stack: Vec<Frame>,
445}
446
447impl Vm {
448    pub fn new() -> Self {
449        Self { stack: Vec::new(), program: None, call_stack: Vec::new() }
450    }
451
452    pub fn load(&mut self, program: VmProgram) {
453        self.program = Some(program);
454    }
455
456    pub fn run_main(&mut self) -> Result<(), String> {
457        let p = self.program.as_ref().ok_or("no program loaded")?;
458        if p.functions.is_empty() {
459            return Ok(());
460        }
461        let main_idx = p.main_index;
462        let func = &p.functions[main_idx];
463        let lc = func.chunk.local_count as usize;
464        for _ in 0..lc {
465            self.stack.push(Value::None);
466        }
467        self.call_stack
468            .push(Frame { fn_index: main_idx, ip: 0, base: 0, local_count: lc });
469        self.exec()
470    }
471
472    fn exec(&mut self) -> Result<(), String> {
473        loop {
474            if self.call_stack.is_empty() {
475                return Ok(());
476            }
477
478            let frame_ip = self.call_stack.last().unwrap().ip;
479
480            // Pre-extract chunk data through an index to avoid holding cross-references
481            let fn_index = self.call_stack.last().unwrap().fn_index;
482            let (code, floats, strings) = {
483                let p = self.program.as_ref().unwrap();
484                let chunk = &p.functions[fn_index].chunk;
485                // Clone small headers to avoid holding borrow across mutable ops
486                (
487                    chunk.code.clone(),
488                    chunk.floats.clone(),
489                    chunk.strings.clone(),
490                )
491            };
492            let code_len = code.len();
493
494            if frame_ip >= code_len {
495                self.call_stack.pop();
496                return Err("unexpected end of code".into());
497            }
498
499            let op = read_u8(&code, frame_ip);
500            let mut ip = frame_ip + 1;
501
502            match op {
503                OP_PUSHI64 => {
504                    let v = read_i64(&code, ip);
505                    ip += 8;
506                    self.stack.push(Value::Number(v as f64));
507                },
508                OP_PUSHF64 => {
509                    let idx = read_u16(&code, ip) as usize;
510                    ip += 2;
511                    self.stack.push(Value::Number(floats[idx]));
512                },
513                OP_PUSHSTR => {
514                    let idx = read_u16(&code, ip) as usize;
515                    ip += 2;
516                    self.stack.push(Value::Str(strings[idx].clone()));
517                },
518                OP_PUSHBOOL => {
519                    let b = read_u8(&code, ip) != 0;
520                    ip += 1;
521                    self.stack.push(Value::Bool(b));
522                },
523                OP_PUSHNONE => self.stack.push(Value::None),
524                OP_POP => {
525                    self.stack.pop();
526                },
527                OP_LOADLOCAL => {
528                    let idx = read_u16(&code, ip) as usize;
529                    ip += 2;
530                    let base = self.call_stack.last().unwrap().base;
531                    self.stack.push(self.stack[base + idx].clone());
532                },
533                OP_STORELOCAL => {
534                    let idx = read_u16(&code, ip) as usize;
535                    ip += 2;
536                    let val = self.stack.pop().unwrap();
537                    let base = self.call_stack.last().unwrap().base;
538                    self.stack[base + idx] = val;
539                },
540                OP_ADD | OP_SUB | OP_MUL | OP_DIV | OP_REM => {
541                    let b = self.stack.pop().unwrap();
542                    let a = self.stack.pop().unwrap();
543                    let r = match (op, &a, &b) {
544                        (OP_ADD, Value::Str(s), v) => Value::Str(format!("{}{}", s, v.display())),
545                        (OP_ADD, v, Value::Str(s)) => Value::Str(format!("{}{}", v.display(), s)),
546                        (OP_ADD, Value::Number(a), Value::Number(b)) => Value::Number(a + b),
547                        (OP_SUB, Value::Number(a), Value::Number(b)) => Value::Number(a - b),
548                        (OP_MUL, Value::Number(a), Value::Number(b)) => Value::Number(a * b),
549                        (OP_DIV, Value::Number(a), Value::Number(b)) => Value::Number(a / b),
550                        (OP_REM, Value::Number(a), Value::Number(b)) => Value::Number(a % b),
551                        _ => return Err("type error in arithmetic".into()),
552                    };
553                    self.stack.push(r);
554                },
555                OP_EQ | OP_NE | OP_LT | OP_LE | OP_GT | OP_GE => {
556                    let b = self.stack.pop().unwrap();
557                    let a = self.stack.pop().unwrap();
558                    let r = match op {
559                        OP_EQ => values_equal(&a, &b),
560                        OP_NE => !values_equal(&a, &b),
561                        OP_LT => num_cmp(&a, &b, |a, b| a < b),
562                        OP_LE => num_cmp(&a, &b, |a, b| a <= b),
563                        OP_GT => num_cmp(&a, &b, |a, b| a > b),
564                        OP_GE => num_cmp(&a, &b, |a, b| a >= b),
565                        _ => false,
566                    };
567                    self.stack.push(Value::Bool(r));
568                },
569                OP_AND => {
570                    let b = self.stack.pop().unwrap();
571                    let a = self.stack.pop().unwrap();
572                    self.stack.push(Value::Bool(a.is_truthy() && b.is_truthy()));
573                },
574                OP_OR => {
575                    let b = self.stack.pop().unwrap();
576                    let a = self.stack.pop().unwrap();
577                    self.stack.push(Value::Bool(a.is_truthy() || b.is_truthy()));
578                },
579                OP_NOT => {
580                    let v = self.stack.pop().unwrap();
581                    self.stack.push(Value::Bool(!v.is_truthy()));
582                },
583                OP_NEG => {
584                    let v = self.stack.pop().unwrap();
585                    if let Value::Number(n) = v {
586                        self.stack.push(Value::Number(-n));
587                    } else {
588                        return Err("type error: negate non-number".into());
589                    }
590                },
591                OP_CALL => {
592                    let idx = read_u16(&code, ip) as usize;
593                    ip += 2;
594                    let (ac, lc) = {
595                        let p = self.program.as_ref().unwrap();
596                        (
597                            p.functions[idx].arg_count as usize,
598                            p.functions[idx].chunk.local_count as usize,
599                        )
600                    };
601                    let extra = lc.saturating_sub(ac);
602                    for _ in 0..extra {
603                        self.stack.push(Value::None);
604                    }
605                    let new_base = self.stack.len() - lc;
606                    self.call_stack.push(Frame {
607                        fn_index: idx,
608                        ip: 0,
609                        base: new_base,
610                        local_count: lc,
611                    });
612                },
613                OP_CALLBUILTIN => {
614                    let si = read_u16(&code, ip) as usize;
615                    ip += 2;
616                    let count = read_u16(&code, ip) as usize;
617                    ip += 2;
618                    let name = strings[si].clone();
619                    let (canon, _) =
620                        resolve_builtin(&name).map_err(|e| format!("{} in {}", e, name))?;
621                    self.call_builtin(canon, count)?;
622                },
623                OP_RET => {
624                    let val = self.stack.pop().unwrap_or(Value::None);
625                    let _frame = self.call_stack.pop().unwrap();
626                    if let Some(caller) = self.call_stack.last() {
627                        self.stack.truncate(caller.base + caller.local_count);
628                        self.stack.push(val);
629                    } else {
630                        self.stack.push(val);
631                        return Ok(());
632                    }
633                },
634                OP_JUMP => {
635                    ip = read_u16(&code, ip) as usize;
636                },
637                OP_JUMPIFFALSE => {
638                    let offset = read_u16(&code, ip) as usize;
639                    ip += 2;
640                    let cond = self.stack.pop().unwrap();
641                    if !cond.is_truthy() {
642                        ip = offset;
643                    }
644                },
645                OP_MAKELIST => {
646                    let count = read_u16(&code, ip) as usize;
647                    ip += 2;
648                    let mut items = Vec::with_capacity(count);
649                    for _ in 0..count {
650                        items.push(self.stack.pop().unwrap());
651                    }
652                    items.reverse();
653                    self.stack.push(Value::List(items));
654                },
655                OP_GETINDEX => {
656                    let idx_v = self.stack.pop().unwrap();
657                    let obj = self.stack.pop().unwrap();
658                    match (&obj, &idx_v) {
659                        (Value::List(list), Value::Number(n)) => {
660                            let i = *n as usize;
661                            self.stack.push(if i < list.len() {
662                                list[i].clone()
663                            } else {
664                                Value::None
665                            });
666                        },
667                        _ => self.stack.push(Value::None),
668                    }
669                },
670                OP_HALT => return Ok(()),
671                _ => return Err(format!("unknown opcode {} at ip {}", op, ip - 1)),
672            }
673
674            self.call_stack.last_mut().unwrap().ip = ip;
675        }
676    }
677
678    fn call_builtin(&mut self, name: &str, arg_count: usize) -> Result<(), String> {
679        match name {
680            "print" => {
681                if let Some(val) = self.stack.pop() {
682                    println!("{}", val.display());
683                }
684                self.stack.push(Value::None);
685            },
686            "format" => {
687                let mut parts: Vec<String> = Vec::with_capacity(arg_count);
688                for _ in 0..arg_count {
689                    if let Some(val) = self.stack.pop() {
690                        parts.push(val.display());
691                    }
692                }
693                parts.reverse();
694                if parts.is_empty() {
695                    self.stack.push(Value::Str(String::new()));
696                    return Ok(());
697                }
698                let fmt_str = parts.remove(0);
699                let mut result = fmt_str;
700                for part in parts {
701                    result = result.replacen("{}", &part, 1);
702                }
703                self.stack.push(Value::Str(result));
704            },
705            "len" => {
706                let val = self.stack.pop().unwrap_or(Value::None);
707                let n = match &val {
708                    Value::Str(s) => s.len() as f64,
709                    Value::List(l) => l.len() as f64,
710                    _ => 0.0,
711                };
712                self.stack.push(Value::Number(n));
713            },
714            "to_str" => {
715                let val = self.stack.pop().unwrap_or(Value::None);
716                self.stack.push(Value::Str(val.display()));
717            },
718            "sin" => {
719                let val = self.stack.pop().unwrap_or(Value::None);
720                if let Value::Number(n) = val {
721                    self.stack.push(Value::Number(n.sin()));
722                } else {
723                    self.stack.push(Value::Number(0.0));
724                }
725            },
726            "cos" => {
727                let val = self.stack.pop().unwrap_or(Value::None);
728                if let Value::Number(n) = val {
729                    self.stack.push(Value::Number(n.cos()));
730                } else {
731                    self.stack.push(Value::Number(0.0));
732                }
733            },
734            _ => return Err(format!("unimplemented builtin: {}", name)),
735        }
736        Ok(())
737    }
738}
739
740// ─── BytecodeBackend ─────────────────────────────────────────────────────────
741
742pub struct BytecodeBackend;
743
744impl CodegenBackend for BytecodeBackend {
745    fn emit(&mut self, program: &crate::MirProgram, out: &std::path::Path) -> anyhow::Result<()> {
746        let vm_prog = compile_mir_program(&program.mir);
747        let mut bytes = Vec::new();
748        bytes.extend_from_slice(b"LINGBC");
749        let fn_count = vm_prog.functions.len() as u32;
750        bytes.extend_from_slice(&fn_count.to_le_bytes());
751        for func in &vm_prog.functions {
752            let name_bytes = func.name.as_bytes();
753            bytes.extend_from_slice(&(name_bytes.len() as u32).to_le_bytes());
754            bytes.extend_from_slice(name_bytes);
755            bytes.extend_from_slice(&func.arg_count.to_le_bytes());
756            bytes.extend_from_slice(&func.chunk.local_count.to_le_bytes());
757            bytes.extend_from_slice(&(func.chunk.code.len() as u32).to_le_bytes());
758            bytes.extend_from_slice(&func.chunk.code);
759            bytes.extend_from_slice(&(func.chunk.floats.len() as u32).to_le_bytes());
760            for &f in &func.chunk.floats {
761                bytes.extend_from_slice(&f.to_bits().to_le_bytes());
762            }
763            bytes.extend_from_slice(&(func.chunk.strings.len() as u32).to_le_bytes());
764            for s in &func.chunk.strings {
765                let sb = s.as_bytes();
766                bytes.extend_from_slice(&(sb.len() as u32).to_le_bytes());
767                bytes.extend_from_slice(sb);
768            }
769        }
770        std::fs::write(out, &bytes)?;
771        Ok(())
772    }
773}