use crate::ast::{PortType, Value};
use crate::derive_support::Wire;
use crate::dsl::ast::{Binding, BindingModifier, Expr, ExternPort, Statement, WireModifier};
use crate::dsl::lexer::Span;
use crate::kernel::{Dataflow, Metadata, PolydatKernel};
pub struct ExprStub {
name: String,
expr: Expr,
modifier: BindingModifier,
}
impl ExprStub {
pub fn new(name: impl Into<String>, expr: Expr) -> Self {
Self { name: name.into(), expr, modifier: BindingModifier::default() }
}
pub fn parse(name: impl Into<String>, source: &str) -> Result<Self, String> {
let tokens = crate::dsl::lexer::lex(source)?;
let expr = crate::dsl::parser::parse_expression(tokens)?;
Ok(Self::new(name, expr))
}
pub fn returning<T: Wire>(mut self) -> Self {
self.expr = Expr::Cast(Box::new(self.expr), T::PORT, Span { line: 0, col: 0 });
self
}
pub fn volatile(mut self) -> Self {
self.modifier.insert(WireModifier::Volatile);
self
}
pub fn into_statement(self) -> Statement {
Statement::Binding(Binding {
targets: vec![self.name],
value: self.expr,
modifier: self.modifier,
type_annotation: None,
span: Span { line: 0, col: 0 },
})
}
}
#[derive(Default)]
pub struct GraphMatter {
statements: Vec<Statement>,
}
impl GraphMatter {
pub fn new() -> Self {
Self::default()
}
pub fn extern_wire<T: Wire>(&mut self, name: impl Into<String>) -> &mut Self {
self.extern_wire_typed(name, T::PORT)
}
pub fn extern_wire_typed(
&mut self,
name: impl Into<String>,
port: PortType,
) -> &mut Self {
let span = Span { line: 0, col: 0 };
let default = match port {
PortType::F64 => Expr::FloatLit(0.0, span),
_ => Expr::IntLit(0, span),
};
self.statements.push(Statement::ExternPort(ExternPort {
name: name.into(),
typ: port.to_keyword().to_string(),
default: Some(default),
span,
}));
self
}
pub fn bind(&mut self, stub: ExprStub) -> &mut Self {
self.statements.push(stub.into_statement());
self
}
pub fn into_statements(self) -> Vec<Statement> {
self.statements
}
}
pub struct ScopedExpr {
kernel: PolydatKernel,
output: String,
}
impl ScopedExpr {
pub fn bind(
parent: &PolydatKernel,
output: impl Into<String>,
matter: GraphMatter,
) -> Result<Self, String> {
let pm = crate::kernel::subcontext::PolydatMatter::builder()
.statements(matter.into_statements())
.build()
.map_err(|e| format!("scoped-expr matter: {e:?}"))?;
let kernel = parent.build_subscope(pm).map_err(|e| format!("scoped-expr subscope: {e:?}"))?;
Ok(Self { kernel, output: output.into() })
}
pub fn set(&mut self, name: &str, value: Value) -> &mut Self {
if let Some(idx) = self.kernel.find_input(name) {
let _ = self.kernel.set_wire_idx(idx, value);
}
self
}
pub fn dataflow(&mut self) -> &mut PolydatKernel {
&mut self.kernel
}
pub fn eval(&mut self) -> Value {
self.kernel.pull(&self.output).clone()
}
pub fn is_true(&mut self) -> bool {
match self.eval() {
Value::Bool(b) => b,
Value::F64(v) => v != 0.0,
v => v.as_u64() != 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::PortType;
#[test]
fn parse_stub_builds_typed_volatile_binding() {
let stmt = ExprStub::parse("__pred", "op_count > 50")
.expect("parse")
.returning::<u64>()
.volatile()
.into_statement();
match stmt {
Statement::Binding(b) => {
assert_eq!(b.targets, vec!["__pred".to_string()]);
assert!(b.modifier.has(WireModifier::Volatile), "must be volatile");
assert!(matches!(b.value, Expr::Cast(_, PortType::U64, _)),
"value must be a Cast to U64, got {:?}", b.value);
}
other => panic!("expected a Binding statement, got {other:?}"),
}
}
#[test]
fn returning_binds_the_rust_generic_as_the_polydat_type() {
let stmt = ExprStub::parse("__m", "elapsed_ms")
.expect("parse")
.returning::<f64>()
.into_statement();
let Statement::Binding(b) = stmt else { panic!("expected Binding") };
assert!(matches!(b.value, Expr::Cast(_, PortType::F64, _)));
assert!(!b.modifier.has(WireModifier::Volatile), "no volatile unless requested");
}
#[test]
fn scoped_expr_binds_to_a_kernel_scope_and_is_callable() {
use crate::ast::Value;
let parent = crate::dsl::compile_polydat("input cycle: u64\nx := 5")
.expect("parent kernel");
let mut matter = GraphMatter::new();
matter
.extern_wire::<u64>("threshold")
.bind(ExprStub::parse("__pred", "threshold > 50")
.expect("parse").returning::<u64>().volatile());
let mut scoped = ScopedExpr::bind(&parent, "__pred", matter)
.expect("bind to parent scope");
assert!(scoped.set("threshold", Value::U64(100)).is_true(), "100 > 50 → true");
assert!(!scoped.set("threshold", Value::U64(10)).is_true(), "10 > 50 → false");
}
}