syn_graphs/
dot.rs

1use crate::enum_of_kws;
2use derive_quote_to_tokens::ToTokens;
3use derive_syn_parse::Parse;
4#[cfg(test)]
5use pretty_assertions::assert_eq;
6use proc_macro2::TokenStream;
7use quote::ToTokens;
8use std::{
9    cmp::{self, Ord, Ordering, PartialOrd},
10    hash::{Hash, Hasher},
11};
12use syn::{
13    ext::IdentExt as _,
14    parse::{Parse, ParseStream},
15    spanned::Spanned as _,
16    token, Token,
17};
18
19pub mod kw {
20    macro_rules! custom_keywords {
21        ($($ident:ident),* $(,)?) => {
22            $(
23                ::syn::custom_keyword!($ident);
24                impl ::std::cmp::PartialOrd for $ident {
25                    fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> {
26                        Some(self.cmp(other))
27                    }
28                }
29                impl ::std::cmp::Ord for $ident {
30                    fn cmp(&self, _: &Self) -> ::std::cmp::Ordering {
31                        ::std::cmp::Ordering::Equal
32                    }
33                }
34            )*
35        };
36    }
37
38    custom_keywords! {
39        strict, graph, digraph, node, edge, subgraph, n, ne, e, se, s, sw, w, nw, c
40    }
41}
42
43pub mod pun {
44    use std::cmp::{Ord, Ordering, PartialOrd};
45
46    syn::custom_punctuation!(DirectedEdge, ->);
47
48    impl Ord for DirectedEdge {
49        fn cmp(&self, _: &Self) -> Ordering {
50            Ordering::Equal
51        }
52    }
53    impl PartialOrd for DirectedEdge {
54        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
55            Some(self.cmp(other))
56        }
57    }
58}
59
60#[derive(Parse, Debug, PartialEq, Eq, Clone, Hash)]
61pub struct Graph {
62    pub strict: Option<kw::strict>,
63    pub directedness: GraphDirectedness,
64    #[call(Self::maybe_id)]
65    pub id: Option<ID>,
66    #[brace]
67    pub brace_token: token::Brace,
68    #[inside(brace_token)]
69    pub stmt_list: StmtList,
70}
71
72impl Ord for Graph {
73    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
74        let Self {
75            strict,
76            directedness,
77            id,
78            brace_token: _,
79            stmt_list,
80        } = self;
81        Ordering::Equal
82            .then(strict.cmp(&other.strict))
83            .then(directedness.cmp(&other.directedness))
84            .then(id.cmp(&other.id))
85            .then(Ordering::Equal /* brace_token */)
86            .then(stmt_list.cmp(&other.stmt_list))
87    }
88}
89
90impl PartialOrd for Graph {
91    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
92        Some(self.cmp(other))
93    }
94}
95
96impl Graph {
97    fn maybe_id(input: ParseStream) -> syn::Result<Option<ID>> {
98        match input.peek(token::Brace) {
99            true => Ok(None),
100            false => Ok(Some(input.parse()?)),
101        }
102    }
103}
104
105impl ToTokens for Graph {
106    fn to_tokens(&self, tokens: &mut TokenStream) {
107        let Self {
108            strict,
109            directedness,
110            id,
111            brace_token,
112            stmt_list,
113        } = self;
114        strict.to_tokens(tokens);
115        directedness.to_tokens(tokens);
116        id.to_tokens(tokens);
117        brace_token.surround(tokens, |inner| stmt_list.to_tokens(inner))
118    }
119}
120
121enum_of_kws!(
122    #[derive(PartialOrd, Ord)]
123    pub enum GraphDirectedness {
124        #[name = "graph"]
125        Graph(kw::graph),
126        #[name = "digraph"]
127        Digraph(kw::digraph),
128    }
129);
130
131#[derive(Clone, Debug, PartialEq, Eq, Hash)]
132pub struct StmtList {
133    pub stmts: Vec<(Stmt, Option<Token![;]>)>,
134}
135
136impl Ord for StmtList {
137    fn cmp(&self, other: &Self) -> Ordering {
138        let Self { stmts } = self;
139
140        // copied from stdlib
141        let l = cmp::min(stmts.len(), other.stmts.len());
142
143        // Slice to the loop iteration range to enable bound check
144        // elimination in the compiler
145        let lhs = &stmts[..l];
146        let rhs = &other.stmts[..l];
147
148        for i in 0..l {
149            let (left_stmt, left_semi) = &lhs[i];
150            let (right_stmt, right_semi) = &rhs[i];
151            match (left_stmt, left_semi.map(|_| ())).cmp(&(right_stmt, right_semi.map(|_| ()))) {
152                Ordering::Equal => (),
153                non_eq => return non_eq,
154            }
155        }
156
157        stmts.len().cmp(&other.stmts.len())
158    }
159}
160
161impl PartialOrd for StmtList {
162    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
163        Some(self.cmp(other))
164    }
165}
166
167impl Parse for StmtList {
168    fn parse(input: ParseStream) -> syn::Result<Self> {
169        let mut list = vec![];
170        while !input.is_empty() {
171            let stmt = input.parse()?;
172            let semi = match input.peek(Token![;]) {
173                true => Some(input.parse()?),
174                false => None,
175            };
176            list.push((stmt, semi))
177        }
178        Ok(Self { stmts: list })
179    }
180}
181
182impl ToTokens for StmtList {
183    fn to_tokens(&self, tokens: &mut TokenStream) {
184        let Self { stmts } = self;
185        for (stmt, semi) in stmts {
186            stmt.to_tokens(tokens);
187            semi.to_tokens(tokens)
188        }
189    }
190}
191#[derive(Clone, Debug, PartialEq, Eq, ToTokens, Hash, PartialOrd, Ord)]
192pub enum Stmt {
193    Attr(StmtAttr),
194    Assign(StmtAssign),
195    Node(StmtNode),
196    Edge(StmtEdge),
197    Subgraph(StmtSubgraph),
198}
199
200impl Parse for Stmt {
201    fn parse(input: ParseStream) -> syn::Result<Self> {
202        if input.fork().parse::<AttrTarget>().is_ok() {
203            return Ok(Self::Attr(input.parse()?));
204        }
205        if input.peek2(Token![=]) {
206            return Ok(Self::Assign(input.parse()?));
207        }
208        if input.fork().parse::<StmtEdge>().is_ok() {
209            return Ok(Self::Edge(input.parse()?));
210        }
211        if input.peek(kw::subgraph) || input.peek(token::Brace) {
212            return Ok(Self::Subgraph(input.parse()?));
213        }
214        Ok(Self::Node(input.parse()?))
215    }
216}
217
218#[test]
219fn parse_stmt() {
220    assert_eq!(
221        Stmt::Edge(StmtEdge {
222            from: EdgeTarget::NodeId(NodeId {
223                id: ID::lit_str("node0"),
224                port: Some(Port::ID {
225                    colon: tok::colon(),
226                    id: ID::ident("f0")
227                })
228            }),
229            edges: vec![(
230                EdgeDirectedness::directed(),
231                EdgeTarget::NodeId(NodeId {
232                    id: ID::lit_str("node1"),
233                    port: Some(Port::ID {
234                        colon: tok::colon(),
235                        id: ID::ident("f0")
236                    })
237                })
238            )],
239            attrs: None
240        }),
241        syn::parse_quote!("node0":f0 -> "node1":f0)
242    )
243}
244#[derive(Clone, ToTokens, Parse, Debug, PartialEq, Eq, Hash)]
245pub struct StmtAssign {
246    pub left: ID,
247    pub eq_token: Token![=],
248    pub right: ID,
249}
250
251impl Ord for StmtAssign {
252    fn cmp(&self, other: &Self) -> Ordering {
253        let Self {
254            left,
255            eq_token: _,
256            right,
257        } = self;
258        Ordering::Equal
259            .then(left.cmp(&other.left))
260            .then(Ordering::Equal /* eq_token */)
261            .then(right.cmp(&other.right))
262    }
263}
264
265impl PartialOrd for StmtAssign {
266    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
267        Some(self.cmp(other))
268    }
269}
270
271#[derive(Clone, Parse, ToTokens, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
272pub struct StmtAttr {
273    pub target: AttrTarget,
274    pub attrs: Attrs,
275}
276
277enum_of_kws!(
278    #[derive(PartialOrd, Ord)]
279    pub enum AttrTarget {
280        #[name = "graph"]
281        Graph(kw::graph),
282        #[name = "node"]
283        Node(kw::node),
284        #[name = "edge"]
285        Edge(kw::edge),
286    }
287);
288
289#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
290pub struct Attrs {
291    /// Non-empty
292    pub lists: Vec<AttrList>,
293}
294
295impl Parse for Attrs {
296    fn parse(input: ParseStream) -> syn::Result<Self> {
297        let mut lists = vec![input.parse()?];
298        while input.peek(token::Bracket) {
299            lists.push(input.parse()?)
300        }
301        Ok(Self { lists })
302    }
303}
304
305impl ToTokens for Attrs {
306    fn to_tokens(&self, tokens: &mut TokenStream) {
307        let Self { lists } = self;
308        for list in lists {
309            list.to_tokens(tokens)
310        }
311    }
312}
313
314#[derive(Clone, Debug, PartialEq, Eq, Hash)]
315pub struct AttrList {
316    pub bracket_token: token::Bracket,
317    pub assigns: Vec<AttrAssign>,
318}
319
320impl Ord for AttrList {
321    fn cmp(&self, other: &Self) -> Ordering {
322        let Self {
323            bracket_token: _,
324            assigns,
325        } = self;
326        assigns.cmp(&other.assigns)
327    }
328}
329
330impl PartialOrd for AttrList {
331    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
332        Some(self.cmp(other))
333    }
334}
335
336impl Parse for AttrList {
337    fn parse(input: ParseStream) -> syn::Result<Self> {
338        let mut assigns = vec![];
339        let content;
340        let bracket_token = syn::bracketed!(content in input);
341        while !content.is_empty() {
342            assigns.push(content.parse()?)
343        }
344        Ok(Self {
345            bracket_token,
346            assigns,
347        })
348    }
349}
350
351impl ToTokens for AttrList {
352    fn to_tokens(&self, tokens: &mut TokenStream) {
353        let Self {
354            bracket_token,
355            assigns,
356        } = self;
357        bracket_token.surround(tokens, |inner| {
358            for assign in assigns {
359                assign.to_tokens(inner)
360            }
361        })
362    }
363}
364
365#[test]
366fn parse_attr_list_penultimate_html() {
367    assert_eq!(
368        AttrList {
369            bracket_token: tok::bracket(),
370            assigns: vec![
371                AttrAssign::standalone(ID::ident("color"), ID::lit_str("#88000022")),
372                AttrAssign::standalone(
373                    ID::ident("label"),
374                    ID::html(quote::quote!(<em>hello!</em>))
375                ),
376                AttrAssign::standalone(ID::ident("shape"), ID::ident("plain")),
377            ],
378        },
379        syn::parse_quote!(
380            [
381                color="#88000022"
382                label=<<em>hello!</em>>
383                shape=plain
384            ]
385        )
386    );
387}
388
389#[derive(Clone, ToTokens, Parse, Debug, PartialEq, Eq, Hash)]
390pub struct AttrAssign {
391    pub left: ID,
392    pub eq_token: Token![=],
393    pub right: ID,
394    #[call(Self::parse_sep)]
395    pub trailing: Option<AttrSep>,
396}
397
398impl Ord for AttrAssign {
399    fn cmp(&self, other: &Self) -> Ordering {
400        let Self {
401            left,
402            eq_token: _,
403            right,
404            trailing,
405        } = self;
406        Ordering::Equal
407            .then(left.cmp(&other.left))
408            .then(right.cmp(&other.right))
409            .then(trailing.cmp(&other.trailing))
410    }
411}
412
413impl PartialOrd for AttrAssign {
414    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
415        Some(self.cmp(other))
416    }
417}
418
419#[cfg(test)]
420impl AttrAssign {
421    fn standalone(left: ID, right: ID) -> Self {
422        Self {
423            left,
424            eq_token: tok::eq(),
425            right,
426            trailing: None,
427        }
428    }
429}
430
431impl AttrAssign {
432    fn parse_sep(input: ParseStream) -> syn::Result<Option<AttrSep>> {
433        if input.peek(Token![,]) || input.peek(Token![;]) {
434            return Ok(Some(input.parse()?));
435        }
436        Ok(None)
437    }
438}
439
440enum_of_kws!(
441    pub enum AttrSep {
442        #[name = "comma"]
443        Comma(token::Comma),
444        #[name = "semi"]
445        Semi(token::Semi),
446    }
447);
448
449impl Ord for AttrSep {
450    fn cmp(&self, other: &Self) -> Ordering {
451        fn discriminant(attr_sep: &AttrSep) -> u8 {
452            match attr_sep {
453                AttrSep::Comma(token::Comma { .. }) => 0,
454                AttrSep::Semi(token::Semi { .. }) => 1,
455            }
456        }
457        discriminant(self).cmp(&discriminant(other))
458    }
459}
460
461impl PartialOrd for AttrSep {
462    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
463        Some(self.cmp(other))
464    }
465}
466
467#[derive(Clone, Parse, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
468pub struct StmtEdge {
469    pub from: EdgeTarget,
470    /// Non-empty
471    #[call(Self::parse_edges)]
472    pub edges: Vec<(EdgeDirectedness, EdgeTarget)>,
473    #[peek(token::Bracket)]
474    pub attrs: Option<Attrs>,
475}
476
477impl StmtEdge {
478    fn parse_edges(input: ParseStream) -> syn::Result<Vec<(EdgeDirectedness, EdgeTarget)>> {
479        let mut edges = vec![(input.parse()?, input.parse()?)];
480        while input.peek(pun::DirectedEdge) || input.peek(Token![-]) {
481            edges.push((input.parse()?, input.parse()?))
482        }
483        Ok(edges)
484    }
485}
486
487#[test]
488fn parse_stmt_edge() {
489    assert_eq!(
490        StmtEdge {
491            from: EdgeTarget::ident("alice"),
492            edges: vec![(EdgeDirectedness::undirected(), EdgeTarget::ident("bob"))],
493            attrs: None
494        },
495        syn::parse_quote! {
496            alice -- bob
497        }
498    )
499}
500
501impl ToTokens for StmtEdge {
502    fn to_tokens(&self, tokens: &mut TokenStream) {
503        let Self { from, edges, attrs } = self;
504        from.to_tokens(tokens);
505        for (dir, to) in edges {
506            dir.to_tokens(tokens);
507            to.to_tokens(tokens)
508        }
509        attrs.to_tokens(tokens)
510    }
511}
512
513#[derive(Clone, Debug, PartialEq, Eq, ToTokens, Hash, PartialOrd, Ord)]
514pub enum EdgeTarget {
515    Subgraph(StmtSubgraph),
516    NodeId(NodeId),
517}
518
519impl Parse for EdgeTarget {
520    fn parse(input: ParseStream) -> syn::Result<Self> {
521        if input.peek(kw::subgraph) || input.peek(token::Brace) {
522            return Ok(Self::Subgraph(input.parse()?));
523        }
524        Ok(Self::NodeId(input.parse()?))
525    }
526}
527
528#[cfg(test)]
529impl EdgeTarget {
530    fn ident(s: &str) -> Self {
531        Self::NodeId(NodeId {
532            id: ID::ident(s),
533            port: None,
534        })
535    }
536}
537
538#[derive(Clone, ToTokens, Parse, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
539pub enum EdgeDirectedness {
540    #[peek(pun::DirectedEdge, name = "->")]
541    Directed(pun::DirectedEdge),
542    #[peek(syn::token::Minus, name = "--")]
543    Undirected(UndirectedEdge),
544}
545
546#[derive(Clone, ToTokens, Parse, Debug, PartialEq, Eq, Default, Hash)]
547pub struct UndirectedEdge(Token![-], Token![-]);
548
549impl Ord for UndirectedEdge {
550    fn cmp(&self, _: &Self) -> Ordering {
551        Ordering::Equal
552    }
553}
554
555impl PartialOrd for UndirectedEdge {
556    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
557        Some(self.cmp(other))
558    }
559}
560
561#[test]
562#[should_panic = "expected `--`"]
563fn custom_punct_for_directed_edge_does_not_work() {
564    // cannot use this because the lexer will always give us Alone, Alone, which isn't parsed
565    syn::custom_punctuation!(Demo, --);
566    let _: Demo = syn::parse_quote!(--);
567}
568
569#[cfg(test)]
570impl EdgeDirectedness {
571    /// ->
572    fn directed() -> Self {
573        Self::Directed(tok::directed_edge())
574    }
575    /// --
576    fn undirected() -> Self {
577        Self::Undirected(tok::undirected_edge())
578    }
579}
580
581#[derive(Clone, ToTokens, Parse, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
582pub struct StmtNode {
583    pub node_id: NodeId,
584    #[peek(token::Bracket)]
585    pub attrs: Option<Attrs>,
586}
587
588#[test]
589fn parse_stmt_node() {
590    assert_eq!(
591        StmtNode {
592            node_id: NodeId {
593                id: ID::ident("noddy"),
594                port: None
595            },
596            attrs: None
597        },
598        syn::parse_quote!(noddy)
599    );
600    assert_eq!(
601        StmtNode {
602            node_id: NodeId {
603                id: ID::ident("noddy"),
604                port: None
605            },
606            attrs: Some(Attrs {
607                lists: vec![AttrList {
608                    bracket_token: tok::bracket(),
609                    assigns: vec![]
610                }]
611            })
612        },
613        syn::parse_quote!(noddy[])
614    );
615    assert_eq!(
616        StmtNode {
617            node_id: NodeId {
618                id: ID::ident("noddy"),
619                port: None
620            },
621            attrs: Some(Attrs {
622                lists: vec![AttrList {
623                    bracket_token: tok::bracket(),
624                    assigns: vec![AttrAssign::standalone(
625                        ID::ident("label"),
626                        ID::lit_str("make way for noddy")
627                    )]
628                }]
629            })
630        },
631        syn::parse_quote!(noddy[label = "make way for noddy"])
632    );
633}
634
635#[derive(Clone, ToTokens, Parse, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
636pub struct NodeId {
637    pub id: ID,
638    #[peek(token::Colon)]
639    pub port: Option<Port>,
640}
641
642#[test]
643fn parse_node_id() {
644    assert_eq!(
645        NodeId {
646            id: ID::ident("noddy"),
647            port: None
648        },
649        syn::parse_quote!(noddy),
650    );
651    assert_eq!(
652        NodeId {
653            id: ID::ident("noddy"),
654            port: Some(Port::ID {
655                colon: tok::colon(),
656                id: ID::lit_str("some port")
657            })
658        },
659        syn::parse_quote!(noddy:"some port"),
660    );
661    assert_eq!(
662        NodeId {
663            id: ID::ident("noddy"),
664            port: Some(Port::Compass {
665                colon: tok::colon(),
666                compass: CompassPoint::C(tok::c())
667            })
668        },
669        syn::parse_quote!(noddy:c),
670    );
671}
672
673#[derive(Clone, ToTokens, Debug, PartialEq, Eq, Hash)]
674pub enum Port {
675    ID {
676        colon: Token![:],
677        id: ID,
678    },
679    Compass {
680        colon: Token![:],
681        compass: CompassPoint,
682    },
683    IDAndCompass {
684        colon1: Token![:],
685        id: ID,
686        colon2: Token![:],
687        compass: CompassPoint,
688    },
689}
690
691impl Ord for Port {
692    fn cmp(&self, other: &Self) -> Ordering {
693        fn port2options(port: &Port) -> (Option<&ID>, Option<&CompassPoint>) {
694            match port {
695                Port::ID { colon: _, id } => (Some(id), None),
696                Port::Compass { colon: _, compass } => (None, Some(compass)),
697                Port::IDAndCompass {
698                    colon1: _,
699                    id,
700                    colon2: _,
701                    compass,
702                } => (Some(id), Some(compass)),
703            }
704        }
705        port2options(self).cmp(&port2options(other))
706    }
707}
708
709impl PartialOrd for Port {
710    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
711        Some(self.cmp(other))
712    }
713}
714
715impl Parse for Port {
716    fn parse(input: ParseStream) -> syn::Result<Self> {
717        let colon = input.parse()?;
718        if input.fork().parse::<CompassPoint>().is_ok() {
719            return Ok(Self::Compass {
720                colon,
721                compass: input.parse()?,
722            });
723        }
724        let id = input.parse()?;
725        match input.peek(Token![:]) {
726            false => Ok(Self::ID { colon, id }),
727            true => Ok(Self::IDAndCompass {
728                colon1: colon,
729                id,
730                colon2: input.parse()?,
731                compass: input.parse()?,
732            }),
733        }
734    }
735}
736
737#[derive(Clone, Parse, Debug, PartialEq, Eq, Hash)]
738pub struct StmtSubgraph {
739    #[call(Self::parse_prelude)]
740    pub prelude: Option<(kw::subgraph, Option<ID>)>,
741    #[brace]
742    pub brace_token: token::Brace,
743    #[inside(brace_token)]
744    pub statements: StmtList,
745}
746
747impl Ord for StmtSubgraph {
748    fn cmp(&self, other: &Self) -> Ordering {
749        let Self {
750            prelude,
751            brace_token: _,
752            statements,
753        } = self;
754        Ordering::Equal
755            .then(prelude.cmp(&other.prelude))
756            .then(Ordering::Equal /* brace_token */)
757            .then(statements.cmp(&other.statements))
758    }
759}
760
761impl PartialOrd for StmtSubgraph {
762    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
763        Some(self.cmp(other))
764    }
765}
766
767impl StmtSubgraph {
768    fn parse_prelude(input: ParseStream) -> syn::Result<Option<(kw::subgraph, Option<ID>)>> {
769        if input.peek(token::Brace) {
770            return Ok(None);
771        }
772        let subgraph = input.parse()?;
773        if input.peek(token::Brace) {
774            return Ok(Some((subgraph, None)));
775        }
776        Ok(Some((subgraph, Some(input.parse()?))))
777    }
778}
779
780impl ToTokens for StmtSubgraph {
781    fn to_tokens(&self, tokens: &mut TokenStream) {
782        let Self {
783            prelude,
784            brace_token,
785            statements,
786        } = self;
787        if let Some((kw, id)) = prelude {
788            kw.to_tokens(tokens);
789            id.to_tokens(tokens)
790        }
791        brace_token.surround(tokens, |inner| statements.to_tokens(inner))
792    }
793}
794
795enum_of_kws!(
796    #[derive(PartialOrd, Ord)]
797    pub enum CompassPoint {
798        #[name = "n"]
799        N(kw::n),
800        #[name = "ne"]
801        NE(kw::ne),
802        #[name = "e"]
803        E(kw::e),
804        #[name = "se"]
805        SE(kw::se),
806        #[name = "s"]
807        S(kw::s),
808        #[name = "sw"]
809        SW(kw::sw),
810        #[name = "w"]
811        W(kw::w),
812        #[name = "nw"]
813        NW(kw::nw),
814        #[name = "c"]
815        C(kw::c),
816    }
817);
818
819#[derive(Clone, ToTokens, Debug, PartialEq, Eq, Hash)]
820pub enum ID {
821    AnyIdent(syn::Ident),
822    AnyLit(syn::Lit),
823    Html(HtmlString),
824    DotInt(DotInt),
825}
826
827impl Ord for ID {
828    fn cmp(&self, other: &Self) -> Ordering {
829        fn id2options(
830            id: &ID,
831        ) -> (
832            Option<&syn::Ident>,
833            Option<String>,
834            Option<&HtmlString>,
835            Option<&DotInt>,
836        ) {
837            match id {
838                ID::AnyIdent(it) => (Some(it), None, None, None),
839                ID::AnyLit(it) => (None, Some(it.to_token_stream().to_string()), None, None),
840                ID::Html(it) => (None, None, Some(it), None),
841                ID::DotInt(it) => (None, None, None, Some(it)),
842            }
843        }
844        id2options(self).cmp(&id2options(other))
845    }
846}
847
848impl PartialOrd for ID {
849    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
850        Some(self.cmp(other))
851    }
852}
853
854impl Parse for ID {
855    fn parse(input: ParseStream) -> syn::Result<Self> {
856        if input.peek(syn::Ident::peek_any) {
857            return Ok(Self::AnyIdent(input.call(syn::Ident::parse_any)?));
858        }
859        if input.peek(syn::Lit) {
860            return Ok(Self::AnyLit(input.parse()?));
861        }
862        if input.peek(Token![<]) {
863            return Ok(Self::Html(input.parse()?));
864        }
865        if input.peek(Token![.]) {
866            return Ok(Self::DotInt(input.parse()?));
867        }
868        Err(input.error("expected an identifier, literal or HTML string"))
869    }
870}
871
872#[test]
873fn parse_id() {
874    assert_eq!(ID::lit_str("stringy"), syn::parse_quote!("stringy"));
875    assert_eq!(ID::ident("identy"), syn::parse_quote!(identy));
876}
877
878#[cfg(test)]
879impl ID {
880    fn lit_str(s: &str) -> Self {
881        Self::AnyLit(syn::Lit::Str(syn::LitStr::new(
882            s,
883            proc_macro2::Span::call_site(),
884        )))
885    }
886    fn ident(s: &str) -> Self {
887        Self::AnyIdent(syn::Ident::new(s, proc_macro2::Span::call_site()))
888    }
889    fn html(stream: TokenStream) -> Self {
890        Self::Html(HtmlString {
891            lt: tok::lt(),
892            stream,
893            gt: tok::gt(),
894        })
895    }
896}
897
898#[derive(Clone, Parse, ToTokens, Debug, PartialEq, Eq, Hash)]
899pub struct DotInt {
900    pub dot: Token![.],
901    pub int: syn::LitInt,
902}
903
904impl Ord for DotInt {
905    fn cmp(&self, other: &Self) -> Ordering {
906        let Self { dot: _, int } = self;
907        int.to_token_stream()
908            .to_string()
909            .cmp(&other.int.to_token_stream().to_string())
910    }
911}
912
913impl PartialOrd for DotInt {
914    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
915        Some(self.cmp(other))
916    }
917}
918
919#[derive(Clone, ToTokens, Debug)]
920pub struct HtmlString {
921    pub lt: Token![<],
922    pub stream: TokenStream,
923    pub gt: Token![>],
924}
925
926impl Ord for HtmlString {
927    fn cmp(&self, other: &Self) -> Ordering {
928        let Self {
929            lt: _,
930            stream,
931            gt: _,
932        } = self;
933        stream.to_string().cmp(&other.stream.to_string())
934    }
935}
936
937impl PartialOrd for HtmlString {
938    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
939        Some(self.cmp(other))
940    }
941}
942
943impl Hash for HtmlString {
944    fn hash<H: Hasher>(&self, state: &mut H) {
945        let Self { lt, stream, gt } = self;
946        lt.hash(state);
947        stream.to_string().hash(state);
948        gt.hash(state);
949    }
950}
951
952impl HtmlString {
953    pub fn source(&self) -> Option<String> {
954        self.stream.span().source_text()
955    }
956}
957
958impl PartialEq for HtmlString {
959    fn eq(&self, other: &Self) -> bool {
960        self.lt == other.lt && self.stream.to_string() == other.stream.to_string()
961    }
962}
963
964impl Eq for HtmlString {}
965
966impl Parse for HtmlString {
967    fn parse(input: ParseStream) -> syn::Result<Self> {
968        use proc_macro2::TokenTree::Punct;
969
970        let lt = input.parse()?;
971        let mut nesting = 1usize;
972        input.step(|cursor| {
973            let mut stream = TokenStream::new();
974            let mut rest = *cursor;
975            while let Some((tt, next)) = rest.token_tree() {
976                match &tt {
977                    Punct(p) if p.as_char() == '>' => {
978                        nesting -= 1;
979                        if nesting == 0 {
980                            return Ok((
981                                Self {
982                                    lt,
983                                    stream,
984                                    gt: syn::parse2(TokenStream::from(tt))
985                                        .expect("just saw that this was a `>`"),
986                                },
987                                next,
988                            ));
989                        }
990                    }
991                    Punct(p) if p.as_char() == '<' => nesting += 1,
992                    _ => {}
993                };
994                rest = next;
995                stream.extend([tt]);
996            }
997            Err(cursor.error("unmatched `<` in html string"))
998        })
999    }
1000}
1001
1002#[test]
1003fn parse_html_string() {
1004    use quote::quote;
1005    assert_eq!(
1006        HtmlString {
1007            lt: tok::lt(),
1008            stream: quote!(hello),
1009            gt: tok::gt(),
1010        },
1011        syn::parse_quote!(<hello>)
1012    );
1013    assert_eq!(
1014        HtmlString {
1015            lt: tok::lt(),
1016            stream: quote!(hello <div> I am in a div </div>),
1017            gt: tok::gt(),
1018        },
1019        syn::parse_quote!(<hello <div> I am in a div </div> >)
1020    );
1021}
1022
1023#[cfg(test)]
1024mod tok {
1025    use super::{kw, pun};
1026    use syn::token;
1027
1028    crate::tok!(
1029        bracket -> token::Bracket,
1030        c -> kw::c,
1031        colon -> token::Colon,
1032        directed_edge -> pun::DirectedEdge,
1033        eq -> token::Eq,
1034        gt -> token::Gt,
1035        lt -> token::Lt,
1036        undirected_edge -> super::UndirectedEdge
1037    );
1038}