blob: bded66bb1b6b5ea281cf02999606498cfdbdb27c [file] [log] [blame]
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<PathBuf>,
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<Context<'n>, 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,
})
}
}