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"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -265,6 +265,7 @@ fn extract_calls_from_expr(expr: &Expr, in_loop: bool, out: &mut Vec<CallInfo>)
|
||||
extract_calls_from_stmt(s, in_loop, out);
|
||||
}
|
||||
}
|
||||
Expr::UnaryBitNot(inner) => extract_calls_from_expr(inner, in_loop, out),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,6 +387,7 @@ fn extract_activate_types_expr(expr: &Expr, in_loop: bool, out: &mut Vec<String>
|
||||
extract_activate_types_stmt(s, in_loop, out);
|
||||
}
|
||||
}
|
||||
Expr::UnaryBitNot(inner) => extract_activate_types_expr(inner, in_loop, out),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,6 +451,7 @@ fn has_sealed_in_loop_expr(expr: &Expr, in_loop: bool) -> bool {
|
||||
Expr::Reason { .. } => false,
|
||||
Expr::Parallel { entries } => entries.iter().any(|(_, e)| has_sealed_in_loop_expr(e, in_loop)),
|
||||
Expr::Trace { body, .. } => body.iter().any(|s| has_sealed_in_loop_stmt(s, in_loop)),
|
||||
Expr::UnaryBitNot(inner) => has_sealed_in_loop_expr(inner, in_loop),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,13 @@ pub enum Bytecode {
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
Mod,
|
||||
BitAnd,
|
||||
BitOr,
|
||||
BitXor,
|
||||
BitNot,
|
||||
Shl,
|
||||
Shr,
|
||||
|
||||
// ── Comparison ────────────────────────────────────────────────────────────
|
||||
Eq,
|
||||
@@ -155,6 +162,13 @@ impl std::fmt::Display for Bytecode {
|
||||
Bytecode::Sub => write!(f, "SUB"),
|
||||
Bytecode::Mul => write!(f, "MUL"),
|
||||
Bytecode::Div => write!(f, "DIV"),
|
||||
Bytecode::Mod => write!(f, "MOD"),
|
||||
Bytecode::BitAnd => write!(f, "BITAND"),
|
||||
Bytecode::BitOr => write!(f, "BITOR"),
|
||||
Bytecode::BitXor => write!(f, "BITXOR"),
|
||||
Bytecode::BitNot => write!(f, "BITNOT"),
|
||||
Bytecode::Shl => write!(f, "SHL"),
|
||||
Bytecode::Shr => write!(f, "SHR"),
|
||||
Bytecode::Eq => write!(f, "EQ"),
|
||||
Bytecode::NotEq => write!(f, "NEQ"),
|
||||
Bytecode::Lt => write!(f, "LT"),
|
||||
|
||||
@@ -273,6 +273,12 @@ impl Codegen {
|
||||
BinOp::GtEq => Bytecode::GtEq,
|
||||
BinOp::And => Bytecode::And,
|
||||
BinOp::Or => Bytecode::Or,
|
||||
BinOp::Mod => Bytecode::Mod,
|
||||
BinOp::BitAnd => Bytecode::BitAnd,
|
||||
BinOp::BitOr => Bytecode::BitOr,
|
||||
BinOp::BitXor => Bytecode::BitXor,
|
||||
BinOp::Shl => Bytecode::Shl,
|
||||
BinOp::Shr => Bytecode::Shr,
|
||||
};
|
||||
self.emit(instr);
|
||||
}
|
||||
@@ -280,6 +286,10 @@ impl Codegen {
|
||||
self.gen_expr(inner)?;
|
||||
self.emit(Bytecode::Not);
|
||||
}
|
||||
Expr::UnaryBitNot(inner) => {
|
||||
self.gen_expr(inner)?;
|
||||
self.emit(Bytecode::BitNot);
|
||||
}
|
||||
Expr::Call { func, args } => {
|
||||
// Push arguments left-to-right
|
||||
for arg in args {
|
||||
@@ -326,8 +336,12 @@ impl Codegen {
|
||||
let after_else = self.current_idx();
|
||||
self.patch_jump(jump_end, after_else);
|
||||
} else {
|
||||
let after_then = self.current_idx();
|
||||
self.patch_jump(jump_false, after_then);
|
||||
// No else branch: if-without-else is a statement, not a value
|
||||
// expression. Discard the then-block's pushed value so both
|
||||
// paths leave the stack at the same height (net zero change).
|
||||
self.emit(Bytecode::Pop);
|
||||
let after_pop = self.current_idx();
|
||||
self.patch_jump(jump_false, after_pop); // false path skips Pop too
|
||||
}
|
||||
}
|
||||
Expr::Match { subject, arms } => {
|
||||
@@ -451,6 +465,15 @@ impl Codegen {
|
||||
fields: field_names,
|
||||
});
|
||||
}
|
||||
Expr::MapLiteral(pairs) => {
|
||||
// Push each key-value pair, then collect with BuildMap.
|
||||
let n = pairs.len() as u32;
|
||||
for (key_expr, val_expr) in pairs {
|
||||
self.gen_expr(key_expr)?;
|
||||
self.gen_expr(val_expr)?;
|
||||
}
|
||||
self.emit(Bytecode::BuildMap(n));
|
||||
}
|
||||
Expr::With { base, updates } => {
|
||||
// Generate base struct clone then apply updates
|
||||
self.gen_expr(base)?;
|
||||
|
||||
@@ -248,6 +248,11 @@ impl Formatter {
|
||||
self.fmt_expr(out, inner, depth);
|
||||
}
|
||||
|
||||
Expr::UnaryBitNot(inner) => {
|
||||
out.push('~');
|
||||
self.fmt_expr(out, inner, depth);
|
||||
}
|
||||
|
||||
Expr::Try(inner) => {
|
||||
self.fmt_expr(out, inner, depth);
|
||||
out.push('?');
|
||||
@@ -427,6 +432,12 @@ impl Formatter {
|
||||
BinOp::GtEq => ">=",
|
||||
BinOp::And => "&&",
|
||||
BinOp::Or => "||",
|
||||
BinOp::Mod => "%",
|
||||
BinOp::BitAnd => "&",
|
||||
BinOp::BitOr => "|",
|
||||
BinOp::BitXor => "^",
|
||||
BinOp::Shl => "<<",
|
||||
BinOp::Shr => ">>",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -139,6 +139,9 @@ impl<'src> Lexer<'src> {
|
||||
// ── Operators that may be multi-char ────────────────────────────
|
||||
'+' => Token::Plus,
|
||||
'*' => Token::Star,
|
||||
'%' => Token::Percent,
|
||||
'^' => Token::Caret,
|
||||
'~' => Token::Tilde,
|
||||
'-' => {
|
||||
if self.eat('>') {
|
||||
Token::Arrow
|
||||
@@ -164,14 +167,18 @@ impl<'src> Lexer<'src> {
|
||||
}
|
||||
}
|
||||
'<' => {
|
||||
if self.eat('=') {
|
||||
if self.eat('<') {
|
||||
Token::Shl
|
||||
} else if self.eat('=') {
|
||||
Token::LtEq
|
||||
} else {
|
||||
Token::Lt
|
||||
}
|
||||
}
|
||||
'>' => {
|
||||
if self.eat('=') {
|
||||
if self.eat('>') {
|
||||
Token::Shr
|
||||
} else if self.eat('=') {
|
||||
Token::GtEq
|
||||
} else {
|
||||
Token::Gt
|
||||
@@ -181,10 +188,7 @@ impl<'src> Lexer<'src> {
|
||||
if self.eat('&') {
|
||||
Token::And
|
||||
} else {
|
||||
return Err(LexError::new(
|
||||
LexErrorKind::UnexpectedChar('&'),
|
||||
self.span_from(start),
|
||||
));
|
||||
Token::Ampersand
|
||||
}
|
||||
}
|
||||
'|' => {
|
||||
|
||||
@@ -194,6 +194,18 @@ pub enum Token {
|
||||
Pipe,
|
||||
/// `?` — used both for Optional types and the Try operator
|
||||
QuestionMark,
|
||||
/// `%` — modulo
|
||||
Percent,
|
||||
/// `&` — bitwise AND
|
||||
Ampersand,
|
||||
/// `^` — bitwise XOR
|
||||
Caret,
|
||||
/// `~` — bitwise NOT
|
||||
Tilde,
|
||||
/// `<<` — left shift
|
||||
Shl,
|
||||
/// `>>` — right shift
|
||||
Shr,
|
||||
|
||||
// ── Special ───────────────────────────────────────────────────────────────
|
||||
Eof,
|
||||
@@ -240,6 +252,12 @@ impl std::fmt::Display for Token {
|
||||
Token::At => write!(f, "@"),
|
||||
Token::Pipe => write!(f, "|"),
|
||||
Token::QuestionMark => write!(f, "?"),
|
||||
Token::Percent => write!(f, "%"),
|
||||
Token::Ampersand => write!(f, "&"),
|
||||
Token::Caret => write!(f, "^"),
|
||||
Token::Tilde => write!(f, "~"),
|
||||
Token::Shl => write!(f, "<<"),
|
||||
Token::Shr => write!(f, ">>"),
|
||||
Token::BoolLiteral(b) => write!(f, "{b}"),
|
||||
Token::IntLiteral(n) => write!(f, "{n}"),
|
||||
Token::FloatLiteral(n) => write!(f, "{n}"),
|
||||
|
||||
@@ -84,6 +84,21 @@ pub enum BinOp {
|
||||
Add, Sub, Mul, Div,
|
||||
Eq, NotEq, Lt, Gt, LtEq, GtEq,
|
||||
And, Or,
|
||||
Mod, // %
|
||||
BitAnd, // &
|
||||
BitOr, // | (single pipe)
|
||||
BitXor, // ^
|
||||
Shl, // <<
|
||||
Shr, // >>
|
||||
}
|
||||
|
||||
// ── Unary operators ───────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum UnaryOp {
|
||||
Neg, // - (unary minus)
|
||||
Not, // ! (logical not)
|
||||
BitNot, // ~
|
||||
}
|
||||
|
||||
// ── Expressions ───────────────────────────────────────────────────────────────
|
||||
@@ -94,6 +109,7 @@ pub enum Expr {
|
||||
Ident(String),
|
||||
BinOp { op: BinOp, left: Box<Expr>, right: Box<Expr> },
|
||||
UnaryNot(Box<Expr>),
|
||||
UnaryBitNot(Box<Expr>),
|
||||
Call { func: Box<Expr>, args: Vec<Expr> },
|
||||
Block(Vec<Stmt>),
|
||||
Match { subject: Box<Expr>, arms: Vec<MatchArm> },
|
||||
|
||||
@@ -11,7 +11,7 @@ mod error;
|
||||
mod parser;
|
||||
|
||||
pub use ast::{
|
||||
BinOp, Decorator, Expr, Field, Literal, MatchArm, Param, Pattern, Program, ProtocolMethod,
|
||||
BinOp, UnaryOp, Decorator, Expr, Field, Literal, MatchArm, Param, Pattern, Program, ProtocolMethod,
|
||||
SeedStmt, Stmt, TestTarget, TypeExpr, Variant,
|
||||
};
|
||||
pub use error::{ParseError, ParseErrorKind};
|
||||
|
||||
@@ -717,6 +717,48 @@ impl Parser {
|
||||
self.parse_pipe_expr()
|
||||
}
|
||||
|
||||
fn parse_bitwise_or_expr(&mut self) -> Result<Expr, ParseError> {
|
||||
let mut left = self.parse_bitwise_xor_expr()?;
|
||||
while self.eat(&Token::Pipe) {
|
||||
let right = self.parse_bitwise_xor_expr()?;
|
||||
left = Expr::BinOp { op: BinOp::BitOr, left: Box::new(left), right: Box::new(right) };
|
||||
}
|
||||
Ok(left)
|
||||
}
|
||||
|
||||
fn parse_bitwise_xor_expr(&mut self) -> Result<Expr, ParseError> {
|
||||
let mut left = self.parse_bitwise_and_expr()?;
|
||||
while self.eat(&Token::Caret) {
|
||||
let right = self.parse_bitwise_and_expr()?;
|
||||
left = Expr::BinOp { op: BinOp::BitXor, left: Box::new(left), right: Box::new(right) };
|
||||
}
|
||||
Ok(left)
|
||||
}
|
||||
|
||||
fn parse_bitwise_and_expr(&mut self) -> Result<Expr, ParseError> {
|
||||
let mut left = self.parse_shift_expr()?;
|
||||
while self.eat(&Token::Ampersand) {
|
||||
let right = self.parse_shift_expr()?;
|
||||
left = Expr::BinOp { op: BinOp::BitAnd, left: Box::new(left), right: Box::new(right) };
|
||||
}
|
||||
Ok(left)
|
||||
}
|
||||
|
||||
fn parse_shift_expr(&mut self) -> Result<Expr, ParseError> {
|
||||
let mut left = self.parse_additive()?;
|
||||
loop {
|
||||
let op = match self.peek() {
|
||||
Token::Shl => BinOp::Shl,
|
||||
Token::Shr => BinOp::Shr,
|
||||
_ => break,
|
||||
};
|
||||
self.advance();
|
||||
let right = self.parse_additive()?;
|
||||
left = Expr::BinOp { op, left: Box::new(left), right: Box::new(right) };
|
||||
}
|
||||
Ok(left)
|
||||
}
|
||||
|
||||
/// pipe_expr = or_expr (|> ident)*
|
||||
/// `a |> f` desugars to `Call(f, [a])`
|
||||
fn parse_pipe_expr(&mut self) -> Result<Expr, ParseError> {
|
||||
@@ -766,7 +808,7 @@ impl Parser {
|
||||
}
|
||||
|
||||
fn parse_comparison(&mut self) -> Result<Expr, ParseError> {
|
||||
let mut left = self.parse_additive()?;
|
||||
let mut left = self.parse_bitwise_or_expr()?;
|
||||
loop {
|
||||
let op = match self.peek() {
|
||||
Token::Lt => BinOp::Lt,
|
||||
@@ -803,6 +845,7 @@ impl Parser {
|
||||
let op = match self.peek() {
|
||||
Token::Star => BinOp::Mul,
|
||||
Token::Slash => BinOp::Div,
|
||||
Token::Percent => BinOp::Mod,
|
||||
_ => break,
|
||||
};
|
||||
self.advance();
|
||||
@@ -817,6 +860,10 @@ impl Parser {
|
||||
let inner = self.parse_unary()?;
|
||||
return Ok(Expr::UnaryNot(Box::new(inner)));
|
||||
}
|
||||
if self.eat(&Token::Tilde) {
|
||||
let inner = self.parse_unary()?;
|
||||
return Ok(Expr::UnaryBitNot(Box::new(inner)));
|
||||
}
|
||||
self.parse_postfix()
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,28 @@ pub fn register(env: &mut TypeEnv) {
|
||||
env.register_fn("array_last", fn_type(vec![arr_int.clone()], Type::Optional(Box::new(Type::Int))));
|
||||
// array_contains([String], String) -> Bool
|
||||
env.register_fn("array_contains", fn_type(vec![arr_str, Type::String], Type::Bool));
|
||||
// New list/stack/queue builtins
|
||||
env.register_fn("list_push", fn_type(vec![arr_unk.clone(), Type::Unknown], arr_unk.clone()));
|
||||
env.register_fn("list_pop", fn_type(vec![arr_unk.clone()], arr_unk.clone()));
|
||||
env.register_fn("list_pop_front", fn_type(vec![arr_unk.clone()], arr_unk.clone()));
|
||||
env.register_fn("list_peek_last", fn_type(vec![arr_unk.clone()], Type::Unknown));
|
||||
env.register_fn("list_range", fn_type(vec![Type::Int, Type::Int], arr_unk.clone()));
|
||||
env.register_fn("list_new", fn_type(vec![], arr_unk.clone()));
|
||||
env.register_fn("list_empty", fn_type(vec![arr_unk.clone()], Type::Bool));
|
||||
env.register_fn("list_map", fn_type(vec![arr_unk.clone(), Type::String], arr_unk.clone()));
|
||||
env.register_fn("list_filter", fn_type(vec![arr_unk.clone(), Type::String], arr_unk.clone()));
|
||||
env.register_fn("list_reduce", fn_type(vec![arr_unk.clone(), Type::Unknown, Type::String], Type::Unknown));
|
||||
env.register_fn("fn_ref", fn_type(vec![Type::String], Type::String));
|
||||
// Stack
|
||||
env.register_fn("stack_push", fn_type(vec![arr_unk.clone(), Type::Unknown], arr_unk.clone()));
|
||||
env.register_fn("stack_pop", fn_type(vec![arr_unk.clone()], arr_unk.clone()));
|
||||
env.register_fn("stack_peek", fn_type(vec![arr_unk.clone()], Type::Unknown));
|
||||
env.register_fn("stack_new", fn_type(vec![], arr_unk.clone()));
|
||||
// Queue
|
||||
env.register_fn("queue_enqueue", fn_type(vec![arr_unk.clone(), Type::Unknown], arr_unk.clone()));
|
||||
env.register_fn("queue_dequeue", fn_type(vec![arr_unk.clone()], arr_unk.clone()));
|
||||
env.register_fn("queue_peek", fn_type(vec![arr_unk.clone()], Type::Unknown));
|
||||
env.register_fn("queue_new", fn_type(vec![], arr_unk));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -16,6 +16,45 @@ pub fn register(env: &mut TypeEnv) {
|
||||
env.register_fn("math_abs_int", fn_type(vec![Type::Int], Type::Int));
|
||||
env.register_fn("math_max_int", fn_type(vec![Type::Int, Type::Int], Type::Int));
|
||||
env.register_fn("math_min_int", fn_type(vec![Type::Int, Type::Int], Type::Int));
|
||||
// Trig
|
||||
env.register_fn("math_sin", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_cos", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_tan", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_asin", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_acos", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_atan2", fn_type(vec![Type::Float, Type::Float], Type::Float));
|
||||
env.register_fn("math_exp", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_ln", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_log2", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_log10", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_mod", fn_type(vec![Type::Float, Type::Float], Type::Float));
|
||||
env.register_fn("math_pi", fn_type(vec![], Type::Float));
|
||||
env.register_fn("math_e", fn_type(vec![], Type::Float));
|
||||
// Conversion
|
||||
env.register_fn("int_to_float", fn_type(vec![Type::Int], Type::Float));
|
||||
env.register_fn("float_to_int", fn_type(vec![Type::Float], Type::Int));
|
||||
env.register_fn("is_nil", fn_type(vec![Type::Unknown], Type::Bool));
|
||||
env.register_fn("unwrap_or", fn_type(vec![Type::Unknown, Type::Unknown], Type::Unknown));
|
||||
// Decimal
|
||||
env.register_fn("decimal_add", fn_type(vec![Type::Float, Type::Float], Type::Float));
|
||||
env.register_fn("decimal_sub", fn_type(vec![Type::Float, Type::Float], Type::Float));
|
||||
env.register_fn("decimal_mul", fn_type(vec![Type::Float, Type::Float], Type::Float));
|
||||
env.register_fn("decimal_div", fn_type(vec![Type::Float, Type::Float], Type::Float));
|
||||
env.register_fn("decimal_round", fn_type(vec![Type::Float, Type::Int], Type::Float));
|
||||
// Time
|
||||
env.register_fn("time_now_utc", fn_type(vec![], Type::Int));
|
||||
env.register_fn("time_to_parts", fn_type(vec![Type::Int], Type::Unknown));
|
||||
env.register_fn("time_from_parts", fn_type(vec![Type::Unknown], Type::Int));
|
||||
env.register_fn("time_format", fn_type(vec![Type::Int, Type::String], Type::String));
|
||||
env.register_fn("time_parse", fn_type(vec![Type::String], Type::Int));
|
||||
env.register_fn("time_add", fn_type(vec![Type::Int, Type::Int, Type::String], Type::Int));
|
||||
env.register_fn("time_diff", fn_type(vec![Type::Int, Type::Int, Type::String], Type::Int));
|
||||
env.register_fn("time_start_of", fn_type(vec![Type::Int, Type::String], Type::Int));
|
||||
env.register_fn("time_tz_offset", fn_type(vec![Type::String], Type::Int));
|
||||
env.register_fn("time_to_tz", fn_type(vec![Type::Int, Type::String], Type::Unknown));
|
||||
// Observer
|
||||
env.register_fn("observe", fn_type(vec![Type::String, Type::String], Type::Int));
|
||||
env.register_fn("unobserve", fn_type(vec![Type::Int], Type::Unknown));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -27,6 +27,14 @@ pub fn register(env: &mut TypeEnv) {
|
||||
env.register_fn("string_from_float", fn_type(vec![Type::Float], Type::String));
|
||||
env.register_fn("string_is_empty", fn_type(vec![Type::String], Type::Bool));
|
||||
env.register_fn("string_concat", fn_type(vec![Type::String, Type::String], Type::String));
|
||||
// New string builtins
|
||||
env.register_fn("str_char_at", fn_type(vec![Type::String, Type::Int], Type::String));
|
||||
env.register_fn("str_char_code", fn_type(vec![Type::String, Type::Int], Type::Int));
|
||||
env.register_fn("str_from_char_code", fn_type(vec![Type::Int], Type::String));
|
||||
env.register_fn("str_pad_left", fn_type(vec![Type::String, Type::Int, Type::String], Type::String));
|
||||
env.register_fn("str_pad_right", fn_type(vec![Type::String, Type::Int, Type::String], Type::String));
|
||||
env.register_fn("format_float", fn_type(vec![Type::Float, Type::Int], Type::String));
|
||||
env.register_fn("str_format", fn_type(vec![Type::String, Type::Unknown], Type::String));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -354,6 +354,30 @@ impl<'g> Evaluator<'g> {
|
||||
BinOp::GtEq => self.compare_ord(lv, rv, |o| o != std::cmp::Ordering::Less),
|
||||
BinOp::And => Ok(EvalValue::Bool(lv.as_bool() && rv.as_bool())),
|
||||
BinOp::Or => Ok(EvalValue::Bool(lv.as_bool() || rv.as_bool())),
|
||||
BinOp::Mod => match (lv, rv) {
|
||||
(EvalValue::Int(a), EvalValue::Int(b)) if b != 0 => Ok(EvalValue::Int(a % b)),
|
||||
_ => Ok(EvalValue::Int(0)),
|
||||
},
|
||||
BinOp::BitAnd => match (lv, rv) {
|
||||
(EvalValue::Int(a), EvalValue::Int(b)) => Ok(EvalValue::Int(a & b)),
|
||||
_ => Ok(EvalValue::Int(0)),
|
||||
},
|
||||
BinOp::BitOr => match (lv, rv) {
|
||||
(EvalValue::Int(a), EvalValue::Int(b)) => Ok(EvalValue::Int(a | b)),
|
||||
_ => Ok(EvalValue::Int(0)),
|
||||
},
|
||||
BinOp::BitXor => match (lv, rv) {
|
||||
(EvalValue::Int(a), EvalValue::Int(b)) => Ok(EvalValue::Int(a ^ b)),
|
||||
_ => Ok(EvalValue::Int(0)),
|
||||
},
|
||||
BinOp::Shl => match (lv, rv) {
|
||||
(EvalValue::Int(a), EvalValue::Int(b)) => Ok(EvalValue::Int(a << (b as u32))),
|
||||
_ => Ok(EvalValue::Int(0)),
|
||||
},
|
||||
BinOp::Shr => match (lv, rv) {
|
||||
(EvalValue::Int(a), EvalValue::Int(b)) => Ok(EvalValue::Int(a >> (b as u32))),
|
||||
_ => Ok(EvalValue::Int(0)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,6 +438,8 @@ pub fn expr_to_text(expr: &Expr) -> String {
|
||||
BinOp::Eq => "==", BinOp::NotEq => "!=",
|
||||
BinOp::Lt => "<", BinOp::Gt => ">", BinOp::LtEq => "<=", BinOp::GtEq => ">=",
|
||||
BinOp::And => "&&", BinOp::Or => "||",
|
||||
BinOp::Mod => "%", BinOp::BitAnd => "&", BinOp::BitOr => "|",
|
||||
BinOp::BitXor => "^", BinOp::Shl => "<<", BinOp::Shr => ">>",
|
||||
};
|
||||
format!("{} {op_str} {}", expr_to_text(left), expr_to_text(right))
|
||||
}
|
||||
|
||||
@@ -493,6 +493,10 @@ impl TypeChecker {
|
||||
Expr::Reason { .. } => Type::String,
|
||||
Expr::Parallel { .. } => Type::Unknown,
|
||||
Expr::Trace { .. } => Type::Unknown,
|
||||
Expr::UnaryBitNot(inner) => {
|
||||
self.infer_expr(inner);
|
||||
Type::Int
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,6 +553,8 @@ impl TypeChecker {
|
||||
}
|
||||
Type::Bool
|
||||
}
|
||||
BinOp::Mod | BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor
|
||||
| BinOp::Shl | BinOp::Shr => Type::Int,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user