use crate::value::LoraValue;
use lora_store::GraphStorage;
use super::expr::EvalContext;
mod bits_ns;
mod bytes_ns;
mod cast_ns;
mod crypto;
mod edge;
mod geo;
mod index_ns;
mod json_ns;
mod list;
mod map_ns;
mod math_ns;
mod node;
mod number;
mod path;
mod string_ns;
mod temporal;
mod text;
mod type_ns;
mod uuid_ns;
mod value;
mod vector_ns;
pub(super) fn dispatch<S: GraphStorage>(
name: &str,
args: &[LoraValue],
ctx: &EvalContext<'_, S>,
) -> Option<LoraValue> {
let (ns, op) = name.split_once('.')?;
match ns {
"list" => list::dispatch(op, args),
"string" => string_ns::dispatch(op, args),
"text" => text::dispatch(op, args),
"map" => map_ns::dispatch(op, args),
"number" => number::dispatch(op, args),
"math" => math_ns::dispatch(op, args),
"temporal" => temporal::dispatch(op, args),
"bytes" => bytes_ns::dispatch(op, args),
"bits" => bits_ns::dispatch(op, args),
"cast" => cast_ns::dispatch(op, args),
"crypto" => crypto::dispatch(op, args),
"uuid" => uuid_ns::dispatch(op, args),
"json" => json_ns::dispatch(op, args),
"geo" => geo::dispatch(op, args),
"vector" => vector_ns::dispatch(op, args),
"type" => type_ns::dispatch(op, args),
"node" => node::dispatch(op, args, ctx),
"edge" => edge::dispatch(op, args, ctx),
"path" => path::dispatch(op, args, ctx),
"value" => value::dispatch(op, args, ctx),
"index" => index_ns::dispatch(op, args, ctx),
_ => None,
}
}
#[cfg(test)]
fn name_is_known(name: &str) -> bool {
let Some((ns, op)) = name.split_once('.') else {
return false;
};
let probe = &[];
match ns {
"list" => list::dispatch(op, probe).is_some(),
"string" => string_ns::dispatch(op, probe).is_some(),
"text" => text::dispatch(op, probe).is_some(),
"map" => map_ns::dispatch(op, probe).is_some(),
"number" => number::dispatch(op, probe).is_some(),
"math" => math_ns::dispatch(op, probe).is_some(),
"temporal" => temporal::dispatch(op, probe).is_some(),
"bytes" => bytes_ns::dispatch(op, probe).is_some(),
"bits" => bits_ns::dispatch(op, probe).is_some(),
"cast" => cast_ns::dispatch(op, probe).is_some(),
"crypto" => crypto::dispatch(op, probe).is_some(),
"uuid" => uuid_ns::dispatch(op, probe).is_some(),
"json" => json_ns::dispatch(op, probe).is_some(),
"geo" => geo::dispatch(op, probe).is_some(),
"vector" => vector_ns::dispatch(op, probe).is_some(),
"type" => type_ns::known(op).is_some(),
"node" => node::known(op).is_some(),
"edge" => edge::known(op).is_some(),
"path" => path::known(op).is_some(),
"value" => value::known(op).is_some(),
"index" => index_ns::known(op).is_some(),
_ => false,
}
}
#[cfg(test)]
mod drift_tests {
use super::name_is_known;
use lora_analyzer::BUILTIN_SPECS;
#[test]
fn every_signature_has_a_dispatch_arm() {
let mut missing: Vec<&str> = Vec::new();
for spec in BUILTIN_SPECS {
if !name_is_known(spec.name) {
missing.push(spec.name);
}
}
assert!(
missing.is_empty(),
"the following signatures have no executor dispatch arm: {missing:?}"
);
}
#[test]
fn signature_table_has_no_duplicates() {
let mut seen: std::collections::HashSet<&str> = std::collections::HashSet::new();
for spec in BUILTIN_SPECS {
assert!(
seen.insert(spec.name),
"duplicate signature entry: {}",
spec.name
);
}
}
#[test]
fn every_signature_is_two_segments_and_snake_case() {
for spec in BUILTIN_SPECS {
let name = spec.name;
let parts: Vec<&str> = name.split('.').collect();
assert_eq!(parts.len(), 2, "expected two segments for '{name}'");
for part in parts {
assert!(!part.is_empty(), "empty segment in '{name}'");
for ch in part.chars() {
assert!(
ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '_',
"non-snake_case character {ch:?} in '{name}'"
);
}
}
}
}
#[test]
fn arity_max_is_at_least_arity_min() {
for spec in BUILTIN_SPECS {
let name = spec.name;
let min = spec.arity.min;
let max = spec.arity.max;
if let Some(mx) = max {
assert!(
mx >= min,
"arity max < min for '{name}': min={min} max={mx}"
);
}
}
}
}