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    /// Like ParallelMap but wraps each result in Result.Ok/Err, never fails.
126    /// Stack: list, closure → {results: [Result], succeeded: int, failed: int}
127    ParallelSettle,
128    /// Store closure for deferred execution, push TaskHandle.
129    /// Stack: closure → TaskHandle
130    Spawn,
131
132    // --- Imports ---
133    /// Import a file. arg: u16 = constant index (path string).
134    Import,
135    /// Selective import. arg1: u16 = path string, arg2: u16 = names list constant.
136    SelectiveImport,
137
138    // --- Deadline ---
139    /// Pop duration value, push deadline onto internal deadline stack.
140    DeadlineSetup,
141    /// Pop deadline from internal deadline stack.
142    DeadlineEnd,
143
144    // --- Enum ---
145    /// Build an enum variant value.
146    /// arg1: u16 = constant index (enum name), arg2: u16 = constant index (variant name),
147    /// arg3: u16 = field count. Fields are on stack.
148    BuildEnum,
149
150    // --- Match ---
151    /// Match an enum pattern. Checks enum_name + variant on the top of stack (dup'd match value).
152    /// arg1: u16 = constant index (enum name), arg2: u16 = constant index (variant name).
153    /// If match succeeds, pushes true; else pushes false.
154    MatchEnum,
155
156    // --- Loop control ---
157    /// Pop the top iterator from the iterator stack (cleanup on break from for-in).
158    PopIterator,
159
160    // --- Defaults ---
161    /// Push the number of arguments passed to the current function call.
162    GetArgc,
163
164    // --- Type checking ---
165    /// Runtime type check on a variable.
166    /// arg1: u16 = constant index (variable name),
167    /// arg2: u16 = constant index (expected type name).
168    /// Throws a TypeError if the variable's type doesn't match.
169    CheckType,
170
171    // --- Result try operator ---
172    /// Try-unwrap: if top is Result.Ok(v), replace with v. If Result.Err(e), return it.
173    TryUnwrap,
174
175    // --- Spread call ---
176    /// Call with spread arguments. Stack: [callee, args_list] -> result.
177    CallSpread,
178    /// Method call with spread arguments. Stack: [object, args_list] -> result.
179    /// Followed by 2 bytes for method name constant index.
180    MethodCallSpread,
181
182    // --- Misc ---
183    /// Duplicate top of stack.
184    Dup,
185    /// Swap top two stack values.
186    Swap,
187    /// Yield a value from a generator. Pops value, sends through channel, suspends.
188    Yield,
189}
190
191/// A constant value in the constant pool.
192#[derive(Debug, Clone, PartialEq)]
193pub enum Constant {
194    Int(i64),
195    Float(f64),
196    String(String),
197    Bool(bool),
198    Nil,
199    Duration(u64),
200}
201
202impl fmt::Display for Constant {
203    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204        match self {
205            Constant::Int(n) => write!(f, "{n}"),
206            Constant::Float(n) => write!(f, "{n}"),
207            Constant::String(s) => write!(f, "\"{s}\""),
208            Constant::Bool(b) => write!(f, "{b}"),
209            Constant::Nil => write!(f, "nil"),
210            Constant::Duration(ms) => write!(f, "{ms}ms"),
211        }
212    }
213}
214
215/// A compiled chunk of bytecode.
216#[derive(Debug, Clone)]
217pub struct Chunk {
218    /// The bytecode instructions.
219    pub code: Vec<u8>,
220    /// Constant pool.
221    pub constants: Vec<Constant>,
222    /// Source line numbers for each instruction (for error reporting).
223    pub lines: Vec<u32>,
224    /// Source column numbers for each instruction (for error reporting).
225    /// Parallel to `lines`; 0 means no column info available.
226    pub columns: Vec<u32>,
227    /// Current column to use when emitting instructions (set by compiler).
228    current_col: u32,
229    /// Compiled function bodies (for closures).
230    pub functions: Vec<CompiledFunction>,
231}
232
233/// A compiled function (closure body).
234#[derive(Debug, Clone)]
235pub struct CompiledFunction {
236    pub name: String,
237    pub params: Vec<String>,
238    /// Index of the first parameter with a default value, or None if all required.
239    pub default_start: Option<usize>,
240    pub chunk: Chunk,
241    /// True if the function body contains `yield` expressions (generator function).
242    pub is_generator: bool,
243}
244
245impl Chunk {
246    pub fn new() -> Self {
247        Self {
248            code: Vec::new(),
249            constants: Vec::new(),
250            lines: Vec::new(),
251            columns: Vec::new(),
252            current_col: 0,
253            functions: Vec::new(),
254        }
255    }
256
257    /// Set the current column for subsequent emit calls.
258    pub fn set_column(&mut self, col: u32) {
259        self.current_col = col;
260    }
261
262    /// Add a constant and return its index.
263    pub fn add_constant(&mut self, constant: Constant) -> u16 {
264        // Reuse existing constant if possible
265        for (i, c) in self.constants.iter().enumerate() {
266            if c == &constant {
267                return i as u16;
268            }
269        }
270        let idx = self.constants.len();
271        self.constants.push(constant);
272        idx as u16
273    }
274
275    /// Emit a single-byte instruction.
276    pub fn emit(&mut self, op: Op, line: u32) {
277        let col = self.current_col;
278        self.code.push(op as u8);
279        self.lines.push(line);
280        self.columns.push(col);
281    }
282
283    /// Emit an instruction with a u16 argument.
284    pub fn emit_u16(&mut self, op: Op, arg: u16, line: u32) {
285        let col = self.current_col;
286        self.code.push(op as u8);
287        self.code.push((arg >> 8) as u8);
288        self.code.push((arg & 0xFF) as u8);
289        self.lines.push(line);
290        self.lines.push(line);
291        self.lines.push(line);
292        self.columns.push(col);
293        self.columns.push(col);
294        self.columns.push(col);
295    }
296
297    /// Emit an instruction with a u8 argument.
298    pub fn emit_u8(&mut self, op: Op, arg: u8, line: u32) {
299        let col = self.current_col;
300        self.code.push(op as u8);
301        self.code.push(arg);
302        self.lines.push(line);
303        self.lines.push(line);
304        self.columns.push(col);
305        self.columns.push(col);
306    }
307
308    /// Emit a method call: op + u16 (method name) + u8 (arg count).
309    pub fn emit_method_call(&mut self, name_idx: u16, arg_count: u8, line: u32) {
310        self.emit_method_call_inner(Op::MethodCall, name_idx, arg_count, line);
311    }
312
313    /// Emit an optional method call (?.) — returns nil if receiver is nil.
314    pub fn emit_method_call_opt(&mut self, name_idx: u16, arg_count: u8, line: u32) {
315        self.emit_method_call_inner(Op::MethodCallOpt, name_idx, arg_count, line);
316    }
317
318    fn emit_method_call_inner(&mut self, op: Op, name_idx: u16, arg_count: u8, line: u32) {
319        let col = self.current_col;
320        self.code.push(op as u8);
321        self.code.push((name_idx >> 8) as u8);
322        self.code.push((name_idx & 0xFF) as u8);
323        self.code.push(arg_count);
324        self.lines.push(line);
325        self.lines.push(line);
326        self.lines.push(line);
327        self.lines.push(line);
328        self.columns.push(col);
329        self.columns.push(col);
330        self.columns.push(col);
331        self.columns.push(col);
332    }
333
334    /// Current code offset (for jump patching).
335    pub fn current_offset(&self) -> usize {
336        self.code.len()
337    }
338
339    /// Emit a jump instruction with a placeholder offset. Returns the position to patch.
340    pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
341        let col = self.current_col;
342        self.code.push(op as u8);
343        let patch_pos = self.code.len();
344        self.code.push(0xFF);
345        self.code.push(0xFF);
346        self.lines.push(line);
347        self.lines.push(line);
348        self.lines.push(line);
349        self.columns.push(col);
350        self.columns.push(col);
351        self.columns.push(col);
352        patch_pos
353    }
354
355    /// Patch a jump instruction at the given position to jump to the current offset.
356    pub fn patch_jump(&mut self, patch_pos: usize) {
357        let target = self.code.len() as u16;
358        self.code[patch_pos] = (target >> 8) as u8;
359        self.code[patch_pos + 1] = (target & 0xFF) as u8;
360    }
361
362    /// Patch a jump to a specific target position.
363    pub fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
364        let target = target as u16;
365        self.code[patch_pos] = (target >> 8) as u8;
366        self.code[patch_pos + 1] = (target & 0xFF) as u8;
367    }
368
369    /// Read a u16 argument at the given position.
370    pub fn read_u16(&self, pos: usize) -> u16 {
371        ((self.code[pos] as u16) << 8) | (self.code[pos + 1] as u16)
372    }
373
374    /// Disassemble for debugging.
375    pub fn disassemble(&self, name: &str) -> String {
376        let mut out = format!("== {name} ==\n");
377        let mut ip = 0;
378        while ip < self.code.len() {
379            let op = self.code[ip];
380            let line = self.lines.get(ip).copied().unwrap_or(0);
381            out.push_str(&format!("{:04} [{:>4}] ", ip, line));
382            ip += 1;
383
384            match op {
385                x if x == Op::Constant as u8 => {
386                    let idx = self.read_u16(ip);
387                    ip += 2;
388                    let val = &self.constants[idx as usize];
389                    out.push_str(&format!("CONSTANT {:>4} ({})\n", idx, val));
390                }
391                x if x == Op::Nil as u8 => out.push_str("NIL\n"),
392                x if x == Op::True as u8 => out.push_str("TRUE\n"),
393                x if x == Op::False as u8 => out.push_str("FALSE\n"),
394                x if x == Op::GetVar as u8 => {
395                    let idx = self.read_u16(ip);
396                    ip += 2;
397                    out.push_str(&format!(
398                        "GET_VAR {:>4} ({})\n",
399                        idx, self.constants[idx as usize]
400                    ));
401                }
402                x if x == Op::DefLet as u8 => {
403                    let idx = self.read_u16(ip);
404                    ip += 2;
405                    out.push_str(&format!(
406                        "DEF_LET {:>4} ({})\n",
407                        idx, self.constants[idx as usize]
408                    ));
409                }
410                x if x == Op::DefVar as u8 => {
411                    let idx = self.read_u16(ip);
412                    ip += 2;
413                    out.push_str(&format!(
414                        "DEF_VAR {:>4} ({})\n",
415                        idx, self.constants[idx as usize]
416                    ));
417                }
418                x if x == Op::SetVar as u8 => {
419                    let idx = self.read_u16(ip);
420                    ip += 2;
421                    out.push_str(&format!(
422                        "SET_VAR {:>4} ({})\n",
423                        idx, self.constants[idx as usize]
424                    ));
425                }
426                x if x == Op::Add as u8 => out.push_str("ADD\n"),
427                x if x == Op::Sub as u8 => out.push_str("SUB\n"),
428                x if x == Op::Mul as u8 => out.push_str("MUL\n"),
429                x if x == Op::Div as u8 => out.push_str("DIV\n"),
430                x if x == Op::Mod as u8 => out.push_str("MOD\n"),
431                x if x == Op::Negate as u8 => out.push_str("NEGATE\n"),
432                x if x == Op::Equal as u8 => out.push_str("EQUAL\n"),
433                x if x == Op::NotEqual as u8 => out.push_str("NOT_EQUAL\n"),
434                x if x == Op::Less as u8 => out.push_str("LESS\n"),
435                x if x == Op::Greater as u8 => out.push_str("GREATER\n"),
436                x if x == Op::LessEqual as u8 => out.push_str("LESS_EQUAL\n"),
437                x if x == Op::GreaterEqual as u8 => out.push_str("GREATER_EQUAL\n"),
438                x if x == Op::Not as u8 => out.push_str("NOT\n"),
439                x if x == Op::Jump as u8 => {
440                    let target = self.read_u16(ip);
441                    ip += 2;
442                    out.push_str(&format!("JUMP {:>4}\n", target));
443                }
444                x if x == Op::JumpIfFalse as u8 => {
445                    let target = self.read_u16(ip);
446                    ip += 2;
447                    out.push_str(&format!("JUMP_IF_FALSE {:>4}\n", target));
448                }
449                x if x == Op::JumpIfTrue as u8 => {
450                    let target = self.read_u16(ip);
451                    ip += 2;
452                    out.push_str(&format!("JUMP_IF_TRUE {:>4}\n", target));
453                }
454                x if x == Op::Pop as u8 => out.push_str("POP\n"),
455                x if x == Op::Call as u8 => {
456                    let argc = self.code[ip];
457                    ip += 1;
458                    out.push_str(&format!("CALL {:>4}\n", argc));
459                }
460                x if x == Op::TailCall as u8 => {
461                    let argc = self.code[ip];
462                    ip += 1;
463                    out.push_str(&format!("TAIL_CALL {:>4}\n", argc));
464                }
465                x if x == Op::Return as u8 => out.push_str("RETURN\n"),
466                x if x == Op::Closure as u8 => {
467                    let idx = self.read_u16(ip);
468                    ip += 2;
469                    out.push_str(&format!("CLOSURE {:>4}\n", idx));
470                }
471                x if x == Op::BuildList as u8 => {
472                    let count = self.read_u16(ip);
473                    ip += 2;
474                    out.push_str(&format!("BUILD_LIST {:>4}\n", count));
475                }
476                x if x == Op::BuildDict as u8 => {
477                    let count = self.read_u16(ip);
478                    ip += 2;
479                    out.push_str(&format!("BUILD_DICT {:>4}\n", count));
480                }
481                x if x == Op::Subscript as u8 => out.push_str("SUBSCRIPT\n"),
482                x if x == Op::Slice as u8 => out.push_str("SLICE\n"),
483                x if x == Op::GetProperty as u8 => {
484                    let idx = self.read_u16(ip);
485                    ip += 2;
486                    out.push_str(&format!(
487                        "GET_PROPERTY {:>4} ({})\n",
488                        idx, self.constants[idx as usize]
489                    ));
490                }
491                x if x == Op::GetPropertyOpt as u8 => {
492                    let idx = self.read_u16(ip);
493                    ip += 2;
494                    out.push_str(&format!(
495                        "GET_PROPERTY_OPT {:>4} ({})\n",
496                        idx, self.constants[idx as usize]
497                    ));
498                }
499                x if x == Op::SetProperty as u8 => {
500                    let idx = self.read_u16(ip);
501                    ip += 2;
502                    out.push_str(&format!(
503                        "SET_PROPERTY {:>4} ({})\n",
504                        idx, self.constants[idx as usize]
505                    ));
506                }
507                x if x == Op::SetSubscript as u8 => {
508                    let idx = self.read_u16(ip);
509                    ip += 2;
510                    out.push_str(&format!(
511                        "SET_SUBSCRIPT {:>4} ({})\n",
512                        idx, self.constants[idx as usize]
513                    ));
514                }
515                x if x == Op::MethodCall as u8 => {
516                    let idx = self.read_u16(ip);
517                    ip += 2;
518                    let argc = self.code[ip];
519                    ip += 1;
520                    out.push_str(&format!(
521                        "METHOD_CALL {:>4} ({}) argc={}\n",
522                        idx, self.constants[idx as usize], argc
523                    ));
524                }
525                x if x == Op::MethodCallOpt as u8 => {
526                    let idx = self.read_u16(ip);
527                    ip += 2;
528                    let argc = self.code[ip];
529                    ip += 1;
530                    out.push_str(&format!(
531                        "METHOD_CALL_OPT {:>4} ({}) argc={}\n",
532                        idx, self.constants[idx as usize], argc
533                    ));
534                }
535                x if x == Op::Concat as u8 => {
536                    let count = self.read_u16(ip);
537                    ip += 2;
538                    out.push_str(&format!("CONCAT {:>4}\n", count));
539                }
540                x if x == Op::IterInit as u8 => out.push_str("ITER_INIT\n"),
541                x if x == Op::IterNext as u8 => {
542                    let target = self.read_u16(ip);
543                    ip += 2;
544                    out.push_str(&format!("ITER_NEXT {:>4}\n", target));
545                }
546                x if x == Op::Throw as u8 => out.push_str("THROW\n"),
547                x if x == Op::TryCatchSetup as u8 => {
548                    let target = self.read_u16(ip);
549                    ip += 2;
550                    out.push_str(&format!("TRY_CATCH_SETUP {:>4}\n", target));
551                }
552                x if x == Op::PopHandler as u8 => out.push_str("POP_HANDLER\n"),
553                x if x == Op::Pipe as u8 => out.push_str("PIPE\n"),
554                x if x == Op::Parallel as u8 => out.push_str("PARALLEL\n"),
555                x if x == Op::ParallelMap as u8 => out.push_str("PARALLEL_MAP\n"),
556                x if x == Op::ParallelSettle as u8 => out.push_str("PARALLEL_SETTLE\n"),
557                x if x == Op::Spawn as u8 => out.push_str("SPAWN\n"),
558                x if x == Op::Import as u8 => {
559                    let idx = self.read_u16(ip);
560                    ip += 2;
561                    out.push_str(&format!(
562                        "IMPORT {:>4} ({})\n",
563                        idx, self.constants[idx as usize]
564                    ));
565                }
566                x if x == Op::SelectiveImport as u8 => {
567                    let path_idx = self.read_u16(ip);
568                    ip += 2;
569                    let names_idx = self.read_u16(ip);
570                    ip += 2;
571                    out.push_str(&format!(
572                        "SELECTIVE_IMPORT {:>4} ({}) names: {:>4} ({})\n",
573                        path_idx,
574                        self.constants[path_idx as usize],
575                        names_idx,
576                        self.constants[names_idx as usize]
577                    ));
578                }
579                x if x == Op::DeadlineSetup as u8 => out.push_str("DEADLINE_SETUP\n"),
580                x if x == Op::DeadlineEnd as u8 => out.push_str("DEADLINE_END\n"),
581                x if x == Op::BuildEnum as u8 => {
582                    let enum_idx = self.read_u16(ip);
583                    ip += 2;
584                    let variant_idx = self.read_u16(ip);
585                    ip += 2;
586                    let field_count = self.read_u16(ip);
587                    ip += 2;
588                    out.push_str(&format!(
589                        "BUILD_ENUM {:>4} ({}) {:>4} ({}) fields={}\n",
590                        enum_idx,
591                        self.constants[enum_idx as usize],
592                        variant_idx,
593                        self.constants[variant_idx as usize],
594                        field_count
595                    ));
596                }
597                x if x == Op::MatchEnum as u8 => {
598                    let enum_idx = self.read_u16(ip);
599                    ip += 2;
600                    let variant_idx = self.read_u16(ip);
601                    ip += 2;
602                    out.push_str(&format!(
603                        "MATCH_ENUM {:>4} ({}) {:>4} ({})\n",
604                        enum_idx,
605                        self.constants[enum_idx as usize],
606                        variant_idx,
607                        self.constants[variant_idx as usize]
608                    ));
609                }
610                x if x == Op::PopIterator as u8 => out.push_str("POP_ITERATOR\n"),
611                x if x == Op::TryUnwrap as u8 => out.push_str("TRY_UNWRAP\n"),
612                x if x == Op::CallSpread as u8 => out.push_str("CALL_SPREAD\n"),
613                x if x == Op::MethodCallSpread as u8 => {
614                    let idx = self.read_u16(ip + 1);
615                    ip += 2;
616                    out.push_str(&format!("METHOD_CALL_SPREAD {idx}\n"));
617                }
618                x if x == Op::Dup as u8 => out.push_str("DUP\n"),
619                x if x == Op::Swap as u8 => out.push_str("SWAP\n"),
620                x if x == Op::Yield as u8 => out.push_str("YIELD\n"),
621                _ => {
622                    out.push_str(&format!("UNKNOWN(0x{:02x})\n", op));
623                }
624            }
625        }
626        out
627    }
628}
629
630impl Default for Chunk {
631    fn default() -> Self {
632        Self::new()
633    }
634}