diff --git a/src/internal/execute.rs b/src/internal/execute.rs index 5f8b489..f5b6290 100644 --- a/src/internal/execute.rs +++ b/src/internal/execute.rs @@ -2,7 +2,7 @@ use core::fmt::Write; use crate::Value; -use super::parse::{Ast, AstKind, For, Print, ValuePath}; +use super::parse::{Ast, AstKind, For, Has, Print, ValuePath}; pub fn execute(ast: &Ast, value: &dyn Value) -> String { let mut out = String::new(); @@ -17,6 +17,7 @@ pub fn execute_(out: &mut String, ast: &Ast, value: &dyn Value) { AstKind::Text(t) => out.push_str(&t), AstKind::Print(p) => print(out, p, value), AstKind::For(f) => for_(out, f, value), + AstKind::Has(h) => has(out, h, value), e => todo!("{e:?}"), } } @@ -27,8 +28,6 @@ pub fn lookup<'a>( path: &ValuePath, value: &'a dyn Value, ) -> Option<&'a dyn Value> { - dbg!(value); - dbg!(path); let mut value = value; for name in &path.0 { match value.lookup(name) { @@ -65,3 +64,14 @@ pub fn for_(out: &mut String, f: &For, value: &dyn Value) { idx += 1; } } + +pub fn has(out: &mut String, f: &Has, value: &dyn Value) { + let value = match lookup(out, &f.expr, value) { + Some(v) => v, + None => return, + }; + + while let Some(value) = value.has() { + execute_(out, &f.body, value); + } +} diff --git a/src/internal/parse.rs b/src/internal/parse.rs index 8490e36..b584481 100644 --- a/src/internal/parse.rs +++ b/src/internal/parse.rs @@ -18,8 +18,8 @@ pub struct For { #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Has { - expr: Box, - body: Ast, + pub expr: Expr, + pub body: Ast, } #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -108,6 +108,8 @@ impl<'a> ParseState<'a> { self.maybe_parse_print(); } else if remaining.starts_with("for") { self.maybe_parse_for(); + } else if remaining.starts_with("has") { + self.maybe_parse_has(); } } @@ -169,6 +171,47 @@ impl<'a> ParseState<'a> { self.new_empty_text(); } + pub fn maybe_parse_has(&mut self) { + assert!(self.remaining().starts_with("has")); + self.current_byte_offset += "has".len(); + self.skip_whitespace(); + let path = match self.parse_path() { + None => return, + Some(vp) => vp, + }; + self.skip_whitespace(); + if !self.remaining().starts_with('}') { + return; + } + self.current_byte_offset += 1; + + let mut inner = ParseState { + template: self.template, + last_byte_offset: self.last_byte_offset, + speculative_text_starts_at: self.current_byte_offset, + speculative_text_ends_at: self.current_byte_offset, + current_byte_offset: self.current_byte_offset, + ast: Ast::new(), + }; + while inner.has_input() { + if inner.remaining().starts_with("{/has}") { + break; + } + inner.parse_one(); + } + inner.text_might_end(); + inner.add_current_text_to_ast(); + self.current_byte_offset = inner.current_byte_offset; + if !self.remaining().starts_with("{/has}") { + return; + } + self.current_byte_offset += "{/has}".len(); + let body = inner.ast; + + self.push_node_and_text(AstKind::Has(Has { expr: path, body })); + self.new_empty_text(); + } + pub fn parse_path(&mut self) -> Option { assert!(self.remaining().starts_with('.')); self.current_byte_offset += 1; @@ -225,7 +268,7 @@ pub fn parse(template: &str) -> Ast { mod test { use crate::internal::parse::{AstKind, Print, ValuePath}; - use super::{parse, Ast, For}; + use super::{parse, Ast, For, Has}; fn go(tmpl: &str, rhs: Ast) { let ast = parse(tmpl); @@ -272,6 +315,17 @@ mod test { ); } + #[test] + fn parse_has() { + go( + "{has .}{.}{/has}", + vec![AstKind::Has(Has { + expr: ValuePath(vec![]), + body: vec![AstKind::Print(Print(ValuePath(vec![])))], + })], + ); + } + #[test] fn parse_for_2() { go( diff --git a/src/lib.rs b/src/lib.rs index 0203532..90ef381 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -192,6 +192,37 @@ impl Value for [&dyn Value; N] { } } +impl Value for Option +where + T: Value, +{ + fn print(&self, out: &mut String) { + if let Some(v) = self { + v.print(out) + } + } + + fn lookup(&self, _: &str) -> Option<&dyn Value> { + None + } + + fn has(&self) -> Option<&dyn Value> { + self.as_ref().map(|v| v as &dyn Value) + } + + fn if_(&self) -> bool { + self.is_some() + } + + fn for_(&self, index: usize) -> Option<&dyn Value> { + if index == 0 { + self.has() + } else { + None + } + } +} + mod build {} pub struct Newt { @@ -275,4 +306,14 @@ mod test { "Bob, Larry, ", ) } + + #[test] + fn print_has() { + go("{has .}{.}{/has}", &Some("Bob"), "Bob"); + } + + #[test] + fn print_has_none() { + go("{has .}{.}{/has}", &None::<&str>, ""); + } }