use std::collections::HashMap; use std::path::{Path, PathBuf}; use crate::config::Config; use crate::CompileError; use parser::node::{BlockDef, Macro, Match}; use parser::Node; pub(crate) struct Heritage<'a> { pub(crate) root: &'a Context<'a>, pub(crate) blocks: BlockAncestry<'a>, } impl Heritage<'_> { pub(crate) fn new<'n>( mut ctx: &'n Context<'n>, contexts: &'n HashMap<&'n Path, Context<'n>>, ) -> Heritage<'n> { let mut blocks: BlockAncestry<'n> = ctx .blocks .iter() .map(|(name, def)| (*name, vec![(ctx, *def)])) .collect(); while let Some(ref path) = ctx.extends { ctx = &contexts[path.as_path()]; for (name, def) in &ctx.blocks { blocks.entry(name).or_default().push((ctx, def)); } } Heritage { root: ctx, blocks } } } type BlockAncestry<'a> = HashMap<&'a str, Vec<(&'a Context<'a>, &'a BlockDef<'a>)>>; #[derive(Clone)] pub(crate) struct Context<'a> { pub(crate) nodes: &'a [Node<'a>], pub(crate) extends: Option, pub(crate) blocks: HashMap<&'a str, &'a BlockDef<'a>>, pub(crate) macros: HashMap<&'a str, &'a Macro<'a>>, pub(crate) imports: HashMap<&'a str, PathBuf>, } impl Context<'_> { pub(crate) fn new<'n>( config: &Config<'_>, path: &Path, nodes: &'n [Node<'n>], ) -> Result, CompileError> { let mut extends = None; let mut blocks = HashMap::new(); let mut macros = HashMap::new(); let mut imports = HashMap::new(); let mut nested = vec![nodes]; let mut top = true; while let Some(nodes) = nested.pop() { for n in nodes { match n { Node::Extends(e) if top => match extends { Some(_) => return Err("multiple extend blocks found".into()), None => { extends = Some(config.find_template(e.path, Some(path))?); } }, Node::Macro(m) if top => { macros.insert(m.name, m); } Node::Import(import) if top => { let path = config.find_template(import.path, Some(path))?; imports.insert(import.scope, path); } Node::Extends(_) | Node::Macro(_) | Node::Import(_) if !top => { return Err( "extends, macro or import blocks not allowed below top level".into(), ); } Node::BlockDef(b) => { blocks.insert(b.name, b); nested.push(&b.nodes); } Node::If(i) => { for cond in &i.branches { nested.push(&cond.nodes); } } Node::Loop(l) => { nested.push(&l.body); nested.push(&l.else_nodes); } Node::Match(Match { arms, .. }) => { for arm in arms { nested.push(&arm.nodes); } } _ => {} } } top = false; } Ok(Context { nodes, extends, blocks, macros, imports, }) } }