use crate::assembly::WireRef;
use crate::node::GkNode;
use crate::nodes::identity::ConstU64;
use crate::dsl::registry;
pub enum ConstArg {
Int(u64),
Float(f64),
Str(String),
FloatArray(#[allow(dead_code)] Vec<f64>),
}
impl ConstArg {
pub fn as_u64(&self) -> u64 {
match self { ConstArg::Int(v) => *v, _ => 0 }
}
pub fn as_f64(&self) -> f64 {
match self { ConstArg::Float(v) => *v, ConstArg::Int(v) => *v as f64, _ => 0.0 }
}
pub fn as_str(&self) -> &str {
match self { ConstArg::Str(s) => s, _ => "" }
}
#[allow(dead_code)]
pub fn as_float_array(&self) -> &[f64] {
match self { ConstArg::FloatArray(v) => v, _ => &[] }
}
}
pub mod compile_ctx {
use std::cell::RefCell;
thread_local! {
static BINDING: RefCell<Option<String>> = const { RefCell::new(None) };
}
pub fn scoped_binding(name: &str) -> BindingScope {
BINDING.with(|b| *b.borrow_mut() = Some(name.to_string()));
BindingScope(())
}
pub fn current_binding() -> Option<String> {
BINDING.with(|b| b.borrow().clone())
}
pub struct BindingScope(());
impl Drop for BindingScope {
fn drop(&mut self) {
BINDING.with(|b| *b.borrow_mut() = None);
}
}
}
pub fn build_node(
func: &str,
wires: &[WireRef],
wire_types: &[crate::node::PortType],
consts: &[ConstArg],
) -> Result<Box<dyn GkNode>, String> {
use crate::dsl::registry::NodeRegistration;
for reg in inventory::iter::<NodeRegistration> {
let sigs = (reg.signatures)();
let owning_sig = sigs.iter().find(|s| s.name == func);
if owning_sig.is_none() { continue; }
let sig = owning_sig.unwrap();
if let Err(msg) = check_param_constraints(sig, consts) {
return Err(format!("bad constant {func}: {msg}"));
}
if let Some(validator) = reg.validate
&& let Err(reason) = validator(func, consts)
{
return Err(format!("bad constant {func}: {reason}"));
}
if let Some(result) = (reg.build)(func, wires, wire_types, consts) {
return result;
}
}
match func {
"identity" => return Ok(Box::new(crate::nodes::identity::Identity::new())),
"lut_sample" | "icd_normal" => {
use crate::sampling::icd::IcdSample;
return Ok(Box::new(IcdSample::normal(
consts.first().map(|c| c.as_f64()).unwrap_or(0.0),
consts.get(1).map(|c| c.as_f64()).unwrap_or(1.0),
)));
}
"icd_exponential" | "dist_exponential" => {
use crate::sampling::icd::IcdSample;
return Ok(Box::new(IcdSample::exponential(
consts.first().map(|c| c.as_f64()).unwrap_or(1.0),
)));
}
"dist_normal" => {
use crate::sampling::icd::IcdSample;
return Ok(Box::new(IcdSample::normal(
consts.first().map(|c| c.as_f64()).unwrap_or(0.0),
consts.get(1).map(|c| c.as_f64()).unwrap_or(1.0),
)));
}
"dist_uniform" => {
use crate::sampling::icd::IcdSample;
return Ok(Box::new(IcdSample::uniform(
consts.first().map(|c| c.as_f64()).unwrap_or(0.0),
consts.get(1).map(|c| c.as_f64()).unwrap_or(1.0),
)));
}
"dist_pareto" => {
use crate::sampling::icd::IcdSample;
return Ok(Box::new(IcdSample::pareto(
consts.first().map(|c| c.as_f64()).unwrap_or(1.0),
consts.get(1).map(|c| c.as_f64()).unwrap_or(1.0),
)));
}
"dist_zipf" => {
use crate::sampling::icd::IcdSample;
return Ok(Box::new(IcdSample::zipf(
consts.first().map(|c| c.as_u64()).unwrap_or(100),
consts.get(1).map(|c| c.as_f64()).unwrap_or(1.0),
)));
}
"histribution" => {
let spec = consts.first().map(|c| c.as_str()).unwrap_or("1");
if let Err(e) = validate_histribution_spec(spec) {
return Err(format!("bad constant histribution: spec: {e}"));
}
return Ok(Box::new(crate::sampling::histribution::Histribution::new(spec)));
}
"dist_empirical" => {
let spec = consts.first().map(|c| c.as_str()).unwrap_or("0.0 1.0");
if let Err(e) = validate_dist_empirical_spec(spec) {
return Err(format!("bad constant dist_empirical: spec: {e}"));
}
return Ok(Box::new(crate::sampling::lut::EmpiricalSample::from_spec(spec)));
}
_ => {}
}
if let Some(sig) = registry::lookup(func)
&& sig.is_variadic() {
if wires.is_empty() {
if let Some(id) = sig.identity {
return Ok(Box::new(ConstU64::new(id)));
}
return Err(format!("variadic function '{func}' requires at least one input"));
}
if let Some(ctor) = sig.variadic_ctor {
return Ok(ctor(wires.len()));
}
}
let mut msg = format!("unknown function: '{func}'\n");
if let Some(suggestion) = registry::suggest_function(func) {
msg.push_str(&format!("\n Did you mean '{suggestion}'?"));
}
msg.push_str("\n\n This function is not registered in the GK function library.");
msg.push_str("\n Use 'nbrs describe gk functions' to see all available functions.");
Err(msg)
}
fn check_param_constraints(
sig: &crate::dsl::registry::FuncSig,
consts: &[ConstArg],
) -> Result<(), String> {
use crate::node::SlotType;
let mut const_idx = 0usize;
for spec in sig.params {
if matches!(spec.slot_type, SlotType::Wire) {
continue;
}
if let Some(constraint) = &spec.constraint
&& let Some(arg) = consts.get(const_idx)
{
constraint.check(arg, spec.name)?;
}
const_idx += 1;
}
Ok(())
}
fn validate_histribution_spec(spec: &str) -> Result<(), String> {
let labeled = spec.contains(':');
let mut any = false;
for elem in spec.split([' ', ',', ';']) {
let elem = elem.trim();
if elem.is_empty() { continue; }
if labeled {
let parts: Vec<&str> = elem.splitn(2, ':').collect();
if parts.len() != 2 {
return Err(format!("all elements must be labeled: '{elem}'"));
}
parts[0].parse::<u64>()
.map_err(|_| format!("invalid label '{}'", parts[0]))?;
parts[1].parse::<f64>()
.map_err(|_| format!("invalid weight '{}'", parts[1]))?;
} else {
elem.parse::<f64>()
.map_err(|_| format!("invalid weight '{elem}'"))?;
}
any = true;
}
if !any {
return Err("spec must not be empty".into());
}
Ok(())
}
fn validate_dist_empirical_spec(spec: &str) -> Result<(), String> {
let mut count = 0usize;
for tok in spec.split([' ', ',', ';']) {
let tok = tok.trim();
if tok.is_empty() { continue; }
tok.parse::<f64>()
.map_err(|_| format!("invalid data point '{tok}'"))?;
count += 1;
}
if count < 2 {
return Err(format!("needs at least 2 data points, got {count}"));
}
Ok(())
}