use crate::lex::{Tok, Token};
use crate::spec::SpecError;
use std::collections::BTreeMap;
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InputExpr {
FromNode(String),
Record(BTreeMap<String, InputExpr>),
List(Vec<InputExpr>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Node {
pub block: PathBuf,
pub input: Option<InputExpr>,
pub repeat_until: Option<String>,
pub max_iterations: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct NodeGraph {
pub nodes: Vec<(String, Node)>,
}
impl NodeGraph {
pub fn single(block: PathBuf) -> Self {
Self {
nodes: vec![(
"block".to_string(),
Node {
block,
input: None,
repeat_until: None,
max_iterations: None,
},
)],
}
}
pub fn get(&self, name: &str) -> Option<&Node> {
self.nodes
.iter()
.find(|(n, _)| n == name)
.map(|(_, node)| node)
}
}
pub fn is_simple_chain(graph: &NodeGraph, branches: &Branches) -> bool {
if !branches.decisions.is_empty() {
return false;
}
for (i, (_, node)) in graph.nodes.iter().enumerate() {
if node.repeat_until.is_some() {
return false;
}
match (i, &node.input) {
(0, None) => {}
(0, Some(_)) => return false, (_, Some(InputExpr::FromNode(referenced))) => {
let (previous_name, _) = &graph.nodes[i - 1];
if referenced != previous_name {
return false; }
}
_ => return false, }
}
let mut referenced_counts = std::collections::HashMap::new();
for (_, node) in &graph.nodes {
if let Some(InputExpr::FromNode(referenced)) = &node.input {
*referenced_counts.entry(referenced.clone()).or_insert(0) += 1;
}
}
referenced_counts.values().all(|&count| count <= 1)
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Branches {
pub decisions: Vec<(String, Vec<(String, String)>)>,
}
pub struct GraphParser<'a> {
pub tokens: &'a [Token],
pub at: usize,
}
impl<'a> GraphParser<'a> {
fn peek(&self) -> Option<&'a Tok> {
self.tokens.get(self.at).map(|t| &t.tok)
}
fn here(&self) -> String {
match self.tokens.get(self.at) {
Some(t) => format!("{} at {}", t.tok.describe(), t.span),
None => "end of input".into(),
}
}
fn expect(&mut self, want: &Tok) -> Result<(), SpecError> {
match self.peek() {
Some(got) if got == want => {
self.at += 1;
Ok(())
}
_ => Err(SpecError::Malformed(format!(
"expected {}, found {}",
want.describe(),
self.here()
))),
}
}
fn ident(&mut self) -> Result<String, SpecError> {
match self.tokens.get(self.at).map(|t| &t.tok) {
Some(Tok::Ident(name)) => {
self.at += 1;
Ok(name.clone())
}
_ => Err(SpecError::Malformed(format!(
"expected a name, found {}",
self.here()
))),
}
}
fn string(&mut self) -> Result<String, SpecError> {
match self.tokens.get(self.at).map(|t| &t.tok) {
Some(Tok::Str(s)) => {
self.at += 1;
Ok(s.clone())
}
_ => Err(SpecError::Malformed(format!(
"expected a quoted string, found {}",
self.here()
))),
}
}
fn skip_semi(&mut self) {
if self.peek() == Some(&Tok::Semicolon) {
self.at += 1;
}
}
pub fn node_graph(&mut self) -> Result<(NodeGraph, usize), SpecError> {
self.expect(&Tok::OpenBrace)?;
let mut nodes = Vec::new();
while self.peek().is_some() && self.peek() != Some(&Tok::CloseBrace) {
let name = self.ident()?;
self.expect(&Tok::Equals)?;
let node = self.node_body()?;
nodes.push((name, node));
self.skip_semi();
}
self.expect(&Tok::CloseBrace)?;
if nodes.is_empty() {
return Err(SpecError::Malformed("nodes needs at least one node".into()));
}
Ok((NodeGraph { nodes }, self.at))
}
fn node_body(&mut self) -> Result<Node, SpecError> {
self.expect(&Tok::OpenBrace)?;
let (mut block, mut input, mut repeat_until, mut max_iterations) = (None, None, None, None);
while self.peek().is_some() && self.peek() != Some(&Tok::CloseBrace) {
let key = self.ident()?;
self.expect(&Tok::Equals)?;
match key.as_str() {
"block" => block = Some(PathBuf::from(self.string()?)),
"in" => input = Some(self.input_expr()?),
"repeat_until" => repeat_until = Some(self.string_or_field()?),
"max_iterations" => max_iterations = Some(self.number()?),
other => return Err(SpecError::UnknownField(other.to_string())),
}
self.skip_semi();
}
self.expect(&Tok::CloseBrace)?;
if let (Some(_), None) = (&repeat_until, &max_iterations) {
return Err(SpecError::Malformed(
"repeat_until requires max_iterations".into(),
));
}
Ok(Node {
block: block.ok_or(SpecError::MissingField("block"))?,
input,
repeat_until,
max_iterations,
})
}
fn string_or_field(&mut self) -> Result<String, SpecError> {
self.string()
}
fn number(&mut self) -> Result<u32, SpecError> {
let s = self.ident()?;
s.parse::<u32>()
.map_err(|_| SpecError::Malformed(format!("`{s}` is not a valid max_iterations")))
}
fn input_expr(&mut self) -> Result<InputExpr, SpecError> {
match self.peek() {
Some(Tok::OpenBrace) => {
self.at += 1;
let mut fields = BTreeMap::new();
while self.peek() != Some(&Tok::CloseBrace) {
let field = self.ident()?;
self.expect(&Tok::Equals)?;
fields.insert(field, self.input_expr()?);
self.skip_semi();
}
self.expect(&Tok::CloseBrace)?;
Ok(InputExpr::Record(fields))
}
Some(Tok::OpenBracket) => {
self.at += 1;
let mut items = Vec::new();
while self.peek() != Some(&Tok::CloseBracket) {
items.push(self.input_expr()?);
if self.peek() == Some(&Tok::Comma) {
self.at += 1;
} else {
break;
}
}
self.expect(&Tok::CloseBracket)?;
Ok(InputExpr::List(items))
}
Some(Tok::Ident(reference)) => {
let reference = reference.clone();
self.at += 1;
reference
.strip_suffix(".out")
.map(|node| InputExpr::FromNode(node.to_string()))
.ok_or_else(|| {
SpecError::Malformed(format!(
"`{reference}` is not a node reference — expected `<node>.out`"
))
})
}
_ => Err(SpecError::Malformed(format!(
"expected a node reference, `{{...}}`, or `[...]`, found {}",
self.here()
))),
}
}
pub fn branches(&mut self) -> Result<(Branches, usize), SpecError> {
self.expect(&Tok::OpenBrace)?;
let mut decisions = Vec::new();
while self.peek().is_some() && self.peek() != Some(&Tok::CloseBrace) {
let node_name = self.ident()?;
self.expect(&Tok::Equals)?;
self.expect(&Tok::OpenBrace)?;
let mut labels = Vec::new();
while self.peek() != Some(&Tok::CloseBrace) {
let label = self.string()?;
self.expect(&Tok::Arrow)?;
let target = self.ident()?;
labels.push((label, target));
self.skip_semi();
}
self.expect(&Tok::CloseBrace)?;
decisions.push((node_name, labels));
self.skip_semi();
}
self.expect(&Tok::CloseBrace)?;
Ok((Branches { decisions }, self.at))
}
}
#[cfg(test)]
mod is_simple_chain_tests {
use super::*;
fn node(block: &str, input: Option<InputExpr>) -> Node {
Node {
block: PathBuf::from(block),
input,
repeat_until: None,
max_iterations: None,
}
}
fn from_node(name: &str) -> InputExpr {
InputExpr::FromNode(name.to_string())
}
#[test]
fn a_genuine_three_node_chain_is_simple() {
let graph = NodeGraph {
nodes: vec![
("a".into(), node("blocks/a", None)),
("b".into(), node("blocks/b", Some(from_node("a")))),
("c".into(), node("blocks/c", Some(from_node("b")))),
],
};
assert!(is_simple_chain(&graph, &Branches::default()));
}
#[test]
fn record_fan_in_is_not_simple() {
let mut fields = BTreeMap::new();
fields.insert("x".to_string(), from_node("a"));
fields.insert("y".to_string(), from_node("b"));
let graph = NodeGraph {
nodes: vec![
("a".into(), node("blocks/a", None)),
("b".into(), node("blocks/b", None)),
(
"c".into(),
node("blocks/c", Some(InputExpr::Record(fields))),
),
],
};
assert!(!is_simple_chain(&graph, &Branches::default()));
}
#[test]
fn list_fan_in_is_not_simple() {
let graph = NodeGraph {
nodes: vec![
("a".into(), node("blocks/a", None)),
("b".into(), node("blocks/b", None)),
(
"c".into(),
node(
"blocks/c",
Some(InputExpr::List(vec![from_node("a"), from_node("b")])),
),
),
],
};
assert!(!is_simple_chain(&graph, &Branches::default()));
}
#[test]
fn a_repeat_until_node_is_not_simple() {
let mut looped = node("blocks/b", Some(from_node("a")));
looped.repeat_until = Some("done".to_string());
looped.max_iterations = Some(5);
let graph = NodeGraph {
nodes: vec![("a".into(), node("blocks/a", None)), ("b".into(), looped)],
};
assert!(!is_simple_chain(&graph, &Branches::default()));
}
#[test]
fn a_branches_decision_is_not_simple() {
let graph = NodeGraph {
nodes: vec![
("a".into(), node("blocks/a", None)),
("b".into(), node("blocks/b", Some(from_node("a")))),
],
};
let branches = Branches {
decisions: vec![("a".to_string(), vec![("done".to_string(), "b".to_string())])],
};
assert!(!is_simple_chain(&graph, &branches));
}
#[test]
fn fan_out_from_a_shared_predecessor_is_not_simple() {
let graph = NodeGraph {
nodes: vec![
("a".into(), node("blocks/a", None)),
("b".into(), node("blocks/b", Some(from_node("a")))),
("c".into(), node("blocks/c", Some(from_node("a")))),
],
};
assert!(!is_simple_chain(&graph, &Branches::default()));
}
}