1use super::{NodeId, NodeVisitor, Visitable};
2use crate::*;
3use css_lexer::Lexer;
4use css_parse::{Arena, Box, Diagnostic, NodeWithMetadata, Parse, Parser, ToCursors, ToSpan, Vec};
5use std::marker::PhantomData;
6use visit_flow::VisitFlow;
7
8include!(concat!(env!("OUT_DIR"), "/css_root_dispatch.rs"));
9
10pub trait ErasedNode: ToSpan + NodeWithMetadata<CssMetadata> {
12 fn accept_dyn(&self, visitor: &mut dyn NodeVisitor) -> VisitFlow;
13}
14
15impl<T: ToSpan + Visitable + NodeWithMetadata<CssMetadata>> ErasedNode for T {
16 fn accept_dyn(&self, mut visitor: &mut dyn NodeVisitor) -> VisitFlow {
17 Visitable::accept(self, &mut visitor)
18 }
19}
20
21pub struct ParsedRoot<'a> {
23 pub root: Option<&'a dyn ErasedNode>,
25 pub diagnostics: Vec<'a, Diagnostic>,
26}
27
28struct ErasedNodeParser<'a, T>(PhantomData<&'a T>);
29
30impl<'a, T> ErasedNodeParser<'a, T>
31where
32 T: Parse<'a> + ToCursors + ToSpan + Visitable + NodeWithMetadata<CssMetadata> + 'a,
33{
34 fn parse(&self, arena: &'a Arena, source: &'a str) -> Option<ParsedRoot<'a>> {
35 let lexer = Lexer::new(&CssAtomSet::ATOMS, source);
36 let result = Parser::new(arena, source, lexer).parse_entirely::<T>();
37 let root = result.output.map(|value| &*Box::new_in(arena, value).leak() as &'a dyn ErasedNode);
38 Some(ParsedRoot { root, diagnostics: result.errors })
39 }
40}
41
42trait ParseFallback<'a> {
43 fn parse(&self, _: &'a Arena, _: &'a str) -> Option<ParsedRoot<'a>> {
44 None
45 }
46}
47
48impl<'a, T> ParseFallback<'a> for ErasedNodeParser<'a, T> {}
49
50#[cfg(test)]
51mod tests {
52 use super::{NodeId, NodeVisitor, VisitNode, parse_root};
53 use css_parse::Arena;
54 use visit_flow::{VisitFlow, VisitFlowExt};
55
56 #[test]
57 fn parseability_follows_the_parse_impl_not_a_list() {
58 let arena = Arena::default();
59 for tag in ["style-value", "font-face-rule-style-value", "font-feature-value"] {
60 let id = NodeId::from_tag_name(tag).unwrap_or_else(|| panic!("{tag} is a node kind"));
61 assert!(parse_root(id, &arena, "").is_none(), "{tag} has no standalone grammar");
62 }
63 for tag in ["style-sheet", "length", "angle", "scope-rule", "bg-layer"] {
64 let id = NodeId::from_tag_name(tag).unwrap_or_else(|| panic!("{tag} is a node kind"));
65 assert!(parse_root(id, &arena, "").is_some(), "{tag} parses on its own");
66 }
67 assert!(parse_root(NodeId::CalcFunction, &arena, "").is_none());
69 }
70
71 #[test]
72 fn parse_root_gives_an_erased_root_that_walks() {
73 struct Kinds(std::vec::Vec<NodeId>);
74
75 impl NodeVisitor for Kinds {
76 fn consider_node(&self, _: VisitNode) -> VisitFlow {
77 VisitFlow::DESCEND
78 }
79
80 fn enter_node(&mut self, node: VisitNode) -> VisitFlow {
81 self.0.extend(node.node_id);
82 VisitFlow::DESCEND
83 }
84
85 fn exit_node(&mut self, _: VisitNode) -> VisitFlow {
86 VisitFlow::DESCEND
87 }
88 }
89
90 let arena = Arena::default();
91 let parsed = parse_root(NodeId::StyleSheet, &arena, "a{color:red}").expect("style-sheet is a root");
92 let root = parsed.root.expect("the source parses");
93 assert!(parsed.diagnostics.is_empty());
94 assert_eq!(root.to_span().end().0, 12);
95
96 let mut kinds = Kinds(std::vec::Vec::new());
97 let _ = root.accept_dyn(&mut kinds);
98 assert!(kinds.0.contains(&NodeId::StyleSheet), "got {:?}", kinds.0);
99 assert!(kinds.0.contains(&NodeId::StyleRule), "got {:?}", kinds.0);
100
101 assert!(parse_root(NodeId::StyleValue, &arena, "red").is_none(), "style-value is not a root");
102 }
103
104 #[test]
105 fn a_walk_of_an_erased_root_prunes_as_a_typed_walk_does() {
106 use super::{Visitable, visitor};
107 use crate::StyleSheet;
108 use css_lexer::Lexer;
109 use css_parse::Parser;
110
111 struct Pruner(std::vec::Vec<NodeId>);
112
113 #[visitor]
114 impl super::Visit for Pruner {
115 fn consider_node(&self, node: VisitNode) -> VisitFlow {
116 if node.node_id == Some(NodeId::StyleRule) { VisitFlow::SKIP_CHILDREN } else { VisitFlow::DESCEND }
117 }
118
119 fn enter_node(&mut self, node: VisitNode) {
120 self.0.extend(node.node_id);
121 }
122 }
123
124 let arena = Arena::default();
125 let source = "a{color:red}";
126 let lexer = Lexer::new(&crate::CssAtomSet::ATOMS, source);
127 let typed =
128 Parser::new(&arena, source, lexer).parse_entirely::<StyleSheet>().output.expect("the source parses");
129 let mut typed_kinds = Pruner(std::vec::Vec::new());
130 let _ = typed.accept(&mut typed_kinds);
131
132 let parsed = parse_root(NodeId::StyleSheet, &arena, source).expect("style-sheet is a root");
133 let mut erased_kinds = Pruner(std::vec::Vec::new());
134 let _ = parsed.root.expect("the source parses").accept_dyn(&mut erased_kinds);
135
136 assert_eq!(typed_kinds.0, erased_kinds.0);
137 assert_eq!(typed_kinds.0, [NodeId::StyleSheet]);
138 }
139}