use anathema_store::smallmap::SmallMap;
use anathema_store::storage::strings::StringId;
use super::const_eval::const_eval;
use super::{Context, Statement, Statements};
use crate::blueprints::{Blueprint, Component, ControlFlow, Else, For, Single};
use crate::error::{Error, Result};
use crate::expressions::Expression;
use crate::{ComponentBlueprintId, Primitive};
pub(crate) struct Scope {
statements: Statements,
}
impl Scope {
pub(crate) fn new(statements: Statements) -> Self {
Self { statements }
}
pub(crate) fn eval(mut self, ctx: &mut Context<'_>) -> Result<Vec<Blueprint>> {
let mut output = vec![];
while let Some(statement) = self.statements.next() {
match statement {
Statement::Node(ident) => output.push(self.eval_node(ident, ctx)?),
Statement::Component(component_id) => output.push(self.eval_component(component_id, ctx)?),
Statement::For { binding, data } => {
if let Some(expr) = self.eval_for(binding, data, ctx)? {
output.push(expr);
}
}
Statement::If(cond) => output.push(self.eval_if(cond, ctx)?),
Statement::Declaration { binding, value } => {
let Some(value) = const_eval(value, ctx) else { continue };
let binding = ctx.strings.get_unchecked(binding);
ctx.globals.declare(binding, value);
}
Statement::ComponentSlot(slot_id) => {
if let Some(bp) = ctx.slots.get(&slot_id).cloned() {
output.push(Blueprint::Slot(bp));
}
}
Statement::ScopeStart
| Statement::ScopeEnd
| Statement::LoadAttribute { .. }
| Statement::AssociatedFunction { .. }
| Statement::Else(_)
| Statement::LoadValue(_) => {
unreachable!("\"{statement:?}\" found: this is a bug in Anathema. Please open an issue")
}
Statement::Eof => break,
}
}
Ok(output)
}
fn eval_node(&mut self, ident: StringId, ctx: &mut Context<'_>) -> Result<Blueprint> {
let ident = ctx.strings.get_unchecked(ident);
let attributes = self.eval_attributes(ctx)?;
let value = self.statements.take_value().and_then(|v| const_eval(v, ctx));
let children = self.consume_scope(ctx)?;
let node = Blueprint::Single(Single {
ident,
children,
attributes,
value,
});
Ok(node)
}
fn eval_for(&mut self, binding: StringId, data: Expression, ctx: &mut Context<'_>) -> Result<Option<Blueprint>> {
let Some(data) = const_eval(data, ctx) else { return Ok(None) };
let binding = ctx.strings.get_unchecked(binding);
ctx.globals.declare_local(binding.clone());
let body = self.consume_scope(ctx)?;
let node = Blueprint::For(For { binding, data, body });
Ok(Some(node))
}
fn consume_scope(&mut self, ctx: &mut Context<'_>) -> Result<Vec<Blueprint>> {
let scope = Scope::new(self.statements.take_scope());
scope.eval(ctx)
}
fn eval_attributes(&mut self, ctx: &mut Context<'_>) -> Result<SmallMap<String, Expression>> {
let mut hm = SmallMap::empty();
for (key, value) in self.statements.take_attributes() {
let Some(value) = const_eval(value, ctx) else { continue };
let key = ctx.strings.get_unchecked(key);
hm.set(key, value);
}
Ok(hm)
}
fn eval_if(&mut self, cond: Expression, ctx: &mut Context<'_>) -> Result<Blueprint> {
let cond = const_eval(cond, ctx).unwrap_or(Expression::Primitive(Primitive::Bool(false)));
let body = self.consume_scope(ctx)?;
if body.is_empty() {
return Err(Error::EmptyBody);
}
let mut elses = vec![Else { cond: Some(cond), body }];
while let Some(cond) = self.statements.next_else() {
let body = self.consume_scope(ctx)?;
let cond = cond.and_then(|v| const_eval(v, ctx));
if body.is_empty() {
return Err(Error::EmptyBody);
}
elses.push(Else { cond, body });
}
Ok(Blueprint::ControlFlow(ControlFlow { elses }))
}
fn eval_component(&mut self, component_id: ComponentBlueprintId, ctx: &mut Context<'_>) -> Result<Blueprint> {
let parent = ctx.component_parent();
let assoc_functions = self.statements.take_assoc_functions();
let attributes = self.eval_attributes(ctx)?;
let mut slots = SmallMap::empty();
let mut scope = self.statements.take_scope();
if !scope.is_next_slot() {
let slot_id = ctx.strings.children();
let scope = Scope::new(scope);
let body = scope.eval(ctx)?;
slots.set(slot_id, body);
} else {
while let Some(slot_id) = scope.next_slot() {
let scope = Scope::new(scope.take_scope());
let body = scope.eval(ctx)?;
slots.set(slot_id, body);
}
}
let body = ctx.load_component(component_id, slots)?;
let name_id = ctx.components.name(component_id);
let name = ctx.strings.get_unchecked(name_id);
let component = Component {
name,
name_id,
id: component_id,
body,
attributes,
assoc_functions,
parent,
};
Ok(Blueprint::Component(component))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::document::Document;
use crate::{ToSourceKind, single};
#[test]
fn eval_node() {
let mut doc = Document::new("node");
let (bp, _) = doc.compile().unwrap();
assert_eq!(bp, single!("node"));
}
#[test]
fn eval_node_with_children() {
let src = "
a
b
";
let mut doc = Document::new(src);
let (blueprint, _) = doc.compile().unwrap();
assert_eq!(blueprint, single!(children @ "a", vec![single!("b")]));
}
#[test]
fn eval_nested_nodes() {
let src = "
node a
node a
";
let mut doc = Document::new(src);
let (blueprint, _) = doc.compile().unwrap();
assert!(matches!(blueprint, Blueprint::Single(Single { value: Some(_), .. })));
}
#[test]
fn eval_for() {
let src = "
for a in a
node
";
let mut doc = Document::new(src);
let (blueprint, _) = doc.compile().unwrap();
assert!(matches!(blueprint, Blueprint::For(For { .. })));
}
#[test]
fn if_else() {
let src = "
if 1 == 2
text
else
border
";
let mut doc = Document::new(src);
let (blueprint, _) = doc.compile().unwrap();
let Blueprint::ControlFlow(controlflow) = blueprint else { panic!() };
assert!(matches!(controlflow.elses[0], Else { .. }));
assert!(!controlflow.elses.is_empty());
}
#[test]
fn eval_component() {
let src = "@comp [a: 1]";
let comp_src = "node a + 2";
let mut doc = Document::new(src);
doc.add_component("comp", comp_src.to_template()).unwrap();
let (blueprint, _) = doc.compile().unwrap();
assert!(matches!(blueprint, Blueprint::Component(Component { .. })));
}
#[test]
fn eval_component_slots() {
let src = "
@comp
$s1
node '1'
$s2
node '2'
";
let comp_src = "
node
$s1
$s2
";
let mut doc = Document::new(src);
doc.add_component("comp", comp_src.to_template()).unwrap();
let (blueprint, _) = doc.compile().unwrap();
assert!(matches!(blueprint, Blueprint::Component(Component { .. })));
}
#[test]
fn eval_two_identical_components() {
let src = "
vstack
@comp (a->b) [ a: 1 ]
@comp (a->b) [ a: 2 ]
";
let mut doc = Document::new(src);
doc.add_component("comp", "node a".to_template()).unwrap();
let _ = doc.compile().unwrap();
}
#[test]
fn component_with_assoc_attrs_state() {
let src = "
@comp (a->b) [a: 1]
";
let mut doc = Document::new(src);
doc.add_component("comp", "node a".to_template()).unwrap();
let _ = doc.compile().unwrap();
}
}