Archived
977a2cd654
- Register print/println/log/print_err in TypeEnv::with_builtins() with polymorphic (Unknown) param type so any value type is accepted without spurious warnings
- Add StructLit expr to AST + parser (uppercase-IDENT { ... } syntax), BuildStruct bytecode instruction + Struct value to runtime
- Add type_params to FnDef AST node and TypeParam variant to TypeExpr; parser parses <T, E> generics; type checker treats TypeParam as Unknown (universal type)
- Rewrite interpreter to support user-defined function calls via call stack (Frame + return_ip); dispatch_builtin handles print/println/log/print_err/__build_list__
- Fix engram_activate_search to unwrap { results: [...] } response envelope from /search; use std::net for sync HTTP to avoid reqwest dependency
- Add run-file command to CLI for single-file execution without el.toml
- Fix worktree engram-crypto path dep
174 lines
7.6 KiB
Rust
174 lines
7.6 KiB
Rust
//! Bytecode instruction set for the Engram virtual machine.
|
|
//!
|
|
//! The VM is a simple stack machine. Every instruction pops its operands
|
|
//! from the stack and pushes its result. Control flow uses relative signed
|
|
//! offsets from the instruction *after* the jump.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// A runtime value on the VM stack.
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum Value {
|
|
Int(i64),
|
|
Float(f64),
|
|
Str(String),
|
|
Bool(bool),
|
|
Nil,
|
|
/// A list of values (used for `activate` results and array literals).
|
|
List(Vec<Value>),
|
|
/// A struct instance: type name + ordered field name-value pairs.
|
|
Struct { type_name: String, fields: Vec<(String, Value)> },
|
|
}
|
|
|
|
impl std::fmt::Display for Value {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Value::Int(n) => write!(f, "{n}"),
|
|
Value::Float(n) => write!(f, "{n}"),
|
|
Value::Str(s) => write!(f, "{s}"),
|
|
Value::Bool(b) => write!(f, "{b}"),
|
|
Value::Nil => write!(f, "nil"),
|
|
Value::List(vs) => {
|
|
let items: Vec<_> = vs.iter().map(|v| v.to_string()).collect();
|
|
write!(f, "[{}]", items.join(", "))
|
|
}
|
|
Value::Struct { type_name, fields } => {
|
|
let fs: Vec<_> = fields.iter().map(|(k, v)| format!("{k}: {v}")).collect();
|
|
write!(f, "{type_name} {{ {} }}", fs.join(", "))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A single VM instruction.
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum Bytecode {
|
|
// ── Stack ─────────────────────────────────────────────────────────────────
|
|
/// Push a constant value onto the stack.
|
|
Push(Value),
|
|
/// Discard the top of stack.
|
|
Pop,
|
|
/// Duplicate the top of stack.
|
|
Dup,
|
|
|
|
// ── Arithmetic ────────────────────────────────────────────────────────────
|
|
Add,
|
|
Sub,
|
|
Mul,
|
|
Div,
|
|
|
|
// ── Comparison ────────────────────────────────────────────────────────────
|
|
Eq,
|
|
NotEq,
|
|
Lt,
|
|
Gt,
|
|
LtEq,
|
|
GtEq,
|
|
|
|
// ── Logical ───────────────────────────────────────────────────────────────
|
|
And,
|
|
Or,
|
|
Not,
|
|
|
|
// ── Locals ───────────────────────────────────────────────────────────────
|
|
/// Load a local variable by name.
|
|
LoadLocal(String),
|
|
/// Store the top of stack into a local variable.
|
|
StoreLocal(String),
|
|
|
|
// ── Functions ─────────────────────────────────────────────────────────────
|
|
/// Call a function by name with `arity` arguments.
|
|
Call { name: String, arity: u32 },
|
|
/// Return from the current function (leaves return value on stack).
|
|
Return,
|
|
|
|
// ── Control flow ──────────────────────────────────────────────────────────
|
|
/// Unconditional jump: `ip += offset` (offset is from the *next* instruction).
|
|
Jump(i32),
|
|
/// Jump if the top of stack is truthy; pops the value.
|
|
JumpIf(i32),
|
|
/// Jump if the top of stack is falsy; pops the value.
|
|
JumpIfNot(i32),
|
|
|
|
// ── Fields & Indexing ─────────────────────────────────────────────────────
|
|
/// Load a named field from the struct on top of stack.
|
|
GetField(String),
|
|
/// Index into an array: pops index then array.
|
|
GetIndex,
|
|
|
|
// ── Special ───────────────────────────────────────────────────────────────
|
|
/// `activate TypeName "query"` — emit a semantic query stub.
|
|
/// In a full implementation this would call into the Engram runtime.
|
|
Activate { type_name: String, query: String },
|
|
/// Mark the start of a sealed section (the runtime enforces protection).
|
|
SealedBegin,
|
|
/// Mark the end of a sealed section.
|
|
SealedEnd,
|
|
/// Build a struct value: pops `fields.len()` values from the stack (in
|
|
/// order), combines them with the field names, and pushes a `Struct` value.
|
|
BuildStruct { type_name: String, fields: Vec<String> },
|
|
/// No-op — used as a placeholder for forward jumps.
|
|
Nop,
|
|
/// Halt the VM.
|
|
Halt,
|
|
}
|
|
|
|
impl std::fmt::Display for Bytecode {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Bytecode::Push(v) => write!(f, "PUSH {v}"),
|
|
Bytecode::Pop => write!(f, "POP"),
|
|
Bytecode::Dup => write!(f, "DUP"),
|
|
Bytecode::Add => write!(f, "ADD"),
|
|
Bytecode::Sub => write!(f, "SUB"),
|
|
Bytecode::Mul => write!(f, "MUL"),
|
|
Bytecode::Div => write!(f, "DIV"),
|
|
Bytecode::Eq => write!(f, "EQ"),
|
|
Bytecode::NotEq => write!(f, "NEQ"),
|
|
Bytecode::Lt => write!(f, "LT"),
|
|
Bytecode::Gt => write!(f, "GT"),
|
|
Bytecode::LtEq => write!(f, "LTE"),
|
|
Bytecode::GtEq => write!(f, "GTE"),
|
|
Bytecode::And => write!(f, "AND"),
|
|
Bytecode::Or => write!(f, "OR"),
|
|
Bytecode::Not => write!(f, "NOT"),
|
|
Bytecode::LoadLocal(n) => write!(f, "LOAD {n}"),
|
|
Bytecode::StoreLocal(n) => write!(f, "STORE {n}"),
|
|
Bytecode::Call { name, arity } => write!(f, "CALL {name}/{arity}"),
|
|
Bytecode::Return => write!(f, "RETURN"),
|
|
Bytecode::Jump(off) => write!(f, "JUMP {off:+}"),
|
|
Bytecode::JumpIf(off) => write!(f, "JUMPIF {off:+}"),
|
|
Bytecode::JumpIfNot(off) => write!(f, "JUMPIFNOT {off:+}"),
|
|
Bytecode::GetField(n) => write!(f, "GETFIELD {n}"),
|
|
Bytecode::GetIndex => write!(f, "GETINDEX"),
|
|
Bytecode::Activate { type_name, query } => {
|
|
write!(f, "ACTIVATE {type_name} \"{query}\"")
|
|
}
|
|
Bytecode::SealedBegin => write!(f, "SEALED_BEGIN"),
|
|
Bytecode::SealedEnd => write!(f, "SEALED_END"),
|
|
Bytecode::BuildStruct { type_name, fields } => {
|
|
write!(f, "BUILD_STRUCT {type_name}({})", fields.join(", "))
|
|
}
|
|
Bytecode::Nop => write!(f, "NOP"),
|
|
Bytecode::Halt => write!(f, "HALT"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Serialize bytecode instructions to bytes for storage/sealing.
|
|
pub fn serialize_bytecode(instructions: &[Bytecode]) -> Result<Vec<u8>, String> {
|
|
serde_json::to_vec(instructions).map_err(|e| e.to_string())
|
|
}
|
|
|
|
/// Deserialize bytecode instructions from bytes.
|
|
pub fn deserialize_bytecode(bytes: &[u8]) -> Result<Vec<Bytecode>, String> {
|
|
serde_json::from_slice(bytes).map_err(|e| e.to_string())
|
|
}
|
|
|
|
impl Bytecode {
|
|
/// Deserialize a bytecode slice from JSON bytes (convenience wrapper).
|
|
pub fn deserialize_all(bytes: &[u8]) -> Result<Vec<Bytecode>, String> {
|
|
deserialize_bytecode(bytes)
|
|
}
|
|
}
|