Skip to main content

mailang_bytecode/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3extern crate alloc;
4
5use alloc::boxed::Box;
6use alloc::rc::Rc;
7use alloc::string::String;
8use alloc::string::ToString;
9use alloc::vec;
10use alloc::vec::Vec;
11use core::cell::RefCell;
12
13#[cfg(all(feature = "serde", feature = "std"))]
14use serde::{Deserialize, Serialize};
15
16pub mod format;
17pub use format::{decode, encode, BytecodeFormatError, FORMAT_MAGIC, FORMAT_VERSION};
18
19/// Opcode is `Copy` and serialized as a single byte in the binary format.
20/// Keep variants in a stable order; append-only.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22#[cfg_attr(
23    all(feature = "serde", feature = "std"),
24    derive(Serialize, Deserialize)
25)]
26#[repr(u8)]
27pub enum Opcode {
28    // Stack operations
29    Push = 0,
30    Pop,
31    Dup,
32
33    // Load/Store
34    LoadLocal,
35    StoreLocal,
36    LoadGlobal,
37    StoreGlobal,
38    LoadUpvalue,
39    StoreUpvalue,
40
41    // Arithmetic
42    Add,
43    Sub,
44    Mul,
45    Div,
46    Mod,
47    Pow,
48    Neg,
49
50    // Bitwise
51    BitAnd,
52    BitOr,
53    BitXor,
54    BitNot,
55    Shl,
56    Shr,
57
58    // Comparison
59    Eq,
60    Ne,
61    Lt,
62    Le,
63    Gt,
64    Ge,
65    // Fused int immediate ops (hot path: n-1, n<=1, ...)
66    AddImm,
67    SubImm,
68    MulImm,
69    EqImm,
70    NeImm,
71    LtImm,
72    LeImm,
73    GtImm,
74    GeImm,
75
76    // Logical
77    And,
78    Or,
79    Not,
80
81    // Control flow
82    Jump,
83    JumpIfFalse,
84    JumpIfTrue,
85    Call,
86    TailCall,
87    Return,
88
89    // Object operations
90    GetProperty,
91    SetProperty,
92    Invoke,
93
94    // Array/Map
95    BuildArray,
96    BuildMap,
97    IndexGet,
98    IndexSet,
99
100    // Class/Trait
101    CreateClass,
102    CreateInstance,
103    GetMethod,
104
105    // Pattern matching
106    MatchPattern,
107
108    // Closure creation
109    MakeClosure,
110
111    // Error handling
112    Throw,
113    TryBegin,
114    TryEnd,
115    WrapOk,
116    WrapErr,
117    WrapSome,
118    UnwrapOk,
119    UnwrapErr,
120    UnwrapSome,
121    /// `?` — Ok/Some → inner; Err/Null early-return from current frame.
122    Try,
123
124    // Special
125    Nop,
126    Halt,
127    /// Direct call to a known chunk. operand = (chunk_index << 16) | arity.
128    /// Stack: [func_slot_placeholder, args...]
129    CallDirect,
130}
131
132impl Opcode {
133    /// Stable discriminant used by the binary bytecode format.
134    pub fn as_u8(self) -> u8 {
135        self as u8
136    }
137
138    pub fn from_u8(v: u8) -> Option<Self> {
139        Some(match v {
140            0 => Opcode::Push,
141            1 => Opcode::Pop,
142            2 => Opcode::Dup,
143            3 => Opcode::LoadLocal,
144            4 => Opcode::StoreLocal,
145            5 => Opcode::LoadGlobal,
146            6 => Opcode::StoreGlobal,
147            7 => Opcode::LoadUpvalue,
148            8 => Opcode::StoreUpvalue,
149            9 => Opcode::Add,
150            10 => Opcode::Sub,
151            11 => Opcode::Mul,
152            12 => Opcode::Div,
153            13 => Opcode::Mod,
154            14 => Opcode::Pow,
155            15 => Opcode::Neg,
156            16 => Opcode::BitAnd,
157            17 => Opcode::BitOr,
158            18 => Opcode::BitXor,
159            19 => Opcode::BitNot,
160            20 => Opcode::Shl,
161            21 => Opcode::Shr,
162            22 => Opcode::Eq,
163            23 => Opcode::Ne,
164            24 => Opcode::Lt,
165            25 => Opcode::Le,
166            26 => Opcode::Gt,
167            27 => Opcode::Ge,
168            28 => Opcode::AddImm,
169            29 => Opcode::SubImm,
170            30 => Opcode::MulImm,
171            31 => Opcode::EqImm,
172            32 => Opcode::NeImm,
173            33 => Opcode::LtImm,
174            34 => Opcode::LeImm,
175            35 => Opcode::GtImm,
176            36 => Opcode::GeImm,
177            37 => Opcode::And,
178            38 => Opcode::Or,
179            39 => Opcode::Not,
180            40 => Opcode::Jump,
181            41 => Opcode::JumpIfFalse,
182            42 => Opcode::JumpIfTrue,
183            43 => Opcode::Call,
184            44 => Opcode::TailCall,
185            45 => Opcode::Return,
186            46 => Opcode::GetProperty,
187            47 => Opcode::SetProperty,
188            48 => Opcode::Invoke,
189            49 => Opcode::BuildArray,
190            50 => Opcode::BuildMap,
191            51 => Opcode::IndexGet,
192            52 => Opcode::IndexSet,
193            53 => Opcode::CreateClass,
194            54 => Opcode::CreateInstance,
195            55 => Opcode::GetMethod,
196            56 => Opcode::MatchPattern,
197            57 => Opcode::MakeClosure,
198            58 => Opcode::Throw,
199            59 => Opcode::TryBegin,
200            60 => Opcode::TryEnd,
201            61 => Opcode::WrapOk,
202            62 => Opcode::WrapErr,
203            63 => Opcode::WrapSome,
204            64 => Opcode::UnwrapOk,
205            65 => Opcode::UnwrapErr,
206            66 => Opcode::UnwrapSome,
207            67 => Opcode::Try,
208            68 => Opcode::Nop,
209            69 => Opcode::Halt,
210            70 => Opcode::CallDirect,
211            _ => return None,
212        })
213    }
214}
215
216/// Fat payloads are boxed so `Value` stays small on the operand stack.
217#[derive(Debug, Clone, PartialEq)]
218#[cfg_attr(
219    all(feature = "serde", feature = "std"),
220    derive(Serialize, Deserialize)
221)]
222pub struct FunctionObj {
223    pub name: Rc<str>,
224    pub arity: usize,
225    pub chunk_index: usize,
226}
227
228#[derive(Debug, Clone, PartialEq)]
229#[cfg_attr(
230    all(feature = "serde", feature = "std"),
231    derive(Serialize, Deserialize)
232)]
233pub struct ClosureObj {
234    pub function_index: usize,
235    pub arity: usize,
236    pub upvalues: Vec<usize>,
237}
238
239#[derive(Debug, Clone, PartialEq)]
240#[cfg_attr(
241    all(feature = "serde", feature = "std"),
242    derive(Serialize, Deserialize)
243)]
244pub struct ClassObj {
245    pub name: Rc<str>,
246    pub methods: Rc<Vec<(String, usize)>>,
247    pub superclass: Option<Rc<str>>,
248    pub properties: Rc<Vec<(String, Value)>>,
249}
250
251#[derive(Debug, Clone)]
252#[cfg_attr(
253    all(feature = "serde", feature = "std"),
254    derive(Serialize, Deserialize)
255)]
256pub enum Value {
257    Null,
258    Bool(bool),
259    Int(i64),
260    Float(f64),
261    Str(Rc<str>),
262    Char(char),
263    Array(Rc<RefCell<Vec<Value>>>),
264    Map(Rc<RefCell<Vec<(Value, Value)>>>),
265    Tuple(Rc<RefCell<Vec<Value>>>),
266    Function(Rc<FunctionObj>),
267    Closure(Rc<ClosureObj>),
268    Class(Rc<ClassObj>),
269    Instance {
270        class_index: usize,
271        fields: Rc<RefCell<Vec<(String, Value)>>>,
272    },
273    Ok(Box<Value>),
274    Err(Box<Value>),
275    Some(Box<Value>),
276    Builtin {
277        name: Rc<str>,
278        arity: usize,
279    },
280}
281
282impl PartialEq for Value {
283    fn eq(&self, other: &Self) -> bool {
284        match (self, other) {
285            (Value::Null, Value::Null) => true,
286            (Value::Bool(a), Value::Bool(b)) => a == b,
287            (Value::Int(a), Value::Int(b)) => a == b,
288            (Value::Float(a), Value::Float(b)) => a == b,
289            (Value::Str(a), Value::Str(b)) => a == b,
290            (Value::Char(a), Value::Char(b)) => a == b,
291            (Value::Array(a), Value::Array(b)) => *a.borrow() == *b.borrow(),
292            (Value::Map(a), Value::Map(b)) => *a.borrow() == *b.borrow(),
293            (Value::Tuple(a), Value::Tuple(b)) => *a.borrow() == *b.borrow(),
294            (Value::Function(a), Value::Function(b)) => a == b,
295            (Value::Closure(a), Value::Closure(b)) => a == b,
296            (Value::Class(a), Value::Class(b)) => a == b,
297            (
298                Value::Instance {
299                    class_index: c1,
300                    fields: f1,
301                },
302                Value::Instance {
303                    class_index: c2,
304                    fields: f2,
305                },
306            ) => c1 == c2 && *f1.borrow() == *f2.borrow(),
307            (Value::Ok(a), Value::Ok(b)) => a == b,
308            (Value::Err(a), Value::Err(b)) => a == b,
309            (Value::Some(a), Value::Some(b)) => a == b,
310            (
311                Value::Builtin {
312                    name: n1,
313                    arity: a1,
314                },
315                Value::Builtin {
316                    name: n2,
317                    arity: a2,
318                },
319            ) => n1 == n2 && a1 == a2,
320            _ => false,
321        }
322    }
323}
324
325/// Compact, `Copy` instruction — fetched by value on the hot path.
326#[derive(Debug, Clone, Copy, PartialEq)]
327#[cfg_attr(
328    all(feature = "serde", feature = "std"),
329    derive(Serialize, Deserialize)
330)]
331pub struct Instruction {
332    pub opcode: Opcode,
333    pub operand: Option<u32>,
334    pub line: u32,
335}
336
337#[derive(Debug, Clone, PartialEq)]
338#[cfg_attr(
339    all(feature = "serde", feature = "std"),
340    derive(Serialize, Deserialize)
341)]
342pub struct Chunk {
343    pub instructions: Vec<Instruction>,
344    pub constants: Vec<Value>,
345    pub name: String,
346}
347
348impl Chunk {
349    pub fn new(name: String) -> Self {
350        Self {
351            instructions: Vec::new(),
352            constants: Vec::new(),
353            name,
354        }
355    }
356
357    pub fn emit(&mut self, opcode: Opcode, operand: Option<u32>, line: u32) {
358        self.instructions.push(Instruction {
359            opcode,
360            operand,
361            line,
362        });
363    }
364
365    pub fn add_constant(&mut self, value: Value) -> u32 {
366        let index = self.constants.len() as u32;
367        self.constants.push(value);
368        index
369    }
370}
371
372#[derive(Debug, Clone, PartialEq)]
373#[cfg_attr(
374    all(feature = "serde", feature = "std"),
375    derive(Serialize, Deserialize)
376)]
377pub struct Bytecode {
378    pub chunks: Vec<Chunk>,
379    pub main_chunk: usize,
380    /// Global variable names indexed by slot ID. Used for LoadGlobal/StoreGlobal.
381    pub global_names: Vec<String>,
382}
383
384impl Bytecode {
385    pub fn new() -> Self {
386        let main_chunk = Chunk::new("main".to_string());
387        Self {
388            chunks: vec![main_chunk],
389            main_chunk: 0,
390            global_names: Vec::new(),
391        }
392    }
393
394    /// Register a global name and return its slot index.
395    pub fn intern_global(&mut self, name: &str) -> u32 {
396        if let Some(pos) = self.global_names.iter().position(|n| n == name) {
397            return pos as u32;
398        }
399        let idx = self.global_names.len() as u32;
400        self.global_names.push(name.to_string());
401        idx
402    }
403}
404
405impl Default for Bytecode {
406    fn default() -> Self {
407        Self::new()
408    }
409}