| 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 | |