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