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 )
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 let l = cmp::min(stmts.len(), other.stmts.len());
142
143 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 )
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 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 #[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 syn::custom_punctuation!(Demo, --);
566 let _: Demo = syn::parse_quote!(--);
567}
568
569#[cfg(test)]
570impl EdgeDirectedness {
571 fn directed() -> Self {
573 Self::Directed(tok::directed_edge())
574 }
575 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 )
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}