#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct ValuePath(pub Vec); pub type Expr = ValuePath; #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct If { expr: Expr, then: Ast, else_: Option, } #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct For { pub expr: Expr, pub body: Ast, } #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Has { expr: Box, body: Ast, } #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Print(pub Expr); #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum AstKind { Text(String), Print(Print), Has(Has), If(If), For(For), } pub type Ast = Vec; pub struct ParseState<'a> { template: &'a str, last_byte_offset: usize, speculative_text_starts_at: usize, speculative_text_ends_at: usize, current_byte_offset: usize, ast: Ast, } impl<'a> ParseState<'a> { pub fn out_of_input(&self) -> bool { self.current_byte_offset >= self.last_byte_offset } pub fn has_input(&self) -> bool { !self.out_of_input() } pub fn remaining(&self) -> &str { &self.template[self.current_byte_offset..] } pub fn add_current_text_to_ast(&mut self) { let text = &self.template [self.speculative_text_starts_at..self.speculative_text_ends_at]; if text.is_empty() { return; } self.ast.push(AstKind::Text( self.template[self.speculative_text_starts_at ..self.speculative_text_ends_at] .to_string(), )); } pub fn text_might_end(&mut self) { self.speculative_text_ends_at = self.current_byte_offset; } pub fn parse(&mut self) { while self.has_input() { self.parse_one(); } self.text_might_end(); self.add_current_text_to_ast(); } pub fn parse_one(&mut self) { assert!(self.has_input()); let started_at = self.current_byte_offset; if self.remaining().starts_with('{') { self.text_might_end(); self.maybe_parse_block(); } else { self.current_byte_offset += 1; } if started_at == self.current_byte_offset { panic!("Parser made no progress"); } } pub fn maybe_parse_block(&mut self) { assert!(self.remaining().starts_with('{')); self.current_byte_offset += 1; self.skip_whitespace(); let remaining = self.remaining(); if remaining.starts_with('.') { self.maybe_parse_print(); } else if remaining.starts_with("for") { self.maybe_parse_for(); } } pub fn maybe_parse_print(&mut self) { assert!(self.remaining().starts_with('.')); 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; self.push_node_and_text(AstKind::Print(Print(path))); self.new_empty_text(); } pub fn maybe_parse_for(&mut self) { assert!(self.remaining().starts_with("for")); self.current_byte_offset += "for".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("{/for}") { 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("{/for}") { return; } self.current_byte_offset += "{/for}".len(); let body = inner.ast; self.push_node_and_text(AstKind::For(For { expr: path, body })); self.new_empty_text(); } pub fn parse_path(&mut self) -> Option { assert!(self.remaining().starts_with('.')); self.current_byte_offset += 1; let end = self .remaining() .find(|ch: char| ch == '}' || ch.is_whitespace()) .unwrap_or(self.last_byte_offset - self.current_byte_offset); let inner = &self.remaining()[..end]; let out = inner.split_terminator('.').map(|s| s.to_string()).collect(); let out = ValuePath(out); self.current_byte_offset += end; Some(out) } pub fn push_node_and_text(&mut self, node: AstKind) { self.add_current_text_to_ast(); self.ast.push(node); } pub fn new_empty_text(&mut self) { self.speculative_text_starts_at = self.current_byte_offset; self.speculative_text_ends_at = self.speculative_text_starts_at; } pub fn skip_whitespace(&mut self) { while self.has_input() { if self.remaining().starts_with(char::is_whitespace) { self.current_byte_offset += self.remaining().chars().next().unwrap().len_utf8(); continue; } break; } } } pub fn parse(template: &str) -> Ast { let mut state = ParseState { template, speculative_text_starts_at: 0, speculative_text_ends_at: 0, current_byte_offset: 0, last_byte_offset: template.len(), ast: Ast::new(), }; state.parse(); state.ast } #[cfg(test)] mod test { use crate::internal::parse::{AstKind, Print, ValuePath}; use super::{parse, Ast, For}; fn go(tmpl: &str, rhs: Ast) { let ast = parse(tmpl); assert_eq!(ast, rhs); } #[test] fn parse_print_only() { go("{.}", vec![AstKind::Print(Print(ValuePath(vec![])))]); } #[test] fn parse_print() { go( "hello, {.}!", vec![ AstKind::Text("hello, ".to_string()), AstKind::Print(Print(ValuePath(vec![]))), AstKind::Text("!".to_string()), ], ); } #[test] fn parse_print_lookup() { go( "hello, {.name}!", vec![ AstKind::Text("hello, ".to_string()), AstKind::Print(Print(ValuePath(vec!["name".to_string()]))), AstKind::Text("!".to_string()), ], ); } #[test] fn parse_for() { go( "{for .}{.}{/for}", vec![AstKind::For(For { expr: ValuePath(vec![]), body: vec![AstKind::Print(Print(ValuePath(vec![])))], })], ); } #[test] fn parse_for_2() { go( "{for .}{.}, {/for}", vec![AstKind::For(For { expr: ValuePath(vec![]), body: vec![ AstKind::Print(Print(ValuePath(vec![]))), AstKind::Text(", ".to_string()), ], })], ); } #[test] fn parse_for_nested() { go( "{for .items}{for .names}{.},{/for}{/for}", vec![AstKind::For(For { expr: ValuePath(vec!["items".to_string()]), body: vec![AstKind::For(For { expr: ValuePath(vec!["names".to_string()]), body: vec![ AstKind::Print(Print(ValuePath(vec![]))), AstKind::Text(",".to_string()), ], })], })], ) } }