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