use std::{borrow::Cow, collections::HashMap, ffi::OsStr, fmt::Display, path::Path};
use crate::{CatResult, Moo, prelude::*};
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct ModuleName(pub Vec<String>);
#[derive(Debug, Clone, Default)]
pub struct Scope<'input>(Cow<'input, HashMap<Moo<'input>, LazyExpr<'input>>>);
#[derive(Debug, Clone)]
enum LazyModule<'input> {
Awaiting(TeaCatAst<'input>),
Finished(Scope<'input>),
}
#[derive(Debug, Clone)]
enum LazyExpr<'input> {
Awaiting {
captures: Scope<'input>,
node: AstNode<'input>,
},
Finished(TeaCatDom<'input>),
}
#[derive(Default)]
pub struct Executor<'input> {
modules: HashMap<ModuleName, LazyModule<'input>>,
}
impl ModuleName {
pub fn new(path: impl AsRef<Path>) -> Self {
Self::_new(path.as_ref())
}
fn _new(path: &Path) -> Self {
let vec = path
.with_extension("")
.iter()
.map(OsStr::to_string_lossy)
.map(Cow::into_owned)
.collect();
Self(vec)
}
}
impl Display for ModuleName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0.join("."))
}
}
impl<'i> Scope<'i> {
pub fn insert(&mut self, name: Moo<'i>, node: AstNode<'i>, captures: Scope<'i>) {
self.0
.to_mut()
.insert(name, LazyExpr::Awaiting { captures, node });
}
fn insert_expr(&mut self, name: Moo<'i>, expr: LazyExpr<'i>) {
self.0.to_mut().insert(name, expr);
}
}
impl<'i> LazyModule<'i> {
fn get_or_exec(&mut self, executor: &mut Executor<'i>) -> CatResult<&Scope<'i>> {
match self {
Self::Finished(scope) => Ok(scope),
Self::Awaiting(ast) => {
let ast = std::mem::replace(ast, vec![].into());
let mut scope = Scope::default();
let mut dom = TeaCatDom::default();
for node in ast {
executor.exec_node(node, &mut dom, &mut scope)?;
}
*self = Self::Finished(scope);
self.get_or_exec(executor)
}
}
}
}
impl<'i> LazyExpr<'i> {
fn get_or_exec(&mut self, executor: &mut Executor<'i>) -> CatResult<&TeaCatDom<'i>> {
match self {
Self::Finished(dom) => Ok(dom),
Self::Awaiting { captures, node } => {
let node = std::mem::replace(node, AstNode::Space);
let ast = vec![node].into();
let dom = executor.exec_ast_with_scope(ast, captures.clone())?;
*self = Self::Finished(dom);
self.get_or_exec(executor)
}
}
}
}
impl<'i> Executor<'i> {
#[inline]
#[must_use]
pub fn list_modules(&self) -> Vec<ModuleName> {
self.modules.keys().cloned().collect()
}
#[inline]
pub fn clear_modules(&mut self) {
self.modules.clear();
}
#[inline]
pub fn add_module(&mut self, name: ModuleName, ast: TeaCatAst<'i>) {
if ast.module {
self.modules.insert(name, LazyModule::Awaiting(ast));
}
}
#[inline]
pub fn exec_ast(&mut self, ast: TeaCatAst<'i>) -> CatResult<TeaCatDom<'i>> {
self.exec_ast_with_scope(ast, Scope::default())
}
#[inline]
pub fn exec_ast_with_scope(
&mut self,
ast: TeaCatAst<'i>,
mut scope: Scope<'i>,
) -> CatResult<TeaCatDom<'i>> {
let mut dom = TeaCatDom::default();
for node in ast {
self.exec_node(node, &mut dom, &mut scope)?;
}
Ok(dom)
}
fn exec_node(
&mut self,
node: AstNode<'i>,
dom: &mut TeaCatDom<'i>,
scope: &mut Scope<'i>,
) -> CatResult<()> {
match node {
AstNode::Space => dom.push(DomNode::Space),
AstNode::Word(moo) => dom.push(DomNode::Word(moo)),
AstNode::Markup { of, inner } => dom.push(DomNode::Markup {
of,
inner: self.exec_ast_with_scope(inner, scope.clone())?,
}),
AstNode::Tag(tag) => dom.push(DomNode::Tag(DomTag {
name: tag.name,
attrs: tag.attrs,
content: self.exec_ast_with_scope(tag.content, scope.clone())?,
})),
AstNode::With { path, imports } => {
self.get_or_exec_module(path)?
.0
.iter()
.filter(|(c, _)| imports.contains(c))
.for_each(|(c, n)| scope.insert_expr(c.clone(), n.clone()));
}
AstNode::Define { name, content } => scope.insert(name, *content, scope.clone()),
AstNode::ContentBlock(ast) => dom.push(DomNode::ContentBlock(
self.exec_ast_with_scope(ast, scope.clone())?,
)),
AstNode::Variable(name) => {
let Some(var) = scope.0.to_mut().get_mut(&name) else {
return Err(TeaCatErr::NotInScope(name.to_string()));
};
dom.append(var.get_or_exec(self)?.clone());
}
}
Ok(())
}
fn get_or_exec_module(&mut self, path: ModuleName) -> CatResult<Scope<'i>> {
let Some(mut module) = self.modules.get(&path).cloned() else {
return Err(TeaCatErr::ModuleNotFound(path));
};
let scope = module.get_or_exec(self)?.clone();
self.modules.insert(path.clone(), module);
Ok(scope)
}
}