use std::collections::HashMap;
use super::ast::{GenerateLiteral, GenerateQuery, TemplateClause};
use super::GenerateError;
pub type Bindings = HashMap<String, String>;
#[derive(Debug)]
pub struct GenerateResult {
pub text: String,
pub binding_count: usize,
}
pub struct GenerateExecutor {
pub query: GenerateQuery,
}
impl GenerateExecutor {
pub fn new(query: GenerateQuery) -> Self {
Self { query }
}
pub fn evaluate_one(&self, bindings: &Bindings) -> Result<GenerateResult, GenerateError> {
let mut parts = Vec::new();
let mut used_vars: std::collections::HashSet<&str> = std::collections::HashSet::new();
for clause in &self.query.template {
let text = self.eval_clause(clause, bindings, &mut used_vars)?;
parts.push(text);
}
Ok(GenerateResult {
text: parts.concat(),
binding_count: used_vars.len(),
})
}
pub fn evaluate_all(&self, rows: &[Bindings]) -> Result<Vec<GenerateResult>, GenerateError> {
rows.iter().map(|row| self.evaluate_one(row)).collect()
}
pub fn generate_text(
&self,
rows: &[Bindings],
separator: &str,
) -> Result<String, GenerateError> {
let results = self.evaluate_all(rows)?;
let texts: Vec<&str> = results.iter().map(|r| r.text.as_str()).collect();
Ok(texts.join(separator))
}
fn eval_clause<'b>(
&self,
clause: &TemplateClause,
bindings: &'b Bindings,
used_vars: &mut std::collections::HashSet<&'b str>,
) -> Result<String, GenerateError> {
let mut buf = String::new();
if let Some(prefix) = &clause.prefix {
buf.push_str(prefix);
}
buf.push_str(&self.eval_literal_tracked(&clause.expr, bindings, used_vars)?);
if let Some(suffix) = &clause.suffix {
buf.push_str(suffix);
}
Ok(buf)
}
fn eval_literal_tracked<'b>(
&self,
lit: &GenerateLiteral,
bindings: &'b Bindings,
used_vars: &mut std::collections::HashSet<&'b str>,
) -> Result<String, GenerateError> {
match lit {
GenerateLiteral::Text(s) => Ok(s.clone()),
GenerateLiteral::Var(name) => {
let value = bindings
.get(name.as_str())
.ok_or_else(|| GenerateError::UnboundVariable(name.clone()))?;
if let Some(key) = bindings.keys().find(|k| k.as_str() == name.as_str()) {
used_vars.insert(key.as_str());
}
Ok(value.clone())
}
GenerateLiteral::Concat(parts) => {
let mut result = String::new();
for part in parts {
result.push_str(&self.eval_literal_tracked(part, bindings, used_vars)?);
}
Ok(result)
}
}
}
pub fn eval_literal(
&self,
lit: &GenerateLiteral,
bindings: &Bindings,
) -> Result<String, GenerateError> {
let mut used = std::collections::HashSet::new();
self.eval_literal_tracked(lit, bindings, &mut used)
}
}