Refactor: Replace UntypedNode with SyntaxNode
This commit replaces the `UntypedNode` enum with the more accurately named `SyntaxNode`. This change is primarily for clarity and better reflects the role of these nodes as representing the structure of the source code prior to semantic analysis. The corresponding enum `UntypedKind` has also been renamed to `SyntaxKind` to maintain consistency. No functional changes are introduced by this refactoring; it is purely a renaming and organizational update.
This commit is contained in:
+509
-509
File diff suppressed because it is too large
Load Diff
+39
-39
@@ -1,39 +1,39 @@
|
||||
use crate::ast::environment::Environment;
|
||||
use crate::ast::types::{Purity, 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_fn("date", date_ty, Purity::Pure, |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 now_ty = StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Tuple(vec![]),
|
||||
ret: StaticType::DateTime,
|
||||
}));
|
||||
|
||||
env.register_native_fn("now", now_ty, Purity::SideEffectFree, |_| {
|
||||
let ts = chrono::Utc::now().timestamp_millis();
|
||||
Value::DateTime(ts)
|
||||
});
|
||||
}
|
||||
use crate::ast::environment::Environment;
|
||||
use crate::ast::types::{Purity, 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_fn("date", date_ty, Purity::Pure, |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 now_ty = StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Tuple(vec![]),
|
||||
ret: StaticType::DateTime,
|
||||
}));
|
||||
|
||||
env.register_native_fn("now", now_ty, Purity::SideEffectFree, |_| {
|
||||
let ts = chrono::Utc::now().timestamp_millis();
|
||||
Value::DateTime(ts)
|
||||
});
|
||||
}
|
||||
|
||||
+115
-115
@@ -1,115 +1,115 @@
|
||||
use crate::ast::types::{Purity, StaticType, Value};
|
||||
|
||||
/// 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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::{Purity, StaticType, Value};
|
||||
|
||||
/// 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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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,
|
||||
}
|
||||
}
|
||||
|
||||
+176
-176
@@ -1,176 +1,176 @@
|
||||
use crate::ast::environment::Environment;
|
||||
use crate::ast::types::{Purity, Signature, StaticType, Value};
|
||||
use std::f64::consts;
|
||||
|
||||
pub fn register(env: &Environment) {
|
||||
// Constants
|
||||
env.register_constant("PI", StaticType::Float, Value::Float(consts::PI));
|
||||
env.register_constant("E", StaticType::Float, Value::Float(consts::E));
|
||||
|
||||
// Helper to get f64 from Value (Int or Float)
|
||||
fn to_f64(v: &Value) -> Option<f64> {
|
||||
match v {
|
||||
Value::Float(f) => Some(*f),
|
||||
Value::Int(i) => Some(*i as f64),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// Unary functions (float -> float)
|
||||
let register_unary = |name: &'static str, f: fn(f64) -> f64| {
|
||||
let ty = StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Tuple(vec![StaticType::Float]),
|
||||
ret: StaticType::Float,
|
||||
}));
|
||||
env.register_native_fn(name, ty, Purity::Pure, move |args| {
|
||||
if let Some(x) = to_f64(&args[0]) {
|
||||
Value::Float(f(x))
|
||||
} else {
|
||||
Value::Void
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Unary functions (float -> int)
|
||||
let register_to_int = |name: &'static str, f: fn(f64) -> f64| {
|
||||
let ty = StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Tuple(vec![StaticType::Float]),
|
||||
ret: StaticType::Int,
|
||||
}));
|
||||
env.register_native_fn(name, ty, Purity::Pure, move |args| {
|
||||
if let Some(x) = to_f64(&args[0]) {
|
||||
Value::Int(f(x) as i64)
|
||||
} else {
|
||||
Value::Void
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
register_unary("sqrt", f64::sqrt);
|
||||
register_unary("sin", f64::sin);
|
||||
register_unary("cos", f64::cos);
|
||||
register_unary("tan", f64::tan);
|
||||
register_unary("asin", f64::asin);
|
||||
register_unary("acos", f64::acos);
|
||||
register_unary("atan", f64::atan);
|
||||
register_unary("exp", f64::exp);
|
||||
register_unary("ln", f64::ln);
|
||||
register_unary("log10", f64::log10);
|
||||
register_unary("fract", f64::fract);
|
||||
|
||||
// Rounding functions returning Int
|
||||
register_to_int("round", f64::round);
|
||||
register_to_int("floor", f64::floor);
|
||||
register_to_int("ceil", f64::ceil);
|
||||
register_to_int("trunc", f64::trunc);
|
||||
|
||||
// Binary functions (float, float -> float)
|
||||
let register_binary = |name: &'static str, f: fn(f64, f64) -> f64| {
|
||||
let ty = StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]),
|
||||
ret: StaticType::Float,
|
||||
}));
|
||||
env.register_native_fn(name, ty, Purity::Pure, move |args| {
|
||||
if let (Some(a), Some(b)) = (to_f64(&args[0]), to_f64(&args[1])) {
|
||||
Value::Float(f(a, b))
|
||||
} else {
|
||||
Value::Void
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
register_binary("atan2", f64::atan2);
|
||||
register_binary("pow", f64::powf);
|
||||
register_binary("log", f64::log);
|
||||
|
||||
// Specialized abs to handle Int -> Int, Float -> Float
|
||||
let abs_ty = StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Tuple(vec![StaticType::Any]),
|
||||
ret: StaticType::Any,
|
||||
}));
|
||||
env.register_native_fn("abs", abs_ty, Purity::Pure, |args| {
|
||||
if args.len() != 1 {
|
||||
return Value::Void;
|
||||
}
|
||||
match args[0] {
|
||||
Value::Int(i) => Value::Int(i.abs()),
|
||||
Value::Float(f) => Value::Float(f.abs()),
|
||||
_ => Value::Void,
|
||||
}
|
||||
});
|
||||
|
||||
// Variadic min / max
|
||||
let variadic_ty = StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Any,
|
||||
ret: StaticType::Any,
|
||||
}));
|
||||
|
||||
env.register_native_fn("min", variadic_ty.clone(), Purity::Pure, |args| {
|
||||
if args.is_empty() {
|
||||
return Value::Void;
|
||||
}
|
||||
let mut best = args[0].clone();
|
||||
for arg in &args[1..] {
|
||||
if let Some(ord) = arg.partial_cmp(&best)
|
||||
&& ord == std::cmp::Ordering::Less
|
||||
{
|
||||
best = arg.clone();
|
||||
}
|
||||
}
|
||||
best
|
||||
});
|
||||
|
||||
env.register_native_fn("max", variadic_ty, Purity::Pure, |args| {
|
||||
if args.is_empty() {
|
||||
return Value::Void;
|
||||
}
|
||||
let mut best = args[0].clone();
|
||||
for arg in &args[1..] {
|
||||
if let Some(ord) = arg.partial_cmp(&best)
|
||||
&& ord == std::cmp::Ordering::Greater
|
||||
{
|
||||
best = arg.clone();
|
||||
}
|
||||
}
|
||||
best
|
||||
});
|
||||
|
||||
// Random generator factory (Closure-based)
|
||||
let generator_sig = Signature {
|
||||
params: StaticType::Tuple(vec![]),
|
||||
ret: StaticType::Float,
|
||||
};
|
||||
|
||||
let make_random_ty = StaticType::FunctionOverloads(vec![
|
||||
Signature {
|
||||
params: StaticType::Tuple(vec![]),
|
||||
ret: StaticType::Function(Box::new(generator_sig.clone())),
|
||||
},
|
||||
Signature {
|
||||
params: StaticType::Tuple(vec![StaticType::Int]),
|
||||
ret: StaticType::Function(Box::new(generator_sig)),
|
||||
},
|
||||
]);
|
||||
|
||||
env.register_native_fn(
|
||||
"make-random",
|
||||
make_random_ty,
|
||||
Purity::SideEffectFree,
|
||||
|args| {
|
||||
let rng = if args.is_empty() {
|
||||
fastrand::Rng::new()
|
||||
} else if let Value::Int(s) = args[0] {
|
||||
fastrand::Rng::with_seed(s as u64)
|
||||
} else {
|
||||
fastrand::Rng::new()
|
||||
};
|
||||
|
||||
let prng = std::rc::Rc::new(std::cell::RefCell::new(rng));
|
||||
|
||||
Value::Function(std::rc::Rc::new(crate::ast::types::NativeFunction {
|
||||
func: std::rc::Rc::new(move |_args| Value::Float(prng.borrow_mut().f64())),
|
||||
purity: Purity::Impure,
|
||||
}))
|
||||
},
|
||||
);
|
||||
}
|
||||
use crate::ast::environment::Environment;
|
||||
use crate::ast::types::{Purity, Signature, StaticType, Value};
|
||||
use std::f64::consts;
|
||||
|
||||
pub fn register(env: &Environment) {
|
||||
// Constants
|
||||
env.register_constant("PI", StaticType::Float, Value::Float(consts::PI));
|
||||
env.register_constant("E", StaticType::Float, Value::Float(consts::E));
|
||||
|
||||
// Helper to get f64 from Value (Int or Float)
|
||||
fn to_f64(v: &Value) -> Option<f64> {
|
||||
match v {
|
||||
Value::Float(f) => Some(*f),
|
||||
Value::Int(i) => Some(*i as f64),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// Unary functions (float -> float)
|
||||
let register_unary = |name: &'static str, f: fn(f64) -> f64| {
|
||||
let ty = StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Tuple(vec![StaticType::Float]),
|
||||
ret: StaticType::Float,
|
||||
}));
|
||||
env.register_native_fn(name, ty, Purity::Pure, move |args| {
|
||||
if let Some(x) = to_f64(&args[0]) {
|
||||
Value::Float(f(x))
|
||||
} else {
|
||||
Value::Void
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Unary functions (float -> int)
|
||||
let register_to_int = |name: &'static str, f: fn(f64) -> f64| {
|
||||
let ty = StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Tuple(vec![StaticType::Float]),
|
||||
ret: StaticType::Int,
|
||||
}));
|
||||
env.register_native_fn(name, ty, Purity::Pure, move |args| {
|
||||
if let Some(x) = to_f64(&args[0]) {
|
||||
Value::Int(f(x) as i64)
|
||||
} else {
|
||||
Value::Void
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
register_unary("sqrt", f64::sqrt);
|
||||
register_unary("sin", f64::sin);
|
||||
register_unary("cos", f64::cos);
|
||||
register_unary("tan", f64::tan);
|
||||
register_unary("asin", f64::asin);
|
||||
register_unary("acos", f64::acos);
|
||||
register_unary("atan", f64::atan);
|
||||
register_unary("exp", f64::exp);
|
||||
register_unary("ln", f64::ln);
|
||||
register_unary("log10", f64::log10);
|
||||
register_unary("fract", f64::fract);
|
||||
|
||||
// Rounding functions returning Int
|
||||
register_to_int("round", f64::round);
|
||||
register_to_int("floor", f64::floor);
|
||||
register_to_int("ceil", f64::ceil);
|
||||
register_to_int("trunc", f64::trunc);
|
||||
|
||||
// Binary functions (float, float -> float)
|
||||
let register_binary = |name: &'static str, f: fn(f64, f64) -> f64| {
|
||||
let ty = StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]),
|
||||
ret: StaticType::Float,
|
||||
}));
|
||||
env.register_native_fn(name, ty, Purity::Pure, move |args| {
|
||||
if let (Some(a), Some(b)) = (to_f64(&args[0]), to_f64(&args[1])) {
|
||||
Value::Float(f(a, b))
|
||||
} else {
|
||||
Value::Void
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
register_binary("atan2", f64::atan2);
|
||||
register_binary("pow", f64::powf);
|
||||
register_binary("log", f64::log);
|
||||
|
||||
// Specialized abs to handle Int -> Int, Float -> Float
|
||||
let abs_ty = StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Tuple(vec![StaticType::Any]),
|
||||
ret: StaticType::Any,
|
||||
}));
|
||||
env.register_native_fn("abs", abs_ty, Purity::Pure, |args| {
|
||||
if args.len() != 1 {
|
||||
return Value::Void;
|
||||
}
|
||||
match args[0] {
|
||||
Value::Int(i) => Value::Int(i.abs()),
|
||||
Value::Float(f) => Value::Float(f.abs()),
|
||||
_ => Value::Void,
|
||||
}
|
||||
});
|
||||
|
||||
// Variadic min / max
|
||||
let variadic_ty = StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Any,
|
||||
ret: StaticType::Any,
|
||||
}));
|
||||
|
||||
env.register_native_fn("min", variadic_ty.clone(), Purity::Pure, |args| {
|
||||
if args.is_empty() {
|
||||
return Value::Void;
|
||||
}
|
||||
let mut best = args[0].clone();
|
||||
for arg in &args[1..] {
|
||||
if let Some(ord) = arg.partial_cmp(&best)
|
||||
&& ord == std::cmp::Ordering::Less
|
||||
{
|
||||
best = arg.clone();
|
||||
}
|
||||
}
|
||||
best
|
||||
});
|
||||
|
||||
env.register_native_fn("max", variadic_ty, Purity::Pure, |args| {
|
||||
if args.is_empty() {
|
||||
return Value::Void;
|
||||
}
|
||||
let mut best = args[0].clone();
|
||||
for arg in &args[1..] {
|
||||
if let Some(ord) = arg.partial_cmp(&best)
|
||||
&& ord == std::cmp::Ordering::Greater
|
||||
{
|
||||
best = arg.clone();
|
||||
}
|
||||
}
|
||||
best
|
||||
});
|
||||
|
||||
// Random generator factory (Closure-based)
|
||||
let generator_sig = Signature {
|
||||
params: StaticType::Tuple(vec![]),
|
||||
ret: StaticType::Float,
|
||||
};
|
||||
|
||||
let make_random_ty = StaticType::FunctionOverloads(vec![
|
||||
Signature {
|
||||
params: StaticType::Tuple(vec![]),
|
||||
ret: StaticType::Function(Box::new(generator_sig.clone())),
|
||||
},
|
||||
Signature {
|
||||
params: StaticType::Tuple(vec![StaticType::Int]),
|
||||
ret: StaticType::Function(Box::new(generator_sig)),
|
||||
},
|
||||
]);
|
||||
|
||||
env.register_native_fn(
|
||||
"make-random",
|
||||
make_random_ty,
|
||||
Purity::SideEffectFree,
|
||||
|args| {
|
||||
let rng = if args.is_empty() {
|
||||
fastrand::Rng::new()
|
||||
} else if let Value::Int(s) = args[0] {
|
||||
fastrand::Rng::with_seed(s as u64)
|
||||
} else {
|
||||
fastrand::Rng::new()
|
||||
};
|
||||
|
||||
let prng = std::rc::Rc::new(std::cell::RefCell::new(rng));
|
||||
|
||||
Value::Function(std::rc::Rc::new(crate::ast::types::NativeFunction {
|
||||
func: std::rc::Rc::new(move |_args| Value::Float(prng.borrow_mut().f64())),
|
||||
purity: Purity::Impure,
|
||||
}))
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
pub mod core;
|
||||
pub mod datetime;
|
||||
pub mod intrinsics;
|
||||
pub mod math;
|
||||
pub mod series;
|
||||
pub mod streams;
|
||||
pub mod type_registry;
|
||||
|
||||
use crate::ast::environment::Environment;
|
||||
|
||||
pub fn register(env: &Environment) {
|
||||
core::register(env);
|
||||
datetime::register(env);
|
||||
math::register(env);
|
||||
series::register(env);
|
||||
streams::register(env);
|
||||
}
|
||||
pub mod core;
|
||||
pub mod datetime;
|
||||
pub mod intrinsics;
|
||||
pub mod math;
|
||||
pub mod series;
|
||||
pub mod streams;
|
||||
pub mod type_registry;
|
||||
|
||||
use crate::ast::environment::Environment;
|
||||
|
||||
pub fn register(env: &Environment) {
|
||||
core::register(env);
|
||||
datetime::register(env);
|
||||
math::register(env);
|
||||
series::register(env);
|
||||
streams::register(env);
|
||||
}
|
||||
|
||||
+648
-648
File diff suppressed because it is too large
Load Diff
+536
-536
File diff suppressed because it is too large
Load Diff
+273
-273
@@ -1,273 +1,273 @@
|
||||
use crate::ast::types::{Keyword, Purity, Signature, StaticType, Value};
|
||||
use std::any::TypeId;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// 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(&[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: &[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::make_function(Purity::Impure, 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(&[Value]) -> Value + 'static,
|
||||
{
|
||||
let key = Keyword::intern(name);
|
||||
self.fields
|
||||
.push((key, Value::make_function(Purity::Impure, 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(&[Value]) -> Result<Value, String> + 'static,
|
||||
{
|
||||
let closure = move |args: &[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(crate::ast::types::RecordLayout::get_or_create(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>(®istry);
|
||||
if let StaticType::Record(layout) = st {
|
||||
assert!(
|
||||
layout
|
||||
.fields
|
||||
.iter()
|
||||
.any(|(k, _)| *k == Keyword::intern("greet"))
|
||||
);
|
||||
} else {
|
||||
panic!("Expected Record type");
|
||||
}
|
||||
|
||||
// Check runtime behavior
|
||||
if let Value::Record(layout, values) = wrapped {
|
||||
let idx = layout.index_of(Keyword::intern("greet")).unwrap();
|
||||
let greet_fn = &values[idx];
|
||||
|
||||
if let Value::Function(f) = greet_fn {
|
||||
let res = (f.func)(&[]);
|
||||
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: &[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.func)(&[Value::Text("Bob".into()), Value::Int(40)]);
|
||||
if let Value::Record(layout, values) = instance {
|
||||
let idx = layout.index_of(Keyword::intern("greet")).unwrap();
|
||||
let greet_fn = &values[idx];
|
||||
|
||||
if let Value::Function(gf) = greet_fn {
|
||||
let res = (gf.func)(&[]);
|
||||
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, Purity, Signature, StaticType, Value};
|
||||
use std::any::TypeId;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// 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(&[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: &[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::make_function(Purity::Impure, 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(&[Value]) -> Value + 'static,
|
||||
{
|
||||
let key = Keyword::intern(name);
|
||||
self.fields
|
||||
.push((key, Value::make_function(Purity::Impure, 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(&[Value]) -> Result<Value, String> + 'static,
|
||||
{
|
||||
let closure = move |args: &[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(crate::ast::types::RecordLayout::get_or_create(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>(®istry);
|
||||
if let StaticType::Record(layout) = st {
|
||||
assert!(
|
||||
layout
|
||||
.fields
|
||||
.iter()
|
||||
.any(|(k, _)| *k == Keyword::intern("greet"))
|
||||
);
|
||||
} else {
|
||||
panic!("Expected Record type");
|
||||
}
|
||||
|
||||
// Check runtime behavior
|
||||
if let Value::Record(layout, values) = wrapped {
|
||||
let idx = layout.index_of(Keyword::intern("greet")).unwrap();
|
||||
let greet_fn = &values[idx];
|
||||
|
||||
if let Value::Function(f) = greet_fn {
|
||||
let res = (f.func)(&[]);
|
||||
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: &[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.func)(&[Value::Text("Bob".into()), Value::Int(40)]);
|
||||
if let Value::Record(layout, values) = instance {
|
||||
let idx = layout.index_of(Keyword::intern("greet")).unwrap();
|
||||
let greet_fn = &values[idx];
|
||||
|
||||
if let Value::Function(gf) = greet_fn {
|
||||
let res = (gf.func)(&[]);
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user