Skip to main content

harn_vm/
chunk.rs

1use std::fmt;
2
3/// Bytecode opcodes for the Harn VM.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5#[repr(u8)]
6pub enum Op {
7    /// Push a constant from the constant pool onto the stack.
8    Constant, // arg: u16 constant index
9    /// Push nil onto the stack.
10    Nil,
11    /// Push true onto the stack.
12    True,
13    /// Push false onto the stack.
14    False,
15
16    // --- Variable operations ---
17    /// Get a variable by name (from constant pool).
18    GetVar, // arg: u16 constant index (name)
19    /// Define a new immutable variable. Pops value from stack.
20    DefLet, // arg: u16 constant index (name)
21    /// Define a new mutable variable. Pops value from stack.
22    DefVar, // arg: u16 constant index (name)
23    /// Assign to an existing mutable variable. Pops value from stack.
24    SetVar, // arg: u16 constant index (name)
25
26    // --- Arithmetic ---
27    Add,
28    Sub,
29    Mul,
30    Div,
31    Mod,
32    Negate,
33
34    // --- Comparison ---
35    Equal,
36    NotEqual,
37    Less,
38    Greater,
39    LessEqual,
40    GreaterEqual,
41
42    // --- Logical ---
43    Not,
44
45    // --- Control flow ---
46    /// Jump unconditionally. arg: u16 offset.
47    Jump,
48    /// Jump if top of stack is falsy. Does not pop. arg: u16 offset.
49    JumpIfFalse,
50    /// Jump if top of stack is truthy. Does not pop. arg: u16 offset.
51    JumpIfTrue,
52    /// Pop top of stack (discard).
53    Pop,
54
55    // --- Functions ---
56    /// Call a function/builtin. arg: u8 = arg count. Name is on stack below args.
57    Call,
58    /// Tail call: like Call, but replaces the current frame instead of pushing
59    /// a new one. Used for `return f(x)` to enable tail call optimization.
60    /// For builtins, behaves like a regular Call (no frame to replace).
61    TailCall,
62    /// Return from current function. Pops return value.
63    Return,
64    /// Create a closure. arg: u16 = chunk index in function table.
65    Closure,
66
67    // --- Collections ---
68    /// Build a list. arg: u16 = element count. Elements are on stack.
69    BuildList,
70    /// Build a dict. arg: u16 = entry count. Key-value pairs on stack.
71    BuildDict,
72    /// Subscript access: stack has [object, index]. Pushes result.
73    Subscript,
74
75    // --- Object operations ---
76    /// Property access. arg: u16 = constant index (property name).
77    GetProperty,
78    /// Property assignment. arg: u16 = constant index (property name).
79    /// Stack: [value] → assigns to the named variable's property.
80    SetProperty,
81    /// Subscript assignment. arg: u16 = constant index (variable name).
82    /// Stack: [index, value] → assigns to variable[index] = value.
83    SetSubscript,
84    /// Method call. arg1: u16 = constant index (method name), arg2: u8 = arg count.
85    MethodCall,
86
87    // --- String ---
88    /// String concatenation of N parts. arg: u16 = part count.
89    Concat,
90
91    // --- Iteration ---
92    /// Set up a for-in loop. Expects iterable on stack. Pushes iterator state.
93    IterInit,
94    /// Advance iterator. If exhausted, jumps. arg: u16 = jump offset.
95    /// Pushes next value and the variable name is set via DefVar before the loop.
96    IterNext,
97
98    // --- Pipe ---
99    /// Pipe: pops [value, callable], invokes callable(value).
100    Pipe,
101
102    // --- Error handling ---
103    /// Pop value, raise as error.
104    Throw,
105    /// Push exception handler. arg: u16 = offset to catch handler.
106    TryCatchSetup,
107    /// Remove top exception handler (end of try body).
108    PopHandler,
109
110    // --- Concurrency ---
111    /// Execute closure N times sequentially, push results as list.
112    /// Stack: count, closure → result_list
113    Parallel,
114    /// Execute closure for each item in list, push results as list.
115    /// Stack: list, closure → result_list
116    ParallelMap,
117    /// Store closure for deferred execution, push TaskHandle.
118    /// Stack: closure → TaskHandle
119    Spawn,
120
121    // --- Imports ---
122    /// Import a file. arg: u16 = constant index (path string).
123    Import,
124    /// Selective import. arg1: u16 = path string, arg2: u16 = names list constant.
125    SelectiveImport,
126
127    // --- Deadline ---
128    /// Pop duration value, push deadline onto internal deadline stack.
129    DeadlineSetup,
130    /// Pop deadline from internal deadline stack.
131    DeadlineEnd,
132
133    // --- Enum ---
134    /// Build an enum variant value.
135    /// arg1: u16 = constant index (enum name), arg2: u16 = constant index (variant name),
136    /// arg3: u16 = field count. Fields are on stack.
137    BuildEnum,
138
139    // --- Match ---
140    /// Match an enum pattern. Checks enum_name + variant on the top of stack (dup'd match value).
141    /// arg1: u16 = constant index (enum name), arg2: u16 = constant index (variant name).
142    /// If match succeeds, pushes true; else pushes false.
143    MatchEnum,
144
145    // --- Loop control ---
146    /// Pop the top iterator from the iterator stack (cleanup on break from for-in).
147    PopIterator,
148
149    // --- Misc ---
150    /// Duplicate top of stack.
151    Dup,
152    /// Swap top two stack values.
153    Swap,
154}
155
156/// A constant value in the constant pool.
157#[derive(Debug, Clone, PartialEq)]
158pub enum Constant {
159    Int(i64),
160    Float(f64),
161    String(String),
162    Bool(bool),
163    Nil,
164    Duration(u64),
165}
166
167impl fmt::Display for Constant {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        match self {
170            Constant::Int(n) => write!(f, "{n}"),
171            Constant::Float(n) => write!(f, "{n}"),
172            Constant::String(s) => write!(f, "\"{s}\""),
173            Constant::Bool(b) => write!(f, "{b}"),
174            Constant::Nil => write!(f, "nil"),
175            Constant::Duration(ms) => write!(f, "{ms}ms"),
176        }
177    }
178}
179
180/// A compiled chunk of bytecode.
181#[derive(Debug, Clone)]
182pub struct Chunk {
183    /// The bytecode instructions.
184    pub code: Vec<u8>,
185    /// Constant pool.
186    pub constants: Vec<Constant>,
187    /// Source line numbers for each instruction (for error reporting).
188    pub lines: Vec<u32>,
189    /// Source column numbers for each instruction (for error reporting).
190    /// Parallel to `lines`; 0 means no column info available.
191    pub columns: Vec<u32>,
192    /// Current column to use when emitting instructions (set by compiler).
193    current_col: u32,
194    /// Compiled function bodies (for closures).
195    pub functions: Vec<CompiledFunction>,
196}
197
198/// A compiled function (closure body).
199#[derive(Debug, Clone)]
200pub struct CompiledFunction {
201    pub name: String,
202    pub params: Vec<String>,
203    pub chunk: Chunk,
204}
205
206impl Chunk {
207    pub fn new() -> Self {
208        Self {
209            code: Vec::new(),
210            constants: Vec::new(),
211            lines: Vec::new(),
212            columns: Vec::new(),
213            current_col: 0,
214            functions: Vec::new(),
215        }
216    }
217
218    /// Set the current column for subsequent emit calls.
219    pub fn set_column(&mut self, col: u32) {
220        self.current_col = col;
221    }
222
223    /// Add a constant and return its index.
224    pub fn add_constant(&mut self, constant: Constant) -> u16 {
225        // Reuse existing constant if possible
226        for (i, c) in self.constants.iter().enumerate() {
227            if c == &constant {
228                return i as u16;
229            }
230        }
231        let idx = self.constants.len();
232        self.constants.push(constant);
233        idx as u16
234    }
235
236    /// Emit a single-byte instruction.
237    pub fn emit(&mut self, op: Op, line: u32) {
238        let col = self.current_col;
239        self.code.push(op as u8);
240        self.lines.push(line);
241        self.columns.push(col);
242    }
243
244    /// Emit an instruction with a u16 argument.
245    pub fn emit_u16(&mut self, op: Op, arg: u16, line: u32) {
246        let col = self.current_col;
247        self.code.push(op as u8);
248        self.code.push((arg >> 8) as u8);
249        self.code.push((arg & 0xFF) as u8);
250        self.lines.push(line);
251        self.lines.push(line);
252        self.lines.push(line);
253        self.columns.push(col);
254        self.columns.push(col);
255        self.columns.push(col);
256    }
257
258    /// Emit an instruction with a u8 argument.
259    pub fn emit_u8(&mut self, op: Op, arg: u8, line: u32) {
260        let col = self.current_col;
261        self.code.push(op as u8);
262        self.code.push(arg);
263        self.lines.push(line);
264        self.lines.push(line);
265        self.columns.push(col);
266        self.columns.push(col);
267    }
268
269    /// Emit a method call: op + u16 (method name) + u8 (arg count).
270    pub fn emit_method_call(&mut self, name_idx: u16, arg_count: u8, line: u32) {
271        let col = self.current_col;
272        self.code.push(Op::MethodCall as u8);
273        self.code.push((name_idx >> 8) as u8);
274        self.code.push((name_idx & 0xFF) as u8);
275        self.code.push(arg_count);
276        self.lines.push(line);
277        self.lines.push(line);
278        self.lines.push(line);
279        self.lines.push(line);
280        self.columns.push(col);
281        self.columns.push(col);
282        self.columns.push(col);
283        self.columns.push(col);
284    }
285
286    /// Current code offset (for jump patching).
287    pub fn current_offset(&self) -> usize {
288        self.code.len()
289    }
290
291    /// Emit a jump instruction with a placeholder offset. Returns the position to patch.
292    pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
293        let col = self.current_col;
294        self.code.push(op as u8);
295        let patch_pos = self.code.len();
296        self.code.push(0xFF);
297        self.code.push(0xFF);
298        self.lines.push(line);
299        self.lines.push(line);
300        self.lines.push(line);
301        self.columns.push(col);
302        self.columns.push(col);
303        self.columns.push(col);
304        patch_pos
305    }
306
307    /// Patch a jump instruction at the given position to jump to the current offset.
308    pub fn patch_jump(&mut self, patch_pos: usize) {
309        let target = self.code.len() as u16;
310        self.code[patch_pos] = (target >> 8) as u8;
311        self.code[patch_pos + 1] = (target & 0xFF) as u8;
312    }
313
314    /// Read a u16 argument at the given position.
315    pub fn read_u16(&self, pos: usize) -> u16 {
316        ((self.code[pos] as u16) << 8) | (self.code[pos + 1] as u16)
317    }
318
319    /// Disassemble for debugging.
320    pub fn disassemble(&self, name: &str) -> String {
321        let mut out = format!("== {name} ==\n");
322        let mut ip = 0;
323        while ip < self.code.len() {
324            let op = self.code[ip];
325            let line = self.lines.get(ip).copied().unwrap_or(0);
326            out.push_str(&format!("{:04} [{:>4}] ", ip, line));
327            ip += 1;
328
329            match op {
330                x if x == Op::Constant as u8 => {
331                    let idx = self.read_u16(ip);
332                    ip += 2;
333                    let val = &self.constants[idx as usize];
334                    out.push_str(&format!("CONSTANT {:>4} ({})\n", idx, val));
335                }
336                x if x == Op::Nil as u8 => out.push_str("NIL\n"),
337                x if x == Op::True as u8 => out.push_str("TRUE\n"),
338                x if x == Op::False as u8 => out.push_str("FALSE\n"),
339                x if x == Op::GetVar as u8 => {
340                    let idx = self.read_u16(ip);
341                    ip += 2;
342                    out.push_str(&format!(
343                        "GET_VAR {:>4} ({})\n",
344                        idx, self.constants[idx as usize]
345                    ));
346                }
347                x if x == Op::DefLet as u8 => {
348                    let idx = self.read_u16(ip);
349                    ip += 2;
350                    out.push_str(&format!(
351                        "DEF_LET {:>4} ({})\n",
352                        idx, self.constants[idx as usize]
353                    ));
354                }
355                x if x == Op::DefVar as u8 => {
356                    let idx = self.read_u16(ip);
357                    ip += 2;
358                    out.push_str(&format!(
359                        "DEF_VAR {:>4} ({})\n",
360                        idx, self.constants[idx as usize]
361                    ));
362                }
363                x if x == Op::SetVar as u8 => {
364                    let idx = self.read_u16(ip);
365                    ip += 2;
366                    out.push_str(&format!(
367                        "SET_VAR {:>4} ({})\n",
368                        idx, self.constants[idx as usize]
369                    ));
370                }
371                x if x == Op::Add as u8 => out.push_str("ADD\n"),
372                x if x == Op::Sub as u8 => out.push_str("SUB\n"),
373                x if x == Op::Mul as u8 => out.push_str("MUL\n"),
374                x if x == Op::Div as u8 => out.push_str("DIV\n"),
375                x if x == Op::Mod as u8 => out.push_str("MOD\n"),
376                x if x == Op::Negate as u8 => out.push_str("NEGATE\n"),
377                x if x == Op::Equal as u8 => out.push_str("EQUAL\n"),
378                x if x == Op::NotEqual as u8 => out.push_str("NOT_EQUAL\n"),
379                x if x == Op::Less as u8 => out.push_str("LESS\n"),
380                x if x == Op::Greater as u8 => out.push_str("GREATER\n"),
381                x if x == Op::LessEqual as u8 => out.push_str("LESS_EQUAL\n"),
382                x if x == Op::GreaterEqual as u8 => out.push_str("GREATER_EQUAL\n"),
383                x if x == Op::Not as u8 => out.push_str("NOT\n"),
384                x if x == Op::Jump as u8 => {
385                    let target = self.read_u16(ip);
386                    ip += 2;
387                    out.push_str(&format!("JUMP {:>4}\n", target));
388                }
389                x if x == Op::JumpIfFalse as u8 => {
390                    let target = self.read_u16(ip);
391                    ip += 2;
392                    out.push_str(&format!("JUMP_IF_FALSE {:>4}\n", target));
393                }
394                x if x == Op::JumpIfTrue as u8 => {
395                    let target = self.read_u16(ip);
396                    ip += 2;
397                    out.push_str(&format!("JUMP_IF_TRUE {:>4}\n", target));
398                }
399                x if x == Op::Pop as u8 => out.push_str("POP\n"),
400                x if x == Op::Call as u8 => {
401                    let argc = self.code[ip];
402                    ip += 1;
403                    out.push_str(&format!("CALL {:>4}\n", argc));
404                }
405                x if x == Op::TailCall as u8 => {
406                    let argc = self.code[ip];
407                    ip += 1;
408                    out.push_str(&format!("TAIL_CALL {:>4}\n", argc));
409                }
410                x if x == Op::Return as u8 => out.push_str("RETURN\n"),
411                x if x == Op::Closure as u8 => {
412                    let idx = self.read_u16(ip);
413                    ip += 2;
414                    out.push_str(&format!("CLOSURE {:>4}\n", idx));
415                }
416                x if x == Op::BuildList as u8 => {
417                    let count = self.read_u16(ip);
418                    ip += 2;
419                    out.push_str(&format!("BUILD_LIST {:>4}\n", count));
420                }
421                x if x == Op::BuildDict as u8 => {
422                    let count = self.read_u16(ip);
423                    ip += 2;
424                    out.push_str(&format!("BUILD_DICT {:>4}\n", count));
425                }
426                x if x == Op::Subscript as u8 => out.push_str("SUBSCRIPT\n"),
427                x if x == Op::GetProperty as u8 => {
428                    let idx = self.read_u16(ip);
429                    ip += 2;
430                    out.push_str(&format!(
431                        "GET_PROPERTY {:>4} ({})\n",
432                        idx, self.constants[idx as usize]
433                    ));
434                }
435                x if x == Op::SetProperty as u8 => {
436                    let idx = self.read_u16(ip);
437                    ip += 2;
438                    out.push_str(&format!(
439                        "SET_PROPERTY {:>4} ({})\n",
440                        idx, self.constants[idx as usize]
441                    ));
442                }
443                x if x == Op::SetSubscript as u8 => {
444                    let idx = self.read_u16(ip);
445                    ip += 2;
446                    out.push_str(&format!(
447                        "SET_SUBSCRIPT {:>4} ({})\n",
448                        idx, self.constants[idx as usize]
449                    ));
450                }
451                x if x == Op::MethodCall as u8 => {
452                    let idx = self.read_u16(ip);
453                    ip += 2;
454                    let argc = self.code[ip];
455                    ip += 1;
456                    out.push_str(&format!(
457                        "METHOD_CALL {:>4} ({}) argc={}\n",
458                        idx, self.constants[idx as usize], argc
459                    ));
460                }
461                x if x == Op::Concat as u8 => {
462                    let count = self.read_u16(ip);
463                    ip += 2;
464                    out.push_str(&format!("CONCAT {:>4}\n", count));
465                }
466                x if x == Op::IterInit as u8 => out.push_str("ITER_INIT\n"),
467                x if x == Op::IterNext as u8 => {
468                    let target = self.read_u16(ip);
469                    ip += 2;
470                    out.push_str(&format!("ITER_NEXT {:>4}\n", target));
471                }
472                x if x == Op::Throw as u8 => out.push_str("THROW\n"),
473                x if x == Op::TryCatchSetup as u8 => {
474                    let target = self.read_u16(ip);
475                    ip += 2;
476                    out.push_str(&format!("TRY_CATCH_SETUP {:>4}\n", target));
477                }
478                x if x == Op::PopHandler as u8 => out.push_str("POP_HANDLER\n"),
479                x if x == Op::Pipe as u8 => out.push_str("PIPE\n"),
480                x if x == Op::Parallel as u8 => out.push_str("PARALLEL\n"),
481                x if x == Op::ParallelMap as u8 => out.push_str("PARALLEL_MAP\n"),
482                x if x == Op::Spawn as u8 => out.push_str("SPAWN\n"),
483                x if x == Op::Import as u8 => {
484                    let idx = self.read_u16(ip);
485                    ip += 2;
486                    out.push_str(&format!(
487                        "IMPORT {:>4} ({})\n",
488                        idx, self.constants[idx as usize]
489                    ));
490                }
491                x if x == Op::SelectiveImport as u8 => {
492                    let path_idx = self.read_u16(ip);
493                    ip += 2;
494                    let names_idx = self.read_u16(ip);
495                    ip += 2;
496                    out.push_str(&format!(
497                        "SELECTIVE_IMPORT {:>4} ({}) names: {:>4} ({})\n",
498                        path_idx,
499                        self.constants[path_idx as usize],
500                        names_idx,
501                        self.constants[names_idx as usize]
502                    ));
503                }
504                x if x == Op::DeadlineSetup as u8 => out.push_str("DEADLINE_SETUP\n"),
505                x if x == Op::DeadlineEnd as u8 => out.push_str("DEADLINE_END\n"),
506                x if x == Op::BuildEnum as u8 => {
507                    let enum_idx = self.read_u16(ip);
508                    ip += 2;
509                    let variant_idx = self.read_u16(ip);
510                    ip += 2;
511                    let field_count = self.read_u16(ip);
512                    ip += 2;
513                    out.push_str(&format!(
514                        "BUILD_ENUM {:>4} ({}) {:>4} ({}) fields={}\n",
515                        enum_idx,
516                        self.constants[enum_idx as usize],
517                        variant_idx,
518                        self.constants[variant_idx as usize],
519                        field_count
520                    ));
521                }
522                x if x == Op::MatchEnum as u8 => {
523                    let enum_idx = self.read_u16(ip);
524                    ip += 2;
525                    let variant_idx = self.read_u16(ip);
526                    ip += 2;
527                    out.push_str(&format!(
528                        "MATCH_ENUM {:>4} ({}) {:>4} ({})\n",
529                        enum_idx,
530                        self.constants[enum_idx as usize],
531                        variant_idx,
532                        self.constants[variant_idx as usize]
533                    ));
534                }
535                x if x == Op::PopIterator as u8 => out.push_str("POP_ITERATOR\n"),
536                x if x == Op::Dup as u8 => out.push_str("DUP\n"),
537                x if x == Op::Swap as u8 => out.push_str("SWAP\n"),
538                _ => {
539                    out.push_str(&format!("UNKNOWN(0x{:02x})\n", op));
540                }
541            }
542        }
543        out
544    }
545}
546
547impl Default for Chunk {
548    fn default() -> Self {
549        Self::new()
550    }
551}