1use std::collections::HashMap;
2use std::path::Path;
3use std::sync::Arc;
4
5use parser::node::{BlockDef, Macro};
6use parser::{Node, Parsed, WithSpan};
7use rustc_hash::FxBuildHasher;
8
9use crate::config::Config;
10use crate::{CompileError, FileInfo};
11
12pub(crate) struct Heritage<'a> {
13 pub(crate) root: &'a Context<'a>,
14 pub(crate) blocks: BlockAncestry<'a>,
15}
16
17impl 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
39type BlockAncestry<'a> = HashMap<&'a str, Vec<(&'a Context<'a>, &'a BlockDef<'a>)>, FxBuildHasher>;
40
41#[derive(Clone)]
42pub(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
52impl 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
150fn 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