use indexmap::IndexMap;
use crate::compiler::ast::{Node, Stmt};
use crate::error::{DialogueError, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VariableDecl {
pub name: String,
pub default_src: String,
}
#[derive(Debug, Clone)]
pub struct Program {
pub(crate) nodes: IndexMap<String, Vec<Node>>,
declarations: Vec<VariableDecl>,
}
impl Program {
#[must_use]
pub fn node_exists(&self, title: &str) -> bool {
self.nodes.contains_key(title)
}
pub fn node_titles(&self) -> impl Iterator<Item = &str> {
self.nodes.keys().map(String::as_str)
}
#[must_use]
pub fn node_tags(&self, title: &str) -> Option<&[String]> {
self.nodes.get(title)?.first().map(|n| n.tags.as_slice())
}
#[must_use]
pub fn variable_declarations(&self) -> &[VariableDecl] {
&self.declarations
}
pub(crate) fn node_group(&self, title: &str) -> Option<&[Node]> {
self.nodes.get(title).map(Vec::as_slice)
}
pub(crate) fn from_nodes(nodes: Vec<Node>) -> Result<Self> {
let mut map: IndexMap<String, Vec<Node>> = IndexMap::new();
let mut declarations: Vec<VariableDecl> = Vec::new();
let mut seen_decls: indexmap::IndexSet<String> = indexmap::IndexSet::new();
for node in nodes {
collect_declarations(&node.body, &mut declarations, &mut seen_decls);
let entry = map.entry(node.title.clone()).or_default();
if !entry.is_empty() {
let existing_ungrouped = entry.iter().all(|n| n.when.is_none());
if existing_ungrouped && node.when.is_none() {
return Err(DialogueError::DuplicateNode(node.title));
}
}
entry.push(node);
}
Ok(Self {
nodes: map,
declarations,
})
}
}
fn collect_declarations(
stmts: &[Stmt],
out: &mut Vec<VariableDecl>,
seen: &mut indexmap::IndexSet<String>,
) {
for stmt in stmts {
match stmt {
Stmt::Declare {
name, default_src, ..
} if seen.insert(name.clone()) => {
out.push(VariableDecl {
name: name.clone(),
default_src: default_src.clone(),
});
}
Stmt::If {
branches,
else_body,
} => {
for b in branches {
collect_declarations(&b.body, out, seen);
}
collect_declarations(else_body, out, seen);
}
Stmt::Once {
body, else_body, ..
} => {
collect_declarations(body, out, seen);
collect_declarations(else_body, out, seen);
}
Stmt::Options(items) => {
for item in items {
collect_declarations(&item.body, out, seen);
}
}
_ => {}
}
}
}