Skip to main content

css_ast/visit/
root.rs

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
10/// A node whose type is erased.
11pub 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
21/// What [`parse_root`] gives back.
22pub struct ParsedRoot<'a> {
23	/// The root node, or `None` if the source does not parse as the node kind.
24	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		// Math functions are generic over their leaf type. You cannot name them as one root.
68		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}