use std::path::PathBuf;
use std::sync::Mutex;
use crate::dsl::registry::{FuncSig, FuncCategory};
use crate::ast::{PolydatNode, PortType, Value};
pub type ExternResolver = Box<dyn Fn(&str, PortType) -> Option<Value> + Send + Sync>;
static RESOLVERS: Mutex<Vec<ExternResolver>> = Mutex::new(Vec::new());
pub fn register_extern_resolver(resolver: ExternResolver) {
let mut r = RESOLVERS.lock().unwrap();
r.push(resolver);
}
pub fn clear_extern_resolvers() {
let mut r = RESOLVERS.lock().unwrap();
r.clear();
}
pub(crate) fn resolve_extern(slot_name: &str, slot_type: PortType) -> Option<Value> {
let r = RESOLVERS.lock().unwrap();
for resolver in r.iter() {
if let Some(value) = resolver(slot_name, slot_type) {
return Some(value);
}
}
None
}
#[derive(Debug, Clone)]
pub enum FactoryArg {
Int(u64),
Float(f64),
Str(String),
}
pub trait NodeFactory: Send + Sync {
fn signatures(&self) -> Vec<FuncSig>;
fn build(
&self,
name: &str,
wire_count: usize,
consts: &[FactoryArg],
) -> Result<Box<dyn PolydatNode>, String>;
}
pub struct PolydatRuntime {
factories: Vec<Box<dyn NodeFactory>>,
polydat_lib_paths: Vec<PathBuf>,
}
impl PolydatRuntime {
pub fn new() -> Self {
Self {
factories: Vec::new(),
polydat_lib_paths: Vec::new(),
}
}
pub fn register_factory(&mut self, factory: Box<dyn NodeFactory>) {
self.factories.push(factory);
}
pub fn add_polydat_lib(&mut self, path: PathBuf) {
self.polydat_lib_paths.push(path);
}
pub fn registry(&self) -> Vec<FuncSig> {
let mut sigs = crate::dsl::registry::registry();
for factory in &self.factories {
sigs.extend(factory.signatures());
}
sigs
}
pub fn by_category(&self) -> Vec<(FuncCategory, Vec<FuncSig>)> {
let sigs = self.registry();
let mut groups: std::collections::HashMap<FuncCategory, Vec<FuncSig>> =
std::collections::HashMap::new();
for sig in sigs {
groups.entry(sig.category).or_default().push(sig);
}
FuncCategory::display_order().iter()
.filter_map(|cat| groups.remove(cat).map(|funcs| (*cat, funcs)))
.collect()
}
pub fn build_from_factory(
&self,
name: &str,
wire_count: usize,
consts: &[FactoryArg],
) -> Option<Result<Box<dyn PolydatNode>, String>> {
for factory in &self.factories {
if factory.signatures().iter().any(|s| s.name == name) {
return Some(factory.build(name, wire_count, consts));
}
}
None
}
pub fn factory_count(&self) -> usize {
self.factories.len()
}
pub fn polydat_lib_paths(&self) -> &[PathBuf] {
&self.polydat_lib_paths
}
}
impl Default for PolydatRuntime {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dsl::registry::{Arity, ParamSpec};
use crate::ast::SlotType;
static RESOLVER_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn extern_resolver_register_and_lookup() {
let _guard = RESOLVER_TEST_LOCK.lock().unwrap();
clear_extern_resolvers();
register_extern_resolver(Box::new(|name, _typ| {
if name == "region" {
Some(Value::Str("us-east-1".into()))
} else {
None
}
}));
let v = resolve_extern("region", PortType::Str);
assert_eq!(v, Some(Value::Str("us-east-1".into())));
let v = resolve_extern("missing", PortType::Str);
assert_eq!(v, None);
clear_extern_resolvers();
}
#[test]
fn extern_resolver_first_match_wins() {
let _guard = RESOLVER_TEST_LOCK.lock().unwrap();
clear_extern_resolvers();
register_extern_resolver(Box::new(|name, _typ| {
if name == "k" {
Some(Value::U64(1))
} else {
None
}
}));
register_extern_resolver(Box::new(|name, _typ| {
if name == "k" {
Some(Value::U64(2))
} else {
None
}
}));
let v = resolve_extern("k", PortType::U64);
assert_eq!(v, Some(Value::U64(1)));
clear_extern_resolvers();
}
#[test]
fn extern_resolver_clear_removes_all() {
let _guard = RESOLVER_TEST_LOCK.lock().unwrap();
register_extern_resolver(Box::new(|_, _| Some(Value::U64(99))));
assert!(resolve_extern("anything", PortType::U64).is_some());
clear_extern_resolvers();
assert!(resolve_extern("anything", PortType::U64).is_none());
}
#[test]
fn default_runtime_has_builtins() {
let rt = PolydatRuntime::new();
let reg = rt.registry();
assert!(reg.len() >= 50);
}
#[test]
fn factory_signatures_merged() {
struct TestFactory;
impl NodeFactory for TestFactory {
fn signatures(&self) -> Vec<FuncSig> {
vec![FuncSig {
name: "test_node",
category: FuncCategory::Diagnostic,
outputs: 1,
description: "a test node from a factory",
help: "",
identity: None,
variadic_ctor: None,
params: &[
ParamSpec { name: "input", slot_type: SlotType::Wire, required: true, example: "cycle", constraint: None },
],
arity: Arity::Fixed,
commutativity: crate::ast::Commutativity::Positional,
default_resolver: None,
output_type: crate::dsl::registry::OutputType::Fixed,
output_port: None,
}]
}
fn build(&self, _name: &str, _wc: usize, _consts: &[FactoryArg])
-> Result<Box<dyn PolydatNode>, String> {
Ok(Box::new(crate::library::identity::Identity::new(crate::ast::PortType::U64)))
}
}
let mut rt = PolydatRuntime::new();
let before = rt.registry().len();
rt.register_factory(Box::new(TestFactory));
let after = rt.registry().len();
assert_eq!(after, before + 1);
assert!(rt.registry().iter().any(|s| s.name == "test_node"));
}
#[test]
fn factory_build_dispatch() {
struct TestFactory;
impl NodeFactory for TestFactory {
fn signatures(&self) -> Vec<FuncSig> {
vec![FuncSig {
name: "custom_identity",
category: FuncCategory::Diagnostic,
outputs: 1,
description: "custom identity from factory",
help: "",
identity: None,
variadic_ctor: None,
params: &[
ParamSpec { name: "input", slot_type: SlotType::Wire, required: true, example: "cycle", constraint: None },
],
arity: Arity::Fixed,
commutativity: crate::ast::Commutativity::Positional,
default_resolver: None,
output_type: crate::dsl::registry::OutputType::Fixed,
output_port: None,
}]
}
fn build(&self, name: &str, _wc: usize, _consts: &[FactoryArg])
-> Result<Box<dyn PolydatNode>, String> {
match name {
"custom_identity" => Ok(Box::new(crate::library::identity::Identity::new(crate::ast::PortType::U64))),
_ => Err(format!("unknown: {name}")),
}
}
}
let mut rt = PolydatRuntime::new();
rt.register_factory(Box::new(TestFactory));
let result = rt.build_from_factory("custom_identity", 1, &[]);
assert!(result.is_some());
assert!(result.unwrap().is_ok());
let result = rt.build_from_factory("hash", 1, &[]);
assert!(result.is_none());
}
#[test]
fn by_category_includes_factory_nodes() {
struct TestFactory;
impl NodeFactory for TestFactory {
fn signatures(&self) -> Vec<FuncSig> {
vec![FuncSig {
name: "factory_hash",
category: FuncCategory::Hashing,
outputs: 1,
description: "a factory hashing node",
help: "",
identity: None,
variadic_ctor: None,
params: &[
ParamSpec { name: "input", slot_type: SlotType::Wire, required: true, example: "cycle", constraint: None },
],
arity: Arity::Fixed,
commutativity: crate::ast::Commutativity::Positional,
default_resolver: None,
output_type: crate::dsl::registry::OutputType::Fixed,
output_port: None,
}]
}
fn build(&self, _: &str, _: usize, _: &[FactoryArg])
-> Result<Box<dyn PolydatNode>, String> {
Ok(Box::new(crate::library::identity::Identity::new(crate::ast::PortType::U64)))
}
}
let mut rt = PolydatRuntime::new();
rt.register_factory(Box::new(TestFactory));
let grouped = rt.by_category();
let hashing = grouped.iter().find(|(c, _)| *c == FuncCategory::Hashing).unwrap();
assert!(hashing.1.iter().any(|s| s.name == "factory_hash"));
assert!(hashing.1.iter().any(|s| s.name == "hash"));
}
}