Archived
Add operators, math, time, string, collection, and HOF builtins to El
This commit is contained in:
+725
-9
@@ -1152,6 +1152,40 @@ fn run_sub_interpreter_with_stack(
|
||||
_ => Value::Nil,
|
||||
});
|
||||
}
|
||||
Bytecode::Mod => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Int(1)), stack.pop().unwrap_or(Value::Int(0)));
|
||||
stack.push(match (a, b) {
|
||||
(Value::Int(x), Value::Int(y)) => if y == 0 { Value::Int(0) } else { 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::Int(0),
|
||||
});
|
||||
}
|
||||
Bytecode::BitAnd => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0)));
|
||||
stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x & y), _ => Value::Int(0) });
|
||||
}
|
||||
Bytecode::BitOr => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0)));
|
||||
stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x | y), _ => Value::Int(0) });
|
||||
}
|
||||
Bytecode::BitXor => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0)));
|
||||
stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x ^ y), _ => Value::Int(0) });
|
||||
}
|
||||
Bytecode::BitNot => {
|
||||
let a = stack.pop().unwrap_or(Value::Int(0));
|
||||
stack.push(match a { Value::Int(x) => Value::Int(!x), _ => Value::Int(0) });
|
||||
}
|
||||
Bytecode::Shl => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0)));
|
||||
stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x << (y as u32)), _ => Value::Int(0) });
|
||||
}
|
||||
Bytecode::Shr => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0)));
|
||||
stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x >> (y as u32)), _ => Value::Int(0) });
|
||||
}
|
||||
Bytecode::Eq => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||
stack.push(Value::Bool(a == b));
|
||||
@@ -1473,6 +1507,58 @@ fn run_interpreter_with_args(instructions: &[el_compiler::Bytecode], program_arg
|
||||
_ => Value::Nil,
|
||||
});
|
||||
}
|
||||
Bytecode::Mod => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Int(1)), stack.pop().unwrap_or(Value::Int(0)));
|
||||
stack.push(match (a, b) {
|
||||
(Value::Int(x), Value::Int(y)) => if y == 0 { Value::Int(0) } else { 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::Int(0),
|
||||
});
|
||||
}
|
||||
Bytecode::BitAnd => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0)));
|
||||
stack.push(match (a, b) {
|
||||
(Value::Int(x), Value::Int(y)) => Value::Int(x & y),
|
||||
_ => Value::Int(0),
|
||||
});
|
||||
}
|
||||
Bytecode::BitOr => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0)));
|
||||
stack.push(match (a, b) {
|
||||
(Value::Int(x), Value::Int(y)) => Value::Int(x | y),
|
||||
_ => Value::Int(0),
|
||||
});
|
||||
}
|
||||
Bytecode::BitXor => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0)));
|
||||
stack.push(match (a, b) {
|
||||
(Value::Int(x), Value::Int(y)) => Value::Int(x ^ y),
|
||||
_ => Value::Int(0),
|
||||
});
|
||||
}
|
||||
Bytecode::BitNot => {
|
||||
let a = stack.pop().unwrap_or(Value::Int(0));
|
||||
stack.push(match a {
|
||||
Value::Int(x) => Value::Int(!x),
|
||||
_ => Value::Int(0),
|
||||
});
|
||||
}
|
||||
Bytecode::Shl => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0)));
|
||||
stack.push(match (a, b) {
|
||||
(Value::Int(x), Value::Int(y)) => Value::Int(x << (y as u32)),
|
||||
_ => Value::Int(0),
|
||||
});
|
||||
}
|
||||
Bytecode::Shr => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0)));
|
||||
stack.push(match (a, b) {
|
||||
(Value::Int(x), Value::Int(y)) => Value::Int(x >> (y as u32)),
|
||||
_ => Value::Int(0),
|
||||
});
|
||||
}
|
||||
Bytecode::Eq => {
|
||||
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||
stack.push(Value::Bool(a == b));
|
||||
@@ -1543,16 +1629,84 @@ fn run_interpreter_with_args(instructions: &[el_compiler::Bytecode], program_arg
|
||||
BuiltinResult::Handled | BuiltinResult::HttpServe => {}
|
||||
BuiltinResult::Exit(code) => std::process::exit(code),
|
||||
BuiltinResult::NotBuiltin => {
|
||||
// Try user-defined function
|
||||
if let Some(&entry) = fn_table.get(name.as_str()) {
|
||||
// Save current locals and return address
|
||||
let saved = locals.clone();
|
||||
call_stack.push((ip + 1, saved));
|
||||
ip = entry;
|
||||
continue;
|
||||
// Handle HOFs that need fn_table access
|
||||
match name.as_str() {
|
||||
"list_map" => {
|
||||
let fn_name = match stack.pop().unwrap_or(Value::Nil) {
|
||||
Value::Str(s) => s,
|
||||
_ => String::new(),
|
||||
};
|
||||
let list = match stack.pop().unwrap_or(Value::List(vec![])) {
|
||||
Value::List(l) => l,
|
||||
_ => vec![],
|
||||
};
|
||||
let mut result = Vec::new();
|
||||
if let Some(&entry) = fn_table.get(&fn_name) {
|
||||
for item in list {
|
||||
let mapped = run_sub_interpreter_with_stack(instructions, &fn_table, entry, vec![item]);
|
||||
result.push(mapped);
|
||||
}
|
||||
}
|
||||
stack.push(Value::List(result));
|
||||
}
|
||||
"list_filter" => {
|
||||
let fn_name = match stack.pop().unwrap_or(Value::Nil) {
|
||||
Value::Str(s) => s,
|
||||
_ => String::new(),
|
||||
};
|
||||
let list = match stack.pop().unwrap_or(Value::List(vec![])) {
|
||||
Value::List(l) => l,
|
||||
_ => vec![],
|
||||
};
|
||||
let mut result = Vec::new();
|
||||
if let Some(&entry) = fn_table.get(&fn_name) {
|
||||
for item in list {
|
||||
let keep = run_sub_interpreter_with_stack(instructions, &fn_table, entry, vec![item.clone()]);
|
||||
if matches!(keep, Value::Bool(true)) {
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
stack.push(Value::List(result));
|
||||
}
|
||||
"list_reduce" => {
|
||||
let fn_name = match stack.pop().unwrap_or(Value::Nil) {
|
||||
Value::Str(s) => s,
|
||||
_ => String::new(),
|
||||
};
|
||||
let init = stack.pop().unwrap_or(Value::Nil);
|
||||
let list = match stack.pop().unwrap_or(Value::List(vec![])) {
|
||||
Value::List(l) => l,
|
||||
_ => vec![],
|
||||
};
|
||||
let mut acc = init;
|
||||
if let Some(&entry) = fn_table.get(&fn_name) {
|
||||
for item in list {
|
||||
acc = run_sub_interpreter_with_stack(instructions, &fn_table, entry, vec![acc, item]);
|
||||
}
|
||||
}
|
||||
stack.push(acc);
|
||||
}
|
||||
"fn_ref" => {
|
||||
let name_val = match stack.pop().unwrap_or(Value::Nil) {
|
||||
Value::Str(s) => s,
|
||||
_ => String::new(),
|
||||
};
|
||||
stack.push(Value::Str(name_val));
|
||||
}
|
||||
_ => {
|
||||
// Try user-defined function
|
||||
if let Some(&entry) = fn_table.get(name.as_str()) {
|
||||
// Save current locals and return address
|
||||
let saved = locals.clone();
|
||||
call_stack.push((ip + 1, saved));
|
||||
ip = entry;
|
||||
continue;
|
||||
}
|
||||
// Unknown — push Nil
|
||||
stack.push(Value::Nil);
|
||||
}
|
||||
}
|
||||
// Unknown — push Nil
|
||||
stack.push(Value::Nil);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5368,6 +5522,480 @@ fn dispatch_builtin(
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
|
||||
// ── Math additions ────────────────────────────────────────────────────
|
||||
"math_sin" => {
|
||||
let a = stack.pop().unwrap_or(Value::Float(0.0));
|
||||
let v = match a { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 };
|
||||
stack.push(Value::Float(v.sin())); BuiltinResult::Handled
|
||||
}
|
||||
"math_cos" => {
|
||||
let a = stack.pop().unwrap_or(Value::Float(0.0));
|
||||
let v = match a { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 };
|
||||
stack.push(Value::Float(v.cos())); BuiltinResult::Handled
|
||||
}
|
||||
"math_tan" => {
|
||||
let a = stack.pop().unwrap_or(Value::Float(0.0));
|
||||
let v = match a { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 };
|
||||
stack.push(Value::Float(v.tan())); BuiltinResult::Handled
|
||||
}
|
||||
"math_asin" => {
|
||||
let a = stack.pop().unwrap_or(Value::Float(0.0));
|
||||
let v = match a { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 };
|
||||
stack.push(Value::Float(v.asin())); BuiltinResult::Handled
|
||||
}
|
||||
"math_acos" => {
|
||||
let a = stack.pop().unwrap_or(Value::Float(0.0));
|
||||
let v = match a { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 };
|
||||
stack.push(Value::Float(v.acos())); BuiltinResult::Handled
|
||||
}
|
||||
"math_atan2" => {
|
||||
let x = match stack.pop().unwrap_or(Value::Float(1.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 1.0 };
|
||||
let y = match stack.pop().unwrap_or(Value::Float(0.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 };
|
||||
stack.push(Value::Float(y.atan2(x))); BuiltinResult::Handled
|
||||
}
|
||||
"math_exp" => {
|
||||
let a = stack.pop().unwrap_or(Value::Float(0.0));
|
||||
let v = match a { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 };
|
||||
stack.push(Value::Float(v.exp())); BuiltinResult::Handled
|
||||
}
|
||||
"math_ln" => {
|
||||
let a = stack.pop().unwrap_or(Value::Float(1.0));
|
||||
let v = match a { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 1.0 };
|
||||
stack.push(Value::Float(v.ln())); BuiltinResult::Handled
|
||||
}
|
||||
"math_log2" => {
|
||||
let a = stack.pop().unwrap_or(Value::Float(1.0));
|
||||
let v = match a { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 1.0 };
|
||||
stack.push(Value::Float(v.log2())); BuiltinResult::Handled
|
||||
}
|
||||
"math_log10" => {
|
||||
let a = stack.pop().unwrap_or(Value::Float(1.0));
|
||||
let v = match a { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 1.0 };
|
||||
stack.push(Value::Float(v.log10())); BuiltinResult::Handled
|
||||
}
|
||||
"math_mod" => {
|
||||
let b = match stack.pop().unwrap_or(Value::Int(1)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 1.0 };
|
||||
let a = match stack.pop().unwrap_or(Value::Int(0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 };
|
||||
stack.push(Value::Float(a % b)); 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 }
|
||||
"int_to_float" => {
|
||||
let a = stack.pop().unwrap_or(Value::Int(0));
|
||||
let v = match a { Value::Int(i) => i as f64, Value::Float(f) => f, _ => 0.0 };
|
||||
stack.push(Value::Float(v)); BuiltinResult::Handled
|
||||
}
|
||||
"float_to_int" => {
|
||||
let a = stack.pop().unwrap_or(Value::Float(0.0));
|
||||
let v = match a { Value::Float(f) => f as i64, Value::Int(i) => i, _ => 0 };
|
||||
stack.push(Value::Int(v)); BuiltinResult::Handled
|
||||
}
|
||||
"is_nil" => {
|
||||
let a = stack.pop().unwrap_or(Value::Nil);
|
||||
stack.push(Value::Bool(matches!(a, Value::Nil))); BuiltinResult::Handled
|
||||
}
|
||||
"unwrap_or" => {
|
||||
let default_val = stack.pop().unwrap_or(Value::Nil);
|
||||
let val = stack.pop().unwrap_or(Value::Nil);
|
||||
stack.push(match val { Value::Nil => default_val, other => other });
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"decimal_add" => {
|
||||
let b = match stack.pop().unwrap_or(Value::Float(0.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 };
|
||||
let a = match stack.pop().unwrap_or(Value::Float(0.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 };
|
||||
stack.push(Value::Float(a + b)); BuiltinResult::Handled
|
||||
}
|
||||
"decimal_sub" => {
|
||||
let b = match stack.pop().unwrap_or(Value::Float(0.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 };
|
||||
let a = match stack.pop().unwrap_or(Value::Float(0.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 };
|
||||
stack.push(Value::Float(a - b)); BuiltinResult::Handled
|
||||
}
|
||||
"decimal_mul" => {
|
||||
let b = match stack.pop().unwrap_or(Value::Float(1.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 1.0 };
|
||||
let a = match stack.pop().unwrap_or(Value::Float(1.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 1.0 };
|
||||
stack.push(Value::Float(a * b)); BuiltinResult::Handled
|
||||
}
|
||||
"decimal_div" => {
|
||||
let b = match stack.pop().unwrap_or(Value::Float(1.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 1.0 };
|
||||
let a = match stack.pop().unwrap_or(Value::Float(0.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 };
|
||||
stack.push(Value::Float(if b == 0.0 { 0.0 } else { a / b })); BuiltinResult::Handled
|
||||
}
|
||||
"decimal_round" => {
|
||||
let decimals = match stack.pop().unwrap_or(Value::Int(2)) { Value::Int(i) => i, _ => 2 };
|
||||
let f = match stack.pop().unwrap_or(Value::Float(0.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 };
|
||||
let factor = 10f64.powi(decimals as i32);
|
||||
stack.push(Value::Float((f * factor).round() / factor)); BuiltinResult::Handled
|
||||
}
|
||||
|
||||
// ── String additions ──────────────────────────────────────────────────
|
||||
"str_char_at" => {
|
||||
let i = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i as usize, _ => 0 };
|
||||
let s = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() };
|
||||
let ch = s.chars().nth(i).map(|c| c.to_string()).unwrap_or_default();
|
||||
stack.push(Value::Str(ch)); BuiltinResult::Handled
|
||||
}
|
||||
"str_char_code" => {
|
||||
let i = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i as usize, _ => 0 };
|
||||
let s = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() };
|
||||
let code = s.chars().nth(i).map(|c| c as i64).unwrap_or(0);
|
||||
stack.push(Value::Int(code)); BuiltinResult::Handled
|
||||
}
|
||||
"str_from_char_code" => {
|
||||
let n = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i as u32, _ => 0 };
|
||||
let ch = char::from_u32(n).map(|c| c.to_string()).unwrap_or_default();
|
||||
stack.push(Value::Str(ch)); BuiltinResult::Handled
|
||||
}
|
||||
"str_pad_left" => {
|
||||
let pad = match stack.pop().unwrap_or(Value::Str(" ".to_string())) { Value::Str(s) => s, _ => " ".to_string() };
|
||||
let width = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i as usize, _ => 0 };
|
||||
let s = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() };
|
||||
let pad_char = pad.chars().next().unwrap_or(' ');
|
||||
let cur_len = s.chars().count();
|
||||
let result = if cur_len >= width { s } else {
|
||||
let pad_count = width - cur_len;
|
||||
let mut r = String::new();
|
||||
for _ in 0..pad_count { r.push(pad_char); }
|
||||
r.push_str(&s);
|
||||
r
|
||||
};
|
||||
stack.push(Value::Str(result)); BuiltinResult::Handled
|
||||
}
|
||||
"str_pad_right" => {
|
||||
let pad = match stack.pop().unwrap_or(Value::Str(" ".to_string())) { Value::Str(s) => s, _ => " ".to_string() };
|
||||
let width = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i as usize, _ => 0 };
|
||||
let s = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() };
|
||||
let pad_char = pad.chars().next().unwrap_or(' ');
|
||||
let cur_len = s.chars().count();
|
||||
let result = if cur_len >= width { s } else {
|
||||
let mut r = s;
|
||||
let pad_count = width - cur_len;
|
||||
for _ in 0..pad_count { r.push(pad_char); }
|
||||
r
|
||||
};
|
||||
stack.push(Value::Str(result)); BuiltinResult::Handled
|
||||
}
|
||||
"format_float" => {
|
||||
let decimals = match stack.pop().unwrap_or(Value::Int(2)) { Value::Int(i) => i, _ => 2 };
|
||||
let f = match stack.pop().unwrap_or(Value::Float(0.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 };
|
||||
stack.push(Value::Str(format!("{:.prec$}", f, prec = decimals as usize)));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"str_format" => {
|
||||
let map = match stack.pop().unwrap_or(Value::Map(vec![])) { Value::Map(m) => m, _ => vec![] };
|
||||
let template = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() };
|
||||
let mut result = template;
|
||||
for (key, val) in &map {
|
||||
let placeholder = format!("{{{}}}", key);
|
||||
let replacement = match val {
|
||||
Value::Str(s) => s.clone(),
|
||||
Value::Int(i) => i.to_string(),
|
||||
Value::Float(f) => f.to_string(),
|
||||
Value::Bool(b) => b.to_string(),
|
||||
_ => String::new(),
|
||||
};
|
||||
result = result.replace(&placeholder, &replacement);
|
||||
}
|
||||
stack.push(Value::Str(result)); BuiltinResult::Handled
|
||||
}
|
||||
|
||||
// ── List additions ────────────────────────────────────────────────────
|
||||
"list_push" => {
|
||||
let item = stack.pop().unwrap_or(Value::Nil);
|
||||
let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] };
|
||||
let mut new_list = list;
|
||||
new_list.push(item);
|
||||
stack.push(Value::List(new_list)); BuiltinResult::Handled
|
||||
}
|
||||
"list_pop" => {
|
||||
let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] };
|
||||
let mut new_list = list;
|
||||
new_list.pop();
|
||||
stack.push(Value::List(new_list)); BuiltinResult::Handled
|
||||
}
|
||||
"list_pop_front" => {
|
||||
let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] };
|
||||
let new_list = if list.is_empty() { vec![] } else { list[1..].to_vec() };
|
||||
stack.push(Value::List(new_list)); BuiltinResult::Handled
|
||||
}
|
||||
"list_peek_last" => {
|
||||
let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] };
|
||||
let val = list.last().cloned().unwrap_or(Value::Nil);
|
||||
stack.push(val); BuiltinResult::Handled
|
||||
}
|
||||
"list_range" => {
|
||||
let end = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 };
|
||||
let start = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 };
|
||||
let list: Vec<Value> = (start..end).map(Value::Int).collect();
|
||||
stack.push(Value::List(list)); BuiltinResult::Handled
|
||||
}
|
||||
"list_new" => { stack.push(Value::List(vec![])); BuiltinResult::Handled }
|
||||
"list_empty" => {
|
||||
let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] };
|
||||
stack.push(Value::Bool(list.is_empty())); BuiltinResult::Handled
|
||||
}
|
||||
"list_map" | "list_filter" | "list_reduce" | "fn_ref" => {
|
||||
// These require fn_table access; handled in NotBuiltin branch of main loop.
|
||||
BuiltinResult::NotBuiltin
|
||||
}
|
||||
|
||||
// ── Stack aliases ─────────────────────────────────────────────────────
|
||||
"stack_push" => {
|
||||
let item = stack.pop().unwrap_or(Value::Nil);
|
||||
let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] };
|
||||
let mut new_list = list; new_list.push(item);
|
||||
stack.push(Value::List(new_list)); BuiltinResult::Handled
|
||||
}
|
||||
"stack_pop" => {
|
||||
let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] };
|
||||
let mut new_list = list; new_list.pop();
|
||||
stack.push(Value::List(new_list)); BuiltinResult::Handled
|
||||
}
|
||||
"stack_peek" => {
|
||||
let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] };
|
||||
let val = list.last().cloned().unwrap_or(Value::Nil);
|
||||
stack.push(val); BuiltinResult::Handled
|
||||
}
|
||||
"stack_new" => { stack.push(Value::List(vec![])); BuiltinResult::Handled }
|
||||
|
||||
// ── Queue aliases ─────────────────────────────────────────────────────
|
||||
"queue_enqueue" => {
|
||||
let item = stack.pop().unwrap_or(Value::Nil);
|
||||
let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] };
|
||||
let mut new_list = list; new_list.push(item);
|
||||
stack.push(Value::List(new_list)); BuiltinResult::Handled
|
||||
}
|
||||
"queue_dequeue" => {
|
||||
let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] };
|
||||
let new_list = if list.is_empty() { vec![] } else { list[1..].to_vec() };
|
||||
stack.push(Value::List(new_list)); BuiltinResult::Handled
|
||||
}
|
||||
"queue_peek" => {
|
||||
let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] };
|
||||
let val = list.first().cloned().unwrap_or(Value::Nil);
|
||||
stack.push(val); BuiltinResult::Handled
|
||||
}
|
||||
"queue_new" => { stack.push(Value::List(vec![])); BuiltinResult::Handled }
|
||||
|
||||
// ── Time builtins ─────────────────────────────────────────────────────
|
||||
"time_now_utc" => {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
let ms = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_millis() as i64;
|
||||
stack.push(Value::Int(ms)); BuiltinResult::Handled
|
||||
}
|
||||
"time_to_parts" => {
|
||||
let ts = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 };
|
||||
let (year, month, day, hour, min, sec, ms) = epoch_ms_to_parts(ts);
|
||||
let days_since_epoch = ts / 86_400_000;
|
||||
let weekday_num = ((days_since_epoch % 7 + 4) % 7 + 7) % 7;
|
||||
let weekday_names = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
|
||||
let weekday_name = weekday_names[weekday_num as usize].to_string();
|
||||
let pairs = vec![
|
||||
("year".to_string(), Value::Int(year as i64)),
|
||||
("month".to_string(), Value::Int(month as i64)),
|
||||
("day".to_string(), Value::Int(day as i64)),
|
||||
("hour".to_string(), Value::Int(hour as i64)),
|
||||
("minute".to_string(), Value::Int(min as i64)),
|
||||
("second".to_string(), Value::Int(sec as i64)),
|
||||
("ms".to_string(), Value::Int(ms as i64)),
|
||||
("weekday_num".to_string(), Value::Int(weekday_num)),
|
||||
("weekday_name".to_string(), Value::Str(weekday_name)),
|
||||
("tz".to_string(), Value::Str("UTC".to_string())),
|
||||
];
|
||||
stack.push(Value::Map(pairs)); BuiltinResult::Handled
|
||||
}
|
||||
"time_from_parts" => {
|
||||
let parts = match stack.pop().unwrap_or(Value::Map(vec![])) { Value::Map(m) => m, _ => vec![] };
|
||||
let get_i = |m: &Vec<(String,Value)>, k: &str, def: i64| -> i64 {
|
||||
m.iter().find(|(key,_)| key == k).and_then(|(_,v)| if let Value::Int(i) = v { Some(*i) } else { None }).unwrap_or(def)
|
||||
};
|
||||
let year = get_i(&parts, "year", 1970) as i32;
|
||||
let month = get_i(&parts, "month", 1) as u32;
|
||||
let day = get_i(&parts, "day", 1) as u32;
|
||||
let hour = get_i(&parts, "hour", 0) as u32;
|
||||
let min = get_i(&parts, "minute", 0) as u32;
|
||||
let sec = get_i(&parts, "second", 0) as u32;
|
||||
let ms = get_i(&parts, "ms", 0) as u32;
|
||||
stack.push(Value::Int(parts_to_epoch_ms(year, month, day, hour, min, sec, ms)));
|
||||
BuiltinResult::Handled
|
||||
}
|
||||
"time_format" => {
|
||||
let fmt = match stack.pop().unwrap_or(Value::Str("ISO".to_string())) { Value::Str(s) => s, _ => "ISO".to_string() };
|
||||
let ts = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 };
|
||||
let (year, month, day, hour, min, sec, _ms) = epoch_ms_to_parts(ts);
|
||||
let result = if fmt == "ISO" || fmt == "iso" {
|
||||
format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", year, month, day, hour, min, sec)
|
||||
} else {
|
||||
fmt.replace("{YYYY}", &format!("{:04}", year))
|
||||
.replace("{MM}", &format!("{:02}", month))
|
||||
.replace("{DD}", &format!("{:02}", day))
|
||||
.replace("{HH}", &format!("{:02}", hour))
|
||||
.replace("{mm}", &format!("{:02}", min))
|
||||
.replace("{ss}", &format!("{:02}", sec))
|
||||
};
|
||||
stack.push(Value::Str(result)); BuiltinResult::Handled
|
||||
}
|
||||
"time_parse" => {
|
||||
let s = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() };
|
||||
let result = (|| -> Option<i64> {
|
||||
let s = s.trim().trim_end_matches('Z');
|
||||
let (date_part, time_part) = if let Some(t_pos) = s.find('T') {
|
||||
(&s[..t_pos], Some(&s[t_pos+1..]))
|
||||
} else { (s, None) };
|
||||
let dp: Vec<&str> = date_part.split('-').collect();
|
||||
if dp.len() < 3 { return None; }
|
||||
let year = dp[0].parse::<i32>().ok()?;
|
||||
let month = dp[1].parse::<u32>().ok()?;
|
||||
let day = dp[2].parse::<u32>().ok()?;
|
||||
let (hour, min, sec) = if let Some(tp) = time_part {
|
||||
let tp = tp.trim_end_matches('Z');
|
||||
let tp_parts: Vec<&str> = tp.split(':').collect();
|
||||
let h = tp_parts.get(0).and_then(|s| s.parse::<u32>().ok()).unwrap_or(0);
|
||||
let m = tp_parts.get(1).and_then(|s| s.parse::<u32>().ok()).unwrap_or(0);
|
||||
let sv = tp_parts.get(2).and_then(|s| s.parse::<f64>().ok()).unwrap_or(0.0);
|
||||
(h, m, sv as u32)
|
||||
} else { (0, 0, 0) };
|
||||
Some(parts_to_epoch_ms(year, month, day, hour, min, sec, 0))
|
||||
})().unwrap_or(0);
|
||||
stack.push(Value::Int(result)); BuiltinResult::Handled
|
||||
}
|
||||
"time_add" => {
|
||||
let unit = match stack.pop().unwrap_or(Value::Str("ms".to_string())) { Value::Str(s) => s, _ => "ms".to_string() };
|
||||
let amt = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 };
|
||||
let ts = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 };
|
||||
let result = match unit.as_str() {
|
||||
"ms" => ts + amt,
|
||||
"sec" | "second" | "seconds" => ts + amt * 1_000,
|
||||
"min" | "minute" | "minutes" => ts + amt * 60_000,
|
||||
"hour" | "hours" => ts + amt * 3_600_000,
|
||||
"day" | "days" => ts + amt * 86_400_000,
|
||||
"week" | "weeks" => ts + amt * 604_800_000,
|
||||
"month" | "months" => {
|
||||
let (y, m, d, h, mn, s, ms) = epoch_ms_to_parts(ts);
|
||||
let total_months = (y as i64) * 12 + (m as i64 - 1) + amt;
|
||||
let ny = (total_months / 12) as i32;
|
||||
let nm = (total_months % 12 + 1) as u32;
|
||||
let nd = d.min(days_in_month(ny, nm));
|
||||
parts_to_epoch_ms(ny, nm, nd, h, mn, s, ms)
|
||||
}
|
||||
"year" | "years" => {
|
||||
let (y, m, d, h, mn, s, ms) = epoch_ms_to_parts(ts);
|
||||
let ny = y + amt as i32;
|
||||
let nd = d.min(days_in_month(ny, m));
|
||||
parts_to_epoch_ms(ny, m, nd, h, mn, s, ms)
|
||||
}
|
||||
_ => ts + amt,
|
||||
};
|
||||
stack.push(Value::Int(result)); BuiltinResult::Handled
|
||||
}
|
||||
"time_diff" => {
|
||||
let unit = match stack.pop().unwrap_or(Value::Str("ms".to_string())) { Value::Str(s) => s, _ => "ms".to_string() };
|
||||
let ts2 = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 };
|
||||
let ts1 = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 };
|
||||
let diff_ms = ts2 - ts1;
|
||||
let result = match unit.as_str() {
|
||||
"ms" => diff_ms,
|
||||
"sec" | "second" | "seconds" => diff_ms / 1_000,
|
||||
"min" | "minute" | "minutes" => diff_ms / 60_000,
|
||||
"hour" | "hours" => diff_ms / 3_600_000,
|
||||
"day" | "days" => diff_ms / 86_400_000,
|
||||
"week" | "weeks" => diff_ms / 604_800_000,
|
||||
_ => diff_ms,
|
||||
};
|
||||
stack.push(Value::Int(result)); BuiltinResult::Handled
|
||||
}
|
||||
"time_start_of" => {
|
||||
let unit = match stack.pop().unwrap_or(Value::Str("day".to_string())) { Value::Str(s) => s, _ => "day".to_string() };
|
||||
let ts = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 };
|
||||
let (year, month, day, hour, _min, _sec, _ms) = epoch_ms_to_parts(ts);
|
||||
let result = match unit.as_str() {
|
||||
"minute" => { let (y,mo,d,h,mn,_,_) = epoch_ms_to_parts(ts); parts_to_epoch_ms(y,mo,d,h,mn,0,0) }
|
||||
"hour" => parts_to_epoch_ms(year, month, day, hour, 0, 0, 0),
|
||||
"day" => parts_to_epoch_ms(year, month, day, 0, 0, 0, 0),
|
||||
"week" => {
|
||||
let days_since_epoch = ts / 86_400_000;
|
||||
let weekday = ((days_since_epoch % 7 + 4) % 7 + 7) % 7;
|
||||
let monday_offset = if weekday == 0 { 6 } else { weekday - 1 };
|
||||
(days_since_epoch - monday_offset) * 86_400_000
|
||||
}
|
||||
"month" => parts_to_epoch_ms(year, month, 1, 0, 0, 0, 0),
|
||||
"year" => parts_to_epoch_ms(year, 1, 1, 0, 0, 0, 0),
|
||||
_ => ts,
|
||||
};
|
||||
stack.push(Value::Int(result)); BuiltinResult::Handled
|
||||
}
|
||||
"time_tz_offset" => {
|
||||
let tz = match stack.pop().unwrap_or(Value::Str("UTC".to_string())) { Value::Str(s) => s, _ => "UTC".to_string() };
|
||||
let offset: i64 = match tz.as_str() {
|
||||
"UTC" | "GMT" => 0,
|
||||
"EST" | "America/New_York" => -300,
|
||||
"CST" | "America/Chicago" => -360,
|
||||
"MST" | "America/Denver" => -420,
|
||||
"PST" | "America/Los_Angeles" => -480,
|
||||
"EDT" => -240, "CDT" => -300, "MDT" => -360, "PDT" => -420,
|
||||
"CET" => 60, "CEST" => 120, "EET" => 120, "EEST" => 180,
|
||||
"IST" => 330, "JST" => 540, "KST" => 540, "CST_CHINA" => 480,
|
||||
"AEST" => 600, "AEDT" => 660, "NZST" => 720, "BRT" => -180, "ART" => -180,
|
||||
_ => 0,
|
||||
};
|
||||
stack.push(Value::Int(offset)); BuiltinResult::Handled
|
||||
}
|
||||
"time_to_tz" => {
|
||||
let tz = match stack.pop().unwrap_or(Value::Str("UTC".to_string())) { Value::Str(s) => s, _ => "UTC".to_string() };
|
||||
let ts = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 };
|
||||
let offset_minutes: i64 = match tz.as_str() {
|
||||
"UTC" | "GMT" => 0,
|
||||
"EST" | "America/New_York" => -300,
|
||||
"CST" | "America/Chicago" => -360,
|
||||
"MST" | "America/Denver" => -420,
|
||||
"PST" | "America/Los_Angeles" => -480,
|
||||
"EDT" => -240, "CDT" => -300, "MDT" => -360, "PDT" => -420,
|
||||
"CET" => 60, "CEST" => 120, "IST" => 330, "JST" => 540,
|
||||
"CST_CHINA" => 480, "AEST" => 600, "BRT" => -180,
|
||||
_ => 0,
|
||||
};
|
||||
let local_ms = ts + offset_minutes * 60_000;
|
||||
let (year, month, day, hour, min, sec, ms) = epoch_ms_to_parts(local_ms);
|
||||
let days_since_epoch = local_ms / 86_400_000;
|
||||
let weekday_num = ((days_since_epoch % 7 + 4) % 7 + 7) % 7;
|
||||
let weekday_names = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
|
||||
let weekday_name = weekday_names[weekday_num as usize].to_string();
|
||||
let pairs = vec![
|
||||
("year".to_string(), Value::Int(year as i64)),
|
||||
("month".to_string(), Value::Int(month as i64)),
|
||||
("day".to_string(), Value::Int(day as i64)),
|
||||
("hour".to_string(), Value::Int(hour as i64)),
|
||||
("minute".to_string(), Value::Int(min as i64)),
|
||||
("second".to_string(), Value::Int(sec as i64)),
|
||||
("ms".to_string(), Value::Int(ms as i64)),
|
||||
("weekday_num".to_string(), Value::Int(weekday_num)),
|
||||
("weekday_name".to_string(), Value::Str(weekday_name)),
|
||||
("offset_minutes".to_string(), Value::Int(offset_minutes)),
|
||||
("tz".to_string(), Value::Str(tz)),
|
||||
];
|
||||
stack.push(Value::Map(pairs)); BuiltinResult::Handled
|
||||
}
|
||||
|
||||
// ── Observer system ───────────────────────────────────────────────────
|
||||
"observe" => {
|
||||
let fn_name = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() };
|
||||
let key = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() };
|
||||
let handle = OBSERVER_ID_COUNTER.with(|c| { let id = c.get(); c.set(id + 1); id });
|
||||
OBSERVER_REGISTRY.with(|r| {
|
||||
r.borrow_mut().entry(key).or_default().push((handle, fn_name));
|
||||
});
|
||||
stack.push(Value::Int(handle)); BuiltinResult::Handled
|
||||
}
|
||||
"unobserve" => {
|
||||
let handle = match stack.pop().unwrap_or(Value::Int(-1)) { Value::Int(i) => i, _ => -1 };
|
||||
OBSERVER_REGISTRY.with(|r| {
|
||||
let mut registry = r.borrow_mut();
|
||||
for observers in registry.values_mut() {
|
||||
observers.retain(|(id, _)| *id != handle);
|
||||
}
|
||||
});
|
||||
stack.push(Value::Nil); BuiltinResult::Handled
|
||||
}
|
||||
|
||||
_ => BuiltinResult::NotBuiltin,
|
||||
}
|
||||
}
|
||||
@@ -5435,6 +6063,75 @@ fn is_leap(year: u64) -> bool {
|
||||
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
|
||||
}
|
||||
|
||||
fn is_leap_i32(year: i32) -> bool {
|
||||
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
|
||||
}
|
||||
|
||||
fn days_in_month(year: i32, month: u32) -> u32 {
|
||||
match month {
|
||||
1|3|5|7|8|10|12 => 31,
|
||||
4|6|9|11 => 30,
|
||||
2 => if is_leap_i32(year) { 29 } else { 28 },
|
||||
_ => 30,
|
||||
}
|
||||
}
|
||||
|
||||
fn epoch_ms_to_parts(ms: i64) -> (i32, u32, u32, u32, u32, u32, u32) {
|
||||
let secs = ms / 1000;
|
||||
let ms_frac = (ms % 1000).unsigned_abs() as u32;
|
||||
let mut days = secs / 86400;
|
||||
let mut time_of_day = secs % 86400;
|
||||
if time_of_day < 0 { time_of_day += 86400; days -= 1; }
|
||||
let secs_in_day = time_of_day as u32;
|
||||
let hour = secs_in_day / 3600;
|
||||
let minute = (secs_in_day % 3600) / 60;
|
||||
let second = secs_in_day % 60;
|
||||
let mut year = 1970i32;
|
||||
let mut remaining = days;
|
||||
loop {
|
||||
let days_in_year = if is_leap_i32(year) { 366 } else { 365 };
|
||||
if remaining < days_in_year { break; }
|
||||
remaining -= days_in_year;
|
||||
year += 1;
|
||||
}
|
||||
let leap = is_leap_i32(year);
|
||||
let month_days = [31u32, if leap { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
let mut month = 0u32;
|
||||
let mut day_of_month = remaining as u32;
|
||||
for (m, &d) in month_days.iter().enumerate() {
|
||||
if day_of_month < d { month = m as u32 + 1; break; }
|
||||
day_of_month -= d;
|
||||
}
|
||||
(year, month, day_of_month + 1, hour, minute, second, ms_frac)
|
||||
}
|
||||
|
||||
fn parts_to_epoch_ms(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32, ms: u32) -> i64 {
|
||||
let mut days: i64 = 0;
|
||||
if year >= 1970 {
|
||||
for y in 1970..year {
|
||||
days += if is_leap_i32(y) { 366 } else { 365 };
|
||||
}
|
||||
} else {
|
||||
for y in year..1970 {
|
||||
days -= if is_leap_i32(y) { 366 } else { 365 };
|
||||
}
|
||||
}
|
||||
let leap = is_leap_i32(year);
|
||||
let month_days = [31u32, if leap { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
for m in 0..(month as usize - 1) {
|
||||
days += month_days[m] as i64;
|
||||
}
|
||||
days += (day as i64) - 1;
|
||||
let secs = days * 86400 + (hour as i64) * 3600 + (min as i64) * 60 + (sec as i64);
|
||||
secs * 1000 + ms as i64
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static OBSERVER_REGISTRY: std::cell::RefCell<std::collections::HashMap<String, Vec<(i64, String)>>> =
|
||||
std::cell::RefCell::new(std::collections::HashMap::new());
|
||||
static OBSERVER_ID_COUNTER: std::cell::Cell<i64> = std::cell::Cell::new(1);
|
||||
}
|
||||
|
||||
/// 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;
|
||||
@@ -5476,6 +6173,25 @@ fn is_builtin(name: &str) -> bool {
|
||||
| "tcp_connect" | "tcp_listen" | "tcp_accept" | "tcp_send" | "tcp_recv" | "tcp_close"
|
||||
| "udp_bind" | "udp_send" | "udp_recv" | "udp_close"
|
||||
| "ws_connect" | "ws_send" | "ws_recv" | "ws_close"
|
||||
// Math additions
|
||||
| "math_sin" | "math_cos" | "math_tan" | "math_asin" | "math_acos" | "math_atan2"
|
||||
| "math_exp" | "math_ln" | "math_log2" | "math_log10" | "math_mod" | "math_pi" | "math_e"
|
||||
| "int_to_float" | "float_to_int" | "is_nil" | "unwrap_or"
|
||||
| "decimal_add" | "decimal_sub" | "decimal_mul" | "decimal_div" | "decimal_round"
|
||||
// String additions
|
||||
| "str_char_at" | "str_char_code" | "str_from_char_code"
|
||||
| "str_pad_left" | "str_pad_right" | "format_float" | "str_format"
|
||||
// List additions
|
||||
| "list_push" | "list_pop" | "list_pop_front" | "list_peek_last" | "list_range"
|
||||
| "list_new" | "list_empty"
|
||||
| "stack_push" | "stack_pop" | "stack_peek" | "stack_new"
|
||||
| "queue_enqueue" | "queue_dequeue" | "queue_peek" | "queue_new"
|
||||
| "list_map" | "list_filter" | "list_reduce" | "fn_ref"
|
||||
// Time
|
||||
| "time_now_utc" | "time_to_parts" | "time_from_parts" | "time_format" | "time_parse"
|
||||
| "time_add" | "time_diff" | "time_start_of" | "time_tz_offset" | "time_to_tz"
|
||||
// Observer
|
||||
| "observe" | "unobserve"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user