Compare commits
3 commits
323a38db3a
...
6e0a7375c0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e0a7375c0 | ||
|
|
3c4f063d08 | ||
|
|
84c4d9d484 |
|
|
@ -2,30 +2,46 @@ use core::fmt::Write;
|
|||
|
||||
use crate::Value;
|
||||
|
||||
use super::parse::{Ast, AstKind, For, Has, If, Print, ValuePath};
|
||||
use super::parse::{Ast, AstArena, AstNodeKind, For, Has, If, Path, Print};
|
||||
|
||||
pub fn execute(ast: &Ast, value: &dyn Value) -> String {
|
||||
let mut out = String::new();
|
||||
execute_(&mut out, ast, value);
|
||||
let nodes = ast.arena.deref_many(&ast.root);
|
||||
execute_(&mut out, nodes, value, &ast.arena);
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
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),
|
||||
AstKind::Has(h) => has(out, h, value),
|
||||
AstKind::If(h) => if_(out, h, value),
|
||||
}
|
||||
pub fn execute_(
|
||||
out: &mut String,
|
||||
nodes: &[AstNodeKind],
|
||||
value: &dyn Value,
|
||||
arena: &AstArena,
|
||||
) {
|
||||
for node in nodes {
|
||||
execute_one(out, node, value, arena)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute_one(
|
||||
out: &mut String,
|
||||
node: &AstNodeKind,
|
||||
value: &dyn Value,
|
||||
arena: &AstArena,
|
||||
) {
|
||||
match node {
|
||||
AstNodeKind::Text(t) => out.push_str(&t),
|
||||
AstNodeKind::Print(p) => print(out, p, value),
|
||||
AstNodeKind::For(f) => for_(out, f, value, arena),
|
||||
AstNodeKind::Has(h) => has(out, h, value, arena),
|
||||
AstNodeKind::With(_) => todo!(),
|
||||
AstNodeKind::If(h) => if_(out, h, value, arena),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lookup<'a>(
|
||||
out: &mut String,
|
||||
path: &ValuePath,
|
||||
path: &Path,
|
||||
value: &'a dyn Value,
|
||||
) -> Option<&'a dyn Value> {
|
||||
let mut value = value;
|
||||
|
|
@ -44,7 +60,7 @@ pub fn lookup<'a>(
|
|||
}
|
||||
|
||||
pub fn print(out: &mut String, p: &Print, value: &dyn Value) {
|
||||
let value = match lookup(out, &p.0, value) {
|
||||
let value = match lookup(out, &p.path, value) {
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
};
|
||||
|
|
@ -52,39 +68,43 @@ pub fn print(out: &mut String, p: &Print, value: &dyn Value) {
|
|||
value.print(out)
|
||||
}
|
||||
|
||||
pub fn for_(out: &mut String, f: &For, value: &dyn Value) {
|
||||
let value = match lookup(out, &f.expr, value) {
|
||||
pub fn for_(out: &mut String, f: &For, value: &dyn Value, arena: &AstArena) {
|
||||
let value = match lookup(out, &f.path, value) {
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let mut idx = 0;
|
||||
while let Some(value) = value.for_(idx) {
|
||||
execute_(out, &f.body, value);
|
||||
let nodes = arena.deref_many(&f.body);
|
||||
execute_(out, nodes, value, arena);
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has(out: &mut String, f: &Has, value: &dyn Value) {
|
||||
let value = match lookup(out, &f.expr, value) {
|
||||
pub fn has(out: &mut String, f: &Has, value: &dyn Value, arena: &AstArena) {
|
||||
let value = match lookup(out, &f.path, value) {
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
};
|
||||
|
||||
if let Some(value) = value.has() {
|
||||
execute_(out, &f.body, value);
|
||||
let nodes = arena.deref_many(&f.then);
|
||||
execute_(out, nodes, value, arena);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn if_(out: &mut String, f: &If, value: &dyn Value) {
|
||||
let value = match lookup(out, &f.expr, value) {
|
||||
pub fn if_(out: &mut String, f: &If, value: &dyn Value, arena: &AstArena) {
|
||||
let value = match lookup(out, &f.path, value) {
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
};
|
||||
|
||||
if value.if_() {
|
||||
execute_(out, &f.then, value);
|
||||
let nodes = arena.deref_many(&f.then);
|
||||
execute_(out, nodes, value, arena);
|
||||
} else if let Some(else_) = f.else_.as_ref() {
|
||||
execute_(out, else_, value);
|
||||
let nodes = arena.deref_many(&else_);
|
||||
execute_(out, nodes, value, arena);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@
|
|||
|
||||
pub mod execute;
|
||||
pub mod parse;
|
||||
mod parse_backup;
|
||||
|
|
|
|||
|
|
@ -1,457 +1,546 @@
|
|||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct ValuePath(pub Vec<String>);
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
pub type Expr = ValuePath;
|
||||
pub use super::parse_backup::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct If {
|
||||
pub expr: Expr,
|
||||
pub then: Ast,
|
||||
pub else_: Option<Ast>,
|
||||
}
|
||||
|
||||
#[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 {
|
||||
pub expr: Expr,
|
||||
pub 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),
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum AstNodeKind {
|
||||
If(If),
|
||||
For(For),
|
||||
Has(Has),
|
||||
With(With),
|
||||
Print(Print),
|
||||
Text(String),
|
||||
}
|
||||
pub type Ast = Vec<AstKind>;
|
||||
|
||||
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,
|
||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Path(pub Vec<String>);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct If {
|
||||
pub path: Path,
|
||||
pub then: Vec<AstNodeKind>,
|
||||
pub else_: Option<Vec<AstNodeKind>>,
|
||||
}
|
||||
impl<'a> ParseState<'a> {
|
||||
pub fn out_of_input(&self) -> bool {
|
||||
self.current_byte_offset >= self.last_byte_offset
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct For {
|
||||
pub path: Path,
|
||||
pub body: Vec<AstNodeKind>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Has {
|
||||
pub path: Path,
|
||||
pub then: Vec<AstNodeKind>,
|
||||
pub else_: Option<Vec<AstNodeKind>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct With {
|
||||
pub path: Path,
|
||||
pub body: Vec<AstNodeKind>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Print {
|
||||
pub path: Path,
|
||||
}
|
||||
|
||||
pub fn parse(source: &str) -> Vec<AstNodeKind> {
|
||||
parse_many_until_eof(source).1
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct ParseResult<'a, T>(
|
||||
&'a str, // remaining
|
||||
T, // output
|
||||
);
|
||||
impl<'a, T> ParseResult<'a, T> {
|
||||
fn remaining(&self) -> &'a str {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn has_input(&self) -> bool {
|
||||
!self.out_of_input()
|
||||
fn output(&self) -> &T {
|
||||
&self.1
|
||||
}
|
||||
|
||||
pub fn remaining(&self) -> &str {
|
||||
&self.template[self.current_byte_offset..]
|
||||
fn map<U>(self, f: impl FnOnce(T) -> U) -> ParseResult<'a, U> {
|
||||
ParseResult(self.0, f(self.1))
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
));
|
||||
fn parse_block(rest: &str) -> ParseResult<Option<AstNodeKind>> {
|
||||
let rest_ = match rest.strip_prefix('{') {
|
||||
Some(v) => v,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
let rest_ = rest_.trim_start();
|
||||
if rest_.starts_with("for ") {
|
||||
parse_for(rest).map(|v| v.map(AstNodeKind::For))
|
||||
} else if rest_.starts_with("if ") {
|
||||
parse_if(rest).map(|v| v.map(AstNodeKind::If))
|
||||
} else if rest_.starts_with("has ") {
|
||||
parse_has(rest).map(|v| v.map(AstNodeKind::Has))
|
||||
} else if rest_.starts_with("with ") {
|
||||
parse_with(rest).map(|v| v.map(AstNodeKind::With))
|
||||
} else {
|
||||
parse_print(rest).map(|v| v.map(AstNodeKind::Print))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_might_end(&mut self) {
|
||||
self.speculative_text_ends_at = self.current_byte_offset;
|
||||
#[test]
|
||||
fn test_parse_block_print_simple() {
|
||||
assert_eq!(
|
||||
parse_block("{.}"),
|
||||
ParseResult("", Some(AstNodeKind::Print(Print { path: Path(vec![]) })))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_block_print_simple_trailing() {
|
||||
assert_eq!(
|
||||
parse_block("{.} Text"),
|
||||
ParseResult(
|
||||
" Text",
|
||||
Some(AstNodeKind::Print(Print { path: Path(vec![]) }))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_print(rest: &str) -> ParseResult<Option<Print>> {
|
||||
let rest = match rest.strip_prefix('{') {
|
||||
Some(r) => r,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
let rest = rest.trim_start();
|
||||
|
||||
let ParseResult(rest, path) = parse_path(rest);
|
||||
let path = match path {
|
||||
Some(p) => p,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
|
||||
let rest = rest.trim_start();
|
||||
if let Some(rest) = rest.strip_prefix('}') {
|
||||
ParseResult(rest, Some(Print { path }))
|
||||
} else {
|
||||
ParseResult(rest, None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(&mut self) {
|
||||
while self.has_input() {
|
||||
self.parse_one();
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_print_trailing() {
|
||||
assert_eq!(
|
||||
parse_print("{.} World"),
|
||||
ParseResult(" World", Some(Print { path: Path(vec![]) }))
|
||||
)
|
||||
}
|
||||
|
||||
self.text_might_end();
|
||||
self.add_current_text_to_ast();
|
||||
fn parse_for(rest: &str) -> ParseResult<Option<For>> {
|
||||
let rest = match rest.strip_prefix('{') {
|
||||
Some(r) => r,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
let rest = rest.trim_start();
|
||||
|
||||
let rest = match rest.strip_prefix("for ") {
|
||||
Some(r) => r,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
|
||||
let ParseResult(rest, path) = parse_path(rest);
|
||||
let path = match path {
|
||||
Some(p) => p,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
|
||||
let rest = rest.trim_start();
|
||||
if let Some(rest) = rest.strip_prefix('}') {
|
||||
let ParseResult(rest, body) =
|
||||
parse_many_until(rest, |s| s.starts_with("{/for}"));
|
||||
let body = match body {
|
||||
Some(b) => b,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
let rest = rest.strip_prefix("{/for}").unwrap();
|
||||
|
||||
ParseResult(rest, Some(For { path, body }))
|
||||
} else {
|
||||
ParseResult(rest, None)
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_for_basic() {
|
||||
assert_eq!(
|
||||
parse_for("{for .}{.}{/for}"),
|
||||
ParseResult(
|
||||
"",
|
||||
Some(For {
|
||||
path: Path(vec![]),
|
||||
body: vec![AstNodeKind::Print(Print { path: Path(vec![]) })]
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if started_at == self.current_byte_offset {
|
||||
panic!("Parser made no progress");
|
||||
}
|
||||
fn parse_with(rest: &str) -> ParseResult<Option<With>> {
|
||||
let rest = match rest.strip_prefix('{') {
|
||||
Some(r) => r,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
let rest = rest.trim_start();
|
||||
|
||||
let rest = match rest.strip_prefix("with ") {
|
||||
Some(r) => r,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
|
||||
let ParseResult(rest, path) = parse_path(rest);
|
||||
let path = match path {
|
||||
Some(p) => p,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
|
||||
let rest = rest.trim_start();
|
||||
if let Some(rest) = rest.strip_prefix('}') {
|
||||
let ParseResult(rest, body) =
|
||||
parse_many_until(rest, |s| s.starts_with("{/with}"));
|
||||
let body = match body {
|
||||
Some(b) => b,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
let rest = rest.strip_prefix("{/with}").unwrap();
|
||||
|
||||
ParseResult(rest, Some(With { path, body }))
|
||||
} else {
|
||||
ParseResult(rest, None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maybe_parse_block(&mut self) {
|
||||
assert!(self.remaining().starts_with('{'));
|
||||
self.current_byte_offset += 1;
|
||||
self.skip_whitespace();
|
||||
#[test]
|
||||
fn test_parse_with_basic() {
|
||||
assert_eq!(
|
||||
parse_with("{with .}{.}{/with}"),
|
||||
ParseResult(
|
||||
"",
|
||||
Some(With {
|
||||
path: Path(vec![]),
|
||||
body: vec![AstNodeKind::Print(Print { path: Path(vec![]) })]
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
let remaining = self.remaining();
|
||||
if remaining.starts_with('.') {
|
||||
self.maybe_parse_print();
|
||||
} else if remaining.starts_with("for") {
|
||||
self.maybe_parse_for();
|
||||
} else if remaining.starts_with("has") {
|
||||
self.maybe_parse_has();
|
||||
} else if remaining.starts_with("if") {
|
||||
self.maybe_parse_if();
|
||||
}
|
||||
}
|
||||
fn parse_if(rest: &str) -> ParseResult<Option<If>> {
|
||||
let rest = match rest.strip_prefix('{') {
|
||||
Some(r) => r,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
let rest = rest.trim_start();
|
||||
|
||||
pub fn maybe_parse_print(&mut self) {
|
||||
assert!(self.remaining().starts_with('.'));
|
||||
let path = match self.parse_path() {
|
||||
None => return,
|
||||
Some(vp) => vp,
|
||||
let rest = match rest.strip_prefix("if ") {
|
||||
Some(r) => r,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
|
||||
let ParseResult(rest, path) = parse_path(rest);
|
||||
let path = match path {
|
||||
Some(p) => p,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
|
||||
let rest = rest.trim_start();
|
||||
if let Some(rest) = rest.strip_prefix('}') {
|
||||
let ParseResult(rest, body) = parse_many_until(rest, |s| {
|
||||
s.starts_with("{/if}") || s.starts_with("{else}")
|
||||
});
|
||||
let then = match body {
|
||||
Some(b) => b,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
|
||||
self.skip_whitespace();
|
||||
if !self.remaining().starts_with('}') {
|
||||
return;
|
||||
}
|
||||
self.current_byte_offset += 1;
|
||||
let ParseResult(rest, else_) =
|
||||
if let Some(rest) = rest.strip_prefix("{else}") {
|
||||
let ParseResult(rest, else_) =
|
||||
parse_many_until(rest, |s| s.starts_with("{/if}"));
|
||||
let else_ = match else_ {
|
||||
Some(b) => b,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
let rest = rest.strip_prefix("{/if}").unwrap();
|
||||
|
||||
self.push_node_and_text(AstKind::Print(Print(path)));
|
||||
self.new_empty_text();
|
||||
}
|
||||
ParseResult(rest, Some(else_))
|
||||
} else {
|
||||
let rest = rest.strip_prefix("{/if}").unwrap();
|
||||
|
||||
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 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 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(),
|
||||
ParseResult(rest, None)
|
||||
};
|
||||
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
|
||||
|
||||
ParseResult(rest, Some(If { path, then, else_ }))
|
||||
} else {
|
||||
ParseResult(rest, None)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_if_basic() {
|
||||
assert_eq!(
|
||||
parse_if("{if .}{.}{/if}"),
|
||||
ParseResult(
|
||||
"",
|
||||
Some(If {
|
||||
path: Path(vec![]),
|
||||
then: vec![AstNodeKind::Print(Print { path: Path(vec![]) })],
|
||||
else_: None,
|
||||
}),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_if_else_basic() {
|
||||
assert_eq!(
|
||||
parse_if("{if .}{.}{else}Else{/if}"),
|
||||
ParseResult(
|
||||
"",
|
||||
Some(If {
|
||||
path: Path(vec![]),
|
||||
then: vec![AstNodeKind::Print(Print { path: Path(vec![]) })],
|
||||
else_: Some(vec![AstNodeKind::Text("Else".to_string())]),
|
||||
}),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_has(rest: &str) -> ParseResult<Option<Has>> {
|
||||
let rest = match rest.strip_prefix('{') {
|
||||
Some(r) => r,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
let rest = rest.trim_start();
|
||||
|
||||
let rest = match rest.strip_prefix("has ") {
|
||||
Some(r) => r,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
|
||||
let ParseResult(rest, path) = parse_path(rest);
|
||||
let path = match path {
|
||||
Some(p) => p,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
|
||||
let rest = rest.trim_start();
|
||||
if let Some(rest) = rest.strip_prefix('}') {
|
||||
let ParseResult(rest, body) = parse_many_until(rest, |s| {
|
||||
s.starts_with("{/has}") || s.starts_with("{else}")
|
||||
});
|
||||
let then = match body {
|
||||
Some(b) => b,
|
||||
None => return ParseResult(rest, 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();
|
||||
let ParseResult(rest, else_) =
|
||||
if let Some(rest) = rest.strip_prefix("{else}") {
|
||||
let ParseResult(rest, else_) =
|
||||
parse_many_until(rest, |s| s.starts_with("{/has}"));
|
||||
let else_ = match else_ {
|
||||
Some(b) => b,
|
||||
None => return ParseResult(rest, None),
|
||||
};
|
||||
let rest = rest.strip_prefix("{/has}").unwrap();
|
||||
|
||||
ParseResult(rest, Some(else_))
|
||||
} else {
|
||||
let rest = rest.strip_prefix("{/has}").unwrap();
|
||||
|
||||
ParseResult(rest, None)
|
||||
};
|
||||
|
||||
ParseResult(rest, Some(Has { path, then, else_ }))
|
||||
} else {
|
||||
ParseResult(rest, None)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_path(rest: &str) -> ParseResult<Option<Path>> {
|
||||
if !rest.starts_with('.') {
|
||||
return ParseResult(rest, None);
|
||||
}
|
||||
|
||||
pub fn parse_path(&mut self) -> Option<ValuePath> {
|
||||
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);
|
||||
let rest = &rest[1..];
|
||||
let terminator = rest
|
||||
.find(|c: char| c.is_whitespace() || c == '}')
|
||||
.unwrap_or(rest.len());
|
||||
|
||||
self.current_byte_offset += end;
|
||||
let (path, rest) = rest.split_at(terminator);
|
||||
let path_parts = path
|
||||
.split('.')
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Some(out)
|
||||
}
|
||||
ParseResult(rest, Some(Path(path_parts)))
|
||||
}
|
||||
|
||||
pub fn push_node_and_text(&mut self, node: AstKind) {
|
||||
self.add_current_text_to_ast();
|
||||
self.ast.push(node);
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_path_deep() {
|
||||
assert_eq!(
|
||||
parse_path(".test.best"),
|
||||
ParseResult(
|
||||
"",
|
||||
Some(Path(vec!["test".to_string(), "best".to_string()]))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_path_dot_only() {
|
||||
assert_eq!(parse_path("."), ParseResult("", Some(Path(vec![]))))
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_path_deep_trailing() {
|
||||
assert_eq!(
|
||||
parse_path(".test.best Rest"),
|
||||
ParseResult(
|
||||
" Rest",
|
||||
Some(Path(vec!["test".to_string(), "best".to_string()]))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_print_simple() {
|
||||
assert_eq!(
|
||||
parse_block("{.}"),
|
||||
ParseResult("", Some(AstNodeKind::Print(Print { path: Path(vec![]) })))
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_many_until_eof(rest: &str) -> ParseResult<Vec<AstNodeKind>> {
|
||||
parse_many_until(rest, |s| s.is_empty()).map(|v| v.unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_many_until_eof_only_text() {
|
||||
assert_eq!(
|
||||
parse_many_until_eof("Hello "),
|
||||
ParseResult("", vec![AstNodeKind::Text("Hello ".to_string()),]),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_many_until_eof_text_then_print() {
|
||||
assert_eq!(
|
||||
parse_many_until_eof("Hello {.}"),
|
||||
ParseResult(
|
||||
"",
|
||||
vec![
|
||||
AstNodeKind::Text("Hello ".to_string()),
|
||||
AstNodeKind::Print(Print { path: Path(vec![]) })
|
||||
]
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_many_until_eof_text_then_print_then_text() {
|
||||
assert_eq!(
|
||||
parse_many_until_eof("Hello {.} world"),
|
||||
ParseResult(
|
||||
"",
|
||||
vec![
|
||||
AstNodeKind::Text("Hello ".to_string()),
|
||||
AstNodeKind::Print(Print { path: Path(vec![]) }),
|
||||
AstNodeKind::Text(" world".to_string()),
|
||||
]
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_many_until(
|
||||
rest: &str,
|
||||
should_terminate: impl Fn(&str) -> bool,
|
||||
) -> ParseResult<Option<Vec<AstNodeKind>>> {
|
||||
let mut out = Vec::new();
|
||||
let mut text_start = rest;
|
||||
let mut remaining = rest;
|
||||
loop {
|
||||
if should_terminate(remaining) {
|
||||
break;
|
||||
}
|
||||
|
||||
if remaining == "" {
|
||||
return ParseResult("", None);
|
||||
}
|
||||
|
||||
if remaining.starts_with('{') {
|
||||
let text_end = text_start.len() - remaining.len();
|
||||
let ParseResult(rest, block) = parse_block(remaining);
|
||||
if let Some(block) = block {
|
||||
let text = &text_start[..text_end];
|
||||
if !text.is_empty() {
|
||||
out.push(AstNodeKind::Text(text.to_string()));
|
||||
}
|
||||
out.push(block);
|
||||
text_start = rest;
|
||||
}
|
||||
remaining = rest;
|
||||
continue;
|
||||
}
|
||||
|
||||
remaining = &remaining[1..];
|
||||
}
|
||||
let text_end = text_start.len() - remaining.len();
|
||||
let text = &text_start[..text_end];
|
||||
if !text.is_empty() {
|
||||
out.push(AstNodeKind::Text(text.to_string()));
|
||||
}
|
||||
|
||||
ParseResult(remaining, Some(out))
|
||||
}
|
||||
|
||||
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
|
||||
#[test]
|
||||
fn test_parse_many_until() {
|
||||
assert_eq!(
|
||||
parse_many_until("Hello {.} {/end}World", |rest| {
|
||||
rest.starts_with("{/end}")
|
||||
}),
|
||||
ParseResult(
|
||||
"{/end}World",
|
||||
Some(vec![
|
||||
AstNodeKind::Text("Hello ".to_string()),
|
||||
AstNodeKind::Print(Print { path: Path(vec![]) }),
|
||||
AstNodeKind::Text(" ".to_string()),
|
||||
])
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::internal::parse::{AstKind, Print, ValuePath};
|
||||
|
||||
use super::{parse, Ast, For, Has, If};
|
||||
|
||||
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_has() {
|
||||
go(
|
||||
"{has .}{.}{/has}",
|
||||
vec![AstKind::Has(Has {
|
||||
expr: ValuePath(vec![]),
|
||||
body: vec![AstKind::Print(Print(ValuePath(vec![])))],
|
||||
})],
|
||||
);
|
||||
}
|
||||
|
||||
#[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(
|
||||
"{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()),
|
||||
],
|
||||
#[test]
|
||||
fn test_parse_complex() {
|
||||
assert_eq!(
|
||||
parse(
|
||||
"{for .}{if .}{with .}{.}{/with}{else}{has .}Yo{else}{/has}{/if}{/for}"
|
||||
),
|
||||
vec![AstNodeKind::For(For {
|
||||
path: Path(vec![]),
|
||||
body: vec![AstNodeKind::If(If {
|
||||
path: Path(vec![]),
|
||||
then: vec![AstNodeKind::With(With {
|
||||
path: Path(vec![]),
|
||||
body: vec![AstNodeKind::Print(Print {
|
||||
path: Path(vec![])
|
||||
})]
|
||||
})],
|
||||
})],
|
||||
)
|
||||
}
|
||||
else_: Some(vec![AstNodeKind::Has(Has {
|
||||
path: Path(vec![]),
|
||||
then: vec![AstNodeKind::Text("Yo".to_string())],
|
||||
else_: Some(vec![]),
|
||||
})])
|
||||
})]
|
||||
})]
|
||||
)
|
||||
}
|
||||
|
|
|
|||
188
src/internal/parse_backup.rs
Normal file
188
src/internal/parse_backup.rs
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{
|
||||
parse, AstMany, AstNodeKind, AstNodeRef, For, Has, If, Path, Print,
|
||||
};
|
||||
|
||||
fn go(input: &str, expected: Vec<AstNodeKind>) {
|
||||
assert_eq!(parse(input).arena.0, expected)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_text() {
|
||||
go(
|
||||
"Hello, world!",
|
||||
vec![AstNodeKind::Text("Hello, world!".to_string())],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_print() {
|
||||
go(
|
||||
"{ .test }",
|
||||
vec![AstNodeKind::Print(Print {
|
||||
path: Path(vec!["test".to_string()]),
|
||||
})],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_print_just_dot() {
|
||||
go(
|
||||
"{ . }",
|
||||
vec![AstNodeKind::Print(Print { path: Path(vec![]) })],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_print_nested_path() {
|
||||
go(
|
||||
"{ .foo.bar }",
|
||||
vec![AstNodeKind::Print(Print {
|
||||
path: Path(vec!["foo".to_string(), "bar".to_string()]),
|
||||
})],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_has() {
|
||||
go(
|
||||
"{ has .test }{.}{/has}",
|
||||
vec![
|
||||
AstNodeKind::Print(Print { path: Path(vec![]) }),
|
||||
AstNodeKind::Has(Has {
|
||||
path: Path(vec!["test".to_string()]),
|
||||
body: AstMany {
|
||||
start: AstNodeRef(0),
|
||||
end: AstNodeRef(1),
|
||||
},
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_for() {
|
||||
go(
|
||||
"{ for .test }{.}{/for}",
|
||||
vec![
|
||||
AstNodeKind::Print(Print { path: Path(vec![]) }),
|
||||
AstNodeKind::For(For {
|
||||
path: Path(vec!["test".to_string()]),
|
||||
body: AstMany {
|
||||
start: AstNodeRef(0),
|
||||
end: AstNodeRef(1),
|
||||
},
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_if() {
|
||||
go(
|
||||
"{ if .test }{.test}{/if}",
|
||||
vec![
|
||||
AstNodeKind::Print(Print {
|
||||
path: Path(vec!["test".to_string()]),
|
||||
}),
|
||||
AstNodeKind::If(If {
|
||||
path: Path(vec!["test".to_string()]),
|
||||
then: AstMany {
|
||||
start: AstNodeRef(0),
|
||||
end: AstNodeRef(1),
|
||||
},
|
||||
else_: None,
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn parse_if_else() {
|
||||
go(
|
||||
"{ if .test }{.test}{else}Hello{/if}",
|
||||
vec![
|
||||
AstNodeKind::Print(Print {
|
||||
path: Path(vec!["test".to_string()]),
|
||||
}),
|
||||
AstNodeKind::If(If {
|
||||
path: Path(vec!["test".to_string()]),
|
||||
then: AstMany {
|
||||
start: AstNodeRef(0),
|
||||
end: AstNodeRef(1),
|
||||
},
|
||||
else_: Some(AstMany {
|
||||
start: AstNodeRef(0),
|
||||
end: AstNodeRef(1),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
use super::parse::AstNodeKind;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct AstArena(Vec<AstNodeKind>);
|
||||
impl AstArena {
|
||||
pub const fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
pub fn next_id(&self) -> AstNodeRef {
|
||||
AstNodeRef(
|
||||
self.0
|
||||
.len()
|
||||
.try_into()
|
||||
.expect("Tried to store too many AST nodes"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn add(&mut self, n: AstNodeKind) -> AstNodeRef {
|
||||
let id = self.next_id();
|
||||
self.0.push(n);
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
pub fn add_many(&mut self, mut many: Vec<AstNodeKind>) -> AstMany {
|
||||
let start = self.next_id();
|
||||
let end = AstNodeRef(
|
||||
(start.0 as usize + many.len())
|
||||
.try_into()
|
||||
.expect("Tried to store too many AST nodes"),
|
||||
);
|
||||
self.0.append(&mut many);
|
||||
|
||||
AstMany { start, end }
|
||||
}
|
||||
|
||||
pub fn deref(&self, rf: AstNodeRef) -> &AstNodeKind {
|
||||
&self.0[rf.0 as usize]
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn deref_many(&self, rf: AstMany) -> &[AstNodeKind] {
|
||||
&self.0[rf.start.0 as usize..rf.end.0 as usize]
|
||||
}
|
||||
*/
|
||||
pub fn deref_many(&self, rf: &Vec<AstNodeKind>) -> &[AstNodeKind] {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)]
|
||||
pub struct AstNodeRef(u32);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)]
|
||||
pub struct AstMany {
|
||||
start: AstNodeRef,
|
||||
end: AstNodeRef,
|
||||
}
|
||||
|
||||
pub(crate) struct Ast {
|
||||
pub(crate) arena: AstArena,
|
||||
pub(crate) root: Vec<AstNodeKind>,
|
||||
}
|
||||
|
|
@ -333,7 +333,7 @@ pub struct Newt {
|
|||
impl Newt {
|
||||
pub fn build(tmpl: &str) -> Self {
|
||||
let ast = parse(tmpl);
|
||||
Self { ast }
|
||||
Self { ast: todo!() }
|
||||
}
|
||||
|
||||
pub fn execute(&self, value: &dyn Value) -> String {
|
||||
|
|
|
|||
Loading…
Reference in a new issue