Initial parser rewrite start
This commit is contained in:
parent
323a38db3a
commit
84c4d9d484
|
|
@ -2,30 +2,46 @@ use core::fmt::Write;
|
||||||
|
|
||||||
use crate::Value;
|
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 {
|
pub fn execute(ast: &Ast, value: &dyn Value) -> String {
|
||||||
let mut out = String::new();
|
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
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute_(out: &mut String, ast: &Ast, value: &dyn Value) {
|
pub fn execute_(
|
||||||
for node in ast {
|
out: &mut String,
|
||||||
match node {
|
nodes: &[AstNodeKind],
|
||||||
AstKind::Text(t) => out.push_str(&t),
|
value: &dyn Value,
|
||||||
AstKind::Print(p) => print(out, p, value),
|
arena: &AstArena,
|
||||||
AstKind::For(f) => for_(out, f, value),
|
) {
|
||||||
AstKind::Has(h) => has(out, h, value),
|
for node in nodes {
|
||||||
AstKind::If(h) => if_(out, h, value),
|
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>(
|
pub fn lookup<'a>(
|
||||||
out: &mut String,
|
out: &mut String,
|
||||||
path: &ValuePath,
|
path: &Path,
|
||||||
value: &'a dyn Value,
|
value: &'a dyn Value,
|
||||||
) -> Option<&'a dyn Value> {
|
) -> Option<&'a dyn Value> {
|
||||||
let mut value = value;
|
let mut value = value;
|
||||||
|
|
@ -44,7 +60,7 @@ pub fn lookup<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print(out: &mut String, p: &Print, value: &dyn Value) {
|
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,
|
Some(v) => v,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
@ -52,39 +68,43 @@ pub fn print(out: &mut String, p: &Print, value: &dyn Value) {
|
||||||
value.print(out)
|
value.print(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_(out: &mut String, f: &For, value: &dyn Value) {
|
pub fn for_(out: &mut String, f: &For, value: &dyn Value, arena: &AstArena) {
|
||||||
let value = match lookup(out, &f.expr, value) {
|
let value = match lookup(out, &f.path, value) {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
while let Some(value) = value.for_(idx) {
|
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;
|
idx += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has(out: &mut String, f: &Has, value: &dyn Value) {
|
pub fn has(out: &mut String, f: &Has, value: &dyn Value, arena: &AstArena) {
|
||||||
let value = match lookup(out, &f.expr, value) {
|
let value = match lookup(out, &f.path, value) {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(value) = value.has() {
|
if let Some(value) = value.has() {
|
||||||
execute_(out, &f.body, value);
|
let nodes = arena.deref_many(f.body);
|
||||||
|
execute_(out, nodes, value, arena);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn if_(out: &mut String, f: &If, value: &dyn Value) {
|
pub fn if_(out: &mut String, f: &If, value: &dyn Value, arena: &AstArena) {
|
||||||
let value = match lookup(out, &f.expr, value) {
|
let value = match lookup(out, &f.path, value) {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
if value.if_() {
|
if value.if_() {
|
||||||
execute_(out, &f.then, value);
|
let nodes = arena.deref_many(f.then);
|
||||||
} else if let Some(else_) = f.else_.as_ref() {
|
execute_(out, nodes, value, arena);
|
||||||
execute_(out, else_, value);
|
} else if let Some(else_) = f.else_ {
|
||||||
|
let nodes = arena.deref_many(else_);
|
||||||
|
execute_(out, nodes, value, arena);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,390 +1,479 @@
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
use std::ops::ControlFlow;
|
||||||
pub struct ValuePath(pub Vec<String>);
|
|
||||||
|
|
||||||
pub type Expr = ValuePath;
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub struct AstArena(Vec<AstNodeKind>);
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
impl AstArena {
|
||||||
pub struct If {
|
pub const fn new() -> Self {
|
||||||
pub expr: Expr,
|
Self(Vec::new())
|
||||||
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),
|
|
||||||
If(If),
|
|
||||||
For(For),
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
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 {
|
pub fn next_id(&self) -> AstNodeRef {
|
||||||
!self.out_of_input()
|
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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)]
|
||||||
|
pub struct AstNodeRef(u32);
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum AstNodeKind {
|
||||||
|
If(If),
|
||||||
|
For(For),
|
||||||
|
Has(Has),
|
||||||
|
With(With),
|
||||||
|
Print(Print),
|
||||||
|
Text(String),
|
||||||
|
}
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)]
|
||||||
|
pub struct AstMany {
|
||||||
|
start: AstNodeRef,
|
||||||
|
end: AstNodeRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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: AstMany,
|
||||||
|
pub else_: Option<AstMany>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct For {
|
||||||
|
pub path: Path,
|
||||||
|
pub body: AstMany,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Has {
|
||||||
|
pub path: Path,
|
||||||
|
pub body: AstMany,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct With {
|
||||||
|
pub path: Path,
|
||||||
|
pub body: AstMany,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Print {
|
||||||
|
pub path: Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Ast {
|
||||||
|
pub arena: AstArena,
|
||||||
|
pub root: AstMany,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ParseCtx<'a> {
|
||||||
|
pub source: &'a str,
|
||||||
|
pub current_byte_offset: usize,
|
||||||
|
|
||||||
|
pub arena: AstArena,
|
||||||
|
}
|
||||||
|
impl<'a> ParseCtx<'a> {
|
||||||
|
pub fn new(source: &'a str) -> Self {
|
||||||
|
Self {
|
||||||
|
source,
|
||||||
|
current_byte_offset: 0,
|
||||||
|
arena: AstArena::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn loop_expecting_progress<T>(
|
||||||
|
&mut self,
|
||||||
|
mut inner: impl FnMut(&mut Self) -> ControlFlow<T>,
|
||||||
|
) -> T {
|
||||||
|
loop {
|
||||||
|
let start = self.current_byte_offset;
|
||||||
|
let cf = inner(self);
|
||||||
|
match cf {
|
||||||
|
ControlFlow::Continue(_) => {
|
||||||
|
if self.current_byte_offset == start {
|
||||||
|
panic!("Parser didn't make progress");
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
ControlFlow::Break(v) => break v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn at_eof(&self) -> bool {
|
||||||
|
self.current_byte_offset >= self.source.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current(&self) -> char {
|
||||||
|
self.source[self.current_byte_offset..]
|
||||||
|
.chars()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remaining(&self) -> &str {
|
pub fn remaining(&self) -> &str {
|
||||||
&self.template[self.current_byte_offset..]
|
&self.source[self.current_byte_offset..]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_current_text_to_ast(&mut self) {
|
pub fn looking_at(&self, ch: char) -> bool {
|
||||||
let text = &self.template
|
!self.at_eof() && self.current() == ch
|
||||||
[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) {
|
pub fn forward(&mut self) {
|
||||||
self.speculative_text_ends_at = self.current_byte_offset;
|
self.current_byte_offset += self.current().len_utf8()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(&mut self) {
|
pub fn forward_str(&mut self, str: &str) {
|
||||||
while self.has_input() {
|
self.current_byte_offset += str.len()
|
||||||
self.parse_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.text_might_end();
|
|
||||||
self.add_current_text_to_ast();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_one(&mut self) {
|
pub fn scan_until(&mut self, ch: char) {
|
||||||
assert!(self.has_input());
|
self.loop_expecting_progress(|s| {
|
||||||
let started_at = self.current_byte_offset;
|
if s.at_eof() {
|
||||||
if self.remaining().starts_with('{') {
|
return ControlFlow::Break(());
|
||||||
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();
|
|
||||||
} else if remaining.starts_with("has") {
|
|
||||||
self.maybe_parse_has();
|
|
||||||
} else if remaining.starts_with("if") {
|
|
||||||
self.maybe_parse_if();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 }));
|
let current = s.current();
|
||||||
self.new_empty_text();
|
if current == ch {
|
||||||
}
|
return ControlFlow::Break(());
|
||||||
|
|
||||||
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();
|
s.current_byte_offset += current.len_utf8();
|
||||||
}
|
|
||||||
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 }));
|
ControlFlow::Continue(())
|
||||||
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<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);
|
|
||||||
|
|
||||||
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) {
|
pub fn skip_whitespace(&mut self) {
|
||||||
while self.has_input() {
|
self.loop_expecting_progress(|s| {
|
||||||
if self.remaining().starts_with(char::is_whitespace) {
|
if s.at_eof() {
|
||||||
self.current_byte_offset +=
|
return ControlFlow::Break(());
|
||||||
self.remaining().chars().next().unwrap().len_utf8();
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
let current = s.current();
|
||||||
|
if !current.is_whitespace() {
|
||||||
|
return ControlFlow::Break(());
|
||||||
|
}
|
||||||
|
s.current_byte_offset += current.len_utf8();
|
||||||
|
|
||||||
|
ControlFlow::Continue(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_until(
|
||||||
|
&mut self,
|
||||||
|
mut f: impl FnMut(&mut Self) -> bool,
|
||||||
|
) -> Option<AstMany> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let missing_terminator = self.loop_expecting_progress(|s| {
|
||||||
|
if f(s) {
|
||||||
|
return ControlFlow::Break(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.at_eof() {
|
||||||
|
return ControlFlow::Break(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (text, trailing) = s.parse_text_with_trailing();
|
||||||
|
if let Some(text) = text {
|
||||||
|
out.push(AstNodeKind::Text(text));
|
||||||
|
}
|
||||||
|
if let Some(block) = trailing {
|
||||||
|
out.push(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlFlow::Continue(())
|
||||||
|
});
|
||||||
|
|
||||||
|
if missing_terminator {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(self.arena.add_many(out))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_text_with_trailing(
|
||||||
|
&mut self,
|
||||||
|
) -> (Option<String>, Option<AstNodeKind>) {
|
||||||
|
let text_starts_at = self.current_byte_offset;
|
||||||
|
|
||||||
|
let (text, trailing) = self.loop_expecting_progress(|s| {
|
||||||
|
s.scan_until('{');
|
||||||
|
if s.at_eof() {
|
||||||
|
return ControlFlow::Break((&s.source[text_starts_at..], None));
|
||||||
|
}
|
||||||
|
|
||||||
|
let text_maybe_ends_at = s.current_byte_offset;
|
||||||
|
if let Some(block) = s.parse_block() {
|
||||||
|
return ControlFlow::Break((
|
||||||
|
&s.source[text_starts_at..text_maybe_ends_at],
|
||||||
|
Some(block),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlFlow::Continue(())
|
||||||
|
});
|
||||||
|
|
||||||
|
let text = if text.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(text.to_string())
|
||||||
|
};
|
||||||
|
|
||||||
|
(text, trailing)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_block(&mut self) -> Option<AstNodeKind> {
|
||||||
|
if !self.looking_at('{') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.forward();
|
||||||
|
|
||||||
|
self.skip_whitespace();
|
||||||
|
let remaining = self.remaining();
|
||||||
|
if remaining.starts_with(".") {
|
||||||
|
self.parse_print().map(AstNodeKind::Print)
|
||||||
|
} else if remaining.starts_with("for") {
|
||||||
|
self.parse_for().map(AstNodeKind::For)
|
||||||
|
} else if remaining.starts_with("has") {
|
||||||
|
self.parse_has().map(AstNodeKind::Has)
|
||||||
|
} else if remaining.starts_with("if") {
|
||||||
|
self.parse_if().map(AstNodeKind::If)
|
||||||
|
} else if remaining.starts_with("with") {
|
||||||
|
self.parse_with().map(AstNodeKind::With)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_path(&mut self) -> Option<Path> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
if !self.looking_at('.') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.forward();
|
||||||
|
|
||||||
|
self.loop_expecting_progress(|s| {
|
||||||
|
let start = s.current_byte_offset;
|
||||||
|
s.loop_expecting_progress(|s| {
|
||||||
|
if s.current().is_whitespace()
|
||||||
|
|| s.current() == '}'
|
||||||
|
|| s.current() == '.'
|
||||||
|
{
|
||||||
|
return ControlFlow::Break(());
|
||||||
|
}
|
||||||
|
s.forward();
|
||||||
|
|
||||||
|
ControlFlow::Continue(())
|
||||||
|
});
|
||||||
|
let end = s.current_byte_offset;
|
||||||
|
let str = &s.source[start..end];
|
||||||
|
if str.is_empty() {
|
||||||
|
return ControlFlow::Break(());
|
||||||
|
}
|
||||||
|
out.push(str.to_string());
|
||||||
|
|
||||||
|
if !s.looking_at('.') {
|
||||||
|
return ControlFlow::Break(());
|
||||||
|
}
|
||||||
|
s.forward();
|
||||||
|
ControlFlow::Continue(())
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(Path(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_print(&mut self) -> Option<Print> {
|
||||||
|
let path = self.parse_path()?;
|
||||||
|
self.skip_whitespace();
|
||||||
|
if !self.looking_at('}') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.forward();
|
||||||
|
|
||||||
|
Some(Print { path })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_if(&mut self) -> Option<If> {
|
||||||
|
if !self.remaining().starts_with("if") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.forward_str("for");
|
||||||
|
self.skip_whitespace();
|
||||||
|
let path = self.parse_path()?;
|
||||||
|
self.skip_whitespace();
|
||||||
|
if !self.looking_at('}') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.forward();
|
||||||
|
|
||||||
|
let then = self.parse_until(|s| {
|
||||||
|
s.remaining().starts_with("{/if}")
|
||||||
|
|| s.remaining().starts_with("{else}")
|
||||||
|
})?;
|
||||||
|
if self.remaining().starts_with("{/if}") {
|
||||||
|
self.forward_str("{/if}");
|
||||||
|
|
||||||
|
return Some(If {
|
||||||
|
path,
|
||||||
|
then,
|
||||||
|
else_: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.forward_str("{else}");
|
||||||
|
let else_ = self.parse_until(|s| s.remaining().starts_with("{/if}"))?;
|
||||||
|
self.forward_str("{/if}");
|
||||||
|
|
||||||
|
Some(If {
|
||||||
|
path,
|
||||||
|
then,
|
||||||
|
else_: Some(else_),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_for(&mut self) -> Option<For> {
|
||||||
|
if !self.remaining().starts_with("for") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.forward_str("for");
|
||||||
|
self.skip_whitespace();
|
||||||
|
let path = self.parse_path()?;
|
||||||
|
self.skip_whitespace();
|
||||||
|
if !self.looking_at('}') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.forward();
|
||||||
|
|
||||||
|
let many = self.parse_until(|s| s.remaining().starts_with("{/for}"))?;
|
||||||
|
self.forward_str("{/for}");
|
||||||
|
|
||||||
|
Some(For { path, body: many })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_with(&mut self) -> Option<With> {
|
||||||
|
if !self.remaining().starts_with("with") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.forward_str("with");
|
||||||
|
self.skip_whitespace();
|
||||||
|
let path = self.parse_path()?;
|
||||||
|
self.skip_whitespace();
|
||||||
|
if !self.looking_at('}') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.forward();
|
||||||
|
|
||||||
|
let many =
|
||||||
|
self.parse_until(|s| s.remaining().starts_with("{/with}"))?;
|
||||||
|
self.forward_str("{/with}");
|
||||||
|
|
||||||
|
Some(With { path, body: many })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_has(&mut self) -> Option<Has> {
|
||||||
|
if !self.remaining().starts_with("has") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.forward_str("has");
|
||||||
|
self.skip_whitespace();
|
||||||
|
let path = self.parse_path()?;
|
||||||
|
self.skip_whitespace();
|
||||||
|
if !self.looking_at('}') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.forward();
|
||||||
|
|
||||||
|
let many = self.parse_until(|s| s.remaining().starts_with("{/has}"))?;
|
||||||
|
self.forward_str("{/has}");
|
||||||
|
|
||||||
|
Some(Has { path, body: many })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(template: &str) -> Ast {
|
pub fn parse(source: &str) -> Ast {
|
||||||
let mut state = ParseState {
|
let mut ctx = ParseCtx::new(source);
|
||||||
template,
|
let root = ctx.parse_until(|ctx| ctx.at_eof()).unwrap();
|
||||||
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
|
Ast {
|
||||||
|
arena: ctx.arena,
|
||||||
|
root,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::internal::parse::{AstKind, Print, ValuePath};
|
use super::{
|
||||||
|
parse, AstMany, AstNodeKind, AstNodeRef, For, Has, If, Path, Print,
|
||||||
|
};
|
||||||
|
|
||||||
use super::{parse, Ast, For, Has, If};
|
fn go(input: &str, expected: Vec<AstNodeKind>) {
|
||||||
|
assert_eq!(parse(input).arena.0, expected)
|
||||||
fn go(tmpl: &str, rhs: Ast) {
|
|
||||||
let ast = parse(tmpl);
|
|
||||||
assert_eq!(ast, rhs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_print_only() {
|
fn parse_text() {
|
||||||
go("{.}", vec![AstKind::Print(Print(ValuePath(vec![])))]);
|
go(
|
||||||
|
"Hello, world!",
|
||||||
|
vec![AstNodeKind::Text("Hello, world!".to_string())],
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_print() {
|
fn parse_print() {
|
||||||
go(
|
go(
|
||||||
"hello, {.}!",
|
"{ .test }",
|
||||||
vec![
|
vec![AstNodeKind::Print(Print {
|
||||||
AstKind::Text("hello, ".to_string()),
|
path: Path(vec!["test".to_string()]),
|
||||||
AstKind::Print(Print(ValuePath(vec![]))),
|
})],
|
||||||
AstKind::Text("!".to_string()),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_print_lookup() {
|
fn parse_print_just_dot() {
|
||||||
go(
|
go(
|
||||||
"hello, {.name}!",
|
"{ . }",
|
||||||
vec![
|
vec![AstNodeKind::Print(Print { path: Path(vec![]) })],
|
||||||
AstKind::Text("hello, ".to_string()),
|
|
||||||
AstKind::Print(Print(ValuePath(vec!["name".to_string()]))),
|
|
||||||
AstKind::Text("!".to_string()),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_for() {
|
fn parse_print_nested_path() {
|
||||||
go(
|
go(
|
||||||
"{for .}{.}{/for}",
|
"{ .foo.bar }",
|
||||||
vec![AstKind::For(For {
|
vec![AstNodeKind::Print(Print {
|
||||||
expr: ValuePath(vec![]),
|
path: Path(vec!["foo".to_string(), "bar".to_string()]),
|
||||||
body: vec![AstKind::Print(Print(ValuePath(vec![])))],
|
|
||||||
})],
|
})],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -392,66 +481,76 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_has() {
|
fn parse_has() {
|
||||||
go(
|
go(
|
||||||
"{has .}{.}{/has}",
|
"{ has .test }{.}{/has}",
|
||||||
vec![AstKind::Has(Has {
|
vec![
|
||||||
expr: ValuePath(vec![]),
|
AstNodeKind::Print(Print { path: Path(vec![]) }),
|
||||||
body: vec![AstKind::Print(Print(ValuePath(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]
|
#[test]
|
||||||
fn parse_if() {
|
fn parse_if() {
|
||||||
go(
|
go(
|
||||||
"{if .}{.}{/if}",
|
"{ if .test }{.test}{/if}",
|
||||||
vec![AstKind::If(If {
|
vec![
|
||||||
expr: ValuePath(vec![]),
|
AstNodeKind::Print(Print {
|
||||||
then: vec![AstKind::Print(Print(ValuePath(vec![])))],
|
path: Path(vec!["test".to_string()]),
|
||||||
else_: None,
|
}),
|
||||||
})],
|
AstNodeKind::If(If {
|
||||||
|
path: Path(vec!["test".to_string()]),
|
||||||
|
then: AstMany {
|
||||||
|
start: AstNodeRef(0),
|
||||||
|
end: AstNodeRef(1),
|
||||||
|
},
|
||||||
|
else_: None,
|
||||||
|
}),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_if_else() {
|
fn parse_if_else() {
|
||||||
go(
|
go(
|
||||||
"{if .}{.}{else}Empty{/if}",
|
"{ if .test }{.test}{else}Hello{/if}",
|
||||||
vec![AstKind::If(If {
|
vec![
|
||||||
expr: ValuePath(vec![]),
|
AstNodeKind::Print(Print {
|
||||||
then: vec![AstKind::Print(Print(ValuePath(vec![])))],
|
path: Path(vec!["test".to_string()]),
|
||||||
else_: Some(vec![AstKind::Text("Empty".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),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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()),
|
|
||||||
],
|
|
||||||
})],
|
|
||||||
})],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue