use std::path::PathBuf;
use std::sync::Mutex;
use crate::ast::{PolydatNode, PortType, Value};
use crate::compile::assembly::WireRef;
use crate::dsl::factory::{BuildContext, ConstArg};
use crate::dsl::registry::{FuncCategory, FuncSig};
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
}
pub trait NodeFactory: Send + Sync {
fn signatures(&self) -> &[FuncSig];
fn validate(&self, _name: &str, _consts: &[ConstArg]) -> Result<(), String> {
Ok(())
}
fn build(
&self,
ctx: &BuildContext,
name: &str,
wires: &[WireRef],
wire_types: &[PortType],
consts: &[ConstArg],
) -> Result<Box<dyn PolydatNode>, String>;
}
pub struct PolydatRuntime {
polydat_lib_paths: Vec<PathBuf>,
}
impl PolydatRuntime {
pub fn new() -> Self {
Self {
polydat_lib_paths: Vec::new(),
}
}
pub fn add_polydat_lib(&mut self, path: PathBuf) {
self.polydat_lib_paths.push(path);
}
pub fn registry(&self) -> Vec<FuncSig> {
crate::dsl::registry::registry()
}
pub fn by_category(&self) -> Vec<(FuncCategory, Vec<FuncSig>)> {
crate::dsl::registry::by_category()
}
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::*;
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);
}
}