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