Formatting

This commit is contained in:
Michael Schimmel
2026-02-22 02:35:06 +01:00
parent 2123f1d279
commit 329b885c4b
25 changed files with 8053 additions and 6400 deletions
+473 -334
View File
@@ -1,334 +1,473 @@
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));
env.register_constant("true", StaticType::Bool, Value::Bool(true));
env.register_constant("false", StaticType::Bool, Value::Bool(false));
}
fn register_arithmetic(env: &Environment) {
// --- Add (+) ---
let add_ty = StaticType::FunctionOverloads(vec![
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int },
Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float },
Signature { params: StaticType::Tuple(vec![StaticType::Text, StaticType::Text]), ret: StaticType::Text },
Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]), ret: StaticType::DateTime },
]);
env.register_native("+", add_ty, true, |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: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int },
Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float },
Signature { params: StaticType::Tuple(vec![StaticType::Int]), ret: StaticType::Int }, // Negation
Signature { params: StaticType::Tuple(vec![StaticType::Float]), ret: StaticType::Float },
Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::DateTime]), ret: StaticType::Int },
Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]), ret: StaticType::DateTime },
]);
env.register_native("-", sub_ty, true, |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: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int },
Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float },
]);
env.register_native("*", mul_ty, true, |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: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Float },
Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float },
]);
env.register_native("/", div_ty, true, |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: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }
));
env.register_native("//", int_div_ty, true, |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: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }
));
env.register_native("%", mod_ty, true, |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: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Bool },
Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Bool },
Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::DateTime]), ret: StaticType::Bool },
]);
// --- Greater Than (>) ---
env.register_native(">", cmp_ty.clone(), true, |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(), true, |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(), true, |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(), true, |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: StaticType::Tuple(vec![StaticType::Any, StaticType::Any]), ret: StaticType::Bool }
));
env.register_native("=", eq_ty.clone(), true, |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, true, |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: StaticType::Tuple(vec![StaticType::Bool]), ret: StaticType::Bool },
Signature { params: StaticType::Tuple(vec![StaticType::Int]), ret: StaticType::Int },
]);
env.register_native("not", not_ty, true, |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: StaticType::Tuple(vec![StaticType::Bool, StaticType::Bool]), ret: StaticType::Bool },
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int },
]);
env.register_native("and", logic_op_ty.clone(), true, |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(), true, |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(), true, |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: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }
));
env.register_native("<<", shift_ty.clone(), true, |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, true, |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,
}
});
}
use crate::ast::environment::Environment;
use crate::ast::types::{Signature, StaticType, Value};
use std::rc::Rc;
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));
env.register_constant("true", StaticType::Bool, Value::Bool(true));
env.register_constant("false", StaticType::Bool, Value::Bool(false));
}
fn register_arithmetic(env: &Environment) {
// --- Add (+) ---
let add_ty = StaticType::FunctionOverloads(vec![
Signature {
params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]),
ret: StaticType::Int,
},
Signature {
params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]),
ret: StaticType::Float,
},
Signature {
params: StaticType::Tuple(vec![StaticType::Text, StaticType::Text]),
ret: StaticType::Text,
},
Signature {
params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]),
ret: StaticType::DateTime,
},
]);
env.register_native("+", add_ty, true, |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: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]),
ret: StaticType::Int,
},
Signature {
params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]),
ret: StaticType::Float,
},
Signature {
params: StaticType::Tuple(vec![StaticType::Int]),
ret: StaticType::Int,
}, // Negation
Signature {
params: StaticType::Tuple(vec![StaticType::Float]),
ret: StaticType::Float,
},
Signature {
params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::DateTime]),
ret: StaticType::Int,
},
Signature {
params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]),
ret: StaticType::DateTime,
},
]);
env.register_native("-", sub_ty, true, |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: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]),
ret: StaticType::Int,
},
Signature {
params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]),
ret: StaticType::Float,
},
]);
env.register_native("*", mul_ty, true, |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: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]),
ret: StaticType::Float,
},
Signature {
params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]),
ret: StaticType::Float,
},
]);
env.register_native("/", div_ty, true, |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: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]),
ret: StaticType::Int,
}));
env.register_native("//", int_div_ty, true, |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: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]),
ret: StaticType::Int,
}));
env.register_native("%", mod_ty, true, |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: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]),
ret: StaticType::Bool,
},
Signature {
params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]),
ret: StaticType::Bool,
},
Signature {
params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::DateTime]),
ret: StaticType::Bool,
},
]);
// --- Greater Than (>) ---
env.register_native(">", cmp_ty.clone(), true, |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(), true, |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(), true, |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(), true, |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: StaticType::Tuple(vec![StaticType::Any, StaticType::Any]),
ret: StaticType::Bool,
}));
env.register_native("=", eq_ty.clone(), true, |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, true, |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: StaticType::Tuple(vec![StaticType::Bool]),
ret: StaticType::Bool,
},
Signature {
params: StaticType::Tuple(vec![StaticType::Int]),
ret: StaticType::Int,
},
]);
env.register_native("not", not_ty, true, |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: StaticType::Tuple(vec![StaticType::Bool, StaticType::Bool]),
ret: StaticType::Bool,
},
Signature {
params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]),
ret: StaticType::Int,
},
]);
env.register_native("and", logic_op_ty.clone(), true, |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(), true, |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(), true, |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: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]),
ret: StaticType::Int,
}));
env.register_native("<<", shift_ty.clone(), true, |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, true, |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,
}
});
}
+29 -25
View File
@@ -1,25 +1,29 @@
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: StaticType::Tuple(vec![StaticType::Text]),
ret: StaticType::DateTime
}));
env.register_native("date", date_ty, true, |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
});
}
use crate::ast::environment::Environment;
use crate::ast::types::{Signature, StaticType, Value};
use chrono::{NaiveDate, NaiveDateTime};
pub fn register(env: &Environment) {
let date_ty = StaticType::Function(Box::new(Signature {
params: StaticType::Tuple(vec![StaticType::Text]),
ret: StaticType::DateTime,
}));
env.register_native("date", date_ty, true, |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
});
}
+116 -108
View File
@@ -1,108 +1,116 @@
use std::rc::Rc;
use crate::ast::types::{Value, StaticType};
/// Looks up a specialized intrinsic function for the given operator and argument types.
/// Returns (Executable Value, Return Type) if a fast-path exists.
pub fn lookup(name: &str, args: &[StaticType]) -> Option<(Value, StaticType)> {
match (name, args) {
// --- Integer Arithmetic ---
("+", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Value::Int(a + b)
} else {
Value::Int(0) // Should not happen if type checker works
}
})),
StaticType::Int
)),
("-", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Value::Int(a - b)
} else {
Value::Int(0)
}
})),
StaticType::Int
)),
("-", [StaticType::Int, StaticType::Int, StaticType::Int]) => Some((
// Variadic optimization for 3 args (common in some Lisp dialects, though - usually is binary/unary)
// MyC's core.rs supports variadic subtraction.
Value::Function(Rc::new(|args| {
let a = match args[0] { Value::Int(i) => i, _ => 0 };
let b = match args[1] { Value::Int(i) => i, _ => 0 };
let c = match args[2] { Value::Int(i) => i, _ => 0 };
Value::Int(a - b - c)
})),
StaticType::Int
)),
("*", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Value::Int(a * b)
} else {
Value::Int(0)
}
})),
StaticType::Int
)),
// --- Integer Comparison ---
("<=", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Value::Bool(a <= b)
} else {
Value::Bool(false)
}
})),
StaticType::Bool
)),
("<", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Value::Bool(a < b)
} else {
Value::Bool(false)
}
})),
StaticType::Bool
)),
(">", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Value::Bool(a > b)
} else {
Value::Bool(false)
}
})),
StaticType::Bool
)),
(">=", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Value::Bool(a >= b)
} else {
Value::Bool(false)
}
})),
StaticType::Bool
)),
("=", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Value::Bool(a == b)
} else {
Value::Bool(false)
}
})),
StaticType::Bool
)),
// --- Constant Unary for -1 (decrement optimization) ---
// Special case: tak uses (- x 1). The specializer sees Call("-", [Int, Int]).
_ => None
}
}
use crate::ast::types::{StaticType, Value};
use std::rc::Rc;
/// Looks up a specialized intrinsic function for the given operator and argument types.
/// Returns (Executable Value, Return Type) if a fast-path exists.
pub fn lookup(name: &str, args: &[StaticType]) -> Option<(Value, StaticType)> {
match (name, args) {
// --- Integer Arithmetic ---
("+", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Value::Int(a + b)
} else {
Value::Int(0) // Should not happen if type checker works
}
})),
StaticType::Int,
)),
("-", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Value::Int(a - b)
} else {
Value::Int(0)
}
})),
StaticType::Int,
)),
("-", [StaticType::Int, StaticType::Int, StaticType::Int]) => Some((
// Variadic optimization for 3 args (common in some Lisp dialects, though - usually is binary/unary)
// MyC's core.rs supports variadic subtraction.
Value::Function(Rc::new(|args| {
let a = match args[0] {
Value::Int(i) => i,
_ => 0,
};
let b = match args[1] {
Value::Int(i) => i,
_ => 0,
};
let c = match args[2] {
Value::Int(i) => i,
_ => 0,
};
Value::Int(a - b - c)
})),
StaticType::Int,
)),
("*", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Value::Int(a * b)
} else {
Value::Int(0)
}
})),
StaticType::Int,
)),
// --- Integer Comparison ---
("<=", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Value::Bool(a <= b)
} else {
Value::Bool(false)
}
})),
StaticType::Bool,
)),
("<", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Value::Bool(a < b)
} else {
Value::Bool(false)
}
})),
StaticType::Bool,
)),
(">", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Value::Bool(a > b)
} else {
Value::Bool(false)
}
})),
StaticType::Bool,
)),
(">=", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Value::Bool(a >= b)
} else {
Value::Bool(false)
}
})),
StaticType::Bool,
)),
("=", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Value::Bool(a == b)
} else {
Value::Bool(false)
}
})),
StaticType::Bool,
)),
// --- Constant Unary for -1 (decrement optimization) ---
// Special case: tak uses (- x 1). The specializer sees Call("-", [Int, Int]).
_ => None,
}
}
+11 -11
View File
@@ -1,11 +1,11 @@
pub mod core;
pub mod datetime;
pub mod type_registry;
pub mod intrinsics;
use crate::ast::environment::Environment;
pub fn register(env: &Environment) {
core::register(env);
datetime::register(env);
}
pub mod core;
pub mod datetime;
pub mod intrinsics;
pub mod type_registry;
use crate::ast::environment::Environment;
pub fn register(env: &Environment) {
core::register(env);
datetime::register(env);
}
+276 -249
View File
@@ -1,249 +1,276 @@
use std::collections::HashMap;
use std::rc::Rc;
use std::any::{TypeId};
use crate::ast::types::{Value, StaticType, Keyword, Signature};
/// Represents a Rust type that can be exposed to the script environment.
pub trait Scriptable: 'static + Sized {
/// Returns the name of the type for debugging/AST.
fn type_name() -> &'static str;
/// Returns the static type definition (methods, properties) for the AST.
fn static_type() -> StaticType;
/// Wraps the instance into a Value (usually a Record of closures).
fn wrap(self) -> Value;
}
/// A registry for tracking registered types and their static definitions.
pub struct TypeRegistry {
known_types: HashMap<TypeId, StaticType>,
}
impl Default for TypeRegistry {
fn default() -> Self {
Self::new()
}
}
impl TypeRegistry {
pub fn new() -> Self {
Self {
known_types: HashMap::new(),
}
}
/// Registers a type T. Equivalent to `RegisterType<T>` in Delphi.
pub fn register<T: Scriptable>(&mut self) {
let id = TypeId::of::<T>();
self.known_types.entry(id).or_insert_with(T::static_type);
}
/// Resolves the static type for a Rust type.
pub fn resolve_type<T: 'static>(&self) -> StaticType {
self.known_types.get(&TypeId::of::<T>()).cloned().unwrap_or(StaticType::Any)
}
/// Creates a factory function value that can be bound in the environment.
///
/// # Arguments
/// * `factory_func` - A Rust closure that takes script arguments and returns a Result<T, String>.
pub fn create_factory<T, F>(
factory_func: F
) -> Value
where
T: Scriptable,
F: Fn(Vec<Value>) -> Result<T, String> + 'static
{
// The factory is a script function that calls the Rust factory, gets T, then wraps it.
let closure = move |args: Vec<Value>| -> Value {
match factory_func(args) {
Ok(instance) => instance.wrap(),
Err(msg) => {
// In a real system, we'd propagate this error. For now, panic or return Void.
// Delphi returns Void if nil, but raises exception on error.
// Since Value doesn't have Error, we panic to stop execution.
panic!("Runtime Error in Factory for {}: {}", T::type_name(), msg);
}
}
};
Value::Function(Rc::new(closure))
}
}
/// Helper to build the shadow record for an instance.
/// This is used inside `Scriptable::wrap`.
pub struct RecordBuilder {
fields: Vec<(Keyword, Value)>,
}
impl Default for RecordBuilder {
fn default() -> Self {
Self::new()
}
}
impl RecordBuilder {
pub fn new() -> Self {
Self {
fields: Vec::new(),
}
}
pub fn method<F>(mut self, name: &str, func: F) -> Self
where F: Fn(Vec<Value>) -> Value + 'static
{
let key = Keyword::intern(name);
self.fields.push((key, Value::Function(Rc::new(func))));
self
}
// Helper for methods that return Result (propagating panics for now)
pub fn method_checked<F>(self, name: &str, func: F) -> Self
where F: Fn(Vec<Value>) -> Result<Value, String> + 'static
{
let closure = move |args: Vec<Value>| {
match func(args) {
Ok(v) => v,
Err(e) => panic!("Method call error: {}", e),
}
};
self.method(name, closure)
}
pub fn build(self) -> Value {
let mut keys = Vec::with_capacity(self.fields.len());
let mut values = Vec::with_capacity(self.fields.len());
for (k, v) in self.fields {
keys.push(k);
values.push(v);
}
Value::make_record(keys, values)
}
}
/// Helper to build the StaticType definition.
pub struct TypeBuilder {
fields: Vec<(Keyword, StaticType)>,
}
impl Default for TypeBuilder {
fn default() -> Self {
Self::new()
}
}
impl TypeBuilder {
pub fn new() -> Self {
Self {
fields: Vec::new(),
}
}
pub fn method(mut self, name: &str, params: Vec<StaticType>, ret: StaticType) -> Self {
let key = Keyword::intern(name);
let sig = StaticType::Function(Box::new(Signature { params: StaticType::Tuple(params), ret }));
self.fields.push((key, sig));
self
}
pub fn build(self) -> StaticType {
StaticType::Record(Rc::new(self.fields))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::types::{Value, StaticType};
#[derive(Debug, Clone)]
struct Person {
name: String,
age: u32,
}
impl Scriptable for Person {
fn type_name() -> &'static str { "Person" }
fn static_type() -> StaticType {
TypeBuilder::new()
.method("greet", vec![], StaticType::Text)
.method("older", vec![], StaticType::Int)
.build()
}
fn wrap(self) -> Value {
let name = self.name.clone();
let age = self.age;
RecordBuilder::new()
.method("greet", move |_| Value::Text(format!("Hello {}", name).into()))
.method("older", move |_| Value::Int((age + 1) as i64))
.build()
}
}
#[test]
fn test_register_and_wrap() {
let mut registry = TypeRegistry::new();
registry.register::<Person>();
let p = Person { name: "Alice".to_string(), age: 30 };
let wrapped = p.wrap();
// Check static type
let st = TypeRegistry::resolve_type::<Person>(&registry);
if let StaticType::Record(fields) = st {
assert!(fields.iter().any(|(k, _)| *k == Keyword::intern("greet")));
} else {
panic!("Expected Record type");
}
// Check runtime behavior
if let Value::Record(r) = wrapped {
let idx = r.keys.iter().position(|k| *k == Keyword::intern("greet")).unwrap();
let greet_fn = &r.values[idx];
if let Value::Function(f) = greet_fn {
let res = f(vec![]);
if let Value::Text(s) = res {
assert_eq!(&*s, "Hello Alice");
} else {
panic!("Expected Text result");
}
} else {
panic!("Expected Function value for method");
}
} else {
panic!("Expected Record value");
}
}
#[test]
fn test_factory() {
let factory_val = TypeRegistry::create_factory(|args: Vec<Value>| {
if args.len() != 2 {
return Err("Expected 2 args".to_string());
}
let name = match &args[0] { Value::Text(t) => t.to_string(), _ => return Err("Name must be text".to_string()) };
let age = match &args[1] { Value::Int(i) => *i as u32, _ => return Err("Age must be int".to_string()) };
Ok(Person { name, age })
});
if let Value::Function(f) = factory_val {
let instance = f(vec![Value::Text("Bob".into()), Value::Int(40)]);
if let Value::Record(r) = instance {
let idx = r.keys.iter().position(|k| *k == Keyword::intern("greet")).unwrap();
let greet_fn = &r.values[idx];
if let Value::Function(gf) = greet_fn {
let res = gf(vec![]);
if let Value::Text(s) = res {
assert_eq!(&*s, "Hello Bob");
} else { panic!("Wrong return type"); }
}
} else { panic!("Factory should return Record"); }
} else { panic!("Factory is not a function"); }
}
}
use crate::ast::types::{Keyword, Signature, StaticType, Value};
use std::any::TypeId;
use std::collections::HashMap;
use std::rc::Rc;
/// Represents a Rust type that can be exposed to the script environment.
pub trait Scriptable: 'static + Sized {
/// Returns the name of the type for debugging/AST.
fn type_name() -> &'static str;
/// Returns the static type definition (methods, properties) for the AST.
fn static_type() -> StaticType;
/// Wraps the instance into a Value (usually a Record of closures).
fn wrap(self) -> Value;
}
/// A registry for tracking registered types and their static definitions.
pub struct TypeRegistry {
known_types: HashMap<TypeId, StaticType>,
}
impl Default for TypeRegistry {
fn default() -> Self {
Self::new()
}
}
impl TypeRegistry {
pub fn new() -> Self {
Self {
known_types: HashMap::new(),
}
}
/// Registers a type T. Equivalent to `RegisterType<T>` in Delphi.
pub fn register<T: Scriptable>(&mut self) {
let id = TypeId::of::<T>();
self.known_types.entry(id).or_insert_with(T::static_type);
}
/// Resolves the static type for a Rust type.
pub fn resolve_type<T: 'static>(&self) -> StaticType {
self.known_types
.get(&TypeId::of::<T>())
.cloned()
.unwrap_or(StaticType::Any)
}
/// Creates a factory function value that can be bound in the environment.
///
/// # Arguments
/// * `factory_func` - A Rust closure that takes script arguments and returns a Result<T, String>.
pub fn create_factory<T, F>(factory_func: F) -> Value
where
T: Scriptable,
F: Fn(Vec<Value>) -> Result<T, String> + 'static,
{
// The factory is a script function that calls the Rust factory, gets T, then wraps it.
let closure = move |args: Vec<Value>| -> Value {
match factory_func(args) {
Ok(instance) => instance.wrap(),
Err(msg) => {
// In a real system, we'd propagate this error. For now, panic or return Void.
// Delphi returns Void if nil, but raises exception on error.
// Since Value doesn't have Error, we panic to stop execution.
panic!("Runtime Error in Factory for {}: {}", T::type_name(), msg);
}
}
};
Value::Function(Rc::new(closure))
}
}
/// Helper to build the shadow record for an instance.
/// This is used inside `Scriptable::wrap`.
pub struct RecordBuilder {
fields: Vec<(Keyword, Value)>,
}
impl Default for RecordBuilder {
fn default() -> Self {
Self::new()
}
}
impl RecordBuilder {
pub fn new() -> Self {
Self { fields: Vec::new() }
}
pub fn method<F>(mut self, name: &str, func: F) -> Self
where
F: Fn(Vec<Value>) -> Value + 'static,
{
let key = Keyword::intern(name);
self.fields.push((key, Value::Function(Rc::new(func))));
self
}
// Helper for methods that return Result (propagating panics for now)
pub fn method_checked<F>(self, name: &str, func: F) -> Self
where
F: Fn(Vec<Value>) -> Result<Value, String> + 'static,
{
let closure = move |args: Vec<Value>| match func(args) {
Ok(v) => v,
Err(e) => panic!("Method call error: {}", e),
};
self.method(name, closure)
}
pub fn build(self) -> Value {
let mut keys = Vec::with_capacity(self.fields.len());
let mut values = Vec::with_capacity(self.fields.len());
for (k, v) in self.fields {
keys.push(k);
values.push(v);
}
Value::make_record(keys, values)
}
}
/// Helper to build the StaticType definition.
pub struct TypeBuilder {
fields: Vec<(Keyword, StaticType)>,
}
impl Default for TypeBuilder {
fn default() -> Self {
Self::new()
}
}
impl TypeBuilder {
pub fn new() -> Self {
Self { fields: Vec::new() }
}
pub fn method(mut self, name: &str, params: Vec<StaticType>, ret: StaticType) -> Self {
let key = Keyword::intern(name);
let sig = StaticType::Function(Box::new(Signature {
params: StaticType::Tuple(params),
ret,
}));
self.fields.push((key, sig));
self
}
pub fn build(self) -> StaticType {
StaticType::Record(Rc::new(self.fields))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::types::{StaticType, Value};
#[derive(Debug, Clone)]
struct Person {
name: String,
age: u32,
}
impl Scriptable for Person {
fn type_name() -> &'static str {
"Person"
}
fn static_type() -> StaticType {
TypeBuilder::new()
.method("greet", vec![], StaticType::Text)
.method("older", vec![], StaticType::Int)
.build()
}
fn wrap(self) -> Value {
let name = self.name.clone();
let age = self.age;
RecordBuilder::new()
.method("greet", move |_| {
Value::Text(format!("Hello {}", name).into())
})
.method("older", move |_| Value::Int((age + 1) as i64))
.build()
}
}
#[test]
fn test_register_and_wrap() {
let mut registry = TypeRegistry::new();
registry.register::<Person>();
let p = Person {
name: "Alice".to_string(),
age: 30,
};
let wrapped = p.wrap();
// Check static type
let st = TypeRegistry::resolve_type::<Person>(&registry);
if let StaticType::Record(fields) = st {
assert!(fields.iter().any(|(k, _)| *k == Keyword::intern("greet")));
} else {
panic!("Expected Record type");
}
// Check runtime behavior
if let Value::Record(r) = wrapped {
let idx = r
.keys
.iter()
.position(|k| *k == Keyword::intern("greet"))
.unwrap();
let greet_fn = &r.values[idx];
if let Value::Function(f) = greet_fn {
let res = f(vec![]);
if let Value::Text(s) = res {
assert_eq!(&*s, "Hello Alice");
} else {
panic!("Expected Text result");
}
} else {
panic!("Expected Function value for method");
}
} else {
panic!("Expected Record value");
}
}
#[test]
fn test_factory() {
let factory_val = TypeRegistry::create_factory(|args: Vec<Value>| {
if args.len() != 2 {
return Err("Expected 2 args".to_string());
}
let name = match &args[0] {
Value::Text(t) => t.to_string(),
_ => return Err("Name must be text".to_string()),
};
let age = match &args[1] {
Value::Int(i) => *i as u32,
_ => return Err("Age must be int".to_string()),
};
Ok(Person { name, age })
});
if let Value::Function(f) = factory_val {
let instance = f(vec![Value::Text("Bob".into()), Value::Int(40)]);
if let Value::Record(r) = instance {
let idx = r
.keys
.iter()
.position(|k| *k == Keyword::intern("greet"))
.unwrap();
let greet_fn = &r.values[idx];
if let Value::Function(gf) = greet_fn {
let res = gf(vec![]);
if let Value::Text(s) = res {
assert_eq!(&*s, "Hello Bob");
} else {
panic!("Wrong return type");
}
}
} else {
panic!("Factory should return Record");
}
} else {
panic!("Factory is not a function");
}
}
}