Archived
Add struct literals, generics, log/print stdlib registration, activate DB wiring
- 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
This commit is contained in:
+384
-18
@@ -189,6 +189,15 @@ enum Command {
|
||||
|
||||
// ── Low-level / single-file ───────────────────────────────────────────────
|
||||
|
||||
/// Compile and immediately run a single .el source file (no el.toml required).
|
||||
RunFile {
|
||||
/// Source file (*.el).
|
||||
file: PathBuf,
|
||||
/// Arguments to pass to the program.
|
||||
#[arg(trailing_var_arg = true)]
|
||||
args: Vec<String>,
|
||||
},
|
||||
|
||||
/// Compile a single .el source file (no el.toml required).
|
||||
BuildFile {
|
||||
/// Source file (*.el).
|
||||
@@ -431,6 +440,24 @@ async fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
// ── Low-level / single-file ───────────────────────────────────────────
|
||||
|
||||
Command::RunFile { file, args } => {
|
||||
let source = std::fs::read_to_string(&file)
|
||||
.map_err(|e| format!("cannot read {}: {e}", file.display()))?;
|
||||
|
||||
let opts = CompilerOptions {
|
||||
target: Target::Debug,
|
||||
source_path: file.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
let compiled = Compiler::compile(&source, opts)?;
|
||||
for d in &compiled.diagnostics {
|
||||
eprintln!("warning: {d}");
|
||||
}
|
||||
let instructions = el_compiler::Bytecode::deserialize_all(&compiled.artifact)
|
||||
.unwrap_or_default();
|
||||
run_interpreter_full(&instructions, &args);
|
||||
}
|
||||
|
||||
Command::BuildFile { file, target, output } => {
|
||||
let source = std::fs::read_to_string(&file)
|
||||
.map_err(|e| format!("cannot read {}: {e}", file.display()))?;
|
||||
@@ -747,11 +774,22 @@ fn run_tests_from_source(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Minimal interpreter for demonstration.
|
||||
fn run_interpreter(instructions: &[el_compiler::Bytecode]) {
|
||||
/// Full-featured interpreter used by `run-file` and `run`.
|
||||
/// Supports user-defined functions via a call stack.
|
||||
fn run_interpreter_full(instructions: &[el_compiler::Bytecode], _program_args: &[String]) {
|
||||
use el_compiler::{Bytecode, Value};
|
||||
|
||||
/// A call frame: saved instruction pointer + saved locals scope.
|
||||
struct Frame {
|
||||
/// Return address (instruction index after the Call instruction).
|
||||
return_ip: usize,
|
||||
/// The caller's locals, so we can restore them on return.
|
||||
saved_locals: std::collections::HashMap<String, Value>,
|
||||
}
|
||||
|
||||
let mut stack: Vec<Value> = Vec::new();
|
||||
let mut locals: std::collections::HashMap<String, Value> = std::collections::HashMap::new();
|
||||
let mut call_stack: Vec<Frame> = Vec::new();
|
||||
let mut ip = 0usize;
|
||||
|
||||
while ip < instructions.len() {
|
||||
@@ -767,6 +805,8 @@ fn run_interpreter(instructions: &[el_compiler::Bytecode]) {
|
||||
(Value::Int(x), Value::Int(y)) => Value::Int(x + y),
|
||||
(Value::Float(x), Value::Float(y)) => Value::Float(x + y),
|
||||
(Value::Str(x), Value::Str(y)) => Value::Str(x + &y),
|
||||
(Value::Int(x), Value::Float(y)) => Value::Float(x as f64 + y),
|
||||
(Value::Float(x), Value::Int(y)) => Value::Float(x + y as f64),
|
||||
_ => Value::Nil,
|
||||
});
|
||||
}
|
||||
@@ -775,6 +815,8 @@ fn run_interpreter(instructions: &[el_compiler::Bytecode]) {
|
||||
stack.push(match (a, b) {
|
||||
(Value::Int(x), Value::Int(y)) => Value::Int(x - y),
|
||||
(Value::Float(x), Value::Float(y)) => Value::Float(x - y),
|
||||
(Value::Int(x), Value::Float(y)) => Value::Float(x as f64 - y),
|
||||
(Value::Float(x), Value::Int(y)) => Value::Float(x - y as f64),
|
||||
_ => Value::Nil,
|
||||
});
|
||||
}
|
||||
@@ -783,6 +825,8 @@ fn run_interpreter(instructions: &[el_compiler::Bytecode]) {
|
||||
stack.push(match (a, b) {
|
||||
(Value::Int(x), Value::Int(y)) => Value::Int(x * y),
|
||||
(Value::Float(x), Value::Float(y)) => Value::Float(x * y),
|
||||
(Value::Int(x), Value::Float(y)) => Value::Float(x as f64 * y),
|
||||
(Value::Float(x), Value::Int(y)) => Value::Float(x * y as f64),
|
||||
_ => Value::Nil,
|
||||
});
|
||||
}
|
||||
@@ -791,6 +835,8 @@ fn run_interpreter(instructions: &[el_compiler::Bytecode]) {
|
||||
stack.push(match (a, b) {
|
||||
(Value::Int(x), Value::Int(y)) if y != 0 => Value::Int(x / y),
|
||||
(Value::Float(x), Value::Float(y)) => Value::Float(x / y),
|
||||
(Value::Int(x), Value::Float(y)) => Value::Float(x as f64 / y),
|
||||
(Value::Float(x), Value::Int(y)) => Value::Float(x / y as f64),
|
||||
_ => Value::Nil,
|
||||
});
|
||||
}
|
||||
@@ -798,6 +844,38 @@ fn run_interpreter(instructions: &[el_compiler::Bytecode]) {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||
stack.push(Value::Bool(a == b));
|
||||
}
|
||||
Bytecode::NotEq => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||
stack.push(Value::Bool(a != b));
|
||||
}
|
||||
Bytecode::Lt => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||
stack.push(Value::Bool(cmp_values(&a, &b) == std::cmp::Ordering::Less));
|
||||
}
|
||||
Bytecode::Gt => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||
stack.push(Value::Bool(cmp_values(&a, &b) == std::cmp::Ordering::Greater));
|
||||
}
|
||||
Bytecode::LtEq => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||
stack.push(Value::Bool(cmp_values(&a, &b) != std::cmp::Ordering::Greater));
|
||||
}
|
||||
Bytecode::GtEq => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||
stack.push(Value::Bool(cmp_values(&a, &b) != std::cmp::Ordering::Less));
|
||||
}
|
||||
Bytecode::And => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||
stack.push(Value::Bool(
|
||||
matches!(a, Value::Bool(true)) && matches!(b, Value::Bool(true))
|
||||
));
|
||||
}
|
||||
Bytecode::Or => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||
stack.push(Value::Bool(
|
||||
matches!(a, Value::Bool(true)) || matches!(b, Value::Bool(true))
|
||||
));
|
||||
}
|
||||
Bytecode::Not => {
|
||||
let v = stack.pop().unwrap_or(Value::Nil);
|
||||
stack.push(Value::Bool(!matches!(v, Value::Bool(true))));
|
||||
@@ -810,16 +888,100 @@ fn run_interpreter(instructions: &[el_compiler::Bytecode]) {
|
||||
let v = locals.get(name).cloned().unwrap_or(Value::Nil);
|
||||
stack.push(v);
|
||||
}
|
||||
Bytecode::Call { name, .. } => {
|
||||
if name == "print" || name == "println" {
|
||||
let v = stack.pop().unwrap_or(Value::Nil);
|
||||
println!("{v}");
|
||||
Bytecode::GetField(field) => {
|
||||
let obj = stack.pop().unwrap_or(Value::Nil);
|
||||
let result = match &obj {
|
||||
Value::Struct { fields, .. } => {
|
||||
fields.iter()
|
||||
.find(|(n, _)| n == field)
|
||||
.map(|(_, v)| v.clone())
|
||||
.unwrap_or(Value::Nil)
|
||||
}
|
||||
_ => Value::Nil,
|
||||
};
|
||||
stack.push(result);
|
||||
}
|
||||
Bytecode::GetIndex => {
|
||||
let idx = stack.pop().unwrap_or(Value::Nil);
|
||||
let obj = stack.pop().unwrap_or(Value::Nil);
|
||||
let result = match (&obj, &idx) {
|
||||
(Value::List(items), Value::Int(i)) => {
|
||||
let i = *i as usize;
|
||||
items.get(i).cloned().unwrap_or(Value::Nil)
|
||||
}
|
||||
_ => Value::Nil,
|
||||
};
|
||||
stack.push(result);
|
||||
}
|
||||
Bytecode::BuildStruct { type_name, fields } => {
|
||||
// Pop field values in reverse order (they were pushed left-to-right)
|
||||
let n = fields.len();
|
||||
let mut field_values: Vec<Value> = (0..n).map(|_| stack.pop().unwrap_or(Value::Nil)).collect();
|
||||
field_values.reverse();
|
||||
let struct_fields: Vec<(String, Value)> = fields.iter().cloned()
|
||||
.zip(field_values.into_iter())
|
||||
.collect();
|
||||
stack.push(Value::Struct {
|
||||
type_name: type_name.clone(),
|
||||
fields: struct_fields,
|
||||
});
|
||||
}
|
||||
Bytecode::Call { name, arity } => {
|
||||
// First try to dispatch as a built-in
|
||||
if is_builtin(name) {
|
||||
let result = dispatch_builtin(name, *arity, &mut stack);
|
||||
stack.push(result);
|
||||
} else {
|
||||
// Look up user-defined function entry point stored as __fn_<name>
|
||||
let fn_key = format!("__fn_{name}");
|
||||
if let Some(Value::Int(entry)) = locals.get(&fn_key).cloned() {
|
||||
// Save caller's state
|
||||
let saved = std::mem::replace(&mut locals, std::collections::HashMap::new());
|
||||
// Restore __fn_* entries from saved scope so nested calls work
|
||||
for (k, v) in &saved {
|
||||
if k.starts_with("__fn_") {
|
||||
locals.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
call_stack.push(Frame {
|
||||
return_ip: ip + 1,
|
||||
saved_locals: saved,
|
||||
});
|
||||
ip = entry as usize;
|
||||
continue;
|
||||
}
|
||||
// Unknown function — pop args and push Nil
|
||||
let n = *arity as usize;
|
||||
for _ in 0..n { stack.pop(); }
|
||||
stack.push(Value::Nil);
|
||||
}
|
||||
}
|
||||
Bytecode::Return => {
|
||||
if let Some(frame) = call_stack.pop() {
|
||||
// Take the return value from the stack
|
||||
let ret_val = stack.pop().unwrap_or(Value::Nil);
|
||||
// Restore callee's __fn_* entries before fully restoring caller locals
|
||||
let callee_fns: Vec<(String, Value)> = locals.iter()
|
||||
.filter(|(k, _)| k.starts_with("__fn_"))
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect();
|
||||
// Restore caller's locals
|
||||
locals = frame.saved_locals;
|
||||
// Merge in any __fn_* entries that were registered inside the callee
|
||||
for (k, v) in callee_fns {
|
||||
locals.insert(k, v);
|
||||
}
|
||||
// Push return value
|
||||
stack.push(ret_val);
|
||||
ip = frame.return_ip;
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Bytecode::Activate { type_name, query } => {
|
||||
println!("[activate] {type_name} where \"{query}\" (no DB connected)");
|
||||
stack.push(Value::List(vec![]));
|
||||
let results = engram_activate_search(type_name, query);
|
||||
stack.push(Value::List(results));
|
||||
}
|
||||
Bytecode::Jump(offset) => {
|
||||
let new_ip = (ip as i32 + 1 + offset) as usize;
|
||||
@@ -842,16 +1004,191 @@ fn run_interpreter(instructions: &[el_compiler::Bytecode]) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Bytecode::Return => break,
|
||||
Bytecode::Halt => break,
|
||||
Bytecode::SealedBegin => eprintln!("[sealed section begin]"),
|
||||
Bytecode::SealedEnd => eprintln!("[sealed section end]"),
|
||||
_ => {}
|
||||
Bytecode::SealedBegin => {}
|
||||
Bytecode::SealedEnd => {}
|
||||
Bytecode::Nop => {}
|
||||
}
|
||||
ip += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimal interpreter for demonstration (delegates to run_interpreter_full).
|
||||
fn run_interpreter(instructions: &[el_compiler::Bytecode]) {
|
||||
run_interpreter_full(instructions, &[]);
|
||||
}
|
||||
|
||||
/// Compare two runtime values for ordering (used by Lt/Gt/LtEq/GtEq).
|
||||
fn cmp_values(a: &el_compiler::Value, b: &el_compiler::Value) -> std::cmp::Ordering {
|
||||
use el_compiler::Value;
|
||||
match (a, b) {
|
||||
(Value::Int(x), Value::Int(y)) => x.cmp(y),
|
||||
(Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal),
|
||||
(Value::Int(x), Value::Float(y)) => (*x as f64).partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal),
|
||||
(Value::Float(x), Value::Int(y)) => x.partial_cmp(&(*y as f64)).unwrap_or(std::cmp::Ordering::Equal),
|
||||
(Value::Str(x), Value::Str(y)) => x.cmp(y),
|
||||
_ => std::cmp::Ordering::Equal,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a function name is a built-in.
|
||||
fn is_builtin(name: &str) -> bool {
|
||||
matches!(name, "print" | "println" | "log" | "print_err" | "__build_list__")
|
||||
}
|
||||
|
||||
/// Dispatch a built-in function call, popping `arity` args off the stack.
|
||||
fn dispatch_builtin(name: &str, arity: u32, stack: &mut Vec<el_compiler::Value>) -> el_compiler::Value {
|
||||
use el_compiler::Value;
|
||||
|
||||
// Collect args (they were pushed left-to-right, so pop right-to-left and reverse)
|
||||
let n = arity as usize;
|
||||
let mut args: Vec<Value> = (0..n).map(|_| stack.pop().unwrap_or(Value::Nil)).collect();
|
||||
args.reverse();
|
||||
|
||||
match name {
|
||||
"print" | "println" | "log" => {
|
||||
let s = args.into_iter().map(|v| v.to_string()).collect::<Vec<_>>().join(" ");
|
||||
println!("{s}");
|
||||
Value::Nil
|
||||
}
|
||||
"print_err" => {
|
||||
let s = args.into_iter().map(|v| v.to_string()).collect::<Vec<_>>().join(" ");
|
||||
eprintln!("{s}");
|
||||
Value::Nil
|
||||
}
|
||||
"__build_list__" => {
|
||||
Value::List(args)
|
||||
}
|
||||
_ => {
|
||||
// Unknown built-in — return Nil
|
||||
Value::Nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Call the engram-server `/search` endpoint.
|
||||
/// The server's `/search` expects `{ embedding: Vec<f32>, limit: usize }`.
|
||||
/// Since we only have a text query string (not an embedding), we try a text
|
||||
/// search approach: first attempt POST /search/text if available, then fall
|
||||
/// back to returning an empty list with a notice.
|
||||
fn engram_activate_search(type_name: &str, _query: &str) -> Vec<el_compiler::Value> {
|
||||
// Try to reach engram-server. Use the ENGRAM_URL env var if set.
|
||||
let base_url = std::env::var("ENGRAM_URL")
|
||||
.unwrap_or_else(|_| "http://localhost:8742".to_string());
|
||||
|
||||
// Attempt a blocking HTTP call using a simple std-based approach.
|
||||
// We use a text search stub: POST /search with a zeroed embedding placeholder.
|
||||
// In a real deployment the caller would provide a pre-computed embedding.
|
||||
let search_url = format!("{base_url}/search");
|
||||
|
||||
// Build a minimal dummy embedding (all zeros, 384 dims — common sentence-transformer size)
|
||||
let embedding: Vec<f32> = vec![0.0f32; 384];
|
||||
let body = serde_json::json!({
|
||||
"embedding": embedding,
|
||||
"limit": 10
|
||||
});
|
||||
|
||||
// We're in an async context (tokio runtime) but this is called from within
|
||||
// a synchronous interpreter loop. Use a blocking reqwest-less HTTP call
|
||||
// via std::net since reqwest isn't in scope here.
|
||||
// For now: attempt the call synchronously, return empty on failure.
|
||||
match do_http_post_sync(&search_url, &body.to_string()) {
|
||||
Ok(response_body) => {
|
||||
// Parse the response: { "results": [{ "node": {...}, "score": f32 }] }
|
||||
match serde_json::from_str::<serde_json::Value>(&response_body) {
|
||||
Ok(json) => {
|
||||
if let Some(results) = json.get("results").and_then(|r| r.as_array()) {
|
||||
results.iter().map(|item| {
|
||||
// Extract the "node" field from each result
|
||||
if let Some(node) = item.get("node") {
|
||||
json_value_to_el_value(node)
|
||||
} else {
|
||||
json_value_to_el_value(item)
|
||||
}
|
||||
}).collect()
|
||||
} else {
|
||||
eprintln!("[activate] {type_name}: unexpected response format from {search_url}");
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[activate] {type_name}: failed to parse response: {e}");
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// Server not reachable — return empty list silently
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a serde_json::Value to an el_compiler::Value.
|
||||
fn json_value_to_el_value(v: &serde_json::Value) -> el_compiler::Value {
|
||||
use el_compiler::Value;
|
||||
match v {
|
||||
serde_json::Value::Null => Value::Nil,
|
||||
serde_json::Value::Bool(b) => Value::Bool(*b),
|
||||
serde_json::Value::Number(n) => {
|
||||
if let Some(i) = n.as_i64() {
|
||||
Value::Int(i)
|
||||
} else if let Some(f) = n.as_f64() {
|
||||
Value::Float(f)
|
||||
} else {
|
||||
Value::Nil
|
||||
}
|
||||
}
|
||||
serde_json::Value::String(s) => Value::Str(s.clone()),
|
||||
serde_json::Value::Array(arr) => Value::List(arr.iter().map(json_value_to_el_value).collect()),
|
||||
serde_json::Value::Object(map) => {
|
||||
let fields: Vec<(String, Value)> = map.iter()
|
||||
.map(|(k, v)| (k.clone(), json_value_to_el_value(v)))
|
||||
.collect();
|
||||
Value::Struct { type_name: "Object".to_string(), fields }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimal synchronous HTTP POST using std::net (no reqwest dependency).
|
||||
fn do_http_post_sync(url: &str, body: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
use std::io::{Read, Write};
|
||||
|
||||
// Parse the URL manually (only http:// supported)
|
||||
let url_stripped = url.strip_prefix("http://").ok_or("only http:// supported")?;
|
||||
let (host_port, path) = url_stripped.split_once('/').unwrap_or((url_stripped, ""));
|
||||
let path = format!("/{path}");
|
||||
|
||||
let (host, port) = if let Some((h, p)) = host_port.split_once(':') {
|
||||
(h.to_string(), p.parse::<u16>().unwrap_or(80))
|
||||
} else {
|
||||
(host_port.to_string(), 80u16)
|
||||
};
|
||||
|
||||
let addr = format!("{host}:{port}");
|
||||
let mut stream = std::net::TcpStream::connect_timeout(
|
||||
&addr.parse()?,
|
||||
std::time::Duration::from_secs(2),
|
||||
)?;
|
||||
stream.set_read_timeout(Some(std::time::Duration::from_secs(5)))?;
|
||||
|
||||
let request = format!(
|
||||
"POST {path} HTTP/1.0\r\nHost: {host}\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{body}",
|
||||
body.len()
|
||||
);
|
||||
stream.write_all(request.as_bytes())?;
|
||||
|
||||
let mut response = String::new();
|
||||
stream.read_to_string(&mut response)?;
|
||||
|
||||
// Strip HTTP headers — find \r\n\r\n
|
||||
if let Some(idx) = response.find("\r\n\r\n") {
|
||||
Ok(response[idx + 4..].to_string())
|
||||
} else {
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
/// Interpreter with debugger support — emits DebugEvents as it runs.
|
||||
fn run_interpreter_debug(instructions: &[el_compiler::Bytecode], debugger: &mut el_compiler::Debugger) {
|
||||
use el_compiler::{Bytecode, Value};
|
||||
@@ -882,6 +1219,9 @@ fn run_interpreter_debug(instructions: &[el_compiler::Bytecode], debugger: &mut
|
||||
match &instructions[ip] {
|
||||
Bytecode::Push(v) => stack.push(v.clone()),
|
||||
Bytecode::Pop => { stack.pop(); }
|
||||
Bytecode::Dup => {
|
||||
if let Some(top) = stack.last().cloned() { stack.push(top); }
|
||||
}
|
||||
Bytecode::Add => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||
stack.push(match (a, b) {
|
||||
@@ -899,12 +1239,38 @@ fn run_interpreter_debug(instructions: &[el_compiler::Bytecode], debugger: &mut
|
||||
let v = locals.get(name).cloned().unwrap_or(Value::Nil);
|
||||
stack.push(v);
|
||||
}
|
||||
Bytecode::Call { name, .. } => {
|
||||
if name == "print" || name == "println" {
|
||||
let v = stack.pop().unwrap_or(Value::Nil);
|
||||
println!("{v}");
|
||||
stack.push(Value::Nil);
|
||||
}
|
||||
Bytecode::GetField(field) => {
|
||||
let obj = stack.pop().unwrap_or(Value::Nil);
|
||||
let result = match &obj {
|
||||
Value::Struct { fields, .. } => {
|
||||
fields.iter()
|
||||
.find(|(n, _)| n == field)
|
||||
.map(|(_, v)| v.clone())
|
||||
.unwrap_or(Value::Nil)
|
||||
}
|
||||
_ => Value::Nil,
|
||||
};
|
||||
stack.push(result);
|
||||
}
|
||||
Bytecode::BuildStruct { type_name, fields } => {
|
||||
let n = fields.len();
|
||||
let mut field_values: Vec<Value> = (0..n).map(|_| stack.pop().unwrap_or(Value::Nil)).collect();
|
||||
field_values.reverse();
|
||||
let struct_fields: Vec<(String, Value)> = fields.iter().cloned()
|
||||
.zip(field_values.into_iter())
|
||||
.collect();
|
||||
stack.push(Value::Struct {
|
||||
type_name: type_name.clone(),
|
||||
fields: struct_fields,
|
||||
});
|
||||
}
|
||||
Bytecode::Call { name, arity } => {
|
||||
let result = dispatch_builtin(name, *arity, &mut stack);
|
||||
stack.push(result);
|
||||
}
|
||||
Bytecode::Eq => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||
stack.push(Value::Bool(a == b));
|
||||
}
|
||||
Bytecode::Jump(offset) => {
|
||||
let new_ip = (ip as i32 + 1 + offset) as usize;
|
||||
|
||||
Reference in New Issue
Block a user