1 | use std::collections::HashMap; |
2 | use std::path::Path; |
3 | use std::sync::Arc; |
4 | |
5 | use parser::node::{BlockDef, Macro}; |
6 | use parser::{Node, Parsed, WithSpan}; |
7 | use rustc_hash::FxBuildHasher; |
8 | |
9 | use crate::config::Config; |
10 | use crate::{CompileError, FileInfo}; |
11 | |
12 | pub(crate) struct Heritage<'a> { |
13 | pub(crate) root: &'a Context<'a>, |
14 | pub(crate) blocks: BlockAncestry<'a>, |
15 | } |
16 | |
17 | impl Heritage<'_> { |
18 | pub(crate) fn new<'n>( |
19 | mut ctx: &'n Context<'n>, |
20 | contexts: &'n HashMap<&'n Arc<Path>, Context<'n>, FxBuildHasher>, |
21 | ) -> Heritage<'n> { |
22 | let mut blocks: BlockAncestry<'n> = ctximpl Iterator |
23 | .blocks |
24 | .iter() |
25 | .map(|(name: &&str, def: &&BlockDef<'n>)| (*name, vec![(ctx, *def)])) |
26 | .collect(); |
27 | |
28 | while let Some(path: &Arc) = &ctx.extends { |
29 | ctx = &contexts[path]; |
30 | for (name: &&str, def: &&BlockDef<'n>) in &ctx.blocks { |
31 | blocks.entry(key:name).or_default().push((ctx, def)); |
32 | } |
33 | } |
34 | |
35 | Heritage { root: ctx, blocks } |
36 | } |
37 | } |
38 | |
39 | type BlockAncestry<'a> = HashMap<&'a str, Vec<(&'a Context<'a>, &'a BlockDef<'a>)>, FxBuildHasher>; |
40 | |
41 | #[derive (Clone)] |
42 | pub(crate) struct Context<'a> { |
43 | pub(crate) nodes: &'a [Node<'a>], |
44 | pub(crate) extends: Option<Arc<Path>>, |
45 | pub(crate) blocks: HashMap<&'a str, &'a BlockDef<'a>, FxBuildHasher>, |
46 | pub(crate) macros: HashMap<&'a str, &'a Macro<'a>, FxBuildHasher>, |
47 | pub(crate) imports: HashMap<&'a str, Arc<Path>, FxBuildHasher>, |
48 | pub(crate) path: Option<&'a Path>, |
49 | pub(crate) parsed: &'a Parsed, |
50 | } |
51 | |
52 | impl Context<'_> { |
53 | pub(crate) fn empty(parsed: &Parsed) -> Context<'_> { |
54 | Context { |
55 | nodes: &[], |
56 | extends: None, |
57 | blocks: HashMap::default(), |
58 | macros: HashMap::default(), |
59 | imports: HashMap::default(), |
60 | path: None, |
61 | parsed, |
62 | } |
63 | } |
64 | |
65 | pub(crate) fn new<'n>( |
66 | config: &Config, |
67 | path: &'n Path, |
68 | parsed: &'n Parsed, |
69 | ) -> Result<Context<'n>, CompileError> { |
70 | let mut extends = None; |
71 | let mut blocks = HashMap::default(); |
72 | let mut macros = HashMap::default(); |
73 | let mut imports = HashMap::default(); |
74 | let mut nested = vec![parsed.nodes()]; |
75 | let mut top = true; |
76 | |
77 | while let Some(nodes) = nested.pop() { |
78 | for n in nodes { |
79 | match n { |
80 | Node::Extends(e) => { |
81 | ensure_top(top, e, path, parsed, "extends" )?; |
82 | if extends.is_some() { |
83 | return Err(CompileError::new( |
84 | "multiple extend blocks found" , |
85 | Some(FileInfo::of(e, path, parsed)), |
86 | )); |
87 | } |
88 | extends = Some(config.find_template( |
89 | e.path, |
90 | Some(path), |
91 | Some(FileInfo::of(e, path, parsed)), |
92 | )?); |
93 | } |
94 | Node::Macro(m) => { |
95 | ensure_top(top, m, path, parsed, "macro" )?; |
96 | macros.insert(m.name, &**m); |
97 | } |
98 | Node::Import(import) => { |
99 | ensure_top(top, import, path, parsed, "import" )?; |
100 | let path = config.find_template( |
101 | import.path, |
102 | Some(path), |
103 | Some(FileInfo::of(import, path, parsed)), |
104 | )?; |
105 | imports.insert(import.scope, path); |
106 | } |
107 | Node::BlockDef(b) => { |
108 | blocks.insert(b.name, &**b); |
109 | nested.push(&b.nodes); |
110 | } |
111 | Node::If(i) => { |
112 | for cond in &i.branches { |
113 | nested.push(&cond.nodes); |
114 | } |
115 | } |
116 | Node::Loop(l) => { |
117 | nested.push(&l.body); |
118 | nested.push(&l.else_nodes); |
119 | } |
120 | Node::Match(m) => { |
121 | for arm in &m.arms { |
122 | nested.push(&arm.nodes); |
123 | } |
124 | } |
125 | _ => {} |
126 | } |
127 | } |
128 | top = false; |
129 | } |
130 | |
131 | Ok(Context { |
132 | nodes: parsed.nodes(), |
133 | extends, |
134 | blocks, |
135 | macros, |
136 | imports, |
137 | parsed, |
138 | path: Some(path), |
139 | }) |
140 | } |
141 | |
142 | pub(crate) fn generate_error<T>(&self, msg: &str, node: &WithSpan<'_, T>) -> CompileError { |
143 | CompileError::new( |
144 | msg, |
145 | self.path.map(|path| FileInfo::of(node, path, self.parsed)), |
146 | ) |
147 | } |
148 | } |
149 | |
150 | fn ensure_top<T>( |
151 | top: bool, |
152 | node: &WithSpan<'_, T>, |
153 | path: &Path, |
154 | parsed: &Parsed, |
155 | kind: &str, |
156 | ) -> Result<(), CompileError> { |
157 | if top { |
158 | Ok(()) |
159 | } else { |
160 | Err(CompileError::new( |
161 | msg:format!("` {kind}` blocks are not allowed below top level" ), |
162 | file_info:Some(FileInfo::of(node, path, parsed)), |
163 | )) |
164 | } |
165 | } |
166 | |