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"
);
}
}