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