Skip to main content

ternlang_core/vm/
mod.rs

1pub mod bet;
2
3use crate::trit::Trit;
4use crate::vm::bet::{unpack_trits, BetFault};
5
6use std::fmt;
7use std::sync::Arc;
8
9// ─── Remote transport trait ───────────────────────────────────────────────────
10
11pub trait RemoteTransport: Send + Sync {
12    fn remote_send(&self, node_addr: &str, agent_id: usize, trit: i8) -> std::io::Result<()>;
13    fn remote_await(&self, node_addr: &str, agent_id: usize) -> std::io::Result<i8>;
14}
15
16/// Maximum call depth before the VM returns a `CallStackOverflow` error.
17/// Prevents OOM/freeze when programs contain unbounded recursion or
18/// mutual recursion across imported modules.
19const MAX_CALL_DEPTH: usize = 4096;
20
21#[derive(Debug, PartialEq, Eq)]
22pub enum VmError {
23    StackUnderflow,
24    BetFault(BetFault),
25    Halt,
26    InvalidOpcode(u8),
27    InvalidRegister(u8),
28    PcOutOfBounds(usize),
29    TypeMismatch { expected: String, found: String },
30    // ── Tensor errors ────────────────────────────────────────────────────────
31    TensorIndexOutOfBounds { tensor_id: usize, index: usize, size: usize },
32    TensorNotAllocated(usize),
33    // ── Agent errors ─────────────────────────────────────────────────────────
34    AgentTypeNotRegistered(u16),
35    AgentIdInvalid(usize),
36    RuntimeError(String),
37    CallStackOverflow,
38    // ── File I/O errors ──────────────────────────────────────────────────────
39    FileOpenError(String),
40    FileReadError(String),
41    FileWriteError(String),
42    FileNotOpen(usize),
43    AssertionFailed,
44}
45
46impl fmt::Display for VmError {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        match self {
49            VmError::StackUnderflow =>
50                write!(f, "[BET-001] Stack underflow — you tried to pop a truth that wasn't there.\n          → details: stdlib/errors/BET-001.tern  |  ternlang errors BET-001"),
51            VmError::BetFault(fault) =>
52                write!(f, "[BET-002] BET encoding fault: {fault:?}. The 0b00 state is forbidden — only 01/10/11 are valid trit bits.\n          → details: stdlib/errors/BET-002.tern  |  ternlang errors BET-002"),
53            VmError::Halt =>
54                write!(f, "[BET-003] VM halted cleanly. Execution reached the end. This is not an error — this is peace.\n          → details: stdlib/errors/BET-003.tern  |  ternlang errors BET-003"),
55            VmError::InvalidOpcode(op) =>
56                write!(f, "[BET-004] Unknown opcode 0x{op:02x} — the machine has never seen this instruction. Delete cached .ternbc files and recompile.\n          → details: stdlib/errors/BET-004.tern  |  ternlang errors BET-004"),
57            VmError::InvalidRegister(reg) =>
58                write!(f, "[BET-005] Register {reg} is out of range. The BET has exactly 27 registers (0–26). That's 3³. No more.\n          → details: stdlib/errors/BET-005.tern  |  ternlang errors BET-005"),
59            VmError::PcOutOfBounds(pc) =>
60                write!(f, "[BET-006] PC {pc} is out of bounds — you jumped outside the known universe. Recompile from source.\n          → details: stdlib/errors/BET-006.tern  |  ternlang errors BET-006"),
61            VmError::TypeMismatch { expected, found } =>
62                write!(f, "[BET-007] Runtime type mismatch — expected {expected} but found {found}. Square peg, round hole.\n          → details: stdlib/errors/BET-007.tern  |  ternlang errors BET-007"),
63            VmError::TensorIndexOutOfBounds { tensor_id, index, size } =>
64                write!(f, "[BET-008] Tensor[{tensor_id}]: index {index} is out of bounds — tensor only has {size} element(s). Trittensors don't grow on access.\n          → details: stdlib/errors/BET-008.tern  |  ternlang errors BET-008"),
65            VmError::TensorNotAllocated(idx) =>
66                write!(f, "[BET-009] TensorRef({idx}) doesn't exist — you never allocated it. TALLOC first, then TIDX.\n          → details: stdlib/errors/BET-009.tern  |  ternlang errors BET-009"),
67            VmError::AgentTypeNotRegistered(type_id) =>
68                write!(f, "[BET-010] Agent type_id 0x{type_id:04x} was never registered. You can't spawn what was never declared.\n          → details: stdlib/errors/BET-010.tern  |  ternlang errors BET-010"),
69            VmError::AgentIdInvalid(id) =>
70                write!(f, "[BET-011] Agent #{id} doesn't exist — no agent was spawned at this ID. TSEND and TAWAIT require a live agent.\n          → details: stdlib/errors/BET-011.tern  |  ternlang errors BET-011"),
71            VmError::RuntimeError(msg) =>
72                write!(f, "[BET-012] Runtime error: {msg}"),
73            VmError::CallStackOverflow =>
74                write!(f, "[BET-013] Call stack overflow — max depth ({MAX_CALL_DEPTH}) exceeded. Infinite recursion or unbounded cross-module mutual calls detected.\n          → details: stdlib/errors/BET-013.tern  |  ternlang errors BET-013"),
75            VmError::FileOpenError(e) =>
76                write!(f, "[IO-001] File open error: {e}"),
77            VmError::FileReadError(e) =>
78                write!(f, "[IO-002] File read error: {e}"),
79            VmError::FileWriteError(e) =>
80                write!(f, "[IO-003] File write error: {e}"),
81            VmError::FileNotOpen(id) =>
82                write!(f, "[IO-004] File handle {id} is not open or was closed."),
83            VmError::AssertionFailed =>
84                write!(f, "[ASSERT-001] Assertion failed: an assert() condition evaluated to reject or tend."),
85        }
86    }
87}
88
89#[derive(Debug, Clone, PartialEq)]
90pub enum Value {
91    Trit(Trit),
92    Int(i64),
93    Float(f64),
94    String(String),
95    TensorRef(usize),
96    AgentRef(usize, Option<String>),
97}
98
99impl Default for Value {
100    fn default() -> Self {
101        Value::Trit(Trit::Tend)
102    }
103}
104
105struct TensorInstance {
106    data: Vec<Trit>,
107    rows: usize,
108    cols: usize,
109}
110
111struct AgentInstance {
112    handler_addr: usize,
113    mailbox: std::collections::VecDeque<Value>,
114}
115
116pub struct BetVm {
117    /// Dynamic register file — grows on demand so programs with > 27 locals work correctly
118    /// instead of silently dropping stores and returning zero on reads.
119    registers: Vec<Value>,
120    register_stack: Vec<Vec<Value>>,
121    carry_reg: Trit,
122    stack: Vec<Value>,
123    call_stack: Vec<usize>,
124    tensors: Vec<TensorInstance>,
125    agents: Vec<AgentInstance>,
126    agent_types: std::collections::HashMap<u16, usize>,
127    pc: usize,
128    code: Vec<u8>,
129    node_id: String,
130    remote: Option<Arc<dyn RemoteTransport>>,
131    open_files: Vec<Option<std::fs::File>>,
132    _instructions_count: u64,
133    pub print_log: Vec<String>,
134}
135
136impl BetVm {
137    pub fn new(code: Vec<u8>) -> Self {
138        Self {
139            registers: vec![Value::default(); 27],
140            register_stack: Vec::new(),
141            carry_reg: Trit::Tend,
142            stack: Vec::new(),
143            call_stack: Vec::new(),
144            tensors: Vec::new(),
145            agents: Vec::new(),
146            agent_types: std::collections::HashMap::new(),
147            pc: 0,
148            code,
149            node_id: "127.0.0.1".into(),
150            remote: None,
151            open_files: Vec::new(),
152            _instructions_count: 0,
153            print_log: Vec::new(),
154        }
155    }
156
157
158    /// Drain all lines printed by `print()`/`println()` during execution.
159    pub fn take_output(&mut self) -> Vec<String> {
160        std::mem::take(&mut self.print_log)
161    }
162
163    pub fn set_node_id(&mut self, node_id: String) {
164        self.node_id = node_id;
165    }
166
167    pub fn set_remote(&mut self, transport: Arc<dyn RemoteTransport>) {
168        self.remote = Some(transport);
169    }
170
171    pub fn register_agent_type(&mut self, type_id: u16, handler_addr: usize) {
172        self.agent_types.insert(type_id, handler_addr);
173    }
174
175    pub fn peek_stack(&self) -> Option<Value> {
176        self.stack.last().cloned()
177    }
178
179    pub fn get_register(&self, reg: u8) -> Value {
180        self.registers.get(reg as usize).cloned().unwrap_or_default()
181    }
182
183    pub fn run(&mut self) -> Result<(), VmError> {
184        loop {
185            if self.pc >= self.code.len() { break; }
186            let opcode = self.code[self.pc];
187            self.pc += 1;
188
189            match opcode {
190                0x01 => { // Tpush
191                    let packed = self.read_u8()?;
192                    let trits = unpack_trits(&[packed], 1).map_err(VmError::BetFault)?;
193                    self.stack.push(Value::Trit(trits[0]));
194                }
195                0x02 => { // Tadd
196                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
197                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
198                    match (a.clone(), b.clone()) {
199                        (Value::Trit(av), Value::Trit(bv)) => {
200                            let (sum, carry) = av + bv;
201                            self.stack.push(Value::Trit(sum));
202                            self.carry_reg = carry;
203                        }
204                        (Value::Int(av), Value::Int(bv)) => self.stack.push(Value::Int(av + bv)),
205                        (Value::Float(av), Value::Float(bv)) => self.stack.push(Value::Float(av + bv)),
206                        (Value::Int(av), Value::Trit(bv)) => self.stack.push(Value::Int(av + bv as i64)),
207                        (Value::Trit(av), Value::Int(bv)) => self.stack.push(Value::Int(av as i64 + bv)),
208                        (Value::Float(av), Value::Trit(bv)) => self.stack.push(Value::Float(av + (bv as i8 as f64))),
209                        (Value::Trit(av), Value::Float(bv)) => self.stack.push(Value::Float((av as i8 as f64) + bv)),
210                        (Value::Float(av), Value::Int(bv)) => self.stack.push(Value::Float(av + (bv as f64))),
211                        (Value::Int(av), Value::Float(bv)) => self.stack.push(Value::Float((av as f64) + bv)),
212                        // PARSER-STR-001: string concatenation via + operator
213                        (Value::String(av), Value::String(bv)) => self.stack.push(Value::String(av + &bv)),
214                        _ => return Err(VmError::TypeMismatch { expected: "Numeric".into(), found: format!("{:?}", (a, b)) }),
215                    }
216                }
217                0x03 => { // Tmul
218                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
219                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
220                    match (a.clone(), b.clone()) {
221                        (Value::Trit(av), Value::Trit(bv)) => self.stack.push(Value::Trit(av * bv)),
222                        (Value::Int(av), Value::Int(bv)) => self.stack.push(Value::Int(av * bv)),
223                        (Value::Float(av), Value::Float(bv)) => self.stack.push(Value::Float(av * bv)),
224                        (Value::Int(av), Value::Trit(bv)) => self.stack.push(Value::Int(av * bv as i64)),
225                        (Value::Trit(av), Value::Int(bv)) => self.stack.push(Value::Int(av as i64 * bv)),
226                        (Value::Float(av), Value::Trit(bv)) => self.stack.push(Value::Float(av * (bv as i8 as f64))),
227                        (Value::Trit(av), Value::Float(bv)) => self.stack.push(Value::Float((av as i8 as f64) * bv)),
228                        (Value::Float(av), Value::Int(bv)) => self.stack.push(Value::Float(av * (bv as f64))),
229                        (Value::Int(av), Value::Float(bv)) => self.stack.push(Value::Float((av as f64) * bv)),
230                        _ => return Err(VmError::TypeMismatch { expected: "Numeric".into(), found: format!("{:?}", (a, b)) }),
231                    }
232                }
233                0x04 => { // Tneg
234                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
235                    match a.clone() {
236                        Value::Trit(av) => self.stack.push(Value::Trit(-av)),
237                        Value::Int(av) => self.stack.push(Value::Int(-av)),
238                        Value::Float(av) => self.stack.push(Value::Float(-av)),
239                        _ => return Err(VmError::TypeMismatch { expected: "Numeric".into(), found: format!("{:?}", a) }),
240                    }
241                }
242                0x05 => { // TjmpPos — jumps if top is +1
243                    let addr = self.read_u16()?;
244                    let val = self.stack.last().ok_or(VmError::StackUnderflow)?;
245                    let is_pos = match val {
246                        Value::Trit(Trit::Affirm) => true,
247                        Value::Int(1) => true,
248                        _ => false,
249                    };
250                    if is_pos { self.pc = addr as usize; }
251                }
252                0x06 => { // TjmpZero — jumps if top is 0
253                    let addr = self.read_u16()?;
254                    let val = self.stack.last().ok_or(VmError::StackUnderflow)?;
255                    let is_zero = match val {
256                        Value::Trit(Trit::Tend) => true,
257                        Value::Int(0) => true,
258                        _ => false,
259                    };
260                    if is_zero { self.pc = addr as usize; }
261                }
262                0x07 => { // TjmpNeg — jumps if top is -1
263                    let addr = self.read_u16()?;
264                    let val = self.stack.last().ok_or(VmError::StackUnderflow)?;
265                    let is_neg = match val {
266                        Value::Trit(Trit::Reject) => true,
267                        Value::Int(-1) => true,
268                        _ => false,
269                    };
270                    if is_neg { self.pc = addr as usize; }
271                }
272                0x08 => { // Tstore
273                    let reg = self.read_u8()? as usize;
274                    let val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
275                    if reg >= self.registers.len() { self.registers.resize(reg + 1, Value::default()); }
276                    self.registers[reg] = val;
277                }
278                0x09 => { // Tload
279                    let reg = self.read_u8()? as usize;
280                    if reg >= self.registers.len() { self.registers.resize(reg + 1, Value::default()); }
281                    self.stack.push(self.registers[reg].clone());
282                }
283                0x0a => { // Tdup
284                    let val = self.stack.last().ok_or(VmError::StackUnderflow)?;
285                    self.stack.push(val.clone());
286                }
287                0x0b => { // Tjmp
288                    let addr = self.read_u16()?;
289                    self.pc = addr as usize;
290                }
291                0x0c => { // Tpop
292                    self.stack.pop().ok_or(VmError::StackUnderflow)?;
293                }
294                0x0e => { // Tcons
295                    let b_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
296                    let a_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
297                    
298                    let a = match a_val {
299                        Value::Trit(t) => t,
300                        Value::Int(v) if v == 1 => Trit::Affirm,
301                        Value::Int(v) if v == 0 => Trit::Tend,
302                        Value::Int(v) if v == -1 => Trit::Reject,
303                        _ => return Err(VmError::TypeMismatch { expected: "Trit or Int(-1..1)".into(), found: format!("{:?}", a_val) }),
304                    };
305                    let b = match b_val {
306                        Value::Trit(t) => t,
307                        Value::Int(v) if v == 1 => Trit::Affirm,
308                        Value::Int(v) if v == 0 => Trit::Tend,
309                        Value::Int(v) if v == -1 => Trit::Reject,
310                        _ => return Err(VmError::TypeMismatch { expected: "Trit or Int(-1..1)".into(), found: format!("{:?}", b_val) }),
311                    };
312
313                    let result = match (a, b) {
314                        (Trit::Affirm, Trit::Affirm) => Trit::Affirm,
315                        (Trit::Reject, Trit::Reject) => Trit::Reject,
316                        (Trit::Tend, x) => x,
317                        (x, Trit::Tend) => x,
318                        _ => Trit::Tend,
319                    };
320                    self.stack.push(Value::Trit(result));
321                }
322                0x0f => { // Talloc
323                    let rows = self.read_u16()? as usize;
324                    let cols = self.read_u16()? as usize;
325                    let size = rows * cols;
326                    let idx = self.tensors.len();
327                    self.tensors.push(TensorInstance {
328                        data: vec![Trit::Tend; size],
329                        rows,
330                        cols,
331                    });
332                    self.stack.push(Value::TensorRef(idx));
333                }
334                0x10 => { // Tcall
335                    if self.call_stack.len() >= MAX_CALL_DEPTH {
336                        return Err(VmError::CallStackOverflow);
337                    }
338                    let addr = self.read_u16()? as usize;
339                    self.register_stack.push(self.registers.clone());
340                    self.call_stack.push(self.pc);
341                    self.pc = addr;
342                }
343                0x11 => { // Tret
344                    if let Some(prev) = self.register_stack.pop() {
345                        self.registers = prev;
346                    }
347                    match self.call_stack.pop() {
348                        Some(ret) => self.pc = ret,
349                        None => return Ok(()),
350                    }
351                }
352                0x14 => { // Tless
353                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
354                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
355                    match (a.clone(), b.clone()) {
356                        (Value::Int(x), Value::Int(y)) => {
357                            let r = if x < y { Trit::Affirm } else if x == y { Trit::Tend } else { Trit::Reject };
358                            self.stack.push(Value::Trit(r));
359                        }
360                        (Value::Float(x), Value::Float(y)) => {
361                            let r = if x < y { Trit::Affirm } else if (x - y).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
362                            self.stack.push(Value::Trit(r));
363                        }
364                        (Value::Int(x), Value::Trit(y)) => {
365                            let bv = y as i64;
366                            let r = if x < bv { Trit::Affirm } else if x == bv { Trit::Tend } else { Trit::Reject };
367                            self.stack.push(Value::Trit(r));
368                        }
369                        (Value::Trit(x), Value::Int(y)) => {
370                            let av = x as i64;
371                            let r = if av < y { Trit::Affirm } else if av == y { Trit::Tend } else { Trit::Reject };
372                            self.stack.push(Value::Trit(r));
373                        }
374                        (Value::Int(av), Value::Float(bv)) => {
375                            let a_val = av as f64;
376                            let r = if a_val < bv { Trit::Affirm } else if (a_val - bv).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
377                            self.stack.push(Value::Trit(r));
378                        }
379                        (Value::Float(av), Value::Int(bv)) => {
380                            let b_val = bv as f64;
381                            let r = if av < b_val { Trit::Affirm } else if (av - b_val).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
382                            self.stack.push(Value::Trit(r));
383                        }
384                        (Value::Trit(av), Value::Float(bv)) => {
385                            let a_val = av as i8 as f64;
386                            let r = if a_val < bv { Trit::Affirm } else if (a_val - bv).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
387                            self.stack.push(Value::Trit(r));
388                        }
389                        (Value::Float(av), Value::Trit(bv)) => {
390                            let b_val = bv as i8 as f64;
391                            let r = if av < b_val { Trit::Affirm } else if (av - b_val).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
392                            self.stack.push(Value::Trit(r));
393                        }
394                        (Value::Trit(x), Value::Trit(y)) => {
395                            let av = x as i64;
396                            let bv = y as i64;
397                            let r = if av < bv { Trit::Affirm } else if av == bv { Trit::Tend } else { Trit::Reject };
398                            self.stack.push(Value::Trit(r));
399                        }
400                        _ => return Err(VmError::TypeMismatch { expected: "Numeric".into(), found: format!("{:?}", (a, b)) }),
401                    }
402                }
403                0x15 => { // Tgreater
404                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
405                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
406                    match (a.clone(), b.clone()) {
407                        (Value::Int(x), Value::Int(y)) => {
408                            let r = if x > y { Trit::Affirm } else if x == y { Trit::Tend } else { Trit::Reject };
409                            self.stack.push(Value::Trit(r));
410                        }
411                        (Value::Float(x), Value::Float(y)) => {
412                            let r = if x > y { Trit::Affirm } else if (x - y).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
413                            self.stack.push(Value::Trit(r));
414                        }
415                        (Value::Int(x), Value::Trit(y)) => {
416                            let bv = y as i64;
417                            let r = if x > bv { Trit::Affirm } else if x == bv { Trit::Tend } else { Trit::Reject };
418                            self.stack.push(Value::Trit(r));
419                        }
420                        (Value::Trit(x), Value::Int(y)) => {
421                            let av = x as i64;
422                            let r = if av > y { Trit::Affirm } else if av == y { Trit::Tend } else { Trit::Reject };
423                            self.stack.push(Value::Trit(r));
424                        }
425                        (Value::Int(av), Value::Float(bv)) => {
426                            let a_val = av as f64;
427                            let r = if a_val > bv { Trit::Affirm } else if (a_val - bv).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
428                            self.stack.push(Value::Trit(r));
429                        }
430                        (Value::Float(av), Value::Int(bv)) => {
431                            let b_val = bv as f64;
432                            let r = if av > b_val { Trit::Affirm } else if (av - b_val).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
433                            self.stack.push(Value::Trit(r));
434                        }
435                        (Value::Trit(av), Value::Float(bv)) => {
436                            let a_val = av as i8 as f64;
437                            let r = if a_val > bv { Trit::Affirm } else if (a_val - bv).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
438                            self.stack.push(Value::Trit(r));
439                        }
440                        (Value::Float(av), Value::Trit(bv)) => {
441                            let b_val = bv as i8 as f64;
442                            let r = if av > b_val { Trit::Affirm } else if (av - b_val).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
443                            self.stack.push(Value::Trit(r));
444                        }
445                        (Value::Trit(x), Value::Trit(y)) => {
446                            let av = x as i64;
447                            let bv = y as i64;
448                            let r = if av > bv { Trit::Affirm } else if av == bv { Trit::Tend } else { Trit::Reject };
449                            self.stack.push(Value::Trit(r));
450                        }
451                        _ => return Err(VmError::TypeMismatch { expected: "Numeric".into(), found: format!("{:?}", (a, b)) }),
452                    }
453                }
454                0x16 => { // Teq
455                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
456                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
457                    let is_eq = match (a.clone(), b.clone()) {
458                        (Value::Int(av), Value::Trit(bv)) => av == bv as i64,
459                        (Value::Trit(av), Value::Int(bv)) => av as i64 == bv,
460                        (Value::Float(av), Value::Float(bv)) => (av - bv).abs() < f64::EPSILON,
461                        (Value::Float(av), Value::Trit(bv)) => (av - (bv as i8 as f64)).abs() < f64::EPSILON,
462                        (Value::Trit(av), Value::Float(bv)) => ((av as i8 as f64) - bv).abs() < f64::EPSILON,
463                        (Value::Float(av), Value::Int(bv)) => (av - (bv as f64)).abs() < f64::EPSILON,
464                        (Value::Int(av), Value::Float(bv)) => ((av as f64) - bv).abs() < f64::EPSILON,
465                        _ => a == b,
466                    };
467                    let r = if is_eq { Trit::Affirm } else { Trit::Reject };
468                    self.stack.push(Value::Trit(r));
469                }
470                0x17 => { // TpushInt
471                    let mut b = [0u8; 8];
472                    for i in 0..8 { b[i] = self.read_u8()?; }
473                    self.stack.push(Value::Int(i64::from_le_bytes(b)));
474                }
475                0x18 => { // TaddInt
476                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
477                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
478                    match (a.clone(), b.clone()) {
479                        (Value::Int(x), Value::Int(y)) => self.stack.push(Value::Int(x + y)),
480                        _ => return Err(VmError::TypeMismatch { expected: "Int".into(), found: format!("{:?}", (a, b)) }),
481                    }
482                }
483                0x19 => { // TpushFloat
484                    let mut b = [0u8; 8];
485                    for i in 0..8 { b[i] = self.read_u8()?; }
486                    self.stack.push(Value::Float(f64::from_le_bytes(b)));
487                }
488                0x1e => { // Tdiv
489                    let b_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
490                    let a_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
491                    match (a_val.clone(), b_val.clone()) {
492                        (Value::Int(av), Value::Int(bv)) => {
493                            if bv == 0 { return Err(VmError::RuntimeError("Division by zero".into())); }
494                            self.stack.push(Value::Int(av / bv));
495                        }
496                        (Value::Float(av), Value::Float(bv)) => {
497                            if bv == 0.0 { return Err(VmError::RuntimeError("Division by zero".into())); }
498                            self.stack.push(Value::Float(av / bv));
499                        }
500                        (Value::Int(av), Value::Trit(bv)) => {
501                            let b = bv as i64;
502                            if b == 0 { return Err(VmError::RuntimeError("Division by zero".into())); }
503                            self.stack.push(Value::Int(av / b));
504                        }
505                        (Value::Trit(av), Value::Int(bv)) => {
506                            if bv == 0 { return Err(VmError::RuntimeError("Division by zero".into())); }
507                            self.stack.push(Value::Int(av as i64 / bv));
508                        }
509                        (Value::Float(av), Value::Trit(bv)) => {
510                            let b = bv as i8 as f64;
511                            if b == 0.0 { return Err(VmError::RuntimeError("Division by zero".into())); }
512                            self.stack.push(Value::Float(av / b));
513                        }
514                        (Value::Trit(av), Value::Float(bv)) => {
515                            if bv == 0.0 { return Err(VmError::RuntimeError("Division by zero".into())); }
516                            self.stack.push(Value::Float(av as i8 as f64 / bv));
517                        }
518                        (Value::Float(av), Value::Int(bv)) => {
519                            let b = bv as f64;
520                            if b == 0.0 { return Err(VmError::RuntimeError("Division by zero".into())); }
521                            self.stack.push(Value::Float(av / b));
522                        }
523                        (Value::Int(av), Value::Float(bv)) => {
524                            if bv == 0.0 { return Err(VmError::RuntimeError("Division by zero".into())); }
525                            self.stack.push(Value::Float(av as f64 / bv));
526                        }
527                        _ => return Err(VmError::TypeMismatch { expected: "Numeric".into(), found: format!("{:?}", (a_val, b_val)) }),
528                    }
529                }
530                0x1f => { // Tmod
531                    let b_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
532                    let a_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
533                    match (a_val.clone(), b_val.clone()) {
534                        (Value::Int(av), Value::Int(bv)) => {
535                            if bv == 0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
536                            self.stack.push(Value::Int(av % bv));
537                        }
538                        (Value::Int(av), Value::Trit(bv)) => {
539                            let b = bv as i64;
540                            if b == 0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
541                            self.stack.push(Value::Int(av % b));
542                        }
543                        (Value::Trit(av), Value::Int(bv)) => {
544                            if bv == 0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
545                            self.stack.push(Value::Int(av as i64 % bv));
546                        }
547                        (Value::Float(av), Value::Float(bv)) => {
548                             if bv == 0.0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
549                             self.stack.push(Value::Float(av % bv));
550                        }
551                        (Value::Float(av), Value::Trit(bv)) => {
552                             let b = bv as i8 as f64;
553                             if b == 0.0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
554                             self.stack.push(Value::Float(av % b));
555                        }
556                        (Value::Trit(av), Value::Float(bv)) => {
557                             if bv == 0.0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
558                             self.stack.push(Value::Float(av as i8 as f64 % bv));
559                        }
560                        (Value::Float(av), Value::Int(bv)) => {
561                             let b = bv as f64;
562                             if b == 0.0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
563                             self.stack.push(Value::Float(av % b));
564                        }
565                        (Value::Int(av), Value::Float(bv)) => {
566                             if bv == 0.0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
567                             self.stack.push(Value::Float(av as f64 % bv));
568                        }
569                        _ => return Err(VmError::TypeMismatch { expected: "Int or Trit".into(), found: format!("{:?}", (a_val, b_val)) }),
570                    }
571                }
572                0x20 => { // Tprint
573                    let val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
574                    let line = match &val {
575                        Value::Trit(t) => format!("{:?}", t),
576                        Value::Int(i) => format!("{}", i),
577                        Value::Float(f) => format!("{}", f),
578                        Value::String(s) => s.clone(),
579                        Value::TensorRef(idx) => format!("TensorRef({})", idx),
580                        Value::AgentRef(idx, addr) => format!("AgentRef({}, {:?})", idx, addr),
581                    };
582                    println!("{}", line);
583                    self.print_log.push(line);
584                }
585                0x21 => { // TpushString
586                    let len = self.read_u16()? as usize;
587                    let mut bytes = vec![0u8; len];
588                    for i in 0..len { bytes[i] = self.read_u8()?; }
589                    let s = String::from_utf8(bytes).map_err(|_| VmError::RuntimeError("Invalid UTF-8 string".into()))?;
590                    self.stack.push(Value::String(s));
591                }
592                0x22 => { // Tidx
593                    let col = self.stack.pop().ok_or(VmError::StackUnderflow)?;
594                    let row = self.stack.pop().ok_or(VmError::StackUnderflow)?;
595                    let rf = self.stack.pop().ok_or(VmError::StackUnderflow)?;
596                    let r = match row { Value::Int(v) => v, Value::Trit(t) => t as i64, _ => return Err(VmError::TypeMismatch { expected: "Int or Trit".into(), found: format!("{:?}", row) }) };
597                    let c = match col { Value::Int(v) => v, Value::Trit(t) => t as i64, _ => return Err(VmError::TypeMismatch { expected: "Int or Trit".into(), found: format!("{:?}", col) }) };
598                    match rf {
599                        Value::TensorRef(idx) => {
600                            if idx >= self.tensors.len() {
601                                return Err(VmError::TensorNotAllocated(idx));
602                            }
603                            let tensor = &self.tensors[idx];
604                            // c == -1 is the flat-index sentinel (single-index access m[i])
605                            let pos = if tensor.cols > 1 && c >= 0 { r as usize * tensor.cols + c as usize } else { r as usize };
606                            if pos >= tensor.data.len() {
607                                return Err(VmError::TensorIndexOutOfBounds { tensor_id: idx, index: pos, size: tensor.data.len() });
608                            }
609                            self.stack.push(Value::Trit(tensor.data[pos]));
610                        }
611                        _ => return Err(VmError::TypeMismatch { expected: "TensorRef".into(), found: format!("{:?}", rf) }),
612                    }
613                }
614                0x23 => { // Tset
615                    let val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
616                    let col = self.stack.pop().ok_or(VmError::StackUnderflow)?;
617                    let row = self.stack.pop().ok_or(VmError::StackUnderflow)?;
618                    let rf = self.stack.pop().ok_or(VmError::StackUnderflow)?;
619                    let r = match row { Value::Int(v) => v, Value::Trit(t) => t as i64, _ => return Err(VmError::TypeMismatch { expected: "Int or Trit".into(), found: format!("{:?}", row) }) };
620                    let c = match col { Value::Int(v) => v, Value::Trit(t) => t as i64, _ => return Err(VmError::TypeMismatch { expected: "Int or Trit".into(), found: format!("{:?}", col) }) };
621                    match (rf.clone(), val.clone()) {
622                        (Value::TensorRef(idx), Value::Trit(t)) => {
623                            if idx >= self.tensors.len() { return Err(VmError::TensorNotAllocated(idx)); }
624                            let tensor = &mut self.tensors[idx];
625                            // c == -1 is the flat-index sentinel (single-index access m[i])
626                            let pos = if tensor.cols > 1 && c >= 0 { r as usize * tensor.cols + c as usize } else { r as usize };
627                            if pos >= tensor.data.len() { return Err(VmError::TensorIndexOutOfBounds { tensor_id: idx, index: pos, size: tensor.data.len() }); }
628                            tensor.data[pos] = t;
629                        }
630                        (Value::TensorRef(idx), Value::Int(v)) => {
631                            if idx >= self.tensors.len() { return Err(VmError::TensorNotAllocated(idx)); }
632                            let tensor = &mut self.tensors[idx];
633                            // c == -1 is the flat-index sentinel (single-index access m[i])
634                            let pos = if tensor.cols > 1 && c >= 0 { r as usize * tensor.cols + c as usize } else { r as usize };
635                            if pos >= tensor.data.len() { return Err(VmError::TensorIndexOutOfBounds { tensor_id: idx, index: pos, size: tensor.data.len() }); }
636                            tensor.data[pos] = if v > 0 { Trit::Affirm } else if v < 0 { Trit::Reject } else { Trit::Tend };
637                        }
638                        _ => return Err(VmError::TypeMismatch { expected: "TensorRef, Trit".into(), found: format!("{:?}", (rf, val)) }),
639                    }
640                }
641                0x24 => { // Tshape
642                    let rf = self.stack.pop().ok_or(VmError::StackUnderflow)?;
643                    if let Value::TensorRef(idx) = rf {
644                        if idx >= self.tensors.len() {
645                            return Err(VmError::TensorNotAllocated(idx));
646                        }
647                        let tensor = &self.tensors[idx];
648                        self.stack.push(Value::Int(tensor.rows as i64));
649                        self.stack.push(Value::Int(tensor.cols as i64));
650                    } else { return Err(VmError::TypeMismatch { expected: "TensorRef".into(), found: format!("{:?}", rf) }); }
651                }
652                0x30 => { // Tspawn — (type_id) → AgentRef
653                    let type_id = self.read_u16()?;
654                    if let Some(&handler_addr) = self.agent_types.get(&type_id) {
655                        let id = self.agents.len();
656                        self.agents.push(AgentInstance { handler_addr, mailbox: Default::default() });
657                        self.stack.push(Value::AgentRef(id, None));
658                    } else {
659                        return Err(VmError::AgentTypeNotRegistered(type_id));
660                    }
661                }
662                0x31 => { // Tsend — msg, target → void
663                    let msg = self.stack.pop().ok_or(VmError::StackUnderflow)?;
664                    let target = self.stack.pop().ok_or(VmError::StackUnderflow)?;
665                    if let Value::AgentRef(id, None) = target {
666                        if id < self.agents.len() {
667                            self.agents[id].mailbox.push_back(msg);
668                        } else {
669                            return Err(VmError::AgentIdInvalid(id));
670                        }
671                    } else {
672                        return Err(VmError::TypeMismatch { expected: "Local AgentRef".into(), found: format!("{:?}", target) });
673                    }
674                }
675                0x32 => { // Tawait — target → result
676                    let target = self.stack.pop().ok_or(VmError::StackUnderflow)?;
677                    if let Value::AgentRef(id, None) = target {
678                        if id < self.agents.len() {
679                            if self.call_stack.len() >= MAX_CALL_DEPTH {
680                                return Err(VmError::CallStackOverflow);
681                            }
682                            let handler_addr = self.agents[id].handler_addr;
683                            let msg = self.agents[id].mailbox.pop_front().unwrap_or(Value::default());
684                            // Synchronous handler dispatch — identical to TCALL
685                            self.register_stack.push(self.registers.clone());
686                            self.call_stack.push(self.pc);
687                            self.pc = handler_addr;
688                            self.stack.push(msg);
689                        } else {
690                            return Err(VmError::AgentIdInvalid(id));
691                        }
692                    } else {
693                        return Err(VmError::TypeMismatch { expected: "Local AgentRef".into(), found: format!("{:?}", target) });
694                    }
695                }
696                0x25 => { // TjmpEqInt — imm_int, imm_addr → peek, jumps if eq
697                    let mut b = [0u8; 8];
698                    for i in 0..8 { b[i] = self.read_u8()?; }
699                    let target_val = i64::from_le_bytes(b);
700                    let addr = self.read_u16()?;
701                    let val = self.stack.last().ok_or(VmError::StackUnderflow)?;
702                    let is_eq = match val {
703                        Value::Int(v) => *v == target_val,
704                        Value::Trit(t) => (*t as i8) as i64 == target_val,
705                        _ => false,
706                    };
707                    if is_eq { self.pc = addr as usize; }
708                }
709                0x26 => { // TlessEqual
710                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
711                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
712                    let is_le = match (a.clone(), b.clone()) {
713                        (Value::Int(x), Value::Int(y)) => x <= y,
714                        (Value::Float(x), Value::Float(y)) => x <= y || (x - y).abs() < f64::EPSILON,
715                        (Value::Int(x), Value::Trit(y)) => x <= y as i64,
716                        (Value::Trit(x), Value::Int(y)) => (x as i64) <= y,
717                        (Value::Trit(x), Value::Trit(y)) => (x as i64) <= (y as i64),
718                        _ => false,
719                    };
720                    self.stack.push(Value::Trit(if is_le { Trit::Affirm } else { Trit::Reject }));
721                }
722                0x27 => { // TgreaterEqual
723                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
724                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
725                    let is_ge = match (a.clone(), b.clone()) {
726                        (Value::Int(x), Value::Int(y)) => x >= y,
727                        (Value::Float(x), Value::Float(y)) => x >= y || (x - y).abs() < f64::EPSILON,
728                        (Value::Int(x), Value::Trit(y)) => x >= y as i64,
729                        (Value::Trit(x), Value::Int(y)) => (x as i64) >= y,
730                        (Value::Trit(x), Value::Trit(y)) => (x as i64) >= (y as i64),
731                        _ => false,
732                    };
733                    self.stack.push(Value::Trit(if is_ge { Trit::Affirm } else { Trit::Reject }));
734                }
735                0x28 => { // Tand — min(a, b) in balanced ternary (logical AND)
736                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
737                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
738                    let to_trit = |v: Value| -> Result<Trit, VmError> {
739                        match v {
740                            Value::Trit(t) => Ok(t),
741                            Value::Int(n) if n > 0 => Ok(Trit::Affirm),
742                            Value::Int(0) => Ok(Trit::Tend),
743                            Value::Int(_) => Ok(Trit::Reject),
744                            other => Err(VmError::TypeMismatch { expected: "Trit or Int".into(), found: format!("{:?}", other) }),
745                        }
746                    };
747                    let ta = to_trit(a)?;
748                    let tb = to_trit(b)?;
749                    let result = if (ta as i8) <= (tb as i8) { ta } else { tb };
750                    self.stack.push(Value::Trit(result));
751                }
752                0x29 => { // Tor — max(a, b) in balanced ternary (logical OR)
753                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
754                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
755                    let to_trit = |v: Value| -> Result<Trit, VmError> {
756                        match v {
757                            Value::Trit(t) => Ok(t),
758                            Value::Int(n) if n > 0 => Ok(Trit::Affirm),
759                            Value::Int(0) => Ok(Trit::Tend),
760                            Value::Int(_) => Ok(Trit::Reject),
761                            other => Err(VmError::TypeMismatch { expected: "Trit or Int".into(), found: format!("{:?}", other) }),
762                        }
763                    };
764                    let ta = to_trit(a)?;
765                    let tb = to_trit(b)?;
766                    let result = if (ta as i8) >= (tb as i8) { ta } else { tb };
767                    self.stack.push(Value::Trit(result));
768                }
769                0x2a => { // TjmpEqFloat — peek stack, jump if top Float equals embedded f64 literal
770                    // Emitted by betbc.rs for float match-arm patterns (PARSER-002 fix).
771                    // Layout: [opcode: u8] [target_f64: 8 bytes LE] [jump_addr: u16 LE]
772                    // Peeks the top of stack (does NOT consume it), jumps if value matches
773                    // within machine epsilon.
774                    let mut fb = [0u8; 8];
775                    for i in 0..8 { fb[i] = self.read_u8()?; }
776                    let target_f = f64::from_le_bytes(fb);
777                    let addr = self.read_u16()?;
778                    let val = self.stack.last().ok_or(VmError::StackUnderflow)?;
779                    if let Value::Float(f) = val {
780                        if (f - target_f).abs() < 1e-9 {
781                            self.pc = addr as usize;
782                        }
783                    }
784                }
785                0x33 => { // Topent — path_str, mode_int → handle_int
786                    let mode = self.stack.pop().ok_or(VmError::StackUnderflow)?;
787                    let path = self.stack.pop().ok_or(VmError::StackUnderflow)?;
788                    if let (Value::String(p), Value::Int(m)) = (path, mode) {
789                        use std::fs::OpenOptions;
790                        let mut options = OpenOptions::new();
791                        match m {
792                            0 => { options.read(true); } // Read
793                            1 => { options.write(true).create(true).truncate(true); } // Write
794                            2 => { options.append(true).create(true); } // Append
795                            _ => return Err(VmError::RuntimeError(format!("Invalid file mode: {m}"))),
796                        }
797                        let file = options.open(&p).map_err(|e| VmError::FileOpenError(e.to_string()))?;
798                        let handle = self.open_files.len();
799                        self.open_files.push(Some(file));
800                        self.stack.push(Value::Int(handle as i64));
801                    } else {
802                        return Err(VmError::TypeMismatch { expected: "String, Int".into(), found: "Unknown".into() });
803                    }
804                }
805                0x34 => { // Treadt — handle_int → trit
806                    let handle_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
807                    if let Value::Int(h) = handle_val {
808                        let h = h as usize;
809                        if h >= self.open_files.len() || self.open_files[h].is_none() {
810                            return Err(VmError::FileNotOpen(h));
811                        }
812                        let file = self.open_files[h].as_mut().unwrap();
813                        let mut buf = [0u8; 1];
814                        use std::io::Read;
815                        match file.read_exact(&mut buf) {
816                            Ok(_) => {
817                                let t = match buf[0] {
818                                    b'+' | b'1' => Trit::Affirm,
819                                    b'-' => Trit::Reject,
820                                    _ => Trit::Tend,
821                                };
822                                self.stack.push(Value::Trit(t));
823                            }
824                            Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
825                                self.stack.push(Value::Trit(Trit::Tend)); // EOF as Tend
826                            }
827                            Err(e) => return Err(VmError::FileReadError(e.to_string())),
828                        }
829                    } else {
830                        return Err(VmError::TypeMismatch { expected: "Int".into(), found: format!("{:?}", handle_val) });
831                    }
832                }
833                0x35 => { // Twritet — handle_int, trit → void
834                    let t_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
835                    let h_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
836                    if let (Value::Int(h), Value::Trit(t)) = (h_val, t_val) {
837                        let h = h as usize;
838                        if h >= self.open_files.len() || self.open_files[h].is_none() {
839                            return Err(VmError::FileNotOpen(h));
840                        }
841                        let file = self.open_files[h].as_mut().unwrap();
842                        let out = match t {
843                            Trit::Affirm => b'+',
844                            Trit::Reject => b'-',
845                            Trit::Tend   => b'0',
846                        };
847                        use std::io::Write;
848                        file.write_all(&[out]).map_err(|e| VmError::FileWriteError(e.to_string()))?;
849                    } else {
850                        return Err(VmError::TypeMismatch { expected: "Int, Trit".into(), found: "Unknown".into() });
851                    }
852                }
853                0x36 => { // Tnodeid — push this node's runtime address as a String
854                    // Defers the binding to runtime so --node-addr is respected.
855                    // Previously, Expr::NodeId emitted a hardcoded "127.0.0.1:7373"
856                    // string at compile time, ignoring vm.set_node_id().
857                    self.stack.push(Value::String(self.node_id.clone()));
858                }
859                0x37 => { // Tassert
860                    let val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
861                    let is_affirm = match val {
862                        Value::Trit(Trit::Affirm) => true,
863                        Value::Int(1) => true,
864                        _ => false,
865                    };
866                    if !is_affirm {
867                        return Err(VmError::AssertionFailed);
868                    }
869                }
870                0x00 => return Ok(()),
871                _ => return Err(VmError::InvalidOpcode(opcode)),
872            }
873        }
874        Ok(())
875    }
876
877    fn read_u8(&mut self) -> Result<u8, VmError> {
878        if self.pc >= self.code.len() { return Err(VmError::PcOutOfBounds(self.pc)); }
879        let val = self.code[self.pc];
880        self.pc += 1;
881        Ok(val)
882    }
883
884    fn read_u16(&mut self) -> Result<u16, VmError> {
885        if self.pc + 1 >= self.code.len() { return Err(VmError::PcOutOfBounds(self.pc)); }
886        let val = u16::from_le_bytes([self.code[self.pc], self.code[self.pc + 1]]);
887        self.pc += 2;
888        Ok(val)
889    }
890}