Add RTL functions for arithmetic and comparisons

This commit introduces a new module `ast::rtl` to house the standard
runtime library functions for the language. It includes implementations
for arithmetic operators (+, -, *, /, //, %), logical operators (and,
or, xor, not), bitwise shifts (<<, >>), and comparison operators (=, <>,
<, >, <=, >=).

Additionally, a `date` function for parsing datetime strings has been
added. This enhances the language's capability to handle basic
mathematical and logical operations.
Add RTL functions for arithmetic and comparisons

This commit introduces the runtime library (RTL) functions for basic
arithmetic operations, logical/bitwise operations, and comparisons. It
also includes a `date` function for parsing datetime strings.
Integration tests have been updated to cover these new functionalities.
This commit is contained in:
Michael Schimmel
2026-02-19 13:47:14 +01:00
parent a5957f524b
commit 07bf59eb47
7 changed files with 428 additions and 154 deletions
+1
View File
@@ -36,6 +36,7 @@ Wichtig: zentraler Einstiegspunkt ist Delphi/Myc.Ast.Environment.pasund die dort
* Ich kann Rust noch nicht so gut. Erkläre mir, was du machst und welche Konzepte du nutzt. Ich will was dazulernen.
* Wenn clippy etwas vorschlägt, von dem du glaubst, dass es nicht stabil ist, dann überprüfe die aktuelle Version. Wahrscheinlich ist das Feature mittlerweile im trunk. Wir gehen immer davon aus, dass clippy recht hat.
* Keine Interaktion mit GIT. Kein Commit, oder ähnliches. Schlage auch nichts dergleichen vor.
## Testing
+14 -154
View File
@@ -11,6 +11,7 @@ use crate::ast::vm::{VM, TracingObserver};
use crate::ast::compiler::tco::TCO;
use crate::ast::compiler::dumper::Dumper;
use crate::ast::compiler::macros::{MacroExpander, MacroRegistry, MacroEvaluator};
use crate::ast::rtl;
pub struct Environment {
pub global_names: Rc<RefCell<HashMap<Symbol, u32>>>,
@@ -87,161 +88,20 @@ impl Environment {
values.push(Value::Function(Rc::new(func)));
}
pub fn register_constant(&self, name: &str, ty: StaticType, val: Value) {
let mut names = self.global_names.borrow_mut();
let mut types = self.global_types.borrow_mut();
let mut values = self.global_values.borrow_mut();
let idx = values.len() as u32;
names.insert(Symbol::from(name), idx);
types.insert(idx, ty);
values.push(val);
}
fn register_stdlib(&self) {
use crate::ast::types::Signature;
use chrono::{NaiveDate, NaiveDateTime};
self.register_native("date", StaticType::Function(Box::new(Signature {
params: vec![StaticType::Text],
ret: StaticType::DateTime
})), |args| {
if let Value::Text(s) = &args[0] {
// Try parse YYYY-MM-DD
if let Ok(dt) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
let ts = dt.and_hms_opt(0, 0, 0).unwrap().and_utc().timestamp_millis();
return Value::DateTime(ts);
}
// Try parse YYYY-MM-DD HH:MM:SS
if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") {
return Value::DateTime(dt.and_utc().timestamp_millis());
}
}
Value::Void
});
let plus_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float },
Signature { params: vec![StaticType::Text, StaticType::Text], ret: StaticType::Text },
Signature { params: vec![StaticType::DateTime, StaticType::Int], ret: StaticType::DateTime },
]);
self.register_native("+", plus_ty, |args| {
if args.len() == 2 {
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Int(a + b),
(Value::Float(a), Value::Float(b)) => Value::Float(a + b),
(Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 + b),
(Value::Float(a), Value::Int(b)) => Value::Float(a + *b as f64),
(Value::Text(a), Value::Text(b)) => {
let mut res = a.to_string();
res.push_str(b);
Value::Text(Rc::from(res))
},
(Value::DateTime(ts), Value::Int(ms)) => Value::DateTime(ts + ms),
_ => Value::Void,
}
} else {
let mut acc = 0.0;
for arg in args {
if let Value::Int(i) = arg { acc += i as f64; }
else if let Value::Float(f) = arg { acc += f; }
}
if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) }
}
});
let minus_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float },
Signature { params: vec![StaticType::Int], ret: StaticType::Int },
Signature { params: vec![StaticType::Float], ret: StaticType::Float },
Signature { params: vec![StaticType::DateTime, StaticType::DateTime], ret: StaticType::Int },
Signature { params: vec![StaticType::DateTime, StaticType::Int], ret: StaticType::DateTime },
]);
self.register_native("-", minus_ty, |args| {
if args.is_empty() { return Value::Void; }
if args.len() == 1 {
return match args[0] {
Value::Int(i) => Value::Int(-i),
Value::Float(f) => Value::Float(-f),
_ => Value::Void,
};
}
if args.len() == 2 {
return match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Int(a - b),
(Value::Float(a), Value::Float(b)) => Value::Float(a - b),
(Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 - b),
(Value::Float(a), Value::Int(b)) => Value::Float(a - *b as f64),
(Value::DateTime(a), Value::DateTime(b)) => Value::Int(a - b),
(Value::DateTime(a), Value::Int(b)) => Value::DateTime(a - b),
_ => Value::Void,
};
}
let mut acc = match args[0] {
Value::Int(i) => i as f64,
Value::Float(f) => f,
_ => return Value::Void,
};
for arg in &args[1..] {
if let Value::Int(i) = arg { acc -= *i as f64; }
else if let Value::Float(f) = arg { acc -= f; }
}
if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) }
});
let mult_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float },
]);
self.register_native("*", mult_ty, |args| {
if args.len() == 2 {
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Int(a * b),
(Value::Float(a), Value::Float(b)) => Value::Float(a * b),
(Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 * b),
(Value::Float(a), Value::Int(b)) => Value::Float(a * *b as f64),
_ => Value::Void,
}
} else {
let mut acc = 1.0;
for arg in args {
if let Value::Int(i) = arg { acc *= i as f64; }
else if let Value::Float(f) = arg { acc *= f; }
}
if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) }
}
});
let div_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Float },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float },
]);
self.register_native("/", div_ty, |args| {
if args.len() != 2 { return Value::Void; }
let a = match args[0] { Value::Int(i) => i as f64, Value::Float(f) => f, _ => return Value::Void };
let b = match args[1] { Value::Int(i) => i as f64, Value::Float(f) => f, _ => return Value::Void };
Value::Float(a / b)
});
let cmp_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Bool },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Bool },
Signature { params: vec![StaticType::DateTime, StaticType::DateTime], ret: StaticType::Bool },
]);
self.register_native(">", cmp_ty.clone(), |args| {
if args.len() != 2 { return Value::Void; }
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Bool(a > b),
(Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) > *b),
(Value::Float(a), Value::Int(b)) => Value::Bool(*a > (*b as f64)),
(Value::Float(a), Value::Float(b)) => Value::Bool(a > b),
(Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a > b),
_ => Value::Bool(false),
}
});
self.register_native("<", cmp_ty, |args| {
if args.len() != 2 { return Value::Void; }
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Bool(a < b),
(Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) < *b),
(Value::Float(a), Value::Int(b)) => Value::Bool(*a < (*b as f64)),
(Value::Float(a), Value::Float(b)) => Value::Bool(a < b),
(Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a < b),
_ => Value::Bool(false),
}
});
// Register all standard library functions via RTL module
rtl::register(self);
}
pub fn dump_ast(&self, source: &str) -> Result<String, String> {
+1
View File
@@ -5,3 +5,4 @@ pub mod parser;
pub mod compiler;
pub mod environment;
pub mod vm;
pub mod rtl;
+332
View File
@@ -0,0 +1,332 @@
use std::rc::Rc;
use crate::ast::types::{Value, StaticType, Signature};
use crate::ast::environment::Environment;
pub fn register(env: &Environment) {
register_constants(env);
register_arithmetic(env);
register_comparison(env);
register_logic(env);
}
fn register_constants(env: &Environment) {
// True/False are keywords or literals in parser, but could be exposed as constants too if needed.
// In Delphi RTL: CFalse, CTrue, CNaN
// We register NaN as a value, not a function.
env.register_constant("NaN", StaticType::Float, Value::Float(f64::NAN));
}
fn register_arithmetic(env: &Environment) {
// --- Add (+) ---
let add_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float },
Signature { params: vec![StaticType::Text, StaticType::Text], ret: StaticType::Text },
Signature { params: vec![StaticType::DateTime, StaticType::Int], ret: StaticType::DateTime },
]);
env.register_native("+", add_ty, |args| {
if args.len() == 2 {
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Int(a + b),
(Value::Float(a), Value::Float(b)) => Value::Float(a + b),
(Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 + b),
(Value::Float(a), Value::Int(b)) => Value::Float(a + *b as f64),
(Value::Text(a), Value::Text(b)) => {
let mut res = a.to_string();
res.push_str(b);
Value::Text(Rc::from(res))
},
(Value::DateTime(ts), Value::Int(ms)) => Value::DateTime(ts + ms),
_ => Value::Void,
}
} else {
// Variadic sum
let mut acc = 0.0;
for arg in args {
if let Value::Int(i) = arg { acc += i as f64; }
else if let Value::Float(f) = arg { acc += f; }
}
if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) }
}
});
// --- Subtract (-) ---
let sub_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float },
Signature { params: vec![StaticType::Int], ret: StaticType::Int }, // Negation
Signature { params: vec![StaticType::Float], ret: StaticType::Float },
Signature { params: vec![StaticType::DateTime, StaticType::DateTime], ret: StaticType::Int },
Signature { params: vec![StaticType::DateTime, StaticType::Int], ret: StaticType::DateTime },
]);
env.register_native("-", sub_ty, |args| {
if args.is_empty() { return Value::Void; }
if args.len() == 1 {
return match args[0] {
Value::Int(i) => Value::Int(-i),
Value::Float(f) => Value::Float(-f),
_ => Value::Void,
};
}
if args.len() == 2 {
return match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Int(a - b),
(Value::Float(a), Value::Float(b)) => Value::Float(a - b),
(Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 - b),
(Value::Float(a), Value::Int(b)) => Value::Float(a - *b as f64),
(Value::DateTime(a), Value::DateTime(b)) => Value::Int(a - b),
(Value::DateTime(a), Value::Int(b)) => Value::DateTime(a - b),
_ => Value::Void,
};
}
// Variadic sub
let mut acc = match args[0] {
Value::Int(i) => i as f64,
Value::Float(f) => f,
_ => return Value::Void,
};
for arg in &args[1..] {
if let Value::Int(i) = arg { acc -= *i as f64; }
else if let Value::Float(f) = arg { acc -= f; }
}
if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) }
});
// --- Multiply (*) ---
let mul_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float },
]);
env.register_native("*", mul_ty, |args| {
if args.len() == 2 {
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Int(a * b),
(Value::Float(a), Value::Float(b)) => Value::Float(a * b),
(Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 * b),
(Value::Float(a), Value::Int(b)) => Value::Float(a * *b as f64),
_ => Value::Void,
}
} else {
let mut acc = 1.0;
for arg in args {
if let Value::Int(i) = arg { acc *= i as f64; }
else if let Value::Float(f) = arg { acc *= f; }
}
if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) }
}
});
// --- Divide (/) ---
let div_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Float },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float },
]);
env.register_native("/", div_ty, |args| {
if args.len() != 2 { return Value::Void; }
let a = match args[0] { Value::Int(i) => i as f64, Value::Float(f) => f, _ => return Value::Void };
let b = match args[1] { Value::Int(i) => i as f64, Value::Float(f) => f, _ => return Value::Void };
if b == 0.0 { Value::Float(f64::NAN) } else { Value::Float(a / b) }
});
// --- Integer Divide (//) ---
let int_div_ty = StaticType::Function(Box::new(
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int }
));
env.register_native("//", int_div_ty, |args| {
if args.len() != 2 { return Value::Void; }
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => {
if *b == 0 { Value::Void } else { Value::Int(a / b) }
},
// Also allow float truncation? Delphi RTL says IntDivide takes DataValue and returns DataValue.
// Usually div is for integers. Let's stick to Int.
_ => Value::Void,
}
});
// --- Modulus (%) ---
let mod_ty = StaticType::Function(Box::new(
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int }
));
env.register_native("%", mod_ty, |args| {
if args.len() != 2 { return Value::Void; }
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => {
if *b == 0 { Value::Void } else { Value::Int(a % b) }
},
_ => Value::Void,
}
});
}
fn register_comparison(env: &Environment) {
let cmp_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Bool },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Bool },
Signature { params: vec![StaticType::DateTime, StaticType::DateTime], ret: StaticType::Bool },
]);
// --- Greater Than (>) ---
env.register_native(">", cmp_ty.clone(), |args| {
if args.len() != 2 { return Value::Void; }
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Bool(a > b),
(Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) > *b),
(Value::Float(a), Value::Int(b)) => Value::Bool(*a > (*b as f64)),
(Value::Float(a), Value::Float(b)) => Value::Bool(a > b),
(Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a > b),
_ => Value::Bool(false),
}
});
// --- Less Than (<) ---
env.register_native("<", cmp_ty.clone(), |args| {
if args.len() != 2 { return Value::Void; }
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Bool(a < b),
(Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) < *b),
(Value::Float(a), Value::Int(b)) => Value::Bool(*a < (*b as f64)),
(Value::Float(a), Value::Float(b)) => Value::Bool(a < b),
(Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a < b),
_ => Value::Bool(false),
}
});
// --- Greater Or Equal (>=) ---
env.register_native(">=", cmp_ty.clone(), |args| {
if args.len() != 2 { return Value::Void; }
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Bool(a >= b),
(Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) >= *b),
(Value::Float(a), Value::Int(b)) => Value::Bool(*a >= (*b as f64)),
(Value::Float(a), Value::Float(b)) => Value::Bool(a >= b),
(Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a >= b),
_ => Value::Bool(false),
}
});
// --- Less Or Equal (<=) ---
env.register_native("<=", cmp_ty.clone(), |args| {
if args.len() != 2 { return Value::Void; }
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Bool(a <= b),
(Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) <= *b),
(Value::Float(a), Value::Int(b)) => Value::Bool(*a <= (*b as f64)),
(Value::Float(a), Value::Float(b)) => Value::Bool(a <= b),
(Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a <= b),
_ => Value::Bool(false),
}
});
// --- Equal (=) ---
let eq_ty = StaticType::Function(Box::new(
Signature { params: vec![StaticType::Any, StaticType::Any], ret: StaticType::Bool }
));
env.register_native("=", eq_ty.clone(), |args| {
if args.len() != 2 { return Value::Void; }
// Simple equality check.
// Note: Floating point equality is tricky, but we follow standard behavior for now.
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Bool(a == b),
(Value::Float(a), Value::Float(b)) => Value::Bool((a - b).abs() < f64::EPSILON),
(Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64 - b).abs() < f64::EPSILON),
(Value::Float(a), Value::Int(b)) => Value::Bool((a - *b as f64).abs() < f64::EPSILON),
(Value::Text(a), Value::Text(b)) => Value::Bool(a == b),
(Value::Bool(a), Value::Bool(b)) => Value::Bool(a == b),
(Value::Keyword(a), Value::Keyword(b)) => Value::Bool(a == b),
(Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a == b),
(Value::Void, Value::Void) => Value::Bool(true),
_ => Value::Bool(false),
}
});
// --- Not Equal (<>) ---
env.register_native("<>", eq_ty, |args| {
if args.len() != 2 { return Value::Void; }
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Bool(a != b),
(Value::Float(a), Value::Float(b)) => Value::Bool((a - b).abs() >= f64::EPSILON),
(Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64 - b).abs() >= f64::EPSILON),
(Value::Float(a), Value::Int(b)) => Value::Bool((a - *b as f64).abs() >= f64::EPSILON),
(Value::Text(a), Value::Text(b)) => Value::Bool(a != b),
(Value::Bool(a), Value::Bool(b)) => Value::Bool(a != b),
(Value::Keyword(a), Value::Keyword(b)) => Value::Bool(a != b),
(Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a != b),
(Value::Void, Value::Void) => Value::Bool(false),
_ => Value::Bool(true),
}
});
}
fn register_logic(env: &Environment) {
// --- Not (not) ---
let not_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Bool], ret: StaticType::Bool },
Signature { params: vec![StaticType::Int], ret: StaticType::Int },
]);
env.register_native("not", not_ty, |args| {
if args.len() != 1 { return Value::Void; }
match &args[0] {
Value::Bool(b) => Value::Bool(!b),
Value::Int(i) => Value::Int(!i), // Bitwise NOT
_ => Value::Void,
}
});
// --- And (and) ---
let logic_op_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Bool, StaticType::Bool], ret: StaticType::Bool },
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int },
]);
env.register_native("and", logic_op_ty.clone(), |args| {
if args.len() != 2 { return Value::Void; }
match (&args[0], &args[1]) {
(Value::Bool(a), Value::Bool(b)) => Value::Bool(*a && *b),
(Value::Int(a), Value::Int(b)) => Value::Int(a & b),
_ => Value::Void,
}
});
// --- Or (or) ---
env.register_native("or", logic_op_ty.clone(), |args| {
if args.len() != 2 { return Value::Void; }
match (&args[0], &args[1]) {
(Value::Bool(a), Value::Bool(b)) => Value::Bool(*a || *b),
(Value::Int(a), Value::Int(b)) => Value::Int(a | b),
_ => Value::Void,
}
});
// --- Xor (xor) ---
env.register_native("xor", logic_op_ty.clone(), |args| {
if args.len() != 2 { return Value::Void; }
match (&args[0], &args[1]) {
(Value::Bool(a), Value::Bool(b)) => Value::Bool(*a ^ *b),
(Value::Int(a), Value::Int(b)) => Value::Int(a ^ b),
_ => Value::Void,
}
});
// --- Shift Left (<<) ---
let shift_ty = StaticType::Function(Box::new(
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int }
));
env.register_native("<<", shift_ty.clone(), |args| {
if args.len() != 2 { return Value::Void; }
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Int(a << b),
_ => Value::Void,
}
});
// --- Shift Right (>>) ---
env.register_native(">>", shift_ty, |args| {
if args.len() != 2 { return Value::Void; }
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Int(a >> b),
_ => Value::Void,
}
});
}
+25
View File
@@ -0,0 +1,25 @@
use crate::ast::types::{Value, StaticType, Signature};
use crate::ast::environment::Environment;
use chrono::{NaiveDate, NaiveDateTime};
pub fn register(env: &Environment) {
let date_ty = StaticType::Function(Box::new(Signature {
params: vec![StaticType::Text],
ret: StaticType::DateTime
}));
env.register_native("date", date_ty, |args| {
if let Value::Text(s) = &args[0] {
// Try parse YYYY-MM-DD
if let Ok(dt) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
let ts = dt.and_hms_opt(0, 0, 0).unwrap().and_utc().timestamp_millis();
return Value::DateTime(ts);
}
// Try parse YYYY-MM-DD HH:MM:SS
if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") {
return Value::DateTime(dt.and_utc().timestamp_millis());
}
}
Value::Void
});
}
+9
View File
@@ -0,0 +1,9 @@
pub mod core;
pub mod datetime;
use crate::ast::environment::Environment;
pub fn register(env: &Environment) {
core::register(env);
datetime::register(env);
}
+46
View File
@@ -152,4 +152,50 @@ mod tests {
assert!(has_const, "Logs should contain CONST(10)");
assert!(has_result, "Logs should contain result 30");
}
#[test]
fn test_rtl_operators() {
let env = Environment::new();
// --- Arithmetic ---
assert_eq!(format!("{}", env.run_script("(+ 10 20)").unwrap()), "30");
assert_eq!(format!("{}", env.run_script("(- 20 10)").unwrap()), "10");
assert_eq!(format!("{}", env.run_script("(* 10 20)").unwrap()), "200");
assert_eq!(format!("{}", env.run_script("(/ 20 10)").unwrap()), "2");
assert_eq!(format!("{}", env.run_script("(// 20 3)").unwrap()), "6"); // 20 // 3 = 6
assert_eq!(format!("{}", env.run_script("(% 20 3)").unwrap()), "2"); // 20 % 3 = 2
// --- Logic / Bitwise ---
assert_eq!(format!("{}", env.run_script("(and true false)").unwrap()), "false");
assert_eq!(format!("{}", env.run_script("(or true false)").unwrap()), "true");
assert_eq!(format!("{}", env.run_script("(xor true false)").unwrap()), "true");
assert_eq!(format!("{}", env.run_script("(not true)").unwrap()), "false");
assert_eq!(format!("{}", env.run_script("(<< 1 2)").unwrap()), "4"); // 1 << 2 = 4
assert_eq!(format!("{}", env.run_script("(>> 4 1)").unwrap()), "2"); // 4 >> 1 = 2
assert_eq!(format!("{}", env.run_script("(and 3 1)").unwrap()), "1"); // 3 & 1 = 1
// --- Comparison ---
assert_eq!(format!("{}", env.run_script("(= 10 10)").unwrap()), "true");
assert_eq!(format!("{}", env.run_script("(= 10 20)").unwrap()), "false");
assert_eq!(format!("{}", env.run_script("(<> 10 20)").unwrap()), "true");
assert_eq!(format!("{}", env.run_script("(< 10 20)").unwrap()), "true");
assert_eq!(format!("{}", env.run_script("(> 10 20)").unwrap()), "false");
assert_eq!(format!("{}", env.run_script("(<= 10 10)").unwrap()), "true");
assert_eq!(format!("{}", env.run_script("(>= 10 10)").unwrap()), "true");
// --- NaN ---
assert_eq!(format!("{}", env.run_script("NaN").unwrap()), "NaN");
}
#[test]
fn test_date_parsing() {
let env = Environment::new();
let res = env.run_script("(date \"2023-01-01\")").unwrap();
if let Value::DateTime(_) = res {
// OK
} else {
panic!("Expected DateTime, got {:?}", res);
}
}
}