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    /// Push a new lexical scope onto the environment stack.
26    PushScope,
27    /// Pop the current lexical scope from the environment stack.
28    PopScope,
29
30    // --- Arithmetic ---
31    Add,
32    Sub,
33    Mul,
34    Div,
35    Mod,
36    Negate,
37
38    // --- Comparison ---
39    Equal,
40    NotEqual,
41    Less,
42    Greater,
43    LessEqual,
44    GreaterEqual,
45
46    // --- Logical ---
47    Not,
48
49    // --- Control flow ---
50    /// Jump unconditionally. arg: u16 offset.
51    Jump,
52    /// Jump if top of stack is falsy. Does not pop. arg: u16 offset.
53    JumpIfFalse,
54    /// Jump if top of stack is truthy. Does not pop. arg: u16 offset.
55    JumpIfTrue,
56    /// Pop top of stack (discard).
57    Pop,
58
59    // --- Functions ---
60    /// Call a function/builtin. arg: u8 = arg count. Name is on stack below args.
61    Call,
62    /// Tail call: like Call, but replaces the current frame instead of pushing
63    /// a new one. Used for `return f(x)` to enable tail call optimization.
64    /// For builtins, behaves like a regular Call (no frame to replace).
65    TailCall,
66    /// Return from current function. Pops return value.
67    Return,
68    /// Create a closure. arg: u16 = chunk index in function table.
69    Closure,
70
71    // --- Collections ---
72    /// Build a list. arg: u16 = element count. Elements are on stack.
73    BuildList,
74    /// Build a dict. arg: u16 = entry count. Key-value pairs on stack.
75    BuildDict,
76    /// Subscript access: stack has [object, index]. Pushes result.
77    Subscript,
78    /// Slice access: stack has [object, start_or_nil, end_or_nil]. Pushes sublist/substring.
79    Slice,
80
81    // --- Object operations ---
82    /// Property access. arg: u16 = constant index (property name).
83    GetProperty,
84    /// Optional property access (?.). Like GetProperty but returns nil
85    /// instead of erroring when the object is nil. arg: u16 = constant index.
86    GetPropertyOpt,
87    /// Property assignment. arg: u16 = constant index (property name).
88    /// Stack: [value] → assigns to the named variable's property.
89    SetProperty,
90    /// Subscript assignment. arg: u16 = constant index (variable name).
91    /// Stack: [index, value] → assigns to variable[index] = value.
92    SetSubscript,
93    /// Method call. arg1: u16 = constant index (method name), arg2: u8 = arg count.
94    MethodCall,
95    /// Optional method call (?.). Like MethodCall but returns nil if the
96    /// receiver is nil instead of dispatching. arg1: u16, arg2: u8.
97    MethodCallOpt,
98
99    // --- String ---
100    /// String concatenation of N parts. arg: u16 = part count.
101    Concat,
102
103    // --- Iteration ---
104    /// Set up a for-in loop. Expects iterable on stack. Pushes iterator state.
105    IterInit,
106    /// Advance iterator. If exhausted, jumps. arg: u16 = jump offset.
107    /// Pushes next value and the variable name is set via DefVar before the loop.
108    IterNext,
109
110    // --- Pipe ---
111    /// Pipe: pops [value, callable], invokes callable(value).
112    Pipe,
113
114    // --- Error handling ---
115    /// Pop value, raise as error.
116    Throw,
117    /// Push exception handler. arg: u16 = offset to catch handler.
118    TryCatchSetup,
119    /// Remove top exception handler (end of try body).
120    PopHandler,
121
122    // --- Concurrency ---
123    /// Execute closure N times sequentially, push results as list.
124    /// Stack: count, closure → result_list
125    Parallel,
126    /// Execute closure for each item in list, push results as list.
127    /// Stack: list, closure → result_list
128    ParallelMap,
129    /// Like ParallelMap but wraps each result in Result.Ok/Err, never fails.
130    /// Stack: list, closure → {results: [Result], succeeded: int, failed: int}
131    ParallelSettle,
132    /// Store closure for deferred execution, push TaskHandle.
133    /// Stack: closure → TaskHandle
134    Spawn,
135
136    // --- Imports ---
137    /// Import a file. arg: u16 = constant index (path string).
138    Import,
139    /// Selective import. arg1: u16 = path string, arg2: u16 = names list constant.
140    SelectiveImport,
141
142    // --- Deadline ---
143    /// Pop duration value, push deadline onto internal deadline stack.
144    DeadlineSetup,
145    /// Pop deadline from internal deadline stack.
146    DeadlineEnd,
147
148    // --- Enum ---
149    /// Build an enum variant value.
150    /// arg1: u16 = constant index (enum name), arg2: u16 = constant index (variant name),
151    /// arg3: u16 = field count. Fields are on stack.
152    BuildEnum,
153
154    // --- Match ---
155    /// Match an enum pattern. Checks enum_name + variant on the top of stack (dup'd match value).
156    /// arg1: u16 = constant index (enum name), arg2: u16 = constant index (variant name).
157    /// If match succeeds, pushes true; else pushes false.
158    MatchEnum,
159
160    // --- Loop control ---
161    /// Pop the top iterator from the iterator stack (cleanup on break from for-in).
162    PopIterator,
163
164    // --- Defaults ---
165    /// Push the number of arguments passed to the current function call.
166    GetArgc,
167
168    // --- Type checking ---
169    /// Runtime type check on a variable.
170    /// arg1: u16 = constant index (variable name),
171    /// arg2: u16 = constant index (expected type name).
172    /// Throws a TypeError if the variable's type doesn't match.
173    CheckType,
174
175    // --- Result try operator ---
176    /// Try-unwrap: if top is Result.Ok(v), replace with v. If Result.Err(e), return it.
177    TryUnwrap,
178
179    // --- Spread call ---
180    /// Call with spread arguments. Stack: [callee, args_list] -> result.
181    CallSpread,
182    /// Method call with spread arguments. Stack: [object, args_list] -> result.
183    /// Followed by 2 bytes for method name constant index.
184    MethodCallSpread,
185
186    // --- Misc ---
187    /// Duplicate top of stack.
188    Dup,
189    /// Swap top two stack values.
190    Swap,
191    /// Membership test: stack has [item, collection]. Pushes bool.
192    /// Works for lists (item in list), dicts (key in dict), strings (substr in string), and sets.
193    Contains,
194
195    /// Yield a value from a generator. Pops value, sends through channel, suspends.
196    Yield,
197}
198
199/// A constant value in the constant pool.
200#[derive(Debug, Clone, PartialEq)]
201pub enum Constant {
202    Int(i64),
203    Float(f64),
204    String(String),
205    Bool(bool),
206    Nil,
207    Duration(u64),
208}
209
210impl fmt::Display for Constant {
211    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212        match self {
213            Constant::Int(n) => write!(f, "{n}"),
214            Constant::Float(n) => write!(f, "{n}"),
215            Constant::String(s) => write!(f, "\"{s}\""),
216            Constant::Bool(b) => write!(f, "{b}"),
217            Constant::Nil => write!(f, "nil"),
218            Constant::Duration(ms) => write!(f, "{ms}ms"),
219        }
220    }
221}
222
223/// A compiled chunk of bytecode.
224#[derive(Debug, Clone)]
225pub struct Chunk {
226    /// The bytecode instructions.
227    pub code: Vec<u8>,
228    /// Constant pool.
229    pub constants: Vec<Constant>,
230    /// Source line numbers for each instruction (for error reporting).
231    pub lines: Vec<u32>,
232    /// Source column numbers for each instruction (for error reporting).
233    /// Parallel to `lines`; 0 means no column info available.
234    pub columns: Vec<u32>,
235    /// Source file that this chunk was compiled from, when known. Set for
236    /// chunks compiled from imported modules so runtime errors can report
237    /// the correct file path for each frame instead of always pointing at
238    /// the entry-point pipeline.
239    pub source_file: Option<String>,
240    /// Current column to use when emitting instructions (set by compiler).
241    current_col: u32,
242    /// Compiled function bodies (for closures).
243    pub functions: Vec<CompiledFunction>,
244}
245
246/// A compiled function (closure body).
247#[derive(Debug, Clone)]
248pub struct CompiledFunction {
249    pub name: String,
250    pub params: Vec<String>,
251    /// Index of the first parameter with a default value, or None if all required.
252    pub default_start: Option<usize>,
253    pub chunk: Chunk,
254    /// True if the function body contains `yield` expressions (generator function).
255    pub is_generator: bool,
256}
257
258impl Chunk {
259    pub fn new() -> Self {
260        Self {
261            code: Vec::new(),
262            constants: Vec::new(),
263            lines: Vec::new(),
264            columns: Vec::new(),
265            source_file: None,
266            current_col: 0,
267            functions: Vec::new(),
268        }
269    }
270
271    /// Set the current column for subsequent emit calls.
272    pub fn set_column(&mut self, col: u32) {
273        self.current_col = col;
274    }
275
276    /// Add a constant and return its index.
277    pub fn add_constant(&mut self, constant: Constant) -> u16 {
278        // Reuse existing constant if possible
279        for (i, c) in self.constants.iter().enumerate() {
280            if c == &constant {
281                return i as u16;
282            }
283        }
284        let idx = self.constants.len();
285        self.constants.push(constant);
286        idx as u16
287    }
288
289    /// Emit a single-byte instruction.
290    pub fn emit(&mut self, op: Op, line: u32) {
291        let col = self.current_col;
292        self.code.push(op as u8);
293        self.lines.push(line);
294        self.columns.push(col);
295    }
296
297    /// Emit an instruction with a u16 argument.
298    pub fn emit_u16(&mut self, op: Op, arg: u16, line: u32) {
299        let col = self.current_col;
300        self.code.push(op as u8);
301        self.code.push((arg >> 8) as u8);
302        self.code.push((arg & 0xFF) as u8);
303        self.lines.push(line);
304        self.lines.push(line);
305        self.lines.push(line);
306        self.columns.push(col);
307        self.columns.push(col);
308        self.columns.push(col);
309    }
310
311    /// Emit an instruction with a u8 argument.
312    pub fn emit_u8(&mut self, op: Op, arg: u8, line: u32) {
313        let col = self.current_col;
314        self.code.push(op as u8);
315        self.code.push(arg);
316        self.lines.push(line);
317        self.lines.push(line);
318        self.columns.push(col);
319        self.columns.push(col);
320    }
321
322    /// Emit a method call: op + u16 (method name) + u8 (arg count).
323    pub fn emit_method_call(&mut self, name_idx: u16, arg_count: u8, line: u32) {
324        self.emit_method_call_inner(Op::MethodCall, name_idx, arg_count, line);
325    }
326
327    /// Emit an optional method call (?.) — returns nil if receiver is nil.
328    pub fn emit_method_call_opt(&mut self, name_idx: u16, arg_count: u8, line: u32) {
329        self.emit_method_call_inner(Op::MethodCallOpt, name_idx, arg_count, line);
330    }
331
332    fn emit_method_call_inner(&mut self, op: Op, name_idx: u16, arg_count: u8, line: u32) {
333        let col = self.current_col;
334        self.code.push(op as u8);
335        self.code.push((name_idx >> 8) as u8);
336        self.code.push((name_idx & 0xFF) as u8);
337        self.code.push(arg_count);
338        self.lines.push(line);
339        self.lines.push(line);
340        self.lines.push(line);
341        self.lines.push(line);
342        self.columns.push(col);
343        self.columns.push(col);
344        self.columns.push(col);
345        self.columns.push(col);
346    }
347
348    /// Current code offset (for jump patching).
349    pub fn current_offset(&self) -> usize {
350        self.code.len()
351    }
352
353    /// Emit a jump instruction with a placeholder offset. Returns the position to patch.
354    pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
355        let col = self.current_col;
356        self.code.push(op as u8);
357        let patch_pos = self.code.len();
358        self.code.push(0xFF);
359        self.code.push(0xFF);
360        self.lines.push(line);
361        self.lines.push(line);
362        self.lines.push(line);
363        self.columns.push(col);
364        self.columns.push(col);
365        self.columns.push(col);
366        patch_pos
367    }
368
369    /// Patch a jump instruction at the given position to jump to the current offset.
370    pub fn patch_jump(&mut self, patch_pos: usize) {
371        let target = self.code.len() as u16;
372        self.code[patch_pos] = (target >> 8) as u8;
373        self.code[patch_pos + 1] = (target & 0xFF) as u8;
374    }
375
376    /// Patch a jump to a specific target position.
377    pub fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
378        let target = target as u16;
379        self.code[patch_pos] = (target >> 8) as u8;
380        self.code[patch_pos + 1] = (target & 0xFF) as u8;
381    }
382
383    /// Read a u16 argument at the given position.
384    pub fn read_u16(&self, pos: usize) -> u16 {
385        ((self.code[pos] as u16) << 8) | (self.code[pos + 1] as u16)
386    }
387
388    /// Disassemble for debugging.
389    pub fn disassemble(&self, name: &str) -> String {
390        let mut out = format!("== {name} ==\n");
391        let mut ip = 0;
392        while ip < self.code.len() {
393            let op = self.code[ip];
394            let line = self.lines.get(ip).copied().unwrap_or(0);
395            out.push_str(&format!("{:04} [{:>4}] ", ip, line));
396            ip += 1;
397
398            match op {
399                x if x == Op::Constant as u8 => {
400                    let idx = self.read_u16(ip);
401                    ip += 2;
402                    let val = &self.constants[idx as usize];
403                    out.push_str(&format!("CONSTANT {:>4} ({})\n", idx, val));
404                }
405                x if x == Op::Nil as u8 => out.push_str("NIL\n"),
406                x if x == Op::True as u8 => out.push_str("TRUE\n"),
407                x if x == Op::False as u8 => out.push_str("FALSE\n"),
408                x if x == Op::GetVar as u8 => {
409                    let idx = self.read_u16(ip);
410                    ip += 2;
411                    out.push_str(&format!(
412                        "GET_VAR {:>4} ({})\n",
413                        idx, self.constants[idx as usize]
414                    ));
415                }
416                x if x == Op::DefLet as u8 => {
417                    let idx = self.read_u16(ip);
418                    ip += 2;
419                    out.push_str(&format!(
420                        "DEF_LET {:>4} ({})\n",
421                        idx, self.constants[idx as usize]
422                    ));
423                }
424                x if x == Op::DefVar as u8 => {
425                    let idx = self.read_u16(ip);
426                    ip += 2;
427                    out.push_str(&format!(
428                        "DEF_VAR {:>4} ({})\n",
429                        idx, self.constants[idx as usize]
430                    ));
431                }
432                x if x == Op::SetVar as u8 => {
433                    let idx = self.read_u16(ip);
434                    ip += 2;
435                    out.push_str(&format!(
436                        "SET_VAR {:>4} ({})\n",
437                        idx, self.constants[idx as usize]
438                    ));
439                }
440                x if x == Op::PushScope as u8 => out.push_str("PUSH_SCOPE\n"),
441                x if x == Op::PopScope as u8 => out.push_str("POP_SCOPE\n"),
442                x if x == Op::Add as u8 => out.push_str("ADD\n"),
443                x if x == Op::Sub as u8 => out.push_str("SUB\n"),
444                x if x == Op::Mul as u8 => out.push_str("MUL\n"),
445                x if x == Op::Div as u8 => out.push_str("DIV\n"),
446                x if x == Op::Mod as u8 => out.push_str("MOD\n"),
447                x if x == Op::Negate as u8 => out.push_str("NEGATE\n"),
448                x if x == Op::Equal as u8 => out.push_str("EQUAL\n"),
449                x if x == Op::NotEqual as u8 => out.push_str("NOT_EQUAL\n"),
450                x if x == Op::Less as u8 => out.push_str("LESS\n"),
451                x if x == Op::Greater as u8 => out.push_str("GREATER\n"),
452                x if x == Op::LessEqual as u8 => out.push_str("LESS_EQUAL\n"),
453                x if x == Op::GreaterEqual as u8 => out.push_str("GREATER_EQUAL\n"),
454                x if x == Op::Contains as u8 => out.push_str("CONTAINS\n"),
455                x if x == Op::Not as u8 => out.push_str("NOT\n"),
456                x if x == Op::Jump as u8 => {
457                    let target = self.read_u16(ip);
458                    ip += 2;
459                    out.push_str(&format!("JUMP {:>4}\n", target));
460                }
461                x if x == Op::JumpIfFalse as u8 => {
462                    let target = self.read_u16(ip);
463                    ip += 2;
464                    out.push_str(&format!("JUMP_IF_FALSE {:>4}\n", target));
465                }
466                x if x == Op::JumpIfTrue as u8 => {
467                    let target = self.read_u16(ip);
468                    ip += 2;
469                    out.push_str(&format!("JUMP_IF_TRUE {:>4}\n", target));
470                }
471                x if x == Op::Pop as u8 => out.push_str("POP\n"),
472                x if x == Op::Call as u8 => {
473                    let argc = self.code[ip];
474                    ip += 1;
475                    out.push_str(&format!("CALL {:>4}\n", argc));
476                }
477                x if x == Op::TailCall as u8 => {
478                    let argc = self.code[ip];
479                    ip += 1;
480                    out.push_str(&format!("TAIL_CALL {:>4}\n", argc));
481                }
482                x if x == Op::Return as u8 => out.push_str("RETURN\n"),
483                x if x == Op::Closure as u8 => {
484                    let idx = self.read_u16(ip);
485                    ip += 2;
486                    out.push_str(&format!("CLOSURE {:>4}\n", idx));
487                }
488                x if x == Op::BuildList as u8 => {
489                    let count = self.read_u16(ip);
490                    ip += 2;
491                    out.push_str(&format!("BUILD_LIST {:>4}\n", count));
492                }
493                x if x == Op::BuildDict as u8 => {
494                    let count = self.read_u16(ip);
495                    ip += 2;
496                    out.push_str(&format!("BUILD_DICT {:>4}\n", count));
497                }
498                x if x == Op::Subscript as u8 => out.push_str("SUBSCRIPT\n"),
499                x if x == Op::Slice as u8 => out.push_str("SLICE\n"),
500                x if x == Op::GetProperty as u8 => {
501                    let idx = self.read_u16(ip);
502                    ip += 2;
503                    out.push_str(&format!(
504                        "GET_PROPERTY {:>4} ({})\n",
505                        idx, self.constants[idx as usize]
506                    ));
507                }
508                x if x == Op::GetPropertyOpt as u8 => {
509                    let idx = self.read_u16(ip);
510                    ip += 2;
511                    out.push_str(&format!(
512                        "GET_PROPERTY_OPT {:>4} ({})\n",
513                        idx, self.constants[idx as usize]
514                    ));
515                }
516                x if x == Op::SetProperty as u8 => {
517                    let idx = self.read_u16(ip);
518                    ip += 2;
519                    out.push_str(&format!(
520                        "SET_PROPERTY {:>4} ({})\n",
521                        idx, self.constants[idx as usize]
522                    ));
523                }
524                x if x == Op::SetSubscript as u8 => {
525                    let idx = self.read_u16(ip);
526                    ip += 2;
527                    out.push_str(&format!(
528                        "SET_SUBSCRIPT {:>4} ({})\n",
529                        idx, self.constants[idx as usize]
530                    ));
531                }
532                x if x == Op::MethodCall as u8 => {
533                    let idx = self.read_u16(ip);
534                    ip += 2;
535                    let argc = self.code[ip];
536                    ip += 1;
537                    out.push_str(&format!(
538                        "METHOD_CALL {:>4} ({}) argc={}\n",
539                        idx, self.constants[idx as usize], argc
540                    ));
541                }
542                x if x == Op::MethodCallOpt as u8 => {
543                    let idx = self.read_u16(ip);
544                    ip += 2;
545                    let argc = self.code[ip];
546                    ip += 1;
547                    out.push_str(&format!(
548                        "METHOD_CALL_OPT {:>4} ({}) argc={}\n",
549                        idx, self.constants[idx as usize], argc
550                    ));
551                }
552                x if x == Op::Concat as u8 => {
553                    let count = self.read_u16(ip);
554                    ip += 2;
555                    out.push_str(&format!("CONCAT {:>4}\n", count));
556                }
557                x if x == Op::IterInit as u8 => out.push_str("ITER_INIT\n"),
558                x if x == Op::IterNext as u8 => {
559                    let target = self.read_u16(ip);
560                    ip += 2;
561                    out.push_str(&format!("ITER_NEXT {:>4}\n", target));
562                }
563                x if x == Op::Throw as u8 => out.push_str("THROW\n"),
564                x if x == Op::TryCatchSetup as u8 => {
565                    let target = self.read_u16(ip);
566                    ip += 2;
567                    out.push_str(&format!("TRY_CATCH_SETUP {:>4}\n", target));
568                }
569                x if x == Op::PopHandler as u8 => out.push_str("POP_HANDLER\n"),
570                x if x == Op::Pipe as u8 => out.push_str("PIPE\n"),
571                x if x == Op::Parallel as u8 => out.push_str("PARALLEL\n"),
572                x if x == Op::ParallelMap as u8 => out.push_str("PARALLEL_MAP\n"),
573                x if x == Op::ParallelSettle as u8 => out.push_str("PARALLEL_SETTLE\n"),
574                x if x == Op::Spawn as u8 => out.push_str("SPAWN\n"),
575                x if x == Op::Import as u8 => {
576                    let idx = self.read_u16(ip);
577                    ip += 2;
578                    out.push_str(&format!(
579                        "IMPORT {:>4} ({})\n",
580                        idx, self.constants[idx as usize]
581                    ));
582                }
583                x if x == Op::SelectiveImport as u8 => {
584                    let path_idx = self.read_u16(ip);
585                    ip += 2;
586                    let names_idx = self.read_u16(ip);
587                    ip += 2;
588                    out.push_str(&format!(
589                        "SELECTIVE_IMPORT {:>4} ({}) names: {:>4} ({})\n",
590                        path_idx,
591                        self.constants[path_idx as usize],
592                        names_idx,
593                        self.constants[names_idx as usize]
594                    ));
595                }
596                x if x == Op::DeadlineSetup as u8 => out.push_str("DEADLINE_SETUP\n"),
597                x if x == Op::DeadlineEnd as u8 => out.push_str("DEADLINE_END\n"),
598                x if x == Op::BuildEnum as u8 => {
599                    let enum_idx = self.read_u16(ip);
600                    ip += 2;
601                    let variant_idx = self.read_u16(ip);
602                    ip += 2;
603                    let field_count = self.read_u16(ip);
604                    ip += 2;
605                    out.push_str(&format!(
606                        "BUILD_ENUM {:>4} ({}) {:>4} ({}) fields={}\n",
607                        enum_idx,
608                        self.constants[enum_idx as usize],
609                        variant_idx,
610                        self.constants[variant_idx as usize],
611                        field_count
612                    ));
613                }
614                x if x == Op::MatchEnum as u8 => {
615                    let enum_idx = self.read_u16(ip);
616                    ip += 2;
617                    let variant_idx = self.read_u16(ip);
618                    ip += 2;
619                    out.push_str(&format!(
620                        "MATCH_ENUM {:>4} ({}) {:>4} ({})\n",
621                        enum_idx,
622                        self.constants[enum_idx as usize],
623                        variant_idx,
624                        self.constants[variant_idx as usize]
625                    ));
626                }
627                x if x == Op::PopIterator as u8 => out.push_str("POP_ITERATOR\n"),
628                x if x == Op::TryUnwrap as u8 => out.push_str("TRY_UNWRAP\n"),
629                x if x == Op::CallSpread as u8 => out.push_str("CALL_SPREAD\n"),
630                x if x == Op::MethodCallSpread as u8 => {
631                    let idx = self.read_u16(ip + 1);
632                    ip += 2;
633                    out.push_str(&format!("METHOD_CALL_SPREAD {idx}\n"));
634                }
635                x if x == Op::Dup as u8 => out.push_str("DUP\n"),
636                x if x == Op::Swap as u8 => out.push_str("SWAP\n"),
637                x if x == Op::Yield as u8 => out.push_str("YIELD\n"),
638                _ => {
639                    out.push_str(&format!("UNKNOWN(0x{:02x})\n", op));
640                }
641            }
642        }
643        out
644    }
645}
646
647impl Default for Chunk {
648    fn default() -> Self {
649        Self::new()
650    }
651}