use std::path::PathBuf;
use crate::dsl::registry::{FuncSig, FuncCategory};
use crate::node::GkNode;
#[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 GkNode>, String>;
}
pub struct GkRuntime {
factories: Vec<Box<dyn NodeFactory>>,
gk_lib_paths: Vec<PathBuf>,
}
impl GkRuntime {
pub fn new() -> Self {
Self {
factories: Vec::new(),
gk_lib_paths: Vec::new(),
}
}
pub fn register_factory(&mut self, factory: Box<dyn NodeFactory>) {
self.factories.push(factory);
}
pub fn add_gk_lib(&mut self, path: PathBuf) {
self.gk_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 GkNode>, 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 gk_lib_paths(&self) -> &[PathBuf] {
&self.gk_lib_paths
}
}
impl Default for GkRuntime {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dsl::registry::{Arity, ParamSpec};
use crate::node::SlotType;
#[test]
fn default_runtime_has_builtins() {
let rt = GkRuntime::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::node::Commutativity::Positional,
default_resolver: None,
output_type: crate::dsl::registry::OutputType::Fixed,
}]
}
fn build(&self, _name: &str, _wc: usize, _consts: &[FactoryArg])
-> Result<Box<dyn GkNode>, String> {
Ok(Box::new(crate::nodes::identity::Identity::new()))
}
}
let mut rt = GkRuntime::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::node::Commutativity::Positional,
default_resolver: None,
output_type: crate::dsl::registry::OutputType::Fixed,
}]
}
fn build(&self, name: &str, _wc: usize, _consts: &[FactoryArg])
-> Result<Box<dyn GkNode>, String> {
match name {
"custom_identity" => Ok(Box::new(crate::nodes::identity::Identity::new())),
_ => Err(format!("unknown: {name}")),
}
}
}
let mut rt = GkRuntime::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::node::Commutativity::Positional,
default_resolver: None,
output_type: crate::dsl::registry::OutputType::Fixed,
}]
}
fn build(&self, _: &str, _: usize, _: &[FactoryArg])
-> Result<Box<dyn GkNode>, String> {
Ok(Box::new(crate::nodes::identity::Identity::new()))
}
}
let mut rt = GkRuntime::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"));
}
}