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;
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::U64 => Some(Expr::IntLit(0, span)),
PortType::F64 => Some(Expr::FloatLit(0.0, span)),
PortType::Str => Some(Expr::StringLit(String::new(), span)),
PortType::Bool => Some(Expr::Ident("false".into(), span)),
_ => None,
};
self.statements.push(Statement::ExternPort(ExternPort {
name: name.into(),
typ: port.to_keyword().to_string(),
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: Box<dyn crate::kernel::Kernel>,
output: String,
}
impl ScopedExpr {
pub fn bind(
parent: &dyn crate::kernel::Kernel,
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 = pm
.build_under(parent)
.map_err(|e| format!("scoped-expr subscope: {e:?}"))?;
Ok(Self {
kernel,
output: output.into(),
})
}
pub fn set(
&mut self,
name: &str,
value: Value,
) -> Result<&mut Self, crate::kernel::WriteError> {
use crate::kernel::WriteError;
let Some(idx) = self.kernel.input_index(name) else {
return Err(WriteError::UnknownWire {
key: name.to_string(),
known: self.kernel.input_names(),
});
};
if idx < self.kernel.coord_count() {
return Err(WriteError::CoordinateSlot {
slot: name.to_string(),
});
}
let value = match self.kernel.input_port_type(name) {
Some(ty) => {
let got = value.port_type();
crate::convert::to_port(value, ty).map_err(|_| WriteError::TypeMismatch {
slot: name.to_string(),
expected: ty,
got,
})?
}
None => value,
};
self.kernel.set_input_at(idx, value)?;
Ok(self)
}
pub fn kernel(&mut self) -> &mut dyn crate::kernel::Kernel {
self.kernel.as_mut()
}
pub fn eval(&mut self) -> Value {
self.kernel.pull(&self.output)
}
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"
);
}
}