use super::ast::{Expr, ExternPort, PolydatFile, Statement, TileDef, TilePiece};
pub fn assign_values(
file: &mut PolydatFile,
assignments: &[(String, String)],
) -> Result<(), String> {
for (name, raw) in assignments {
let mut found = false;
each_statement::<std::convert::Infallible>(&mut file.statements, &mut |stmt| {
match stmt {
Statement::ExternPort(port) if &port.name == name => {
port.default = Some(Expr::StringLit(raw.clone(), port.span));
found = true;
}
Statement::InputDecl(decl) if &decl.name == name => {
let span = decl.span;
let typ = decl.ty.clone().unwrap_or_else(|| "u64".to_string());
*stmt = Statement::ExternPort(ExternPort {
name: name.clone(),
typ,
default: Some(Expr::StringLit(raw.clone(), span)),
span,
});
found = true;
}
_ => {}
}
Ok(())
})
.expect("the closure never fails");
if !found {
let declared: Vec<&str> = file
.statements
.iter()
.filter_map(|s| match s {
Statement::ExternPort(p) => Some(p.name.as_str()),
Statement::InputDecl(d) => Some(d.name.as_str()),
_ => None,
})
.collect();
return Err(format!(
"cannot assign '{name}': no extern or input by that name; declared: {}",
if declared.is_empty() {
"(none)".to_string()
} else {
declared.join(", ")
}
));
}
}
Ok(())
}
pub fn convert_input(file: &mut PolydatFile, name: &str) -> Result<(), String> {
for stmt in &mut file.statements {
if let Statement::ExternPort(port) = stmt
&& port.name == name
{
port.typ = crate::ast::PortType::Dyn.to_keyword().to_string();
return Ok(());
}
}
let externs: Vec<&str> = file
.statements
.iter()
.filter_map(|s| match s {
Statement::ExternPort(p) => Some(p.name.as_str()),
_ => None,
})
.collect();
Err(format!(
"cannot convert '{name}': no extern by that name; declared externs: {}",
if externs.is_empty() {
"(none)".to_string()
} else {
externs.join(", ")
}
))
}
pub fn parse_assignment(text: &str) -> Result<(String, String), String> {
let (name, value) = text
.split_once('=')
.ok_or_else(|| format!("expected NAME=VALUE, got '{text}'"))?;
let name = name.trim();
if name.is_empty() || !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
return Err(format!("'{name}' is not a valid wire name in '{text}'"));
}
Ok((name.to_string(), value.trim().to_string()))
}
pub fn add_tiles(file: &mut PolydatFile, tiles: Vec<TileDef>) -> Result<(), String> {
let mut declared: Vec<String> = Vec::new();
each_tile::<std::convert::Infallible>(&mut file.statements, &mut |tile| {
declared.push(tile.name.clone());
Ok(())
})
.expect("the closure never fails");
for tile in &tiles {
if declared.contains(&tile.name) {
return Err(format!(
"the program already declares a tile named '{}'; a host tile cannot \
replace one the program declares. Rename the host tile, or rewrite \
the declared one in place with `tile_named`.",
tile.name,
));
}
declared.push(tile.name.clone());
}
file.statements
.extend(tiles.into_iter().map(Statement::Tile));
Ok(())
}
pub fn each_statement<E>(
statements: &mut [Statement],
f: &mut impl FnMut(&mut Statement) -> Result<(), E>,
) -> Result<(), E> {
for stmt in statements.iter_mut() {
f(stmt)?;
match stmt {
Statement::ModuleDef(m) => each_statement(&mut m.body, f)?,
Statement::For(s) => each_statement(&mut s.body, f)?,
_ => {}
}
}
Ok(())
}
pub fn each_tile<E>(
statements: &mut [Statement],
f: &mut impl FnMut(&mut TileDef) -> Result<(), E>,
) -> Result<(), E> {
each_statement(statements, &mut |stmt| match stmt {
Statement::Tile(t) => f(t),
_ => Ok(()),
})
}
pub fn tile_named<'a>(statements: &'a mut [Statement], name: &str) -> Option<&'a mut TileDef> {
for stmt in statements.iter_mut() {
match stmt {
Statement::Tile(t) if t.name == name => return Some(t),
Statement::ModuleDef(m) => {
if let Some(t) = tile_named(&mut m.body, name) {
return Some(t);
}
}
Statement::For(s) => {
if let Some(t) = tile_named(&mut s.body, name) {
return Some(t);
}
}
_ => {}
}
}
None
}
pub fn each_piece<E>(
pieces: &mut [TilePiece],
f: &mut impl FnMut(&mut TilePiece) -> Result<(), E>,
) -> Result<(), E> {
for piece in pieces.iter_mut() {
f(piece)?;
match piece {
TilePiece::Projection { body, .. } => each_piece(body, f)?,
TilePiece::Branch {
then, otherwise, ..
} => {
each_piece(then, f)?;
if let Some(arm) = otherwise {
each_piece(arm, f)?;
}
}
TilePiece::Static(_) | TilePiece::Hole(_) => {}
}
}
Ok(())
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ScopeWires {
pub names: Vec<String>,
}
impl ScopeWires {
fn push(&mut self, name: &str) {
if !name.starts_with("__") && !self.names.iter().any(|n| n == name) {
self.names.push(name.to_string());
}
}
pub fn binds(&self, name: &str) -> bool {
self.names.iter().any(|n| n == name)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DeclaredWires {
pub root: ScopeWires,
pub inputs: Vec<String>,
pub traversal_bodies: Vec<ScopeWires>,
}
pub fn declared_wires(file: &PolydatFile) -> DeclaredWires {
let mut out = DeclaredWires::default();
for stmt in &file.statements {
match stmt {
Statement::Binding(b) => {
for t in &b.targets {
out.root.push(t);
}
}
Statement::Tile(t) => out.root.push(&t.name),
Statement::InputDecl(i) => out.inputs.push(i.name.clone()),
Statement::ExternPort(p) => out.inputs.push(p.name.clone()),
_ => {}
}
}
for stmt in &file.statements {
if let Statement::For(f) = stmt {
let mut body = ScopeWires::default();
for name in source_element_names(&f.source, file) {
body.push(&name);
}
collect_scope(&f.body, file, &mut body);
for name in out.root.names.iter().chain(out.inputs.iter()) {
body.push(name);
}
out.traversal_bodies.push(body);
}
}
out
}
fn collect_scope(body: &[Statement], file: &PolydatFile, into: &mut ScopeWires) {
for stmt in body {
match stmt {
Statement::Binding(b) => {
for t in &b.targets {
into.push(t);
}
}
Statement::Tile(t) => into.push(&t.name),
Statement::For(f) => {
let _ = (f, file);
}
_ => {}
}
}
}
fn source_element_names(source: &crate::dsl::ast::ForSource, file: &PolydatFile) -> Vec<String> {
use crate::dsl::ast::ForSourceKind;
match &source.kind {
ForSourceKind::Comprehension(c) => {
c.coordinate_specs().into_iter().map(|(v, _)| v).collect()
}
ForSourceKind::Producer(name) => producer_element_names(name, file),
ForSourceKind::Derived { base, .. } => producer_element_names(base, file),
}
}
fn producer_element_names(name: &str, file: &PolydatFile) -> Vec<String> {
let mut seen = 0usize;
let mut target = name.to_string();
while seen <= file.statements.len() {
seen += 1;
let found = file.statements.iter().find_map(|s| match s {
Statement::Binding(b) if b.targets.contains(&target) => match &b.value {
Expr::For(src) => Some(src),
_ => None,
},
_ => None,
});
let Some(src) = found else { return Vec::new() };
use crate::dsl::ast::ForSourceKind;
match &src.kind {
ForSourceKind::Comprehension(c) => {
return c.coordinate_specs().into_iter().map(|(v, _)| v).collect();
}
ForSourceKind::Producer(next) | ForSourceKind::Derived { base: next, .. } => {
target = next.clone();
}
}
}
Vec::new()
}