1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3
4use crate::config::Config;
5use crate::CompileError;
6use parser::node::{BlockDef, Macro, Match};
7use parser::Node;
8
9pub(crate) struct Heritage<'a> {
10 pub(crate) root: &'a Context<'a>,
11 pub(crate) blocks: BlockAncestry<'a>,
12}
13
14impl Heritage<'_> {
15 pub(crate) fn new<'n>(
16 mut ctx: &'n Context<'n>,
17 contexts: &'n HashMap<&'n Path, Context<'n>>,
18 ) -> Heritage<'n> {
19 let mut blocks: BlockAncestry<'n> = ctximpl Iterator
20 .blocks
21 .iter()
22 .map(|(name: &&str, def: &&BlockDef<'_>)| (*name, vec![(ctx, *def)]))
23 .collect();
24
25 while let Some(ref path: &PathBuf) = ctx.extends {
26 ctx = &contexts[path.as_path()];
27 for (name: &&str, def: &&BlockDef<'_>) in &ctx.blocks {
28 blocks.entry(key:name).or_default().push((ctx, def));
29 }
30 }
31
32 Heritage { root: ctx, blocks }
33 }
34}
35
36type BlockAncestry<'a> = HashMap<&'a str, Vec<(&'a Context<'a>, &'a BlockDef<'a>)>>;
37
38#[derive(Clone)]
39pub(crate) struct Context<'a> {
40 pub(crate) nodes: &'a [Node<'a>],
41 pub(crate) extends: Option<PathBuf>,
42 pub(crate) blocks: HashMap<&'a str, &'a BlockDef<'a>>,
43 pub(crate) macros: HashMap<&'a str, &'a Macro<'a>>,
44 pub(crate) imports: HashMap<&'a str, PathBuf>,
45}
46
47impl Context<'_> {
48 pub(crate) fn new<'n>(
49 config: &Config<'_>,
50 path: &Path,
51 nodes: &'n [Node<'n>],
52 ) -> Result<Context<'n>, CompileError> {
53 let mut extends = None;
54 let mut blocks = HashMap::new();
55 let mut macros = HashMap::new();
56 let mut imports = HashMap::new();
57 let mut nested = vec![nodes];
58 let mut top = true;
59
60 while let Some(nodes) = nested.pop() {
61 for n in nodes {
62 match n {
63 Node::Extends(e) if top => match extends {
64 Some(_) => return Err("multiple extend blocks found".into()),
65 None => {
66 extends = Some(config.find_template(e.path, Some(path))?);
67 }
68 },
69 Node::Macro(m) if top => {
70 macros.insert(m.name, m);
71 }
72 Node::Import(import) if top => {
73 let path = config.find_template(import.path, Some(path))?;
74 imports.insert(import.scope, path);
75 }
76 Node::Extends(_) | Node::Macro(_) | Node::Import(_) if !top => {
77 return Err(
78 "extends, macro or import blocks not allowed below top level".into(),
79 );
80 }
81 Node::BlockDef(b) => {
82 blocks.insert(b.name, b);
83 nested.push(&b.nodes);
84 }
85 Node::If(i) => {
86 for cond in &i.branches {
87 nested.push(&cond.nodes);
88 }
89 }
90 Node::Loop(l) => {
91 nested.push(&l.body);
92 nested.push(&l.else_nodes);
93 }
94 Node::Match(Match { arms, .. }) => {
95 for arm in arms {
96 nested.push(&arm.nodes);
97 }
98 }
99 _ => {}
100 }
101 }
102 top = false;
103 }
104
105 Ok(Context {
106 nodes,
107 extends,
108 blocks,
109 macros,
110 imports,
111 })
112 }
113}
114