diff --git a/src/internal/execute.rs b/src/internal/execute.rs index 550865a..2952454 100644 --- a/src/internal/execute.rs +++ b/src/internal/execute.rs @@ -2,32 +2,64 @@ use core::fmt::Write; use crate::Value; -use super::parse::{Ast, AstKind, Print}; +use super::parse::{Ast, AstKind, For, Print, ValuePath}; pub fn execute(ast: &Ast, value: &dyn Value) -> String { let mut out = String::new(); - for node in ast { - match node { - AstKind::Text(t) => out.push_str(&t), - AstKind::Print(p) => print(&mut out, p, value), - e => todo!("{e:?}"), - } - } + execute_(&mut out, ast, value); out } -pub fn print(out: &mut String, p: &Print, value: &dyn Value) { - let path = &p.0 .0; +pub fn execute_(out: &mut String, ast: &Ast, value: &dyn Value) { + for node in ast { + match node { + AstKind::Text(t) => out.push_str(&t), + AstKind::Print(p) => print(out, p, value), + AstKind::For(f) => for_(out, f, value), + e => todo!("{e:?}"), + } + } +} + +pub fn lookup<'a>( + out: &mut String, + path: &ValuePath, + value: &'a dyn Value, +) -> Option<&'a dyn Value> { let mut value = value; - for name in path { + for name in &path.0 { match value.lookup(name) { Some(p) => value = p, None => { - let _ = write!(out, "'{name}' missing in current value"); + let path = path.0.join("."); + let _ = write!(out, "'.{path}' not found in current value"); + return None; }, } } + Some(value) +} + +pub fn print(out: &mut String, p: &Print, value: &dyn Value) { + let value = match lookup(out, &p.0, value) { + Some(v) => v, + None => return, + }; + value.print(out) } + +pub fn for_(out: &mut String, f: &For, value: &dyn Value) { + let value = match lookup(out, &f.expr, value) { + Some(v) => v, + None => return, + }; + + let mut idx = 0; + while let Some(value) = value.for_(idx) { + execute_(out, &f.body, value); + idx += 0; + } +} diff --git a/src/internal/parse.rs b/src/internal/parse.rs index fe92ab7..83fb24f 100644 --- a/src/internal/parse.rs +++ b/src/internal/parse.rs @@ -12,8 +12,8 @@ pub struct If { #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct For { - expr: Expr, - body: Ast, + pub expr: Expr, + pub body: Ast, } #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -76,22 +76,25 @@ impl<'a> ParseState<'a> { pub fn parse(&mut self) { while self.has_input() { - let started_at = self.current_byte_offset; - let block_start = match self.remaining().find('{') { - Some(bs) => bs, - None => { - self.current_byte_offset = self.last_byte_offset; - self.add_current_text_to_ast(); - break; - }, - }; - self.current_byte_offset += block_start; + 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"); - } + if started_at == self.current_byte_offset { + panic!("Parser made no progress"); } } @@ -100,14 +103,20 @@ impl<'a> ParseState<'a> { self.current_byte_offset += 1; self.skip_whitespace(); - if self.remaining().starts_with('.') { + 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 = self.parse_path(); + let path = match self.parse_path() { + None => return, + Some(vp) => vp, + }; self.skip_whitespace(); if !self.remaining().starts_with('}') { @@ -119,7 +128,46 @@ impl<'a> ParseState<'a> { self.new_empty_text(); } - pub fn parse_path(&mut self) -> ValuePath { + 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(); + } + 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 @@ -132,7 +180,7 @@ impl<'a> ParseState<'a> { self.current_byte_offset += end; - out + Some(out) } pub fn push_node_and_text(&mut self, node: AstKind) { @@ -142,12 +190,14 @@ impl<'a> ParseState<'a> { 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 + 1; + 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; @@ -159,7 +209,7 @@ pub fn parse(template: &str) -> Ast { let mut state = ParseState { template, speculative_text_starts_at: 0, - speculative_text_ends_at: 1, + speculative_text_ends_at: 0, current_byte_offset: 0, last_byte_offset: template.len(), ast: Ast::new(),