Archive Rust bootstrap — El compiler is now self-hosting
This commit is contained in:
@@ -0,0 +1,725 @@
|
||||
//! Built-in function dispatch for the El VM.
|
||||
//!
|
||||
//! Provides the set of built-in functions accessible to El programs at runtime.
|
||||
//! This is the core portable set that `elvm` supports on all platforms.
|
||||
|
||||
use el_compiler::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Result of a builtin dispatch attempt.
|
||||
pub enum BuiltinResult {
|
||||
/// The builtin was handled; return value (if any) is on the stack.
|
||||
Handled,
|
||||
/// The `http_serve` builtin was invoked; the server loop is running.
|
||||
HttpServe,
|
||||
/// The program called `exit(code)`.
|
||||
Exit(i32),
|
||||
/// The name is not a known builtin — try user-defined functions.
|
||||
NotBuiltin,
|
||||
}
|
||||
|
||||
/// Dispatch a built-in call.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `name` — function name
|
||||
/// * `arity` — number of arguments already on the stack
|
||||
/// * `stack` — the VM value stack (may be modified)
|
||||
/// * `args` — program arguments (for the `args()` builtin)
|
||||
/// * `state` — global mutable string state
|
||||
pub fn dispatch(
|
||||
name: &str,
|
||||
_arity: u32,
|
||||
stack: &mut Vec<Value>,
|
||||
program_args: &[String],
|
||||
state: &mut HashMap<String, String>,
|
||||
) -> BuiltinResult {
|
||||
match name {
|
||||
// ── I/O ──────────────────────────────────────────────────────────────
|
||||
|
||||
"print" => {
|
||||
let v = stack.pop().unwrap_or(Value::Nil);
|
||||
print!("{v}");
|
||||
stack.push(Value::Nil);
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"println" | "log" => {
|
||||
let v = stack.pop().unwrap_or(Value::Nil);
|
||||
println!("{v}");
|
||||
stack.push(Value::Nil);
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"print_err" | "log_debug" | "log_info" | "log_warn" | "log_error" => {
|
||||
let v = stack.pop().unwrap_or(Value::Nil);
|
||||
eprintln!("{v}");
|
||||
stack.push(Value::Nil);
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
|
||||
// ── Process ───────────────────────────────────────────────────────────
|
||||
|
||||
"args" => {
|
||||
let list = program_args.iter().map(|s| Value::Str(s.clone())).collect();
|
||||
stack.push(Value::List(list));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"exit" => {
|
||||
let code = match stack.pop().unwrap_or(Value::Nil) {
|
||||
Value::Int(n) => n as i32,
|
||||
_ => 0,
|
||||
};
|
||||
BuiltinResult::Exit(code)
|
||||
}
|
||||
"env" => {
|
||||
let key = match stack.pop().unwrap_or(Value::Nil) {
|
||||
Value::Str(s) => s,
|
||||
_ => String::new(),
|
||||
};
|
||||
let val = std::env::var(&key).unwrap_or_default();
|
||||
stack.push(Value::Str(val));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"cwd" => {
|
||||
let path = std::env::current_dir()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|_| ".".to_string());
|
||||
stack.push(Value::Str(path));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"getpid" => {
|
||||
stack.push(Value::Int(std::process::id() as i64));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"sleep_ms" => {
|
||||
let ms = match stack.pop().unwrap_or(Value::Nil) {
|
||||
Value::Int(n) => n as u64,
|
||||
_ => 0,
|
||||
};
|
||||
std::thread::sleep(std::time::Duration::from_millis(ms));
|
||||
stack.push(Value::Nil);
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"sleep_secs" => {
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) {
|
||||
Value::Int(n) => n as u64,
|
||||
Value::Float(f) => f as u64,
|
||||
_ => 0,
|
||||
};
|
||||
std::thread::sleep(std::time::Duration::from_secs(s));
|
||||
stack.push(Value::Nil);
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"shell_exec" | "sh" => {
|
||||
let cmd = match stack.pop().unwrap_or(Value::Nil) {
|
||||
Value::Str(s) => s,
|
||||
_ => String::new(),
|
||||
};
|
||||
let output = std::process::Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(&cmd)
|
||||
.output()
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).to_string())
|
||||
.unwrap_or_default();
|
||||
stack.push(Value::Str(output));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
|
||||
// ── String operations ─────────────────────────────────────────────────
|
||||
|
||||
"str_len" | "string_len" | "native_string_len" => {
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) {
|
||||
Value::Str(s) => s,
|
||||
_ => String::new(),
|
||||
};
|
||||
stack.push(Value::Int(s.len() as i64));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"str_eq" => {
|
||||
let b = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let a = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
stack.push(Value::Bool(a == b));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"str_contains" | "native_string_contains" => {
|
||||
let needle = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let haystack = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
stack.push(Value::Bool(haystack.contains(needle.as_str())));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"str_starts_with" => {
|
||||
let prefix = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
stack.push(Value::Bool(s.starts_with(prefix.as_str())));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"str_ends_with" => {
|
||||
let suffix = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
stack.push(Value::Bool(s.ends_with(suffix.as_str())));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"str_split" => {
|
||||
let delim = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let parts: Vec<Value> = s.split(delim.as_str()).map(|p| Value::Str(p.to_string())).collect();
|
||||
stack.push(Value::List(parts));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"str_replace" => {
|
||||
let replacement = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let pattern = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
stack.push(Value::Str(s.replace(pattern.as_str(), &replacement)));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"str_trim" => {
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
stack.push(Value::Str(s.trim().to_string()));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"str_upper" => {
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
stack.push(Value::Str(s.to_uppercase()));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"str_lower" => {
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
stack.push(Value::Str(s.to_lowercase()));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"str_slice" => {
|
||||
let end = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as usize, _ => 0 };
|
||||
let start = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as usize, _ => 0 };
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let chars: Vec<char> = s.chars().collect();
|
||||
let slice: String = chars[start.min(chars.len())..end.min(chars.len())].iter().collect();
|
||||
stack.push(Value::Str(slice));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"str_index_of" => {
|
||||
let needle = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let haystack = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let idx = haystack.find(needle.as_str()).map(|i| i as i64).unwrap_or(-1);
|
||||
stack.push(Value::Int(idx));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"str_char_at" => {
|
||||
let idx = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as usize, _ => 0 };
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let c = s.chars().nth(idx).map(|c| Value::Str(c.to_string())).unwrap_or(Value::Nil);
|
||||
stack.push(c);
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
|
||||
// ── Type conversion ───────────────────────────────────────────────────
|
||||
|
||||
"int_to_str" | "str_format" | "native_int_to_str" => {
|
||||
let n = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n, _ => 0 };
|
||||
stack.push(Value::Str(n.to_string()));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"float_to_str" => {
|
||||
let f = match stack.pop().unwrap_or(Value::Nil) {
|
||||
Value::Float(f) => f,
|
||||
Value::Int(n) => n as f64,
|
||||
_ => 0.0,
|
||||
};
|
||||
stack.push(Value::Str(f.to_string()));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"bool_to_str" => {
|
||||
let b = match stack.pop().unwrap_or(Value::Nil) { Value::Bool(b) => b, _ => false };
|
||||
stack.push(Value::Str(b.to_string()));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"str_to_int" | "parse_int" | "native_str_to_int" => {
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let v = s.trim().parse::<i64>().map(Value::Int).unwrap_or(Value::Nil);
|
||||
stack.push(v);
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"str_to_float" | "parse_float" => {
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let v = s.trim().parse::<f64>().map(Value::Float).unwrap_or(Value::Nil);
|
||||
stack.push(v);
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"int_to_float" => {
|
||||
let n = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n, _ => 0 };
|
||||
stack.push(Value::Float(n as f64));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"float_to_int" => {
|
||||
let f = match stack.pop().unwrap_or(Value::Nil) { Value::Float(f) => f, Value::Int(n) => n as f64, _ => 0.0 };
|
||||
stack.push(Value::Int(f as i64));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"is_nil" => {
|
||||
let v = stack.pop().unwrap_or(Value::Nil);
|
||||
stack.push(Value::Bool(matches!(v, Value::Nil)));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"unwrap_or" => {
|
||||
let default = stack.pop().unwrap_or(Value::Nil);
|
||||
let v = stack.pop().unwrap_or(Value::Nil);
|
||||
stack.push(if matches!(v, Value::Nil) { default } else { v });
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
|
||||
// ── List operations ───────────────────────────────────────────────────
|
||||
|
||||
"list_len" | "native_list_len" => {
|
||||
let list = match stack.pop().unwrap_or(Value::Nil) { Value::List(l) => l, _ => vec![] };
|
||||
stack.push(Value::Int(list.len() as i64));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"list_get" | "native_list_get" => {
|
||||
let idx = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n, _ => -1 };
|
||||
let list = match stack.pop().unwrap_or(Value::Nil) { Value::List(l) => l, _ => vec![] };
|
||||
let v = if idx >= 0 && (idx as usize) < list.len() { list[idx as usize].clone() } else { Value::Nil };
|
||||
stack.push(v);
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"native_string_chars" => {
|
||||
// Split a string into a list of single-character strings.
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let chars: Vec<Value> = s.chars().map(|c| Value::Str(c.to_string())).collect();
|
||||
stack.push(Value::List(chars));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"list_push" | "native_list_append" => {
|
||||
let item = stack.pop().unwrap_or(Value::Nil);
|
||||
let list = match stack.pop().unwrap_or(Value::Nil) { Value::List(mut l) => { l.push(item); l }, _ => vec![item] };
|
||||
stack.push(Value::List(list));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"list_pop" => {
|
||||
let list = match stack.pop().unwrap_or(Value::Nil) { Value::List(mut l) => { l.pop(); l }, _ => vec![] };
|
||||
stack.push(Value::List(list));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"list_join" => {
|
||||
let sep = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let list = match stack.pop().unwrap_or(Value::Nil) { Value::List(l) => l, _ => vec![] };
|
||||
let s: String = list.iter().map(|v| v.to_string()).collect::<Vec<_>>().join(&sep);
|
||||
stack.push(Value::Str(s));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"list_contains" => {
|
||||
let item = stack.pop().unwrap_or(Value::Nil);
|
||||
let list = match stack.pop().unwrap_or(Value::Nil) { Value::List(l) => l, _ => vec![] };
|
||||
stack.push(Value::Bool(list.contains(&item)));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"list_concat" => {
|
||||
let b = match stack.pop().unwrap_or(Value::Nil) { Value::List(l) => l, _ => vec![] };
|
||||
let a = match stack.pop().unwrap_or(Value::Nil) { Value::List(l) => l, _ => vec![] };
|
||||
let mut result = a;
|
||||
result.extend(b);
|
||||
stack.push(Value::List(result));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"list_reverse" => {
|
||||
let mut list = match stack.pop().unwrap_or(Value::Nil) { Value::List(l) => l, _ => vec![] };
|
||||
list.reverse();
|
||||
stack.push(Value::List(list));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"list_range" => {
|
||||
let end = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n, _ => 0 };
|
||||
let start = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n, _ => 0 };
|
||||
let list: Vec<Value> = (start..end).map(Value::Int).collect();
|
||||
stack.push(Value::List(list));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"list_set" => {
|
||||
let val = stack.pop().unwrap_or(Value::Nil);
|
||||
let idx = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as usize, _ => 0 };
|
||||
let mut list = match stack.pop().unwrap_or(Value::Nil) { Value::List(l) => l, _ => vec![] };
|
||||
if idx < list.len() { list[idx] = val; }
|
||||
stack.push(Value::List(list));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"list_new" | "list_empty" | "native_list_empty" => {
|
||||
stack.push(Value::List(vec![]));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"__build_list__" => {
|
||||
let mut items = Vec::new();
|
||||
for _ in 0.._arity { items.push(stack.pop().unwrap_or(Value::Nil)); }
|
||||
items.reverse();
|
||||
stack.push(Value::List(items));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
|
||||
// ── JSON ──────────────────────────────────────────────────────────────
|
||||
|
||||
"json_get" | "json_get_string" => {
|
||||
let key = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let json_str = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let val = serde_json::from_str::<serde_json::Value>(&json_str)
|
||||
.ok()
|
||||
.and_then(|v| v.get(&key).cloned())
|
||||
.map(|v| crate::json_to_value(&v))
|
||||
.unwrap_or(Value::Nil);
|
||||
stack.push(val);
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"json_stringify" | "json_encode" => {
|
||||
let v = stack.pop().unwrap_or(Value::Nil);
|
||||
let json = value_to_json(&v);
|
||||
stack.push(Value::Str(json.to_string()));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"json_parse" | "json_decode" => {
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let val = serde_json::from_str::<serde_json::Value>(&s)
|
||||
.map(|v| crate::json_to_value(&v))
|
||||
.unwrap_or(Value::Nil);
|
||||
stack.push(val);
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"json_keys" => {
|
||||
let json_str = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let keys = serde_json::from_str::<serde_json::Value>(&json_str)
|
||||
.ok()
|
||||
.and_then(|v| v.as_object().cloned())
|
||||
.map(|o| o.keys().map(|k| Value::Str(k.clone())).collect::<Vec<_>>())
|
||||
.unwrap_or_default();
|
||||
stack.push(Value::List(keys));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"json_set" => {
|
||||
let val = stack.pop().unwrap_or(Value::Nil);
|
||||
let key = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let json_str = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => "{}".to_string() };
|
||||
let mut obj = serde_json::from_str::<serde_json::Map<String, serde_json::Value>>(&json_str)
|
||||
.unwrap_or_default();
|
||||
obj.insert(key, value_to_json(&val));
|
||||
stack.push(Value::Str(serde_json::Value::Object(obj).to_string()));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
|
||||
// ── Time ──────────────────────────────────────────────────────────────
|
||||
|
||||
"now_millis" | "time_now_ms" | "unix_timestamp" | "timestamp" => {
|
||||
let ms = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_millis() as i64)
|
||||
.unwrap_or(0);
|
||||
stack.push(Value::Int(ms));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"time_now_s" => {
|
||||
let s = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs() as i64)
|
||||
.unwrap_or(0);
|
||||
stack.push(Value::Int(s));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"time_now_utc" => {
|
||||
let ms = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_millis() as i64)
|
||||
.unwrap_or(0);
|
||||
stack.push(Value::Int(ms));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
|
||||
// ── Math ──────────────────────────────────────────────────────────────
|
||||
|
||||
"math_sin" => { let x = pop_f64(stack); stack.push(Value::Float(x.sin())); BuiltinResult::Handled }
|
||||
"math_cos" => { let x = pop_f64(stack); stack.push(Value::Float(x.cos())); BuiltinResult::Handled }
|
||||
"math_tan" => { let x = pop_f64(stack); stack.push(Value::Float(x.tan())); BuiltinResult::Handled }
|
||||
"math_asin" => { let x = pop_f64(stack); stack.push(Value::Float(x.asin())); BuiltinResult::Handled }
|
||||
"math_acos" => { let x = pop_f64(stack); stack.push(Value::Float(x.acos())); BuiltinResult::Handled }
|
||||
"math_exp" => { let x = pop_f64(stack); stack.push(Value::Float(x.exp())); BuiltinResult::Handled }
|
||||
"math_ln" => { let x = pop_f64(stack); stack.push(Value::Float(x.ln())); BuiltinResult::Handled }
|
||||
"math_log2" => { let x = pop_f64(stack); stack.push(Value::Float(x.log2())); BuiltinResult::Handled }
|
||||
"math_log10"=> { let x = pop_f64(stack); stack.push(Value::Float(x.log10()));BuiltinResult::Handled }
|
||||
"math_pi" => { stack.push(Value::Float(std::f64::consts::PI)); BuiltinResult::Handled }
|
||||
"math_e" => { stack.push(Value::Float(std::f64::consts::E)); BuiltinResult::Handled }
|
||||
"math_atan2"=> {
|
||||
let y = pop_f64(stack);
|
||||
let x = pop_f64(stack);
|
||||
stack.push(Value::Float(x.atan2(y)));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"math_mod" => {
|
||||
let b = pop_f64(stack);
|
||||
let a = pop_f64(stack);
|
||||
stack.push(Value::Float(a % b));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
|
||||
// ── Crypto / ID ───────────────────────────────────────────────────────
|
||||
|
||||
"uuid_new" | "uuid_v4" | "crypto_uuid" => {
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
stack.push(Value::Str(id));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"blake3_hash" | "crypto_hash_blake3" => {
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let hash = blake3::hash(s.as_bytes());
|
||||
stack.push(Value::Str(hash.to_hex().to_string()));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"hash_sha256" | "crypto_hash_sha256" => {
|
||||
// Use blake3 as a stable hash (sha2 not linked in elvm).
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let hash = blake3::hash(s.as_bytes());
|
||||
stack.push(Value::Str(hash.to_hex().to_string()));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"base64_encode" => {
|
||||
use base64::Engine;
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
stack.push(Value::Str(base64::engine::general_purpose::STANDARD.encode(s.as_bytes())));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"base64_decode" => {
|
||||
use base64::Engine;
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let decoded = base64::engine::general_purpose::STANDARD
|
||||
.decode(s.as_bytes())
|
||||
.map(|b| String::from_utf8_lossy(&b).to_string())
|
||||
.unwrap_or_default();
|
||||
stack.push(Value::Str(decoded));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
|
||||
// ── Filesystem ────────────────────────────────────────────────────────
|
||||
|
||||
"fs_read" => {
|
||||
let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } };
|
||||
let val = std::fs::read_to_string(&path).map(Value::Str).unwrap_or(Value::Nil);
|
||||
stack.push(val);
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"fs_write" => {
|
||||
let content = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let ok = std::fs::write(&path, &content).is_ok();
|
||||
stack.push(Value::Bool(ok));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"fs_exists" => {
|
||||
let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
stack.push(Value::Bool(std::path::Path::new(&path).exists()));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"fs_mkdir" => {
|
||||
let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let ok = std::fs::create_dir_all(&path).is_ok();
|
||||
stack.push(Value::Bool(ok));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"fs_list" => {
|
||||
let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let entries = std::fs::read_dir(&path)
|
||||
.map(|rd| rd.filter_map(|e| e.ok().and_then(|e| e.file_name().into_string().ok().map(Value::Str))).collect::<Vec<_>>())
|
||||
.unwrap_or_default();
|
||||
stack.push(Value::List(entries));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"fs_append" => {
|
||||
use std::io::Write;
|
||||
let content = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let ok = std::fs::OpenOptions::new().create(true).append(true).open(&path)
|
||||
.and_then(|mut f| f.write_all(content.as_bytes()))
|
||||
.is_ok();
|
||||
stack.push(Value::Bool(ok));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"fs_remove" => {
|
||||
let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let p = std::path::Path::new(&path);
|
||||
let ok = if p.is_dir() { std::fs::remove_dir(p).is_ok() } else { std::fs::remove_file(p).is_ok() };
|
||||
stack.push(Value::Bool(ok));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"fs_is_dir" => {
|
||||
let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
stack.push(Value::Bool(std::path::Path::new(&path).is_dir()));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"path_join" => {
|
||||
let name = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let base = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let joined = std::path::Path::new(&base).join(&name);
|
||||
stack.push(Value::Str(joined.to_string_lossy().to_string()));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"path_parent" => {
|
||||
let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let parent = std::path::Path::new(&path).parent()
|
||||
.map(|p| Value::Str(p.to_string_lossy().to_string()))
|
||||
.unwrap_or(Value::Nil);
|
||||
stack.push(parent);
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"fs_list_recursive" => {
|
||||
let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let skip_dirs = ["node_modules", ".git", ".nc", "target", "__pycache__", ".el"];
|
||||
let mut files = Vec::new();
|
||||
if let Ok(walker) = walkdir::WalkDir::new(&path).into_iter().collect::<Result<Vec<_>, _>>() {
|
||||
for entry in walker {
|
||||
let should_skip = entry.path().components().any(|c| {
|
||||
let s = c.as_os_str().to_string_lossy();
|
||||
skip_dirs.iter().any(|d| s == *d)
|
||||
});
|
||||
if should_skip && entry.path() != std::path::Path::new(&path) { continue; }
|
||||
if entry.file_type().is_file() {
|
||||
files.push(Value::Str(entry.path().to_string_lossy().to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
stack.push(Value::List(files));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
|
||||
// ── Global state ──────────────────────────────────────────────────────
|
||||
|
||||
"state_set" => {
|
||||
let value = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let key = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
state.insert(key, value);
|
||||
stack.push(Value::Bool(true));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"state_get" => {
|
||||
let key = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let val = state.get(&key).cloned().map(Value::Str).unwrap_or(Value::Nil);
|
||||
stack.push(val);
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"state_del" => {
|
||||
let key = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
state.remove(&key);
|
||||
stack.push(Value::Bool(true));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"state_keys" => {
|
||||
let keys: Vec<Value> = state.keys().cloned().map(Value::Str).collect();
|
||||
stack.push(Value::List(keys));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
|
||||
// ── HTTP client ───────────────────────────────────────────────────────
|
||||
|
||||
"http_get" => {
|
||||
let url = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let result = reqwest::blocking::get(&url).and_then(|r| r.text())
|
||||
.unwrap_or_else(|e| format!("{{\"error\":\"{e}}}" ));
|
||||
stack.push(Value::Str(result));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"http_post" => {
|
||||
let body = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let url = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let result = reqwest::blocking::Client::new()
|
||||
.post(&url)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(body)
|
||||
.send()
|
||||
.and_then(|r| r.text())
|
||||
.unwrap_or_else(|e| format!("{{\"error\":\"{e}}}" ));
|
||||
stack.push(Value::Str(result));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"http_post_form_auth" => {
|
||||
// args: url (bottom), auth_value, body (top)
|
||||
let body = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let auth = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let url = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
let result = reqwest::blocking::Client::new()
|
||||
.post(&url)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.header("Authorization", format!("Bearer {auth}"))
|
||||
.body(body)
|
||||
.send()
|
||||
.and_then(|r| r.text())
|
||||
.unwrap_or_else(|e| format!("{{\"error\":\"{e}\"}}"));
|
||||
stack.push(Value::Str(result));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
|
||||
// ── App context (no-op stubs — app block not parsed by elvm) ─────────
|
||||
|
||||
"env_get" => {
|
||||
let key = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
stack.push(Value::Str(std::env::var(&key).unwrap_or_default()));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"ctx_env" => { stack.push(Value::Str(std::env::var("NEURON_ENV").unwrap_or_else(|_| "dev".to_string()))); BuiltinResult::Handled }
|
||||
"ctx_service" => { stack.push(Value::Str(String::new())); BuiltinResult::Handled }
|
||||
"ctx_version" => { stack.push(Value::Str(String::new())); BuiltinResult::Handled }
|
||||
"ctx_instance"=> { stack.push(Value::Str(uuid::Uuid::new_v4().to_string())); BuiltinResult::Handled }
|
||||
"config" => { let _ = stack.pop(); stack.push(Value::Str(String::new())); BuiltinResult::Handled }
|
||||
"secret" => { let _ = stack.pop(); stack.push(Value::Str(String::new())); BuiltinResult::Handled }
|
||||
"flag" => { let _ = stack.pop(); stack.push(Value::Bool(false)); BuiltinResult::Handled }
|
||||
|
||||
// ── Color / formatting (terminal output) ─────────────────────────────
|
||||
|
||||
"color_bold" => {
|
||||
let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
stack.push(Value::Str(format!("\x1b[1m{s}\x1b[0m")));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
|
||||
// ── http_serve (stub — available but note elvm doesn't have the tiny_http dep) ──
|
||||
|
||||
"http_serve" => {
|
||||
// elvm does not bundle the HTTP server. Emit a helpful message.
|
||||
let _ = stack.pop();
|
||||
eprintln!("elvm: http_serve is not available in the standalone elvm binary. Use 'el run' for HTTP server programs.");
|
||||
stack.push(Value::Nil);
|
||||
BuiltinResult::HttpServe
|
||||
}
|
||||
|
||||
// ── fn_ref helper ─────────────────────────────────────────────────────
|
||||
|
||||
"fn_ref" => {
|
||||
let name_val = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() };
|
||||
stack.push(Value::Str(name_val));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
|
||||
_ => BuiltinResult::NotBuiltin,
|
||||
}
|
||||
}
|
||||
|
||||
// ── Internal helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
fn pop_f64(stack: &mut Vec<Value>) -> f64 {
|
||||
match stack.pop().unwrap_or(Value::Nil) {
|
||||
Value::Float(f) => f,
|
||||
Value::Int(n) => n as f64,
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn value_to_json(v: &Value) -> serde_json::Value {
|
||||
match v {
|
||||
Value::Int(n) => serde_json::Value::Number((*n).into()),
|
||||
Value::Float(f) => serde_json::Number::from_f64(*f).map(serde_json::Value::Number).unwrap_or(serde_json::Value::Null),
|
||||
Value::Str(s) => serde_json::Value::String(s.clone()),
|
||||
Value::Bool(b) => serde_json::Value::Bool(*b),
|
||||
Value::Nil => serde_json::Value::Null,
|
||||
Value::List(items) => serde_json::Value::Array(items.iter().map(value_to_json).collect()),
|
||||
Value::Map(pairs) => {
|
||||
let mut map = serde_json::Map::new();
|
||||
for (k, v) in pairs { map.insert(k.clone(), value_to_json(v)); }
|
||||
serde_json::Value::Object(map)
|
||||
}
|
||||
Value::ResultOk(inner) => serde_json::json!({ "ok": value_to_json(inner) }),
|
||||
Value::ResultErr(inner) => serde_json::json!({ "err": value_to_json(inner) }),
|
||||
Value::Struct { fields, .. } => {
|
||||
let mut map = serde_json::Map::new();
|
||||
for (k, v) in fields { map.insert(k.clone(), value_to_json(v)); }
|
||||
serde_json::Value::Object(map)
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user