Archived
Merge worktree-agent: add struct literals, generics, print/log builtins
This commit is contained in:
+3
-1
@@ -19,11 +19,13 @@ el-manifest = { workspace = true }
|
||||
el-registry = { workspace = true }
|
||||
el-build = { workspace = true }
|
||||
el-test = { workspace = true }
|
||||
el-fmt = { workspace = true }
|
||||
el-lint = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
|
||||
reqwest = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
walkdir = { workspace = true }
|
||||
tiny_http = { workspace = true }
|
||||
|
||||
+117
-43
@@ -35,8 +35,6 @@ use el_compiler::{Compiler, CompilerOptions, Target};
|
||||
use el_test;
|
||||
use el_manifest::{BuildTarget, Manifest};
|
||||
use el_seal::{seal as seal_fn, unseal as unseal_fn, SealedArtifact, DeploymentBinding, SealAlgorithm, SealConfig};
|
||||
use el_fmt;
|
||||
use el_lint;
|
||||
|
||||
// ── Global state (thread-local for simplicity) ────────────────────────────────
|
||||
|
||||
@@ -229,6 +227,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).
|
||||
@@ -241,15 +248,6 @@ enum Command {
|
||||
output: Option<PathBuf>,
|
||||
},
|
||||
|
||||
/// Compile and run a single .el source file (no el.toml required).
|
||||
RunFile {
|
||||
/// Source file (*.el).
|
||||
file: PathBuf,
|
||||
/// Arguments passed to the program (available via the args() builtin).
|
||||
#[arg(trailing_var_arg = true)]
|
||||
args: Vec<String>,
|
||||
},
|
||||
|
||||
/// Seal an existing release artifact.
|
||||
Seal {
|
||||
artifact: PathBuf,
|
||||
@@ -528,6 +526,9 @@ async fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
|
||||
..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_with_args(&instructions, &args);
|
||||
@@ -926,10 +927,10 @@ fn json_value_to_el_value(v: &serde_json::Value) -> el_compiler::Value {
|
||||
Value::List(arr.iter().map(json_value_to_el_value).collect())
|
||||
}
|
||||
serde_json::Value::Object(obj) => {
|
||||
let pairs = obj.iter()
|
||||
let fields = obj.iter()
|
||||
.map(|(k, v)| (k.clone(), json_value_to_el_value(v)))
|
||||
.collect();
|
||||
Value::Map(pairs)
|
||||
Value::Struct { type_name: "Object".to_string(), fields }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -961,6 +962,13 @@ fn el_value_to_json_value(v: &el_compiler::Value) -> serde_json::Value {
|
||||
Value::ResultErr(inner) => {
|
||||
serde_json::json!({"err": el_value_to_json_value(inner)})
|
||||
}
|
||||
Value::Struct { fields, .. } => {
|
||||
let mut map = serde_json::Map::new();
|
||||
for (k, v) in fields {
|
||||
map.insert(k.clone(), el_value_to_json_value(v));
|
||||
}
|
||||
serde_json::Value::Object(map)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1014,14 +1022,13 @@ fn run_sub_interpreter(
|
||||
entry: usize,
|
||||
) -> el_compiler::Value {
|
||||
use el_compiler::{Bytecode, 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<(usize, std::collections::HashMap<String, Value>)> = Vec::new();
|
||||
let mut ip = entry;
|
||||
let program_args: Vec<String> = vec![];
|
||||
|
||||
// Bind zero params (handle_request takes none)
|
||||
|
||||
while ip < instructions.len() {
|
||||
match &instructions[ip] {
|
||||
Bytecode::Push(v) => stack.push(v.clone()),
|
||||
@@ -1035,6 +1042,8 @@ fn run_sub_interpreter(
|
||||
(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,
|
||||
});
|
||||
}
|
||||
@@ -1043,6 +1052,8 @@ fn run_sub_interpreter(
|
||||
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,
|
||||
});
|
||||
}
|
||||
@@ -1051,6 +1062,8 @@ fn run_sub_interpreter(
|
||||
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,
|
||||
});
|
||||
}
|
||||
@@ -1059,6 +1072,8 @@ fn run_sub_interpreter(
|
||||
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,
|
||||
});
|
||||
}
|
||||
@@ -1072,31 +1087,19 @@ fn run_sub_interpreter(
|
||||
}
|
||||
Bytecode::Lt => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||
stack.push(match (a, b) {
|
||||
(Value::Int(x), Value::Int(y)) => Value::Bool(x < y),
|
||||
_ => Value::Bool(false),
|
||||
});
|
||||
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(match (a, b) {
|
||||
(Value::Int(x), Value::Int(y)) => Value::Bool(x > y),
|
||||
_ => Value::Bool(false),
|
||||
});
|
||||
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(match (a, b) {
|
||||
(Value::Int(x), Value::Int(y)) => Value::Bool(x <= y),
|
||||
_ => Value::Bool(false),
|
||||
});
|
||||
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(match (a, b) {
|
||||
(Value::Int(x), Value::Int(y)) => Value::Bool(x >= y),
|
||||
_ => Value::Bool(false),
|
||||
});
|
||||
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));
|
||||
@@ -1140,11 +1143,15 @@ fn run_sub_interpreter(
|
||||
}
|
||||
Bytecode::GetField(field) => {
|
||||
let obj = stack.pop().unwrap_or(Value::Nil);
|
||||
let v = match obj {
|
||||
let v = match &obj {
|
||||
Value::Map(pairs) => pairs.iter()
|
||||
.find(|(k, _)| k == field)
|
||||
.map(|(_, v)| v.clone())
|
||||
.unwrap_or(Value::Nil),
|
||||
Value::Struct { fields, .. } => fields.iter()
|
||||
.find(|(n, _)| n == field)
|
||||
.map(|(_, v)| v.clone())
|
||||
.unwrap_or(Value::Nil),
|
||||
_ => Value::Nil,
|
||||
};
|
||||
stack.push(v);
|
||||
@@ -1184,14 +1191,17 @@ fn run_sub_interpreter(
|
||||
pairs.reverse();
|
||||
stack.push(Value::Map(pairs));
|
||||
}
|
||||
Bytecode::BuildStruct { fields, .. } => {
|
||||
let mut pairs = Vec::new();
|
||||
for field in fields.iter().rev() {
|
||||
let val = stack.pop().unwrap_or(Value::Nil);
|
||||
pairs.push((field.clone(), val));
|
||||
}
|
||||
pairs.reverse();
|
||||
stack.push(Value::Map(pairs));
|
||||
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::SetField(field) => {
|
||||
let val = stack.pop().unwrap_or(Value::Nil);
|
||||
@@ -1201,6 +1211,12 @@ fn run_sub_interpreter(
|
||||
} else {
|
||||
pairs.push((field.clone(), val));
|
||||
}
|
||||
} else if let Some(Value::Struct { fields, .. }) = stack.last_mut() {
|
||||
if let Some(entry) = fields.iter_mut().find(|(k, _)| k == field) {
|
||||
entry.1 = val;
|
||||
} else {
|
||||
fields.push((field.clone(), val));
|
||||
}
|
||||
}
|
||||
}
|
||||
Bytecode::Jump(offset) => {
|
||||
@@ -1555,9 +1571,9 @@ fn run_interpreter_with_args(instructions: &[el_compiler::Bytecode], program_arg
|
||||
}
|
||||
}
|
||||
Bytecode::Halt => break,
|
||||
Bytecode::SealedBegin => eprintln!("[sealed section begin]"),
|
||||
Bytecode::SealedEnd => eprintln!("[sealed section end]"),
|
||||
_ => {}
|
||||
Bytecode::SealedBegin => {}
|
||||
Bytecode::SealedEnd => {}
|
||||
Bytecode::Nop => {}
|
||||
}
|
||||
ip += 1;
|
||||
}
|
||||
@@ -1592,6 +1608,12 @@ fn dispatch_builtin(
|
||||
stack.push(Value::Nil);
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"log" => {
|
||||
let v = stack.pop().unwrap_or(Value::Nil);
|
||||
println!("{v}");
|
||||
stack.push(Value::Nil);
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"print_err" => {
|
||||
let v = stack.pop().unwrap_or(Value::Nil);
|
||||
eprintln!("{v}");
|
||||
@@ -3111,6 +3133,24 @@ fn is_leap(year: u64) -> bool {
|
||||
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
|
||||
}
|
||||
|
||||
/// 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 known built-in (used by run_sub_interpreter).
|
||||
fn is_builtin(name: &str) -> bool {
|
||||
matches!(name, "print" | "println" | "log" | "print_err" | "__build_list__")
|
||||
}
|
||||
|
||||
/// 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};
|
||||
@@ -3142,6 +3182,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) {
|
||||
@@ -3159,6 +3202,33 @@ fn run_interpreter_debug(instructions: &[el_compiler::Bytecode], debugger: &mut
|
||||
let v = locals.get(name).cloned().unwrap_or(Value::Nil);
|
||||
stack.push(v);
|
||||
}
|
||||
Bytecode::GetField(field) => {
|
||||
let obj = stack.pop().unwrap_or(Value::Nil);
|
||||
let result = match &obj {
|
||||
Value::Map(pairs) => pairs.iter()
|
||||
.find(|(k, _)| k == field)
|
||||
.map(|(_, v)| v.clone())
|
||||
.unwrap_or(Value::Nil),
|
||||
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, &program_args);
|
||||
match result {
|
||||
@@ -3166,6 +3236,10 @@ fn run_interpreter_debug(instructions: &[el_compiler::Bytecode], debugger: &mut
|
||||
BuiltinResult::Exit(code) => std::process::exit(code),
|
||||
}
|
||||
}
|
||||
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;
|
||||
ip = new_ip;
|
||||
|
||||
Reference in New Issue
Block a user