surreal/
instruction.rs

1//! Bytecode instructions for the VM.
2
3use crate::Pid;
4
5// ========== Bit Syntax Types ==========
6
7/// Type specifier for a binary segment
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum BitType {
10    /// Integer (default)
11    Integer,
12    /// IEEE 754 float (32 or 64 bits)
13    Float,
14    /// Raw binary/bytes
15    Binary,
16    /// UTF-8 encoded codepoint
17    Utf8,
18}
19
20/// Endianness for multi-byte values
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Endianness {
23    /// Big endian (network byte order, default)
24    Big,
25    /// Little endian
26    Little,
27}
28
29/// Signedness for integer values
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum Signedness {
32    /// Unsigned (default)
33    Unsigned,
34    /// Signed (two's complement)
35    Signed,
36}
37
38/// A segment specification for binary construction or matching
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct BitSegment {
41    /// Type of the segment
42    pub bit_type: BitType,
43    /// Size in bits (None means use default for type or "rest" for binary)
44    pub size: Option<u32>,
45    /// Endianness for multi-byte values
46    pub endianness: Endianness,
47    /// Signedness for integers
48    pub signedness: Signedness,
49}
50
51impl Default for BitSegment {
52    fn default() -> Self {
53        BitSegment {
54            bit_type: BitType::Integer,
55            size: Some(8), // default is 8 bits for integer
56            endianness: Endianness::Big,
57            signedness: Signedness::Unsigned,
58        }
59    }
60}
61
62impl BitSegment {
63    /// Create a default integer segment with specified size in bits
64    pub fn integer(bits: u32) -> Self {
65        BitSegment {
66            bit_type: BitType::Integer,
67            size: Some(bits),
68            endianness: Endianness::Big,
69            signedness: Signedness::Unsigned,
70        }
71    }
72
73    /// Create a binary segment that matches the rest
74    pub fn binary_rest() -> Self {
75        BitSegment {
76            bit_type: BitType::Binary,
77            size: None, // rest of binary
78            endianness: Endianness::Big,
79            signedness: Signedness::Unsigned,
80        }
81    }
82
83    /// Create a binary segment with fixed byte size
84    pub fn binary(bytes: u32) -> Self {
85        BitSegment {
86            bit_type: BitType::Binary,
87            size: Some(bytes * 8),
88            endianness: Endianness::Big,
89            signedness: Signedness::Unsigned,
90        }
91    }
92
93    /// Create a 32-bit float segment
94    pub fn float32() -> Self {
95        BitSegment {
96            bit_type: BitType::Float,
97            size: Some(32),
98            endianness: Endianness::Big,
99            signedness: Signedness::Unsigned,
100        }
101    }
102
103    /// Create a 64-bit float segment
104    pub fn float64() -> Self {
105        BitSegment {
106            bit_type: BitType::Float,
107            size: Some(64),
108            endianness: Endianness::Big,
109            signedness: Signedness::Unsigned,
110        }
111    }
112
113    /// Set endianness to little
114    pub fn little(mut self) -> Self {
115        self.endianness = Endianness::Little;
116        self
117    }
118
119    /// Set signedness to signed
120    pub fn signed(mut self) -> Self {
121        self.signedness = Signedness::Signed;
122        self
123    }
124}
125
126/// Source of a segment value for binary construction
127#[derive(Debug, Clone)]
128pub enum SegmentSource {
129    /// Immediate integer value
130    Int(i64),
131    /// Value from register
132    Reg(Register),
133}
134
135/// Bytecode instructions
136#[derive(Debug, Clone)]
137pub enum Instruction {
138    /// Process is done
139    End,
140
141    /// Do some work (costs `amount` reductions)
142    Work { amount: u32 },
143
144    /// Spawn a new process running `code`, store child PID in register
145    Spawn {
146        code: Vec<Instruction>,
147        dest: Register,
148    },
149
150    /// Spawn a new process and atomically link to it
151    SpawnLink {
152        code: Vec<Instruction>,
153        dest: Register,
154    },
155
156    /// Send a message to a process
157    Send { to: Source, msg: String },
158
159    /// Receive a message matching a pattern, block if none available
160    /// For now, just receives any user message into a register
161    Receive { dest: Register },
162
163    /// Receive with timeout (in reductions). If no message arrives
164    /// before timeout expires, receives "TIMEOUT" instead.
165    ReceiveTimeout { dest: Register, timeout: u32 },
166
167    /// Link to another process (bidirectional crash notification)
168    Link { target: Source },
169
170    /// Remove a bidirectional link to another process
171    Unlink { target: Source },
172
173    /// Monitor another process (one-way crash notification)
174    /// Returns a monitor reference in dest for later demonitoring
175    Monitor { target: Source, dest: Register },
176
177    /// Cancel a monitor by its reference
178    Demonitor { monitor_ref: Register },
179
180    /// Register current process with a name
181    Register { name: String },
182
183    /// Unregister a name
184    Unregister { name: String },
185
186    /// Look up a registered name, store PID (or None) in register
187    WhereIs { name: String, dest: Register },
188
189    /// Print a value (for debugging)
190    Print { source: Source },
191
192    /// Crash the process (for testing links)
193    Crash,
194
195    /// Exit the process with a reason (from register)
196    /// If reason is :normal, linked processes are not signaled (unless they trap_exit)
197    /// If reason is abnormal, linked processes receive exit signal
198    Exit { reason: Register },
199
200    /// Set the trap_exit flag for this process
201    /// When true, exit signals from linked processes become {:EXIT, Pid, Reason} messages
202    /// When false (default), abnormal exit signals kill this process
203    TrapExit { enable: bool },
204
205    // ========== Arithmetic & Logic ==========
206    /// Load an immediate integer into a register
207    LoadInt { value: i64, dest: Register },
208
209    /// Move (copy) any value from source register to dest register
210    Move { source: Register, dest: Register },
211
212    /// Add two operands, store result in dest
213    Add {
214        a: Operand,
215        b: Operand,
216        dest: Register,
217    },
218
219    /// Subtract b from a, store result in dest
220    Sub {
221        a: Operand,
222        b: Operand,
223        dest: Register,
224    },
225
226    /// Multiply two operands, store result in dest
227    Mul {
228        a: Operand,
229        b: Operand,
230        dest: Register,
231    },
232
233    /// Divide a by b, store result in dest (integer division)
234    Div {
235        a: Operand,
236        b: Operand,
237        dest: Register,
238    },
239
240    /// Modulo a by b, store result in dest
241    Mod {
242        a: Operand,
243        b: Operand,
244        dest: Register,
245    },
246
247    // ========== Comparisons ==========
248    /// Compare equal: dest = (a == b) ? 1 : 0
249    Eq {
250        a: Operand,
251        b: Operand,
252        dest: Register,
253    },
254
255    /// Compare not equal: dest = (a != b) ? 1 : 0
256    Ne {
257        a: Operand,
258        b: Operand,
259        dest: Register,
260    },
261
262    /// Compare less than: dest = (a < b) ? 1 : 0
263    Lt {
264        a: Operand,
265        b: Operand,
266        dest: Register,
267    },
268
269    /// Compare less than or equal: dest = (a <= b) ? 1 : 0
270    Lte {
271        a: Operand,
272        b: Operand,
273        dest: Register,
274    },
275
276    /// Compare greater than: dest = (a > b) ? 1 : 0
277    Gt {
278        a: Operand,
279        b: Operand,
280        dest: Register,
281    },
282
283    /// Compare greater than or equal: dest = (a >= b) ? 1 : 0
284    Gte {
285        a: Operand,
286        b: Operand,
287        dest: Register,
288    },
289
290    // ========== Control Flow ==========
291    /// Unconditional jump to instruction index
292    Jump { target: usize },
293
294    /// Jump to target if condition is truthy (non-zero integer)
295    JumpIf { cond: Operand, target: usize },
296
297    /// Jump to target if condition is falsy (zero or non-integer)
298    JumpUnless { cond: Operand, target: usize },
299
300    /// Call a function: push return address onto call stack, jump to target
301    Call { target: usize },
302
303    /// Return from function: pop return address from call stack, jump to it
304    /// If call stack is empty, ends the process
305    Return,
306
307    // ========== Module Function Calls ==========
308    /// Call a function by MFA (Module:Function/Arity)
309    /// Arguments must be in R0..R(arity-1) before call
310    /// Return value will be in R0
311    CallMFA {
312        module: String,
313        function: String,
314        arity: u8,
315    },
316
317    /// Call a local function in the current module
318    /// More efficient than CallMFA when calling within same module
319    CallLocal { function: String, arity: u8 },
320
321    /// Tail call MFA - call without pushing return frame
322    TailCallMFA {
323        module: String,
324        function: String,
325        arity: u8,
326    },
327
328    /// Tail call local - call without pushing return frame
329    TailCallLocal { function: String, arity: u8 },
330
331    /// Create a function reference and store in register
332    MakeFun {
333        module: String,
334        function: String,
335        arity: u8,
336        dest: Register,
337    },
338
339    /// Apply a function reference (from register) to arguments
340    /// Fun must be a Value::Fun or Value::Closure in the specified register
341    /// Arguments in R0..R(arity-1)
342    Apply { fun: Register, arity: u8 },
343
344    /// Create a closure capturing values from registers
345    /// The closure references module:function but captures current register values
346    /// When applied, captured values are placed after explicit arguments
347    MakeClosure {
348        module: String,
349        function: String,
350        arity: u8,
351        captures: Vec<Register>,
352        dest: Register,
353    },
354
355    /// Spawn a process running module:function/arity
356    /// Arguments for the function must be in R0..R(arity-1)
357    SpawnMFA {
358        module: String,
359        function: String,
360        arity: u8,
361        dest: Register,
362    },
363
364    /// Spawn and link, running module:function/arity
365    SpawnLinkMFA {
366        module: String,
367        function: String,
368        arity: u8,
369        dest: Register,
370    },
371
372    // ========== Stack Operations ==========
373    /// Push a value onto the data stack
374    Push { source: Operand },
375
376    /// Pop a value from the data stack into a register
377    /// Crashes if stack is empty
378    Pop { dest: Register },
379
380    // ========== Atoms & Tuples ==========
381    /// Load an atom into a register
382    LoadAtom { name: String, dest: Register },
383
384    /// Create a tuple from the top `arity` stack elements
385    /// Elements are popped in reverse order (first pushed = first element)
386    MakeTuple { arity: u8, dest: Register },
387
388    /// Get an element from a tuple by index (0-based)
389    /// Crashes if not a tuple or index out of bounds
390    TupleElement {
391        tuple: Register,
392        index: u8,
393        dest: Register,
394    },
395
396    /// Get the arity (size) of a tuple
397    /// Crashes if not a tuple
398    TupleArity { tuple: Register, dest: Register },
399
400    // ========== Pattern Matching ==========
401    /// Match a value against a pattern
402    /// On success: binds variables and continues to next instruction
403    /// On failure: jumps to fail_target
404    Match {
405        source: Register,
406        pattern: Pattern,
407        fail_target: usize,
408    },
409
410    /// Receive with pattern matching
411    /// Scans mailbox for first message matching any clause pattern.
412    /// On match: removes message, binds variables, jumps to clause target.
413    /// No match: blocks until a message arrives.
414    /// Timeout: if set and expires, jumps to timeout_target.
415    ReceiveMatch {
416        /// List of (pattern, jump_target) clauses
417        clauses: Vec<(Pattern, usize)>,
418        /// Optional timeout in reductions
419        timeout: Option<u32>,
420        /// Where to jump on timeout
421        timeout_target: usize,
422    },
423
424    // ========== Lists ==========
425    /// Create a list from the top `length` stack elements
426    /// Elements are popped in reverse order (first pushed = first element)
427    MakeList { length: u8, dest: Register },
428
429    /// Cons: prepend an element to a list [elem | list]
430    /// Crashes if tail is not a list
431    Cons {
432        head: Register,
433        tail: Register,
434        dest: Register,
435    },
436
437    /// Get the head (first element) of a list
438    /// Crashes if not a list or empty
439    ListHead { list: Register, dest: Register },
440
441    /// Get the tail (rest) of a list
442    /// Crashes if not a list or empty
443    ListTail { list: Register, dest: Register },
444
445    /// Check if a list is empty, store 1 (true) or 0 (false)
446    ListIsEmpty { list: Register, dest: Register },
447
448    /// Get the length of a list (O(n))
449    ListLength { list: Register, dest: Register },
450
451    /// Append two lists (a ++ b)
452    /// Crashes if either is not a list
453    ListAppend {
454        a: Register,
455        b: Register,
456        dest: Register,
457    },
458
459    /// Reverse a list
460    /// Crashes if not a list
461    ListReverse { list: Register, dest: Register },
462
463    /// Get the nth element of a list (0-based)
464    /// Crashes if not a list or index out of bounds
465    ListNth {
466        list: Register,
467        n: Register,
468        dest: Register,
469    },
470
471    /// Check if an element is a member of a list
472    /// Stores 1 (true) or 0 (false)
473    ListMember {
474        elem: Register,
475        list: Register,
476        dest: Register,
477    },
478
479    // ========== Type Checking ==========
480    /// Check if value is an integer
481    /// Stores 1 (true) or 0 (false)
482    IsInteger { source: Register, dest: Register },
483
484    /// Check if value is an atom
485    /// Stores 1 (true) or 0 (false)
486    IsAtom { source: Register, dest: Register },
487
488    /// Check if value is a tuple
489    /// Stores 1 (true) or 0 (false)
490    IsTuple { source: Register, dest: Register },
491
492    /// Check if value is a list (including empty list)
493    /// Stores 1 (true) or 0 (false)
494    IsList { source: Register, dest: Register },
495
496    /// Check if value is a PID
497    /// Stores 1 (true) or 0 (false)
498    IsPid { source: Register, dest: Register },
499
500    /// Check if value is a function (Fun or Closure)
501    /// Stores 1 (true) or 0 (false)
502    IsFunction { source: Register, dest: Register },
503
504    /// Check if value is a string/binary
505    /// Stores 1 (true) or 0 (false)
506    IsString { source: Register, dest: Register },
507
508    /// Check if value is a map
509    /// Stores 1 (true) or 0 (false)
510    IsMap { source: Register, dest: Register },
511
512    // ========== Process Dictionary ==========
513    /// Store value in process dictionary, returns old value (or None) in dest
514    PutDict {
515        key: Register,
516        value: Register,
517        dest: Register,
518    },
519
520    /// Get value from process dictionary, stores None if key not found
521    GetDict { key: Register, dest: Register },
522
523    /// Remove key from process dictionary, returns old value (or None) in dest
524    EraseDict { key: Register, dest: Register },
525
526    /// Get all keys from process dictionary as a list
527    GetDictKeys { dest: Register },
528
529    // ========== Maps ==========
530    /// Create a map from key-value pairs on the stack
531    /// Pops `count` pairs (2*count values: k1, v1, k2, v2, ...) from stack
532    MakeMap { count: u8, dest: Register },
533
534    /// Get value from map by key, crashes if key not found
535    MapGet {
536        map: Register,
537        key: Register,
538        dest: Register,
539    },
540
541    /// Get value from map by key, returns default if not found
542    MapGetDefault {
543        map: Register,
544        key: Register,
545        default: Register,
546        dest: Register,
547    },
548
549    /// Insert/update key-value in map, returns new map (maps are immutable)
550    MapPut {
551        map: Register,
552        key: Register,
553        value: Register,
554        dest: Register,
555    },
556
557    /// Remove key from map, returns new map (maps are immutable)
558    MapRemove {
559        map: Register,
560        key: Register,
561        dest: Register,
562    },
563
564    /// Check if key exists in map, stores 1 (true) or 0 (false)
565    MapHas {
566        map: Register,
567        key: Register,
568        dest: Register,
569    },
570
571    /// Get number of entries in map
572    MapSize { map: Register, dest: Register },
573
574    /// Get all keys from map as a list
575    MapKeys { map: Register, dest: Register },
576
577    /// Get all values from map as a list
578    MapValues { map: Register, dest: Register },
579
580    // ========== Binaries ==========
581    /// Create a binary from literal bytes
582    MakeBinary { bytes: Vec<u8>, dest: Register },
583
584    /// Get the size (byte length) of a binary
585    BinarySize { bin: Register, dest: Register },
586
587    /// Get a byte at index (0-based)
588    /// Crashes if not a binary or index out of bounds
589    BinaryAt {
590        bin: Register,
591        index: Register,
592        dest: Register,
593    },
594
595    /// Extract a slice from a binary
596    /// Crashes if not a binary or range out of bounds
597    BinarySlice {
598        bin: Register,
599        start: Register,
600        len: Register,
601        dest: Register,
602    },
603
604    /// Concatenate two binaries
605    /// Crashes if either is not a binary
606    BinaryConcat {
607        a: Register,
608        b: Register,
609        dest: Register,
610    },
611
612    /// Check if value is a binary
613    /// Stores 1 (true) or 0 (false)
614    IsBinary { source: Register, dest: Register },
615
616    /// Convert a string to a binary (UTF-8 encoded)
617    StringToBinary { source: Register, dest: Register },
618
619    /// Convert a binary to a string (assumes UTF-8)
620    /// Crashes if binary is not valid UTF-8
621    BinaryToString { source: Register, dest: Register },
622
623    // ========== Bit Syntax ==========
624    /// Construct a binary from segments
625    /// Each segment specifies a value, type, size, endianness, and signedness
626    /// Usage: push segment values to stack, then call with segment specs
627    BinaryConstructSegments {
628        /// Segment specifications (in order)
629        segments: Vec<(SegmentSource, BitSegment)>,
630        /// Destination register for the constructed binary
631        dest: Register,
632    },
633
634    /// Start matching a binary - prepares for segment extraction
635    /// Stores match state (current position) internally
636    BinaryMatchStart {
637        /// Source binary to match
638        source: Register,
639    },
640
641    /// Match a segment from the binary at current position
642    /// Advances the match position by the segment size
643    /// Jumps to fail_target if match fails (not enough bytes, etc.)
644    BinaryMatchSegment {
645        /// Segment specification
646        segment: BitSegment,
647        /// Destination register for extracted value
648        dest: Register,
649        /// Jump target if match fails
650        fail_target: usize,
651    },
652
653    /// Get the remaining bytes after matching
654    /// Returns the rest of the binary as a new binary value
655    BinaryMatchRest {
656        /// Destination register for remaining bytes
657        dest: Register,
658    },
659
660    /// Get an integer from a binary at bit offset with specified segment
661    /// For random access bit extraction
662    BinaryGetInteger {
663        /// Source binary
664        bin: Register,
665        /// Bit offset (from start)
666        bit_offset: Register,
667        /// Segment specification
668        segment: BitSegment,
669        /// Destination register
670        dest: Register,
671    },
672
673    /// Put an integer into a binary at bit offset with specified segment
674    /// Creates a new binary with the value inserted
675    BinaryPutInteger {
676        /// Source binary
677        bin: Register,
678        /// Bit offset (from start)
679        bit_offset: Register,
680        /// Value to insert
681        value: Register,
682        /// Segment specification
683        segment: BitSegment,
684        /// Destination register
685        dest: Register,
686    },
687
688    // ========== References ==========
689    /// Create a new unique reference
690    MakeRef { dest: Register },
691
692    /// Check if value is a reference
693    /// Stores 1 (true) or 0 (false)
694    IsRef { source: Register, dest: Register },
695
696    // ========== Floats ==========
697    /// Load a floating-point immediate into a register
698    LoadFloat { value: f64, dest: Register },
699
700    /// Check if value is a float
701    /// Stores 1 (true) or 0 (false)
702    IsFloat { source: Register, dest: Register },
703
704    /// Convert integer to float
705    IntToFloat { source: Register, dest: Register },
706
707    /// Convert float to integer (truncates toward zero)
708    FloatToInt { source: Register, dest: Register },
709
710    /// Floor: round down to nearest integer (as float)
711    Floor { source: Register, dest: Register },
712
713    /// Ceil: round up to nearest integer (as float)
714    Ceil { source: Register, dest: Register },
715
716    /// Round: round to nearest integer (as float)
717    Round { source: Register, dest: Register },
718
719    /// Trunc: truncate toward zero (as float)
720    Trunc { source: Register, dest: Register },
721
722    /// Square root
723    Sqrt { source: Register, dest: Register },
724
725    /// Absolute value (works for both int and float)
726    Abs { source: Register, dest: Register },
727
728    /// Power: base^exponent (both operands, result is float)
729    Pow {
730        base: Register,
731        exp: Register,
732        dest: Register,
733    },
734
735    // ========== Timers ==========
736    /// Send a message to a process after a delay (in reductions)
737    /// Returns a timer reference in dest for cancellation
738    SendAfter {
739        delay: u32,
740        to: Source,
741        msg: Register,
742        dest: Register,
743    },
744
745    /// Start a timer that sends {:timeout, ref, msg} to self after delay
746    /// Returns the timer reference in dest
747    StartTimer {
748        delay: u32,
749        msg: Register,
750        dest: Register,
751    },
752
753    /// Cancel a pending timer by its reference
754    /// Returns the remaining time if timer was active, or :ok if already fired
755    CancelTimer { timer_ref: Register, dest: Register },
756
757    /// Read the remaining time on a timer (0 if already fired or cancelled)
758    ReadTimer { timer_ref: Register, dest: Register },
759
760    // ========== IO ==========
761    /// Print a value to stdout with newline
762    PrintLn { source: Register },
763
764    /// Read a line from stdin into register as String
765    /// Returns :eof atom if end of input
766    ReadLine { dest: Register },
767
768    /// Read entire file contents as binary
769    /// Returns {:ok, binary} or {:error, reason}
770    FileRead { path: Register, dest: Register },
771
772    /// Write binary/string to file
773    /// Returns :ok or {:error, reason}
774    FileWrite {
775        path: Register,
776        content: Register,
777        dest: Register,
778    },
779
780    /// Check if file exists
781    /// Returns 1 (true) or 0 (false)
782    FileExists { path: Register, dest: Register },
783
784    /// Delete a file
785    /// Returns :ok or {:error, reason}
786    FileDelete { path: Register, dest: Register },
787
788    // ========== System Info ==========
789    /// Get own PID into register
790    SelfPid { dest: Register },
791
792    /// Get list of all process PIDs
793    ProcessList { dest: Register },
794
795    /// Get count of live processes
796    ProcessCount { dest: Register },
797
798    /// Check if a process is alive
799    /// Returns 1 (true) or 0 (false)
800    IsAlive { pid: Register, dest: Register },
801
802    /// Get process info as tuple
803    /// Returns {status, mailbox_len, links_count, monitors_count, trap_exit}
804    ProcessInfo { pid: Register, dest: Register },
805
806    /// Get list of loaded module names
807    ModuleList { dest: Register },
808
809    /// Check if function is exported
810    /// Returns 1 (true) or 0 (false)
811    FunctionExported {
812        module: Register,
813        function: Register,
814        arity: Register,
815        dest: Register,
816    },
817
818    // ========== Exception Handling ==========
819    /// Begin a try block. Pushes exception handler onto try stack.
820    /// catch_target: where to jump on exception
821    /// after_target: optional cleanup block (always runs)
822    Try {
823        catch_target: usize,
824        after_target: Option<usize>,
825    },
826
827    /// End a try block successfully. Pops handler from try stack.
828    /// If there's an after block, jumps to it.
829    EndTry,
830
831    /// Throw an exception. Unwinds to nearest catch handler.
832    /// class: :error, :exit, or :throw
833    /// reason: the exception reason (from register)
834    Throw { class: Register, reason: Register },
835
836    /// Get the current exception as {class, reason, stacktrace} tuple
837    /// Used in catch blocks to access exception details
838    GetException { dest: Register },
839
840    /// Clear the current exception (after handling)
841    ClearException,
842
843    /// Re-raise the current exception (in catch block)
844    Reraise,
845}
846
847/// An operand for arithmetic/comparison operations
848#[derive(Debug, Clone)]
849pub enum Operand {
850    /// Read from a register
851    Reg(Register),
852    /// Immediate integer value
853    Int(i64),
854}
855
856/// Where to read a value from
857#[derive(Debug, Clone)]
858pub enum Source {
859    /// A register
860    Reg(Register),
861    /// The process's own PID
862    Self_,
863    /// The parent process's PID (if any)
864    Parent,
865    /// A literal PID
866    Pid(Pid),
867    /// A named process from the registry
868    Named(String),
869}
870
871/// Register index (processes have a small set of registers)
872#[derive(Debug, Clone, Copy)]
873pub struct Register(pub u8);
874
875/// A pattern for matching values
876#[derive(Debug, Clone)]
877pub enum Pattern {
878    /// Match any value, ignore it
879    Wildcard,
880
881    /// Match any value, bind it to a register
882    Variable(Register),
883
884    /// Match a specific integer
885    Int(i64),
886
887    /// Match a specific atom
888    Atom(String),
889
890    /// Match a specific string
891    String(String),
892
893    /// Match a specific binary
894    Binary(Vec<u8>),
895
896    /// Match a tuple with specific arity and element patterns
897    Tuple(Vec<Pattern>),
898
899    /// Match an empty list []
900    ListEmpty,
901
902    /// Match a non-empty list [head | tail]
903    ListCons {
904        head: Box<Pattern>,
905        tail: Box<Pattern>,
906    },
907
908    /// Match a map containing specific key-value patterns
909    /// The map may contain additional keys not in the pattern
910    Map(Vec<(Pattern, Pattern)>),
911}