1 | use std::collections::HashMap; |
2 | use std::path::{Path, PathBuf}; |
3 | |
4 | use crate::config::Config; |
5 | use crate::CompileError; |
6 | use parser::node::{BlockDef, Macro, Match}; |
7 | use parser::Node; |
8 | |
9 | pub(crate) struct Heritage<'a> { |
10 | pub(crate) root: &'a Context<'a>, |
11 | pub(crate) blocks: BlockAncestry<'a>, |
12 | } |
13 | |
14 | impl 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 | |
36 | type BlockAncestry<'a> = HashMap<&'a str, Vec<(&'a Context<'a>, &'a BlockDef<'a>)>>; |
37 | |
38 | #[derive (Clone)] |
39 | pub(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 | |
47 | impl 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 | |