Add {for} block
This commit is contained in:
parent
8dfbc70cee
commit
8c0f5d6f46
|
|
@ -2,32 +2,64 @@ use core::fmt::Write;
|
||||||
|
|
||||||
use crate::Value;
|
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 {
|
pub fn execute(ast: &Ast, value: &dyn Value) -> String {
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
for node in ast {
|
execute_(&mut out, ast, value);
|
||||||
match node {
|
|
||||||
AstKind::Text(t) => out.push_str(&t),
|
|
||||||
AstKind::Print(p) => print(&mut out, p, value),
|
|
||||||
e => todo!("{e:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print(out: &mut String, p: &Print, value: &dyn Value) {
|
pub fn execute_(out: &mut String, ast: &Ast, value: &dyn Value) {
|
||||||
let path = &p.0 .0;
|
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;
|
let mut value = value;
|
||||||
for name in path {
|
for name in &path.0 {
|
||||||
match value.lookup(name) {
|
match value.lookup(name) {
|
||||||
Some(p) => value = p,
|
Some(p) => value = p,
|
||||||
None => {
|
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)
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ pub struct If {
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct For {
|
pub struct For {
|
||||||
expr: Expr,
|
pub expr: Expr,
|
||||||
body: Ast,
|
pub body: Ast,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
|
@ -76,22 +76,25 @@ impl<'a> ParseState<'a> {
|
||||||
|
|
||||||
pub fn parse(&mut self) {
|
pub fn parse(&mut self) {
|
||||||
while self.has_input() {
|
while self.has_input() {
|
||||||
let started_at = self.current_byte_offset;
|
self.parse_one();
|
||||||
let block_start = match self.remaining().find('{') {
|
}
|
||||||
Some(bs) => bs,
|
|
||||||
None => {
|
self.text_might_end();
|
||||||
self.current_byte_offset = self.last_byte_offset;
|
self.add_current_text_to_ast();
|
||||||
self.add_current_text_to_ast();
|
}
|
||||||
break;
|
|
||||||
},
|
pub fn parse_one(&mut self) {
|
||||||
};
|
assert!(self.has_input());
|
||||||
self.current_byte_offset += block_start;
|
let started_at = self.current_byte_offset;
|
||||||
|
if self.remaining().starts_with('{') {
|
||||||
self.text_might_end();
|
self.text_might_end();
|
||||||
self.maybe_parse_block();
|
self.maybe_parse_block();
|
||||||
|
} else {
|
||||||
|
self.current_byte_offset += 1;
|
||||||
|
}
|
||||||
|
|
||||||
if started_at == self.current_byte_offset {
|
if started_at == self.current_byte_offset {
|
||||||
panic!("Parser made no progress");
|
panic!("Parser made no progress");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,14 +103,20 @@ impl<'a> ParseState<'a> {
|
||||||
self.current_byte_offset += 1;
|
self.current_byte_offset += 1;
|
||||||
self.skip_whitespace();
|
self.skip_whitespace();
|
||||||
|
|
||||||
if self.remaining().starts_with('.') {
|
let remaining = self.remaining();
|
||||||
|
if remaining.starts_with('.') {
|
||||||
self.maybe_parse_print();
|
self.maybe_parse_print();
|
||||||
|
} else if remaining.starts_with("for") {
|
||||||
|
self.maybe_parse_for();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn maybe_parse_print(&mut self) {
|
pub fn maybe_parse_print(&mut self) {
|
||||||
assert!(self.remaining().starts_with('.'));
|
assert!(self.remaining().starts_with('.'));
|
||||||
let path = self.parse_path();
|
let path = match self.parse_path() {
|
||||||
|
None => return,
|
||||||
|
Some(vp) => vp,
|
||||||
|
};
|
||||||
|
|
||||||
self.skip_whitespace();
|
self.skip_whitespace();
|
||||||
if !self.remaining().starts_with('}') {
|
if !self.remaining().starts_with('}') {
|
||||||
|
|
@ -119,7 +128,46 @@ impl<'a> ParseState<'a> {
|
||||||
self.new_empty_text();
|
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<ValuePath> {
|
||||||
assert!(self.remaining().starts_with('.'));
|
assert!(self.remaining().starts_with('.'));
|
||||||
self.current_byte_offset += 1;
|
self.current_byte_offset += 1;
|
||||||
let end = self
|
let end = self
|
||||||
|
|
@ -132,7 +180,7 @@ impl<'a> ParseState<'a> {
|
||||||
|
|
||||||
self.current_byte_offset += end;
|
self.current_byte_offset += end;
|
||||||
|
|
||||||
out
|
Some(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_node_and_text(&mut self, node: AstKind) {
|
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) {
|
pub fn new_empty_text(&mut self) {
|
||||||
self.speculative_text_starts_at = self.current_byte_offset;
|
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) {
|
pub fn skip_whitespace(&mut self) {
|
||||||
while self.has_input() {
|
while self.has_input() {
|
||||||
if self.remaining().starts_with(char::is_whitespace) {
|
if self.remaining().starts_with(char::is_whitespace) {
|
||||||
|
self.current_byte_offset +=
|
||||||
|
self.remaining().chars().next().unwrap().len_utf8();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -159,7 +209,7 @@ pub fn parse(template: &str) -> Ast {
|
||||||
let mut state = ParseState {
|
let mut state = ParseState {
|
||||||
template,
|
template,
|
||||||
speculative_text_starts_at: 0,
|
speculative_text_starts_at: 0,
|
||||||
speculative_text_ends_at: 1,
|
speculative_text_ends_at: 0,
|
||||||
current_byte_offset: 0,
|
current_byte_offset: 0,
|
||||||
last_byte_offset: template.len(),
|
last_byte_offset: template.len(),
|
||||||
ast: Ast::new(),
|
ast: Ast::new(),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue