dot_parser/
ast.rs

1//! This module implements an Abstract Syntax Tree for Dot graphs. The main
2//! structure is `Graph`, which corresponds to the `graph` non-terminal in the
3//! grammar.
4
5use pest::iterators::Pair;
6use pest::Parser;
7
8pub use either;
9use either::Either;
10use std::collections::HashSet;
11use std::error::Error;
12use std::fmt::{Display, Formatter};
13use std::iter::FromIterator;
14use std::path::Path;
15
16#[cfg(feature = "to_tokens")]
17use quote::{ToTokens, quote, TokenStreamExt};
18#[cfg(feature = "to_tokens")]
19use proc_macro2::*;
20
21mod parser {
22    use pest_derive::Parser;
23
24    #[derive(Parser)]
25    #[grammar = "parser/dot.pest"]
26    pub struct DotParser;
27}
28
29use self::parser::DotParser;
30use self::parser::Rule;
31
32/// Type for errors that occur when parsing.
33pub type PestError = pest::error::Error<Rule>;
34/// Type for I/O related errors. Those may occur when reading a file for parsing.
35pub type IOError = std::io::Error;
36
37/// This enum type contains errors that can occur when using a DotParser. In principle, those
38/// errors should never occur, as all parsing errors should be caught by DotParser::parse.
39/// Therefore, if such error occurs, it is a bug, probably a missing feature.
40#[derive(Debug, Clone)]
41pub enum ParseError<'a> {
42    /// This variant represents the case where we expect one of several rules, but we actually find
43    /// another.
44    ExpectRule {
45        /// The list of accepted `Rule`s.
46        expect: Vec<Rule>,
47        /// The `Rule` actually found.
48        found: Rule,
49    },
50    /// This variant represents the case where we expect a `Pair` but none is present.
51    MissingPair {
52        /// The parent pair, i.e. the one that terminates too early.
53        parent: Pair<'a, Rule>,
54        /// The missing pair should have one of the `Rule` in `expect`.
55        expect: Vec<Rule>,
56    },
57}
58
59impl<'a> ParseError<'a> {
60    fn expect_rule(expect: Vec<Rule>, found: Rule) -> Self {
61        ParseError::ExpectRule { expect, found }
62    }
63
64    fn missing_pair(parent: Pair<'a, Rule>, expect: Vec<Rule>) -> Self {
65        ParseError::MissingPair { parent, expect }
66    }
67}
68
69impl Display for ParseError<'_> {
70    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
71        match self {
72            ParseError::ExpectRule { expect, found } => {
73                let expected = expect
74                    .iter()
75                    .fold(String::new(), |acc, r| format!("{}\"{:?}\", ", acc, r));
76                write!(
77                    f,
78                    "Expect one rule of {}but \"{:?}\" found.",
79                    expected, found
80                )?;
81            }
82            ParseError::MissingPair { parent, expect } => {
83                let mut expected = expect
84                    .iter()
85                    .fold(String::new(), |acc, r| format!("{}\"{:?}\", ", acc, r));
86                // Removing the final ", "
87                expected.pop();
88                expected.pop();
89                write!(
90                    f,
91                    "The Pair:\n{}\nterminates early. Expected one of {}.",
92                    parent.as_str(),
93                    expected
94                )?;
95            }
96        }
97        Ok(())
98    }
99}
100
101impl Error for ParseError<'_> {}
102
103/// An error that can occur when reading from a file.
104#[derive(Debug)]
105pub enum GraphFromFileError<'a> {
106    /// The error occured when manipulating the file (e.g. the file does not exist).
107    FileError(IOError),
108    /// The error occured when parsing the file (e.g. the pest parser returned an error).
109    PestParseError(PestError),
110    /// The error occured when traversing the `Rule`s tree returned by the pest parser.
111    /// Such error occuring is likely a bug or a missing feature of the library.
112    ParseError(ParseError<'a>),
113}
114
115impl Display for GraphFromFileError<'_> {
116    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
117        match self {
118            Self::FileError(e) => write!(f, "{}", e),
119            Self::PestParseError(e) => write!(f, "{}", e),
120            Self::ParseError(e) => write!(f, "{}", e),
121        }
122    }
123}
124
125impl Error for GraphFromFileError<'_> {}
126
127impl From<IOError> for GraphFromFileError<'_> {
128    fn from(e: IOError) -> Self {
129        Self::FileError(e)
130    }
131}
132
133impl From<PestError> for GraphFromFileError<'_> {
134    fn from(e: PestError) -> Self {
135        Self::PestParseError(e)
136    }
137}
138
139impl<'a> From<ParseError<'a>> for GraphFromFileError<'a> {
140    fn from(e: ParseError<'a>) -> Self {
141        Self::ParseError(e)
142    }
143}
144
145#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
146/// This structure is an AST for the DOT graph language.  The generic `A`
147/// denotes the type of attributes. By default (and when parsing), it is
148/// `(&'a str, &'a str)`, i.e. two strings, one for the key and one for the
149/// value of the attribute. The library provides functions to map from one type
150/// to an other.
151pub struct Graph<A> {
152    /// Specifies if the `Graph` is strict or not. A "strict" graph must not
153    /// contain the same edge multiple times. Notice that, for undirected edge,
154    /// an edge from `A` to `B` and an edge from `B` to `A` are equals.
155    pub strict: bool,
156    /// Specifies if the `Graph` is directed.
157    pub is_digraph: bool,
158    /// The name of the `Graph`, if any.
159    pub name: Option<String>,
160    /// The statements that describe the graph.
161    pub stmts: StmtList<A>,
162}
163
164#[cfg(feature = "to_tokens")]
165impl ToTokens for Graph<(String, String)> {
166    fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
167        let name = match &self.name {
168            Some(name) => quote! { std::option::Option::Some( #name.to_string() ) },
169            None => quote! { std::option::Option::None },
170        };
171        let strict = self.strict;
172        let is_digraph = self.is_digraph;
173        let stmts = &self.stmts;
174        let tokens = quote! {
175            dot_parser::ast::Graph::<(&'static str, &'static str)> {
176                strict: #strict,
177                is_digraph: #is_digraph,
178                name: #name,
179                stmts: #stmts
180            }
181        };
182        ts.append_all(tokens);
183    }
184}
185
186impl<'a, A> TryFrom<Pair<'a, Rule>> for Graph<A>
187where
188    AList<A>: TryFrom<Pair<'a, Rule>, Error = ParseError<'a>>
189{
190    type Error = ParseError<'a>;
191    fn try_from(p: Pair<'a, Rule>) -> Result<Self, ParseError<'a>> {
192        let mut inner = p.clone().into_inner();
193        let mut strict = false;
194        let mut name = None;
195        let mut pair = inner.next().ok_or(ParseError::missing_pair(
196            p.clone(),
197            vec![Rule::strict, Rule::digraph, Rule::graph],
198        ))?;
199        if let Rule::strict = pair.as_rule() {
200            strict = true;
201            pair = inner.next().ok_or(ParseError::missing_pair(
202                p.clone(),
203                vec![Rule::digraph, Rule::graph],
204            ))?;
205        }
206        let is_digraph = match pair.as_rule() {
207            Rule::digraph => true,
208            Rule::graph => false,
209            r => {
210                return Err(ParseError::expect_rule(vec![Rule::digraph, Rule::graph], r));
211            }
212        };
213        pair = inner.next().ok_or(ParseError::missing_pair(
214            p.clone(),
215            vec![Rule::ident, Rule::stmt_list],
216        ))?;
217        if let Rule::ident = pair.as_rule() {
218            name = Some(String::from(pair.as_str()));
219            pair = inner
220                .next()
221                .ok_or(ParseError::missing_pair(p.clone(), vec![Rule::stmt_list]))?;
222        }
223        let stmts = StmtList::try_from(pair)?;
224        Ok(Graph {
225            strict,
226            is_digraph,
227            name,
228            stmts,
229        })
230    }
231}
232
233impl Graph<(String, String)> {
234// Those errors are rarely return, so we can afford the size.
235#[allow(clippy::result_large_err)]
236    /// Parses a graph from a file, which path is given. Notice that when doing so, attributes are
237    /// of type `(String, String)`, not the default `(&'a str, &'a str)`, since we are reading and taking
238    /// ownership of the content of the file.
239    pub fn from_file<'a, P>(p: P) -> Result<Self, GraphFromFileError<'a>>
240    where
241        P: AsRef<Path>,
242    {
243        let s = std::fs::read_to_string(p)?;
244        let mut pairs = DotParser::parse(Rule::dotgraph, &s)?;
245        let pair = pairs.next().expect("The toplevel `Pairs` is empty.");
246        match Graph::try_from(pair) {
247            Ok(g) => Ok(g),
248            Err(e) => panic!("{}", e),
249        }
250    }
251}
252
253impl<'a> TryFrom<&'a str> for Graph<(&'a str, &'a str)> {
254    type Error = PestError;
255    fn try_from(s: &'a str) -> Result<Self, PestError> {
256        let mut pairs = DotParser::parse(Rule::dotgraph, s)?;
257        match pairs.next() {
258            None => {
259                panic!("The toplevel `Pairs` is empty.")
260            }
261            Some(pair) => Ok(Graph::try_from(pair).unwrap()),
262        }
263    }
264}
265
266impl<A> Graph<A> {
267    /// Filter and map attributes. The main intended usage of this function is
268    /// to convert attributes as `&'a str` into an enum, e.g.
269    /// to convert `["label"="whatever", "color"="foo"]` into
270    /// `[Attr::Label(whatever), Attr::Color(foo)]`.
271    ///
272    /// To take into account non-standard attributes, the `Attr` enum has to be
273    /// provided by the user.
274    pub fn filter_map<B>(self, f: &dyn Fn(A) -> Option<B>) -> Graph<B> {
275        let new_stmts: StmtList<B> = self.stmts.filter_map_attr(f);
276        Graph {
277            strict: self.strict,
278            is_digraph: self.is_digraph,
279            name: self.name,
280            stmts: new_stmts,
281        }
282    }
283
284    /// Returns all `NodeID`s that appear in the graph.
285    pub fn get_node_ids(self) -> HashSet<NodeID> {
286        let clone = self.stmts;
287        clone.get_node_ids()
288    }
289}
290
291/// This is a thin wrapper over vectors of `[Graph]` in order to read files with multiple dotgraph descriptions.
292pub struct Graphs<A> {
293    /// The set of graphs.
294    pub graphs: Vec::<Graph<A>>
295}
296
297impl <'a, A> TryFrom<Pair<'a, Rule>> for Graphs<A>
298where
299    AList<A>: TryFrom<Pair<'a, Rule>, Error = ParseError<'a>>
300{
301    type Error = ParseError<'a>;
302    fn try_from(p: Pair<'a, Rule>) -> Result<Self, ParseError<'a>> {
303        let mut inner = p.clone().into_inner().into_iter();
304        let first_graph_pair = inner.next().ok_or(ParseError::missing_pair(
305            p.clone(),
306            vec![Rule::dotfile],
307        ))?;
308        let mut graphs = vec!(Graph::try_from(first_graph_pair)?);
309        for p in inner {
310            graphs.push(Graph::try_from(p)?);
311        }
312        Ok(Graphs { graphs })
313    }
314}
315
316impl<'a> TryFrom<&'a str> for Graphs<(&'a str, &'a str)> {
317    type Error = PestError;
318    fn try_from(s: &'a str) -> Result<Self, PestError> {
319        DotParser::parse(Rule::dotfile, s).map(|mut p| match p.next() {
320            None => {
321                panic!("The toplevel `Pairs` is empty.")
322            }
323            Some(pair) => match Graphs::try_from(pair) {
324                Ok(g) => g,
325                Err(e) => panic!("{}", e),
326            },
327        })
328    }
329}
330
331impl Graphs<(String, String)> {
332// Those errors are rarely return, so we can afford the size.
333#[allow(clippy::result_large_err)]
334    /// Parses multiple graphs from a file, which path is given. Notice that when doing so, attributes are
335    /// of type `(String, String)`, not the default `(&'a str, &'a str)`, since we are reading and taking
336    /// ownership of the content of the file.
337    pub fn from_file<'a, P>(p: P) -> Result<Self, GraphFromFileError<'a>>
338    where
339        P: AsRef<Path>,
340    {
341        let s = std::fs::read_to_string(p)?;
342        let mut pairs = DotParser::parse(Rule::dotfile, &s)?;
343        println!("{:#?}", pairs);
344        let pair = pairs.next().expect("The toplevel `Pairs` is empty.");
345        match Graphs::try_from(pair) {
346            Ok(g) => Ok(g),
347            Err(e) => panic!("{}", e),
348        }
349    }
350}
351
352/// A list of statements. This corresponds to the `stmt_list` non-terminal of the
353/// grammar.
354#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
355pub struct StmtList<A> {
356    /// The list of statements.
357    pub stmts: Vec<Stmt<A>>,
358}
359
360#[cfg(feature = "to_tokens")]
361impl ToTokens for StmtList<(String, String)> {
362    fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
363        let stmts = &self.stmts;
364        let tokens = quote! {
365            dot_parser::ast::StmtList {
366                stmts: std::vec![ #( #stmts ),* ],
367            }
368        };
369        ts.append_all(tokens);
370    }
371}
372
373impl<'a, A> TryFrom<Pair<'a, Rule>> for StmtList<A>
374where
375    AList<A>: TryFrom<Pair<'a, Rule>, Error = ParseError<'a>>
376{
377    type Error = ParseError<'a>;
378    fn try_from(p: Pair<'a, Rule>) -> Result<Self, ParseError<'a>> {
379        let inner = p.into_inner();
380        let mut stmts = Vec::new();
381        for stmt in inner {
382            stmts.push(Stmt::try_from(stmt)?);
383        }
384        Ok(StmtList { stmts })
385    }
386}
387
388impl<'a, A> StmtList<A> {
389    fn filter_map_attr<B>(self, f: &'a dyn Fn(A) -> Option<B>) -> StmtList<B> {
390        self.stmts
391            .into_iter()
392            .map(|stmt| stmt.filter_map_attr(f))
393            .collect()
394    }
395
396    fn get_node_ids(&self) -> HashSet<NodeID> {
397        let mut hs = HashSet::new();
398        for stmt in self {
399            hs = hs.union(&stmt.get_node_ids()).cloned().collect();
400        }
401        hs
402    }
403}
404
405impl<A> IntoIterator for StmtList<A> {
406    type Item = Stmt<A>;
407    type IntoIter = std::vec::IntoIter<Self::Item>;
408
409    fn into_iter(self) -> Self::IntoIter {
410        self.stmts.into_iter()
411    }
412}
413
414impl<'a, A> IntoIterator for &'a StmtList<A> {
415    type Item = &'a Stmt<A>;
416    type IntoIter = std::slice::Iter<'a, Stmt<A>>;
417
418    fn into_iter(self) -> Self::IntoIter {
419        self.stmts.iter()
420    }
421}
422
423impl<A> FromIterator<Stmt<A>> for StmtList<A> {
424    fn from_iter<T>(iter: T) -> Self
425    where
426        T: IntoIterator<Item = Stmt<A>>,
427    {
428        Self {
429            stmts: iter.into_iter().collect(),
430        }
431    }
432}
433
434/// A statement of the graph. This corresponds to the `stmt` non-terminal of the
435/// grammar.
436#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
437pub enum Stmt<A> {
438    /// A node statement.
439    NodeStmt(NodeStmt<A>),
440    /// An edge statement.
441    EdgeStmt(EdgeStmt<A>),
442    /// An attribute statement.
443    AttrStmt(AttrStmt<A>),
444    /// An alias statement.
445    IDEq(String, String),
446    /// A subgraph.
447    Subgraph(Subgraph<A>),
448}
449
450#[cfg(feature = "to_tokens")]
451impl ToTokens for Stmt<(String, String)> {
452    fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
453        let tokens = match &self {
454            Self::NodeStmt(stmt) => {
455                quote! {
456                    dot_parser::ast::Stmt::NodeStmt( #stmt )
457                }
458            },
459            Self::EdgeStmt(stmt) => {
460                quote! {
461                    dot_parser::ast::Stmt::EdgeStmt( #stmt )
462                }
463            },
464            Self::AttrStmt(stmt) => {
465                quote! {
466                    dot_parser::ast::Stmt::AttrStmt( #stmt )
467                }
468            },
469            Self::IDEq(s1, s2) => {
470                quote!{
471                    dot_parser::ast::Stmt::IDEq( #s1.to_string(), #s2.to_string() )
472                }
473            },
474            Self::Subgraph(sub) => {
475                quote!{
476                    dot_parser::ast::Stmt::Subgraph( #sub )
477                }
478            },
479        };
480        ts.append_all(tokens);
481    }
482}
483
484impl<'a, A> TryFrom<Pair<'a, Rule>> for Stmt<A>
485where
486    AList<A>: TryFrom<Pair<'a, Rule>, Error = ParseError<'a>>
487{
488    type Error = ParseError<'a>;
489    fn try_from(p: Pair<'a, Rule>) -> Result<Self, ParseError<'a>> {
490        let inner = p
491            .clone()
492            .into_inner()
493            .next()
494            .ok_or(ParseError::missing_pair(
495                p.clone(),
496                std::vec![
497                    Rule::node_stmt,
498                    Rule::edge_stmt,
499                    Rule::attr_stmt,
500                    Rule::id_eq,
501                    Rule::subgraph,
502                ],
503            ))?;
504        match inner.as_rule() {
505            Rule::node_stmt => NodeStmt::try_from(inner).map(Stmt::NodeStmt),
506            Rule::edge_stmt => EdgeStmt::try_from(inner).map(Stmt::EdgeStmt),
507            Rule::attr_stmt => AttrStmt::try_from(inner).map(Stmt::AttrStmt),
508            Rule::id_eq => {
509                let mut inners = inner.into_inner();
510                let id1 = inners
511                    .next()
512                    .ok_or(ParseError::missing_pair(p.clone(), vec![Rule::ident]))?
513                    .as_str().into();
514                let id2 = inners
515                    .next()
516                    .ok_or(ParseError::missing_pair(p.clone(), vec![Rule::ident]))?
517                    .as_str().into();
518                Ok(Stmt::IDEq(id1, id2))
519            }
520            Rule::subgraph => Subgraph::try_from(inner).map(Stmt::Subgraph),
521            other => {
522                let error = ParseError::expect_rule(
523                    vec![
524                        Rule::node_stmt,
525                        Rule::edge_stmt,
526                        Rule::attr_stmt,
527                        Rule::id_eq,
528                        Rule::subgraph,
529                    ],
530                    other,
531                );
532                Err(error)
533            }
534        }
535    }
536}
537
538impl<'a, A> Stmt<A> {
539    /// Convert a statement with attributes of type `A` into a statement with
540    /// attributes of type `B`.
541    fn filter_map_attr<B>(self, f: &'a dyn Fn(A) -> Option<B>) -> Stmt<B> {
542        match self {
543            Stmt::NodeStmt(node) => Stmt::NodeStmt(node.filter_map_attr(f)),
544            Stmt::EdgeStmt(edge) => Stmt::EdgeStmt(edge.filter_map_attr(f)),
545            Stmt::AttrStmt(attr) => Stmt::AttrStmt(attr.filter_map_attr(f)),
546            Stmt::IDEq(a, b) => Stmt::IDEq(a, b),
547            Stmt::Subgraph(sub) => Stmt::Subgraph(sub.filter_map_attr(f)),
548        }
549    }
550
551    /// Returns true if `self` is a `NodeStmt` variant.
552    pub fn is_node_stmt(&self) -> bool {
553        matches!(self, Stmt::NodeStmt(_))
554    }
555
556    /// Returns `Some(&node)` if `&self` if a `&NodeStmt(node)`, and `None`
557    /// otherwise.
558    pub fn get_node_ref(&self) -> Option<&NodeStmt<A>> {
559        if let Stmt::NodeStmt(node) = self {
560            Some(node)
561        } else {
562            None
563        }
564    }
565
566    /// Returns `Some(node)` if `self` if a `NodeStmt(node)`, and `None`
567    /// otherwise.
568    pub fn get_node(self) -> Option<NodeStmt<A>> {
569        if let Stmt::NodeStmt(node) = self {
570            Some(node)
571        } else {
572            None
573        }
574    }
575
576    /// Returns true if `self` is a `EdgeStmt` variant.
577    pub fn is_edge_stmt(&self) -> bool {
578        matches!(self, Stmt::EdgeStmt(_))
579    }
580
581    /// Returns `Some(&edge)` if `&self` if a `&EdgeStmt(edge)`, and `None`
582    /// otherwise.
583    pub fn get_edge_ref(&self) -> Option<&EdgeStmt<A>> {
584        if let Stmt::EdgeStmt(edge) = self {
585            Some(edge)
586        } else {
587            None
588        }
589    }
590
591    /// Returns `Some(edge)` if `self` if a `EdgeStmt(edge)`, and `None`
592    /// otherwise.
593    pub fn get_edge(self) -> Option<EdgeStmt<A>> {
594        if let Stmt::EdgeStmt(edge) = self {
595            Some(edge)
596        } else {
597            None
598        }
599    }
600
601    /// Returns true if `self` is a `AttrStmt` variant.
602    pub fn is_attr_stmt(&self) -> bool {
603        matches!(self, Stmt::AttrStmt(_))
604    }
605
606    /// Returns `Some(&attr)` if `&self` if a `&AttrStmt(attr)`, and `None`
607    /// otherwise.
608    pub fn get_attr_ref(&self) -> Option<&AttrStmt<A>> {
609        if let Stmt::AttrStmt(attr) = self {
610            Some(attr)
611        } else {
612            None
613        }
614    }
615
616    /// Returns `Some(attr)` if `self` if a `AttrStmt(attr)`, and `None`
617    /// otherwise.
618    pub fn get_attr(self) -> Option<AttrStmt<A>> {
619        if let Stmt::AttrStmt(attr) = self {
620            Some(attr)
621        } else {
622            None
623        }
624    }
625
626    /// Returns true if `self` is a `IDEq` variant.
627    pub fn is_ideq_stmt(&self) -> bool {
628        matches!(self, Stmt::IDEq(..))
629    }
630
631    /// Returns `Some((&id1, &id2))` if `&self` if a `&IDEq(id1, id2)` and `None`
632    /// otherwise.
633    pub fn get_ideq_ref(&self) -> Option<(&str, &str)> {
634        if let Stmt::IDEq(id1, id2) = self {
635            Some((id1, id2))
636        } else {
637            None
638        }
639    }
640
641    /// Returns `true` if `self` is a `Subgraph` variant.
642    pub fn is_subgraph(&self) -> bool {
643        matches!(self, Stmt::Subgraph(..))
644    }
645
646    /// Returns all `NodeID`s that appear in this statement.
647    fn get_node_ids(&self) -> HashSet<NodeID> {
648        match self {
649            Stmt::Subgraph(g) => g.get_node_ids(),
650            Stmt::EdgeStmt(e) => e.get_node_ids(),
651            Stmt::NodeStmt(n) => HashSet::from_iter([n.get_node_id().clone()]),
652            _ => HashSet::new(),
653        }
654    }
655}
656
657/// An attribute statement. This corresponds to the rule `attr_stmt`
658/// non-terminal of the grammar.
659#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
660pub enum AttrStmt<A> {
661    /// An `AttrStmt` on the whole graph.
662    Graph(AttrList<A>),
663    /// An `AttrStmt` on each nodes of the graph.
664    Node(AttrList<A>),
665    /// An `AttrStmt` on each edges of the graph.
666    Edge(AttrList<A>),
667}
668
669#[cfg(feature = "to_tokens")]
670impl ToTokens for AttrStmt<(String, String)> {
671    fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
672          let new_tokens = match self {
673            AttrStmt::Graph(attrs) => quote!{ dot_parser::ast::AttrStmt::Graph(#attrs) },
674            AttrStmt::Node(attrs) => quote!{ dot_parser::ast::AttrStmt::Node(#attrs) },
675            AttrStmt::Edge(attrs) => quote!{ dot_parser::ast::AttrStmt::Edge(#attrs) },
676          };
677          ts.append_all(new_tokens);
678    }
679}
680
681impl<'a, A> TryFrom<Pair<'a, Rule>> for AttrStmt<A>
682where
683    AList<A>: TryFrom<Pair<'a, Rule>, Error = ParseError<'a>>
684{
685    type Error = ParseError<'a>;
686    fn try_from(p: Pair<'a, Rule>) -> Result<Self, ParseError<'a>> {
687        let mut inners = p.clone().into_inner();
688        let kind = inners
689            .next()
690            .ok_or(ParseError::missing_pair(
691                p.clone(),
692                vec![Rule::graph, Rule::node, Rule::edge],
693            ))?
694            .as_rule();
695        let attr_list_pair = inners
696            .next()
697            .ok_or(ParseError::missing_pair(p, vec![Rule::attr_list]))?;
698        let attr = AttrList::try_from(attr_list_pair)?;
699        match kind {
700            Rule::graph => Ok(AttrStmt::Graph(attr)),
701            Rule::node => Ok(AttrStmt::Node(attr)),
702            Rule::edge => Ok(AttrStmt::Edge(attr)),
703            r => Err(ParseError::expect_rule(
704                vec![Rule::graph, Rule::node, Rule::edge],
705                r,
706            )),
707        }
708    }
709}
710
711impl<A> AttrStmt<A> {
712    pub (in crate) fn filter_map_attr<B>(self, f: &dyn Fn(A) -> Option<B>) -> AttrStmt<B> {
713        match self {
714            AttrStmt::Graph(attr) => AttrStmt::Graph(attr.filter_map_attr(f)),
715            AttrStmt::Node(attr) => AttrStmt::Node(attr.filter_map_attr(f)),
716            AttrStmt::Edge(attr) => AttrStmt::Edge(attr.filter_map_attr(f)),
717        }
718    }
719}
720
721/// A list of `AList`s, i.e. a list of list of attributes. This (strange)
722/// indirection is induced by the grammar. This structure corresponds to the
723/// `attr_list` non-terminal of the grammar.
724///
725/// Notice methods `flatten` and `flatten_ref` to remove the indirection.
726#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
727pub struct AttrList<A> {
728    /// The list of `AList`s.
729    pub elems: Vec<AList<A>>,
730}
731
732#[cfg(feature = "to_tokens")]
733impl ToTokens for AttrList<(String, String)> {
734    fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
735        let elems = &self.elems;
736        let tokens = quote! {
737            dot_parser::ast::AttrList {
738                elems: std::vec![ #( #elems ),* ]
739            }
740        };
741        ts.append_all(tokens);
742    }
743}
744
745impl<'a, A> TryFrom<Pair<'a, Rule>> for AttrList<A>
746where
747    AList<A>: TryFrom<Pair<'a, Rule>, Error = ParseError<'a>>
748{
749    type Error = ParseError<'a>;
750    fn try_from(p: Pair<'a, Rule>) -> Result<Self, ParseError<'a>> {
751        let mut v: Vec<AList<A>> = Vec::new();
752        let mut inners = p.clone().into_inner();
753        let alist_pair = inners
754            .next()
755            .ok_or(ParseError::missing_pair(p, vec![Rule::a_list]))?;
756        let alist = AList::try_from(alist_pair)?;
757        let mut tail = inners
758            .next()
759            .map(|p| {
760                AttrList::try_from(p)
761                    .map(|alist| alist.elems)
762                    .unwrap_or_default()
763            })
764            .unwrap_or_default();
765        v.push(alist);
766        v.append(&mut tail);
767
768        Ok(AttrList { elems: v })
769    }
770}
771
772impl<A> AttrList<A> {
773    pub (in crate) fn filter_map_attr<B>(self, f: &dyn Fn(A) -> Option<B>) -> AttrList<B> {
774        AttrList {
775            elems: self
776                .into_iter()
777                .map(|alist| alist.filter_map_attr(f))
778                .collect(),
779        }
780    }
781
782    /// Flatten the nested `AList`s: returns a single `AList` that contains all
783    /// attributes contained in the `AttrList`.
784    pub fn flatten(self) -> AList<A> {
785        self.into()
786    }
787
788    /// Flatten the nested `AList`s: returns a single `AList` that contains all
789    /// attributes contained in the `AttrList`.
790    pub fn flatten_ref(&self) -> AList<&A> {
791        self.into()
792    }
793}
794
795impl<A> FromIterator<AList<A>> for AttrList<A> {
796    fn from_iter<T>(iter: T) -> Self
797    where
798        T: IntoIterator<Item = AList<A>>,
799    {
800        Self {
801            elems: iter.into_iter().map(|u| u.into_iter().collect()).collect(),
802        }
803    }
804}
805
806impl<A> IntoIterator for AttrList<A> {
807    type Item = AList<A>;
808    type IntoIter = std::vec::IntoIter<Self::Item>;
809
810    fn into_iter(self) -> Self::IntoIter {
811        self.elems.into_iter()
812    }
813}
814
815impl<'a, A> IntoIterator for &'a AttrList<A> {
816    type Item = &'a AList<A>;
817    type IntoIter = std::slice::Iter<'a, AList<A>>;
818
819    fn into_iter(self) -> Self::IntoIter {
820        self.elems.iter()
821    }
822}
823
824/// A list of attributes. This corresponds to the `a_list` non-terminal of the
825/// grammar.
826#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
827pub struct AList<A> {
828    /// The attributes in the list.
829    pub elems: Vec<A>,
830}
831
832#[cfg(feature = "to_tokens")]
833impl ToTokens for AList<(String, String)> {
834    fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
835        let elems = &self.elems;
836        let elems = elems.iter().map(|(s1, s2)| quote!{ (#s1, #s2) });
837        let tokens = quote! {
838            dot_parser::ast::AList {
839                elems: std::vec![ #( #elems ),* ]
840            }
841        };
842        ts.append_all(tokens);
843    }
844}
845
846#[cfg(feature = "display")]
847impl<A> Display for AList<A>
848where
849    A: Display,
850{
851    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
852        for attr in &self.elems {
853            write!(f, "{}; ", attr)?;
854        }
855        Ok(())
856    }
857}
858
859impl<'a> TryFrom<Pair<'a, Rule>> for AList<(&'a str, &'a str)> {
860    type Error = ParseError<'a>;
861    fn try_from(p: Pair<'a, Rule>) -> Result<Self, ParseError<'a>> {
862        let mut v = Vec::new();
863        let mut inners = p.clone().into_inner();
864        let id1 = inners
865            .next()
866            .ok_or(ParseError::missing_pair(p.clone(), vec![Rule::ident]))?
867            .as_str();
868        let id2 = inners
869            .next()
870            .ok_or(ParseError::missing_pair(p.clone(), vec![Rule::ident]))?
871            .as_str();
872        let mut tail = inners
873            .next()
874            .map(|p| AList::try_from(p).map(|alist| alist.elems).unwrap_or_default())
875            .unwrap_or_default();
876        v.push((id1, id2));
877        v.append(&mut tail);
878
879        Ok(AList {
880            elems: v,
881        })
882    }
883}
884
885impl<'a> TryFrom<Pair<'a, Rule>> for AList<(String, String)> {
886    type Error = ParseError<'a>;
887    fn try_from(p: Pair<'a, Rule>) -> Result<Self, ParseError<'a>> {
888        let mut v: Vec<(String, String)> = Vec::new();
889        let mut inners = p.clone().into_inner();
890        let id1 = inners
891            .next()
892            .ok_or(ParseError::missing_pair(p.clone(), vec![Rule::ident]))?
893            .as_str().into();
894        let id2 = inners
895            .next()
896            .ok_or(ParseError::missing_pair(p.clone(), vec![Rule::ident]))?
897            .as_str().into();
898        let mut tail = inners
899            .next()
900            .map(|p| AList::try_from(p).map(|alist| alist.elems).unwrap_or_default())
901            .unwrap_or_default();
902        v.push((id1, id2));
903        v.append(&mut tail);
904
905        Ok(AList {
906            elems: v,
907        })
908    }
909}
910
911impl<A> AList<A> {
912    pub(crate) fn filter_map_attr<B>(self, f: &dyn Fn(A) -> Option<B>) -> AList<B> {
913        AList {
914            elems: self.into_iter().filter_map(f).collect(),
915        }
916    }
917
918    pub(crate) fn empty() -> Self {
919        AList {
920            elems: Vec::new(),
921        }
922    }
923
924    #[cfg(feature = "display")]
925    /// Returns `true` if the list of attributes is empty
926    pub(crate) fn is_empty(&self) -> bool {
927        self.elems.is_empty()
928    }
929}
930
931impl<A> FromIterator<A> for AList<A> {
932    fn from_iter<T>(iter: T) -> Self
933    where
934        T: IntoIterator<Item = A>,
935    {
936        Self {
937            elems: iter.into_iter().collect(),
938        }
939    }
940}
941
942impl<A> IntoIterator for AList<A> {
943    type Item = A;
944    type IntoIter = std::vec::IntoIter<Self::Item>;
945
946    fn into_iter(self) -> Self::IntoIter {
947        self.elems.into_iter()
948    }
949}
950
951impl<'a, A> IntoIterator for &'a AList<A> {
952    type Item = &'a A;
953    type IntoIter = std::slice::Iter<'a, A>;
954
955    fn into_iter(self) -> Self::IntoIter {
956        self.elems.iter()
957    }
958}
959
960impl<A> From<AttrList<A>> for AList<A> {
961    fn from(attr: AttrList<A>) -> Self {
962        attr.into_iter().flatten().collect()
963    }
964}
965
966impl<'a, A> From<&'a AttrList<A>> for AList<&'a A> {
967    fn from(attr: &'a AttrList<A>) -> Self {
968        attr.into_iter().flatten().collect()
969    }
970}
971
972/// The description of an edge. This corresponds to the `edge_stmt` non-terminal
973/// of the grammar.
974#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
975pub struct EdgeStmt<A> {
976    /// The origin of the edge.
977    pub from: Either<NodeID, Subgraph<A>>,
978    /// The destination of the edge.
979    pub next: EdgeRHS<A>,
980    /// The attributes of the edge.
981    pub attr: Option<AttrList<A>>,
982}
983
984#[cfg(feature = "to_tokens")]
985impl ToTokens for EdgeStmt<(String, String)> {
986    fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
987        let from = match &self.from {
988            Either::Left(id) => {
989                quote!{ dot_parser::ast::either::Either::Left( #id ) }
990            },
991            Either::Right(sub) => {
992                quote!{ dot_parser::ast::either::Either::Right( #sub ) }
993            },
994        };
995        let attr = match &self.attr {
996            Some(attr) => {
997                quote!{ std::option::Option::Some( #attr ) }
998            },
999            None => {
1000                quote!{ std::option::Option::None }
1001            },
1002        };
1003        let next = &self.next;
1004        let tokens = quote! {
1005            dot_parser::ast::EdgeStmt {
1006                from: #from,
1007                next: #next,
1008                attr: #attr,
1009            }
1010        };
1011        ts.append_all(tokens);
1012    }
1013}
1014
1015impl<'a, A> TryFrom<Pair<'a, Rule>> for EdgeStmt<A>
1016where
1017    AList<A>: TryFrom<Pair<'a, Rule>, Error = ParseError<'a>>
1018{
1019    type Error = ParseError<'a>;
1020    fn try_from(p: Pair<'a, Rule>) -> Result<Self, ParseError<'a>> {
1021        let mut inners = p.clone().into_inner();
1022        let from_pair = inners.next().ok_or(ParseError::missing_pair(
1023            p.clone(),
1024            vec![Rule::node_id, Rule::subgraph],
1025        ))?;
1026        let rule = from_pair.as_rule();
1027        let from = match rule {
1028            Rule::node_id => Either::Left(NodeID::try_from(from_pair)?),
1029            Rule::subgraph => Either::Right(Subgraph::try_from(from_pair)?),
1030            r => {
1031                return Err(ParseError::expect_rule(
1032                    vec![Rule::node_id, Rule::subgraph],
1033                    r,
1034                ));
1035            }
1036        };
1037        let next = inners
1038            .next()
1039            .map(EdgeRHS::try_from)
1040            .transpose()?
1041            .ok_or(ParseError::missing_pair(p, vec![Rule::edge_rhs]))?;
1042        let attr = inners.next().map(AttrList::try_from).transpose()?;
1043
1044        Ok(EdgeStmt { from, next, attr })
1045    }
1046}
1047
1048impl<'a, A> EdgeStmt<A> {
1049    fn filter_map_attr<B>(self, f: &'a dyn Fn(A) -> Option<B>) -> EdgeStmt<B> {
1050        let new_from = match self.from {
1051            Either::Left(node_id) => Either::Left(node_id),
1052            Either::Right(subgraph) => Either::Right(subgraph.filter_map_attr(f)),
1053        };
1054
1055        let new_next = self.next.filter_map_attr(f);
1056
1057        EdgeStmt {
1058            from: new_from,
1059            next: new_next,
1060            attr: self.attr.map(|a| a.filter_map_attr(f)),
1061        }
1062    }
1063
1064    /// Flatten the EdgeStmt, i.e. removes cases where multiple EdgeRHS are in a single statement.
1065    pub fn flatten(self) -> Vec<EdgeStmt<A>>
1066    where
1067        A: Clone,
1068    {
1069        let mut from = self.from;
1070        let mut to = self.next;
1071        let attr = self.attr;
1072
1073        let mut v = Vec::new();
1074
1075        loop {
1076            let next_step = EdgeStmt {
1077                from: from.clone(),
1078                next: EdgeRHS {
1079                    to: to.to.clone(),
1080                    next: None,
1081                },
1082                attr: attr.clone(),
1083            };
1084            v.push(next_step);
1085            match to.next {
1086                None => return v,
1087                Some(rhs) => {
1088                    from = to.to;
1089                    to = *rhs;
1090                }
1091            }
1092        }
1093    }
1094
1095    fn get_node_ids(&self) -> HashSet<NodeID> {
1096        let mut nexts = self.next.get_node_ids();
1097        match &self.from {
1098            Either::Left(node_id) => {
1099                nexts.insert(node_id.clone());
1100            }
1101            Either::Right(subgraph) => {
1102                return nexts.union(&subgraph.get_node_ids()).cloned().collect();
1103            }
1104        };
1105        nexts
1106    }
1107}
1108
1109/// The Right hand side of an edge description. This corresponds to the
1110/// `EdgeRHS` non-terminal of the grammar.
1111/// Notice that the grammar allows multiple EdgeRHS in sequence, to chain edges:
1112/// `A -> B -> C`.
1113#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
1114pub struct EdgeRHS<A> {
1115    /// The identifier of the destination of the edge.
1116    pub to: Either<NodeID, Subgraph<A>>,
1117    /// A possible chained RHS.
1118    pub next: Option<Box<EdgeRHS<A>>>,
1119}
1120
1121#[cfg(feature = "to_tokens")]
1122impl ToTokens for EdgeRHS<(String, String)> {
1123    fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
1124        let to = match &self.to {
1125            Either::Left(id) => quote!{ dot_parser::ast::either::Either::Left( #id ) },
1126            Either::Right(sub) => quote!{ dot_parser::ast::either::Either::Right( #sub ) },
1127        };
1128        let next = match &self.next {
1129            Some(next) => quote! {
1130                std::option::Option::Some(
1131                    std::boxed::Box::new( #next )
1132                )
1133            },
1134            None => quote! {
1135                std::option::Option::None
1136            }
1137        };
1138        let tokens = quote! {
1139            dot_parser::ast::EdgeRHS {
1140                to: #to,
1141                next: #next,
1142            }
1143        };
1144        ts.append_all(tokens);
1145    }
1146}
1147
1148impl<'a, A> TryFrom<Pair<'a, Rule>> for EdgeRHS<A>
1149where
1150    AList<A>: TryFrom<Pair<'a, Rule>, Error = ParseError<'a>>
1151{
1152    type Error = ParseError<'a>;
1153    fn try_from(p: Pair<'a, Rule>) -> Result<Self, ParseError<'a>> {
1154        let mut inners = p.clone().into_inner();
1155        let to_pair = inners.next().ok_or(ParseError::missing_pair(
1156            p,
1157            vec![Rule::node_id, Rule::subgraph],
1158        ))?;
1159        let to = match to_pair.as_rule() {
1160            Rule::node_id => Either::Left(NodeID::try_from(to_pair)?),
1161            Rule::subgraph => {
1162                // p is necessarily a subgraph.
1163                Either::Right(Subgraph::try_from(to_pair)?)
1164            }
1165            r => {
1166                return Err(ParseError::expect_rule(
1167                    vec![Rule::node_id, Rule::subgraph],
1168                    r,
1169                ));
1170            }
1171        };
1172        let next = inners.next().map(EdgeRHS::try_from).transpose()?.map(Box::new);
1173        Ok(EdgeRHS { to, next })
1174    }
1175}
1176
1177impl<'a, A> EdgeRHS<A> {
1178    fn filter_map_attr<B>(self, f: &'a dyn Fn(A) -> Option<B>) -> EdgeRHS<B> {
1179        let to = match self.to {
1180            Either::Left(node_id) => Either::Left(node_id),
1181            Either::Right(subgraph) => Either::Right(subgraph.filter_map_attr(f)),
1182        };
1183
1184        let next = self.next.map(|e| Box::new(e.filter_map_attr(f)));
1185
1186        EdgeRHS { to, next }
1187    }
1188
1189    fn get_node_ids(&self) -> HashSet<NodeID> {
1190        let mut nexts: HashSet<NodeID> = self
1191            .next
1192            .as_ref()
1193            .map(|n| n.get_node_ids())
1194            .unwrap_or_default();
1195        match &self.to {
1196            Either::Left(node_id) => {
1197                nexts.insert(node_id.clone());
1198            }
1199            Either::Right(subgraph) => {
1200                return nexts.union(&subgraph.get_node_ids()).cloned().collect();
1201            }
1202        };
1203        nexts
1204    }
1205}
1206
1207/// This structure corresponds to the `node_stmt` non-terminal of the grammar.
1208/// It is basically a node identifier attached to some attributes.
1209#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
1210pub struct NodeStmt<A> {
1211    /// The identifier of the node.
1212    pub node: NodeID,
1213    /// The possible list of attributes.
1214    pub attr: Option<AttrList<A>>,
1215}
1216
1217#[cfg(feature = "to_tokens")]
1218impl ToTokens for NodeStmt<(String, String)> {
1219    fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
1220        let node = &self.node;
1221        let attrs = match &self.attr {
1222            Some(a) => quote! { std::option::Option::Some(#a) },
1223            None => quote! { std::option::Option::None }
1224        };
1225        let tokens = quote! {
1226            dot_parser::ast::NodeStmt {
1227                node: #node,
1228                attr: #attrs
1229            }
1230        };
1231        ts.append_all(tokens);
1232    }
1233}
1234
1235impl<'a, A> TryFrom<Pair<'a, Rule>> for NodeStmt<A>
1236where
1237    AList<A>: TryFrom<Pair<'a, Rule>, Error = ParseError<'a>>
1238{
1239    type Error = ParseError<'a>;
1240    fn try_from(p: Pair<'a, Rule>) -> Result<Self, ParseError<'a>> {
1241        let mut inners = p.clone().into_inner();
1242        let node = inners
1243            .next()
1244            .map(NodeID::try_from)
1245            .transpose()?
1246            .ok_or(ParseError::missing_pair(p, vec![Rule::node_id]))?;
1247        let attr = inners.next().map(AttrList::try_from).transpose()?;
1248        Ok(NodeStmt { node, attr })
1249    }
1250}
1251
1252impl<A> NodeStmt<A> {
1253    pub (in crate) fn filter_map_attr<B>(self, f: &dyn Fn(A) -> Option<B>) -> NodeStmt<B> {
1254        NodeStmt {
1255            node: self.node,
1256            attr: self.attr.map(|a| a.filter_map_attr(f)),
1257        }
1258    }
1259
1260    /// Get the name of the `NodeStmt`, i.e. the identifier of the
1261    /// `NodeID` contained in the `NodeStmt`.
1262    pub fn name(&self) -> &str {
1263        &self.node.id
1264    }
1265
1266    pub (in crate) fn get_node_id(&self) -> &NodeID {
1267        &self.node
1268    }
1269}
1270
1271/// This structure corresponds to the `node_id` non-terminal of the grammar.
1272/// If contains the identifier of the node, and possibly a port.
1273#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
1274pub struct NodeID {
1275    /// The identifier of the node.
1276    pub id: String,
1277    /// The port of the node, if any.
1278    pub port: Option<Port>,
1279}
1280
1281#[cfg(feature = "to_tokens")]
1282impl ToTokens for NodeID {
1283    fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
1284        let id = &self.id;
1285        let port = match &self.port {
1286            Some(port) => quote!{ std::option::Option::Some(#port) },
1287            None => quote! { std::option::Option::None },
1288        };
1289        let tokens = quote!{
1290            dot_parser::ast::NodeID {
1291                id: #id.to_string(),
1292                port: #port,
1293            }
1294        };
1295        ts.append_all(tokens);
1296    }
1297}
1298
1299impl<'a> TryFrom<Pair<'a, Rule>> for NodeID
1300{
1301    type Error = ParseError<'a>;
1302    fn try_from(p: Pair<'a, Rule>) -> Result<Self, ParseError<'a>> {
1303        let mut inners = p.clone().into_inner();
1304        let id = inners
1305            .next()
1306            .ok_or(ParseError::missing_pair(p, vec![Rule::node_id]))?
1307            .as_str()
1308            .to_string();
1309        let port = inners.next().map(Port::try_from).transpose()?;
1310        Ok(NodeID { id, port })
1311    }
1312}
1313
1314/// This enum corresponds to the `port` non-terminal of the grammar.
1315#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
1316pub enum Port {
1317    /// The variant in which an ID is given, and possibly a compass point.
1318    ID(String, Option<CompassPt>),
1319    /// The variant in which only a compass point is given.
1320    Compass(CompassPt),
1321}
1322
1323#[cfg(feature = "to_tokens")]
1324impl ToTokens for Port {
1325    fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
1326        let tokens = match self {
1327            Port::ID(s, cpss) => {
1328                match cpss {
1329                    Some(cpss) => {
1330                        quote!{ dot_parser::ast::Port::ID(#s.to_string(), std::option::Option::Some(#cpss)) }
1331                    },
1332                    None => {
1333                        quote!{ dot_parser::ast::Port::ID(#s.to_string(), std::option::Option::None) }
1334                    }
1335                }
1336            },
1337            Port::Compass(cpss) => {
1338                quote!{ dot_parser::ast::Port::Compass(#cpss) }
1339            }
1340        };
1341        ts.append_all(tokens);
1342    }
1343}
1344
1345impl<'a> TryFrom<Pair<'a, Rule>> for Port
1346{
1347    type Error = ParseError<'a>;
1348    fn try_from(p: Pair<'a, Rule>) -> Result<Self, ParseError<'a>> {
1349        let mut inners = p.clone().into_inner();
1350        let inner = inners.next().ok_or(ParseError::missing_pair(
1351            p,
1352            vec![Rule::compass_pt, Rule::ident],
1353        ))?;
1354        match inner.as_rule() {
1355            Rule::compass_pt => Ok(Port::Compass(CompassPt::try_from(inner)?)),
1356            Rule::ident => {
1357                let opt_comp = inners.next().map(CompassPt::try_from).transpose()?;
1358                Ok(Port::ID(inner.as_str().to_string(), opt_comp))
1359            }
1360            r => Err(ParseError::expect_rule(
1361                vec![Rule::compass_pt, Rule::ident],
1362                r,
1363            )),
1364        }
1365    }
1366}
1367
1368#[cfg(feature = "display")]
1369impl Display for Port {
1370    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
1371        match self {
1372            Port::ID(name, Some(cpss)) => {
1373                write!(f, ": {name} : {cpss}")
1374            }
1375            Port::ID(name, None) => {
1376                write!(f, ": {name}")
1377            }
1378            Port::Compass(cpss) => {
1379                write!(f, ": {}", cpss)
1380            }
1381        }
1382    }
1383}
1384
1385/// A subgraph. This corresponds to the `subgraph` non-terminal of the grammar.
1386#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
1387pub struct Subgraph<A> {
1388    /// The (optional) identifier of the subgraph.
1389    pub id: Option<String>,
1390    /// The statements that describe the subgraph.
1391    pub stmts: StmtList<A>,
1392}
1393
1394#[cfg(feature = "to_tokens")]
1395impl ToTokens for Subgraph<(String, String)> {
1396    fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
1397        let id = match &self.id {
1398            Some(s) => quote!{ std::option::Some(#s) },
1399            None => quote! { std::option::None },
1400        };
1401        let stmts = &self.stmts;
1402        let tokens = quote! {
1403            dot_parser::ast::Subgraph {
1404                id: #id,
1405                stmts: #stmts,
1406            }
1407        };
1408        ts.append_all(tokens);
1409    }
1410}
1411
1412impl<'a, A> TryFrom<Pair<'a, Rule>> for Subgraph<A>
1413where
1414    AList<A>: TryFrom<Pair<'a, Rule>, Error = ParseError<'a>>
1415{
1416    type Error = ParseError<'a>;
1417    fn try_from(p: Pair<'a, Rule>) -> Result<Self, ParseError<'a>> {
1418        let mut inners = p.clone().into_inner();
1419        let mut inner = inners.next().ok_or(ParseError::missing_pair(
1420            p.clone(),
1421            vec![Rule::ident, Rule::stmt_list],
1422        ))?;
1423        let id = if let Rule::ident = inner.as_rule() {
1424            let id_str = inner.as_str().to_string();
1425            inner = inners
1426                .next()
1427                .ok_or(ParseError::missing_pair(p.clone(), vec![Rule::stmt_list]))?;
1428            Some(id_str)
1429        } else {
1430            None
1431        };
1432        let stmts = StmtList::try_from(inner)?;
1433        Ok(Subgraph { id, stmts })
1434    }
1435}
1436
1437impl<A> Subgraph<A> {
1438    fn filter_map_attr<B>(self, f: &dyn Fn(A) -> Option<B>) -> Subgraph<B> {
1439        Subgraph {
1440            id: self.id,
1441            stmts: self
1442                .stmts
1443                .into_iter()
1444                .map(|stmt| stmt.filter_map_attr(f))
1445                .collect(),
1446        }
1447    }
1448
1449    /// Extract a subgraph as a standalone graph.
1450    pub (in crate) fn into_graph(self, strict: bool, is_digraph: bool) -> Graph<A> {
1451        Graph {
1452            strict,
1453            is_digraph,
1454            name: self.id.map(String::from),
1455            stmts: self.stmts,
1456        }
1457    }
1458
1459    fn get_node_ids(&self) -> HashSet<NodeID> {
1460        self.stmts.get_node_ids()
1461    }
1462}
1463
1464/// An enum that corresponds to the `compass_pt` non-terminal of the grammar.
1465#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Copy, Clone)]
1466pub enum CompassPt {
1467    /// A North orientation
1468    N,
1469    /// A North-East orientation
1470    NE,
1471    /// An East orientation
1472    E,
1473    /// A South-East orientation
1474    SE,
1475    /// A South orientation
1476    S,
1477    /// A South-West orientation
1478    SW,
1479    /// A West orientation
1480    W,
1481    /// A North-West orientation
1482    NW,
1483    /// A Central orientation
1484    C,
1485    /// An unspecified orientation
1486    Underscore,
1487}
1488
1489#[cfg(feature = "display")]
1490impl Display for CompassPt {
1491    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
1492        match self {
1493            CompassPt::N => write!(f, "n"),
1494            CompassPt::NE => write!(f, "ne"),
1495            CompassPt::E => write!(f, "e"),
1496            CompassPt::SE => write!(f, "se"),
1497            CompassPt::S => write!(f, "s"),
1498            CompassPt::SW => write!(f, "sw"),
1499            CompassPt::W => write!(f, "w"),
1500            CompassPt::NW => write!(f, "nw"),
1501            CompassPt::C => write!(f, "c"),
1502            CompassPt::Underscore => write!(f, "_"),
1503        }
1504    }
1505}
1506
1507impl<'a> TryFrom<Pair<'a, Rule>> for CompassPt { 
1508    type Error = ParseError<'a>;
1509    fn try_from(p: Pair<'a, Rule>) -> Result<Self, ParseError<'a>> {
1510        match p
1511            .clone()
1512            .into_inner()
1513            .next()
1514            .ok_or(ParseError::missing_pair(
1515                p,
1516                vec![
1517                    Rule::n,
1518                    Rule::ne,
1519                    Rule::e,
1520                    Rule::se,
1521                    Rule::s,
1522                    Rule::sw,
1523                    Rule::w,
1524                    Rule::nw,
1525                    Rule::c,
1526                    Rule::underscore,
1527                ],
1528            ))?
1529            .as_rule()
1530        {
1531            Rule::n => Ok(CompassPt::N),
1532            Rule::ne => Ok(CompassPt::NE),
1533            Rule::e => Ok(CompassPt::E),
1534            Rule::se => Ok(CompassPt::SE),
1535            Rule::s => Ok(CompassPt::S),
1536            Rule::sw => Ok(CompassPt::SW),
1537            Rule::w => Ok(CompassPt::W),
1538            Rule::nw => Ok(CompassPt::NW),
1539            Rule::c => Ok(CompassPt::C),
1540            Rule::underscore => Ok(CompassPt::Underscore),
1541            r => Err(ParseError::expect_rule(
1542                vec![
1543                    Rule::n,
1544                    Rule::ne,
1545                    Rule::e,
1546                    Rule::se,
1547                    Rule::s,
1548                    Rule::sw,
1549                    Rule::w,
1550                    Rule::nw,
1551                    Rule::c,
1552                    Rule::underscore,
1553                ],
1554                r,
1555            )),
1556        }
1557    }
1558}
1559
1560#[cfg(feature = "to_tokens")]
1561impl ToTokens for CompassPt {
1562      fn to_tokens(&self, tokens: &mut TokenStream) {
1563          let new_tokens = match self {
1564            CompassPt::N => quote!{ dot_parser::ast::CompassPt::N},
1565            CompassPt::NE => quote!{ dot_parser::ast::CompassPt::NE},
1566            CompassPt::E => quote!{ dot_parser::ast::CompassPt::E},
1567            CompassPt::SE => quote!{ dot_parser::ast::CompassPt::SE},
1568            CompassPt::S => quote!{ dot_parser::ast::CompassPt::S},
1569            CompassPt::SW => quote!{ dot_parser::ast::CompassPt::SW},
1570            CompassPt::W => quote!{ dot_parser::ast::CompassPt::W},
1571            CompassPt::NW => quote!{ dot_parser::ast::CompassPt::NW},
1572            CompassPt::C => quote!{ dot_parser::ast::CompassPt::C},
1573            CompassPt::Underscore => quote!{ dot_parser::ast::CompassPt::Underscore},
1574          };
1575          tokens.append_all(new_tokens);
1576      }
1577}