From 728d013c6d6f8bca2e806b5225db76e48b6249cb Mon Sep 17 00:00:00 2001 From: soup Date: Sun, 19 May 2024 02:29:24 -0400 Subject: [PATCH] Implement {if} --- src/internal/execute.rs | 18 ++++++- src/internal/parse.rs | 106 ++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 19 ++++++- 3 files changed, 135 insertions(+), 8 deletions(-) diff --git a/src/internal/execute.rs b/src/internal/execute.rs index f5b6290..85fe3aa 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, Has, Print, ValuePath}; +use super::parse::{Ast, AstKind, For, Has, If, Print, ValuePath}; pub fn execute(ast: &Ast, value: &dyn Value) -> String { let mut out = String::new(); @@ -18,6 +18,7 @@ pub fn execute_(out: &mut String, ast: &Ast, value: &dyn Value) { AstKind::Print(p) => print(out, p, value), AstKind::For(f) => for_(out, f, value), AstKind::Has(h) => has(out, h, value), + AstKind::If(h) => if_(out, h, value), e => todo!("{e:?}"), } } @@ -71,7 +72,20 @@ pub fn has(out: &mut String, f: &Has, value: &dyn Value) { None => return, }; - while let Some(value) = value.has() { + if let Some(value) = value.has() { execute_(out, &f.body, value); } } + +pub fn if_(out: &mut String, f: &If, value: &dyn Value) { + let value = match lookup(out, &f.expr, value) { + Some(v) => v, + None => return, + }; + + if value.if_() { + execute_(out, &f.then, value); + } else if let Some(else_) = f.else_.as_ref() { + execute_(out, else_, value); + } +} diff --git a/src/internal/parse.rs b/src/internal/parse.rs index b584481..3389809 100644 --- a/src/internal/parse.rs +++ b/src/internal/parse.rs @@ -5,9 +5,9 @@ pub type Expr = ValuePath; #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct If { - expr: Expr, - then: Ast, - else_: Option, + pub expr: Expr, + pub then: Ast, + pub else_: Option, } #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -110,6 +110,8 @@ impl<'a> ParseState<'a> { self.maybe_parse_for(); } else if remaining.starts_with("has") { self.maybe_parse_has(); + } else if remaining.starts_with("if") { + self.maybe_parse_if(); } } @@ -212,6 +214,78 @@ impl<'a> ParseState<'a> { self.new_empty_text(); } + pub fn maybe_parse_if(&mut self) { + assert!(self.remaining().starts_with("if")); + self.current_byte_offset += "if".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("{/if}") { + break; + } + if inner.remaining().starts_with("{else}") { + break; + } + inner.parse_one(); + } + inner.text_might_end(); + inner.add_current_text_to_ast(); + self.current_byte_offset = inner.current_byte_offset; + + let else_ = if self.remaining().starts_with("{else}") { + self.current_byte_offset += "{else}".len(); + 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("{/if}") { + break; + } + inner.parse_one(); + } + inner.text_might_end(); + inner.add_current_text_to_ast(); + self.current_byte_offset = inner.current_byte_offset; + Some(inner.ast) + } else { + None + }; + if !self.remaining().starts_with("{/if}") { + return; + } + self.current_byte_offset += "{/if}".len(); + let body = inner.ast; + + self.push_node_and_text(AstKind::If(If { + expr: path, + then: body, + else_, + })); + self.new_empty_text(); + } + pub fn parse_path(&mut self) -> Option { assert!(self.remaining().starts_with('.')); self.current_byte_offset += 1; @@ -268,7 +342,7 @@ pub fn parse(template: &str) -> Ast { mod test { use crate::internal::parse::{AstKind, Print, ValuePath}; - use super::{parse, Ast, For, Has}; + use super::{parse, Ast, For, Has, If}; fn go(tmpl: &str, rhs: Ast) { let ast = parse(tmpl); @@ -326,6 +400,30 @@ mod test { ); } + #[test] + fn parse_if() { + go( + "{if .}{.}{/if}", + vec![AstKind::If(If { + expr: ValuePath(vec![]), + then: vec![AstKind::Print(Print(ValuePath(vec![])))], + else_: None, + })], + ); + } + + #[test] + fn parse_if_else() { + go( + "{if .}{.}{else}Empty{/if}", + vec![AstKind::If(If { + expr: ValuePath(vec![]), + then: vec![AstKind::Print(Print(ValuePath(vec![])))], + else_: Some(vec![AstKind::Text("Empty".to_string())]), + })], + ); + } + #[test] fn parse_for_2() { go( diff --git a/src/lib.rs b/src/lib.rs index 90ef381..874c5a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,7 @@ impl Value for &str { } fn has(&self) -> Option<&dyn Value> { - if self.is_empty() { + if self.if_() { None } else { Some(self) @@ -58,7 +58,7 @@ impl Value for &str { } fn if_(&self) -> bool { - self.is_empty() + !self.is_empty() } fn for_(&self, index: usize) -> Option<&dyn Value> { @@ -316,4 +316,19 @@ mod test { fn print_has_none() { go("{has .}{.}{/has}", &None::<&str>, ""); } + + #[test] + fn print_if() { + go("{if .}{.}{/if}", &"Bob", "Bob"); + } + + #[test] + fn print_if_false() { + go("{if .}{.}{/if}", &"", ""); + } + + #[test] + fn print_if_false_else() { + go("{if .}{.}{else}Frank{/if}", &"", "Frank"); + } }