1use crate::lex::{Tok, Token};
10use crate::spec::SpecError;
11use std::collections::BTreeMap;
12use std::path::PathBuf;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum InputExpr {
17 FromNode(String),
19 Record(BTreeMap<String, InputExpr>),
21 List(Vec<InputExpr>),
23}
24
25#[derive(Debug, Clone, PartialEq)]
27pub struct Node {
28 pub block: PathBuf,
31 pub input: Option<InputExpr>,
34 pub repeat_until: Option<String>,
37 pub max_iterations: Option<u32>,
41}
42
43#[derive(Debug, Clone, PartialEq, Default)]
45pub struct NodeGraph {
46 pub nodes: Vec<(String, Node)>,
50}
51
52impl NodeGraph {
53 pub fn single(block: PathBuf) -> Self {
55 Self {
56 nodes: vec![(
57 "block".to_string(),
58 Node {
59 block,
60 input: None,
61 repeat_until: None,
62 max_iterations: None,
63 },
64 )],
65 }
66 }
67
68 pub fn get(&self, name: &str) -> Option<&Node> {
70 self.nodes
71 .iter()
72 .find(|(n, _)| n == name)
73 .map(|(_, node)| node)
74 }
75}
76
77pub fn is_simple_chain(graph: &NodeGraph, branches: &Branches) -> bool {
95 if !branches.decisions.is_empty() {
96 return false;
97 }
98 for (i, (_, node)) in graph.nodes.iter().enumerate() {
99 if node.repeat_until.is_some() {
100 return false;
101 }
102 match (i, &node.input) {
103 (0, None) => {}
104 (0, Some(_)) => return false, (_, Some(InputExpr::FromNode(referenced))) => {
106 let (previous_name, _) = &graph.nodes[i - 1];
107 if referenced != previous_name {
108 return false; }
110 }
111 _ => return false, }
113 }
114 let mut referenced_counts = std::collections::HashMap::new();
121 for (_, node) in &graph.nodes {
122 if let Some(InputExpr::FromNode(referenced)) = &node.input {
123 *referenced_counts.entry(referenced.clone()).or_insert(0) += 1;
124 }
125 }
126 referenced_counts.values().all(|&count| count <= 1)
127}
128
129#[derive(Debug, Clone, PartialEq, Default)]
131pub struct Branches {
132 pub decisions: Vec<(String, Vec<(String, String)>)>,
134}
135
136pub struct GraphParser<'a> {
139 pub tokens: &'a [Token],
141 pub at: usize,
143}
144
145impl<'a> GraphParser<'a> {
146 fn peek(&self) -> Option<&'a Tok> {
147 self.tokens.get(self.at).map(|t| &t.tok)
148 }
149 fn here(&self) -> String {
150 match self.tokens.get(self.at) {
151 Some(t) => format!("{} at {}", t.tok.describe(), t.span),
152 None => "end of input".into(),
153 }
154 }
155 fn expect(&mut self, want: &Tok) -> Result<(), SpecError> {
156 match self.peek() {
157 Some(got) if got == want => {
158 self.at += 1;
159 Ok(())
160 }
161 _ => Err(SpecError::Malformed(format!(
162 "expected {}, found {}",
163 want.describe(),
164 self.here()
165 ))),
166 }
167 }
168 fn ident(&mut self) -> Result<String, SpecError> {
169 match self.tokens.get(self.at).map(|t| &t.tok) {
170 Some(Tok::Ident(name)) => {
171 self.at += 1;
172 Ok(name.clone())
173 }
174 _ => Err(SpecError::Malformed(format!(
175 "expected a name, found {}",
176 self.here()
177 ))),
178 }
179 }
180 fn string(&mut self) -> Result<String, SpecError> {
181 match self.tokens.get(self.at).map(|t| &t.tok) {
182 Some(Tok::Str(s)) => {
183 self.at += 1;
184 Ok(s.clone())
185 }
186 _ => Err(SpecError::Malformed(format!(
187 "expected a quoted string, found {}",
188 self.here()
189 ))),
190 }
191 }
192 fn skip_semi(&mut self) {
193 if self.peek() == Some(&Tok::Semicolon) {
194 self.at += 1;
195 }
196 }
197
198 pub fn node_graph(&mut self) -> Result<(NodeGraph, usize), SpecError> {
206 self.expect(&Tok::OpenBrace)?;
207 let mut nodes = Vec::new();
208 while self.peek().is_some() && self.peek() != Some(&Tok::CloseBrace) {
209 let name = self.ident()?;
210 self.expect(&Tok::Equals)?;
211 let node = self.node_body()?;
212 nodes.push((name, node));
213 self.skip_semi();
214 }
215 self.expect(&Tok::CloseBrace)?;
216 if nodes.is_empty() {
217 return Err(SpecError::Malformed("nodes needs at least one node".into()));
218 }
219 Ok((NodeGraph { nodes }, self.at))
220 }
221
222 fn node_body(&mut self) -> Result<Node, SpecError> {
224 self.expect(&Tok::OpenBrace)?;
225 let (mut block, mut input, mut repeat_until, mut max_iterations) = (None, None, None, None);
226 while self.peek().is_some() && self.peek() != Some(&Tok::CloseBrace) {
227 let key = self.ident()?;
228 self.expect(&Tok::Equals)?;
229 match key.as_str() {
230 "block" => block = Some(PathBuf::from(self.string()?)),
231 "in" => input = Some(self.input_expr()?),
232 "repeat_until" => repeat_until = Some(self.string_or_field()?),
233 "max_iterations" => max_iterations = Some(self.number()?),
234 other => return Err(SpecError::UnknownField(other.to_string())),
235 }
236 self.skip_semi();
237 }
238 self.expect(&Tok::CloseBrace)?;
239 if let (Some(_), None) = (&repeat_until, &max_iterations) {
240 return Err(SpecError::Malformed(
241 "repeat_until requires max_iterations".into(),
242 ));
243 }
244 Ok(Node {
245 block: block.ok_or(SpecError::MissingField("block"))?,
246 input,
247 repeat_until,
248 max_iterations,
249 })
250 }
251
252 fn string_or_field(&mut self) -> Result<String, SpecError> {
256 self.string()
257 }
258
259 fn number(&mut self) -> Result<u32, SpecError> {
260 let s = self.ident()?;
265 s.parse::<u32>()
266 .map_err(|_| SpecError::Malformed(format!("`{s}` is not a valid max_iterations")))
267 }
268
269 fn input_expr(&mut self) -> Result<InputExpr, SpecError> {
271 match self.peek() {
272 Some(Tok::OpenBrace) => {
273 self.at += 1;
274 let mut fields = BTreeMap::new();
275 while self.peek() != Some(&Tok::CloseBrace) {
276 let field = self.ident()?;
277 self.expect(&Tok::Equals)?;
278 fields.insert(field, self.input_expr()?);
279 self.skip_semi();
280 }
281 self.expect(&Tok::CloseBrace)?;
282 Ok(InputExpr::Record(fields))
283 }
284 Some(Tok::OpenBracket) => {
285 self.at += 1;
286 let mut items = Vec::new();
287 while self.peek() != Some(&Tok::CloseBracket) {
288 items.push(self.input_expr()?);
289 if self.peek() == Some(&Tok::Comma) {
290 self.at += 1;
291 } else {
292 break;
293 }
294 }
295 self.expect(&Tok::CloseBracket)?;
296 Ok(InputExpr::List(items))
297 }
298 Some(Tok::Ident(reference)) => {
299 let reference = reference.clone();
300 self.at += 1;
301 reference
302 .strip_suffix(".out")
303 .map(|node| InputExpr::FromNode(node.to_string()))
304 .ok_or_else(|| {
305 SpecError::Malformed(format!(
306 "`{reference}` is not a node reference — expected `<node>.out`"
307 ))
308 })
309 }
310 _ => Err(SpecError::Malformed(format!(
311 "expected a node reference, `{{...}}`, or `[...]`, found {}",
312 self.here()
313 ))),
314 }
315 }
316
317 pub fn branches(&mut self) -> Result<(Branches, usize), SpecError> {
321 self.expect(&Tok::OpenBrace)?;
322 let mut decisions = Vec::new();
323 while self.peek().is_some() && self.peek() != Some(&Tok::CloseBrace) {
324 let node_name = self.ident()?;
325 self.expect(&Tok::Equals)?;
326 self.expect(&Tok::OpenBrace)?;
327 let mut labels = Vec::new();
328 while self.peek() != Some(&Tok::CloseBrace) {
329 let label = self.string()?;
330 self.expect(&Tok::Arrow)?;
331 let target = self.ident()?;
332 labels.push((label, target));
333 self.skip_semi();
334 }
335 self.expect(&Tok::CloseBrace)?;
336 decisions.push((node_name, labels));
337 self.skip_semi();
338 }
339 self.expect(&Tok::CloseBrace)?;
340 Ok((Branches { decisions }, self.at))
341 }
342}
343
344#[cfg(test)]
345mod is_simple_chain_tests {
346 use super::*;
347
348 fn node(block: &str, input: Option<InputExpr>) -> Node {
349 Node {
350 block: PathBuf::from(block),
351 input,
352 repeat_until: None,
353 max_iterations: None,
354 }
355 }
356
357 fn from_node(name: &str) -> InputExpr {
358 InputExpr::FromNode(name.to_string())
359 }
360
361 #[test]
362 fn a_genuine_three_node_chain_is_simple() {
363 let graph = NodeGraph {
364 nodes: vec![
365 ("a".into(), node("blocks/a", None)),
366 ("b".into(), node("blocks/b", Some(from_node("a")))),
367 ("c".into(), node("blocks/c", Some(from_node("b")))),
368 ],
369 };
370 assert!(is_simple_chain(&graph, &Branches::default()));
371 }
372
373 #[test]
374 fn record_fan_in_is_not_simple() {
375 let mut fields = BTreeMap::new();
376 fields.insert("x".to_string(), from_node("a"));
377 fields.insert("y".to_string(), from_node("b"));
378 let graph = NodeGraph {
379 nodes: vec![
380 ("a".into(), node("blocks/a", None)),
381 ("b".into(), node("blocks/b", None)),
382 (
383 "c".into(),
384 node("blocks/c", Some(InputExpr::Record(fields))),
385 ),
386 ],
387 };
388 assert!(!is_simple_chain(&graph, &Branches::default()));
389 }
390
391 #[test]
392 fn list_fan_in_is_not_simple() {
393 let graph = NodeGraph {
394 nodes: vec![
395 ("a".into(), node("blocks/a", None)),
396 ("b".into(), node("blocks/b", None)),
397 (
398 "c".into(),
399 node(
400 "blocks/c",
401 Some(InputExpr::List(vec![from_node("a"), from_node("b")])),
402 ),
403 ),
404 ],
405 };
406 assert!(!is_simple_chain(&graph, &Branches::default()));
407 }
408
409 #[test]
410 fn a_repeat_until_node_is_not_simple() {
411 let mut looped = node("blocks/b", Some(from_node("a")));
412 looped.repeat_until = Some("done".to_string());
413 looped.max_iterations = Some(5);
414 let graph = NodeGraph {
415 nodes: vec![("a".into(), node("blocks/a", None)), ("b".into(), looped)],
416 };
417 assert!(!is_simple_chain(&graph, &Branches::default()));
418 }
419
420 #[test]
421 fn a_branches_decision_is_not_simple() {
422 let graph = NodeGraph {
423 nodes: vec![
424 ("a".into(), node("blocks/a", None)),
425 ("b".into(), node("blocks/b", Some(from_node("a")))),
426 ],
427 };
428 let branches = Branches {
429 decisions: vec![("a".to_string(), vec![("done".to_string(), "b".to_string())])],
430 };
431 assert!(!is_simple_chain(&graph, &branches));
432 }
433
434 #[test]
446 fn fan_out_from_a_shared_predecessor_is_not_simple() {
447 let graph = NodeGraph {
448 nodes: vec![
449 ("a".into(), node("blocks/a", None)),
450 ("b".into(), node("blocks/b", Some(from_node("a")))),
451 ("c".into(), node("blocks/c", Some(from_node("a")))),
452 ],
453 };
454 assert!(!is_simple_chain(&graph, &Branches::default()));
455 }
456}