use super::ast::{Expr, ExternPort, PolydatFile, Statement, TileOptions};
pub fn apply_tile_defaults(file: &mut PolydatFile, defaults: &TileOptions) -> Result<(), String> {
fn visit(statements: &mut [Statement], defaults: &TileOptions) -> Result<(), String> {
let stock = TileOptions::default();
for stmt in statements.iter_mut() {
match stmt {
Statement::Tile(t) => {
let untouched = t.options.open == stock.open
&& t.options.close == stock.close
&& t.options.sigil == stock.sigil;
if !untouched {
continue;
}
let strict = t.options.strict;
t.options = defaults.clone();
t.options.strict = strict || defaults.strict;
t.pieces = super::tile::parse_template(&t.body, &t.options, t.span)
.map_err(|e| format!("tile '{}' under host delimiters: {e}", t.name))?;
}
Statement::ModuleDef(m) => visit(&mut m.body, defaults)?,
_ => {}
}
}
Ok(())
}
visit(&mut file.statements, defaults)
}
pub fn assign_values(
file: &mut PolydatFile,
assignments: &[(String, String)],
) -> Result<(), String> {
for (name, raw) in assignments {
let mut found = false;
for stmt in file.statements.iter_mut() {
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;
}
_ => {}
}
}
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 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()))
}