More maintainable parser
This commit is contained in:
parent
84c4d9d484
commit
3c4f063d08
|
|
@ -6,7 +6,7 @@ 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();
|
||||||
let nodes = ast.arena.deref_many(ast.root);
|
let nodes = ast.arena.deref_many(&ast.root);
|
||||||
execute_(&mut out, nodes, value, &ast.arena);
|
execute_(&mut out, nodes, value, &ast.arena);
|
||||||
|
|
||||||
out
|
out
|
||||||
|
|
@ -76,7 +76,7 @@ pub fn for_(out: &mut String, f: &For, value: &dyn Value, arena: &AstArena) {
|
||||||
|
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
while let Some(value) = value.for_(idx) {
|
while let Some(value) = value.for_(idx) {
|
||||||
let nodes = arena.deref_many(f.body);
|
let nodes = arena.deref_many(&f.body);
|
||||||
execute_(out, nodes, value, arena);
|
execute_(out, nodes, value, arena);
|
||||||
idx += 1;
|
idx += 1;
|
||||||
}
|
}
|
||||||
|
|
@ -89,7 +89,7 @@ pub fn has(out: &mut String, f: &Has, value: &dyn Value, arena: &AstArena) {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(value) = value.has() {
|
if let Some(value) = value.has() {
|
||||||
let nodes = arena.deref_many(f.body);
|
let nodes = arena.deref_many(&f.body);
|
||||||
execute_(out, nodes, value, arena);
|
execute_(out, nodes, value, arena);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -101,10 +101,10 @@ pub fn if_(out: &mut String, f: &If, value: &dyn Value, arena: &AstArena) {
|
||||||
};
|
};
|
||||||
|
|
||||||
if value.if_() {
|
if value.if_() {
|
||||||
let nodes = arena.deref_many(f.then);
|
let nodes = arena.deref_many(&f.then);
|
||||||
execute_(out, nodes, value, arena);
|
execute_(out, nodes, value, arena);
|
||||||
} else if let Some(else_) = f.else_ {
|
} else if let Some(else_) = f.else_.as_ref() {
|
||||||
let nodes = arena.deref_many(else_);
|
let nodes = arena.deref_many(&else_);
|
||||||
execute_(out, nodes, value, arena);
|
execute_(out, nodes, value, arena);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,4 @@
|
||||||
|
|
||||||
pub mod execute;
|
pub mod execute;
|
||||||
pub mod parse;
|
pub mod parse;
|
||||||
|
mod parse_backup;
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,7 @@
|
||||||
use std::ops::ControlFlow;
|
use std::ops::ControlFlow;
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
pub use super::parse_backup::*;
|
||||||
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]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)]
|
|
||||||
pub struct AstNodeRef(u32);
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum AstNodeKind {
|
pub enum AstNodeKind {
|
||||||
If(If),
|
If(If),
|
||||||
|
|
@ -54,11 +11,6 @@ pub enum AstNodeKind {
|
||||||
Print(Print),
|
Print(Print),
|
||||||
Text(String),
|
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)]
|
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct Path(pub Vec<String>);
|
pub struct Path(pub Vec<String>);
|
||||||
|
|
@ -66,26 +18,26 @@ pub struct Path(pub Vec<String>);
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct If {
|
pub struct If {
|
||||||
pub path: Path,
|
pub path: Path,
|
||||||
pub then: AstMany,
|
pub then: Vec<AstNodeKind>,
|
||||||
pub else_: Option<AstMany>,
|
pub else_: Option<Vec<AstNodeKind>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct For {
|
pub struct For {
|
||||||
pub path: Path,
|
pub path: Path,
|
||||||
pub body: AstMany,
|
pub body: Vec<AstNodeKind>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct Has {
|
pub struct Has {
|
||||||
pub path: Path,
|
pub path: Path,
|
||||||
pub body: AstMany,
|
pub body: Vec<AstNodeKind>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct With {
|
pub struct With {
|
||||||
pub path: Path,
|
pub path: Path,
|
||||||
pub body: AstMany,
|
pub body: Vec<AstNodeKind>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
|
@ -93,464 +45,442 @@ pub struct Print {
|
||||||
pub path: Path,
|
pub path: Path,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Ast {
|
pub fn parse(source: &str) -> Vec<AstNodeKind> {
|
||||||
pub arena: AstArena,
|
parse_many_until_eof(source).1
|
||||||
pub root: AstMany,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ParseCtx<'a> {
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub source: &'a str,
|
struct ParseResult<'a, T>(
|
||||||
pub current_byte_offset: usize,
|
&'a str, // remaining
|
||||||
|
T, // output
|
||||||
pub arena: AstArena,
|
);
|
||||||
}
|
impl<'a, T> ParseResult<'a, T> {
|
||||||
impl<'a> ParseCtx<'a> {
|
fn remaining(&self) -> &'a str {
|
||||||
pub fn new(source: &'a str) -> Self {
|
self.0
|
||||||
Self {
|
|
||||||
source,
|
|
||||||
current_byte_offset: 0,
|
|
||||||
arena: AstArena::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn loop_expecting_progress<T>(
|
fn output(&self) -> &T {
|
||||||
&mut self,
|
&self.1
|
||||||
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 {
|
fn map<U>(self, f: impl FnOnce(T) -> U) -> ParseResult<'a, U> {
|
||||||
self.current_byte_offset >= self.source.len()
|
ParseResult(self.0, f(self.1))
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current(&self) -> char {
|
|
||||||
self.source[self.current_byte_offset..]
|
|
||||||
.chars()
|
|
||||||
.next()
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remaining(&self) -> &str {
|
|
||||||
&self.source[self.current_byte_offset..]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn looking_at(&self, ch: char) -> bool {
|
|
||||||
!self.at_eof() && self.current() == ch
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn forward(&mut self) {
|
|
||||||
self.current_byte_offset += self.current().len_utf8()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn forward_str(&mut self, str: &str) {
|
|
||||||
self.current_byte_offset += str.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scan_until(&mut self, ch: char) {
|
|
||||||
self.loop_expecting_progress(|s| {
|
|
||||||
if s.at_eof() {
|
|
||||||
return ControlFlow::Break(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let current = s.current();
|
|
||||||
if current == ch {
|
|
||||||
return ControlFlow::Break(());
|
|
||||||
}
|
|
||||||
s.current_byte_offset += current.len_utf8();
|
|
||||||
|
|
||||||
ControlFlow::Continue(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn skip_whitespace(&mut self) {
|
|
||||||
self.loop_expecting_progress(|s| {
|
|
||||||
if s.at_eof() {
|
|
||||||
return ControlFlow::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(source: &str) -> Ast {
|
fn parse_block(rest: &str) -> ParseResult<Option<AstNodeKind>> {
|
||||||
let mut ctx = ParseCtx::new(source);
|
let rest_ = match rest.strip_prefix('{') {
|
||||||
let root = ctx.parse_until(|ctx| ctx.at_eof()).unwrap();
|
Some(v) => v,
|
||||||
|
None => return ParseResult(rest, None),
|
||||||
Ast {
|
};
|
||||||
arena: ctx.arena,
|
let rest_ = rest_.trim_start();
|
||||||
root,
|
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("with ") {
|
||||||
|
parse_with(rest).map(|v| v.map(AstNodeKind::With))
|
||||||
|
} else {
|
||||||
|
parse_print(rest).map(|v| v.map(AstNodeKind::Print))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[test]
|
||||||
mod test {
|
fn test_parse_block_print_simple() {
|
||||||
use super::{
|
assert_eq!(
|
||||||
parse, AstMany, AstNodeKind, AstNodeRef, For, Has, If, Path, Print,
|
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),
|
||||||
};
|
};
|
||||||
|
|
||||||
fn go(input: &str, expected: Vec<AstNodeKind>) {
|
let rest = rest.trim_start();
|
||||||
assert_eq!(parse(input).arena.0, expected)
|
if let Some(rest) = rest.strip_prefix('}') {
|
||||||
}
|
ParseResult(rest, Some(Print { path }))
|
||||||
|
} else {
|
||||||
#[test]
|
ParseResult(rest, None)
|
||||||
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),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_print_trailing() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_print("{.} World"),
|
||||||
|
ParseResult(" World", Some(Print { path: Path(vec![]) }))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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![]) })]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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![]) })]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
ParseResult(rest, Some(else_))
|
||||||
|
} else {
|
||||||
|
let rest = rest.strip_prefix("{/if}").unwrap();
|
||||||
|
|
||||||
|
ParseResult(rest, 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_path(rest: &str) -> ParseResult<Option<Path>> {
|
||||||
|
if !rest.starts_with('.') {
|
||||||
|
return ParseResult(rest, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let rest = &rest[1..];
|
||||||
|
let terminator = rest
|
||||||
|
.find(|c: char| c.is_whitespace() || c == '}')
|
||||||
|
.unwrap_or(rest.len());
|
||||||
|
|
||||||
|
let (path, rest) = rest.split_at(terminator);
|
||||||
|
let path_parts = path
|
||||||
|
.split('.')
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
ParseResult(rest, Some(Path(path_parts)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_path_deep() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_path(".test.best"),
|
||||||
|
ParseResult(
|
||||||
|
"",
|
||||||
|
Some(Path(vec!["test".to_string(), "best".to_string()]))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_path_dot_only() {
|
||||||
|
assert_eq!(parse_path("."), ParseResult("", Some(Path(vec![]))))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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()),
|
||||||
|
])
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_complex() {
|
||||||
|
assert_eq!(
|
||||||
|
parse("{for .}{if .}{with .}{.}{/with}{else}Yo{/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::Text("Yo".to_string())])
|
||||||
|
})]
|
||||||
|
})]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -333,7 +333,7 @@ pub struct Newt {
|
||||||
impl Newt {
|
impl Newt {
|
||||||
pub fn build(tmpl: &str) -> Self {
|
pub fn build(tmpl: &str) -> Self {
|
||||||
let ast = parse(tmpl);
|
let ast = parse(tmpl);
|
||||||
Self { ast }
|
Self { ast: todo!() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute(&self, value: &dyn Value) -> String {
|
pub fn execute(&self, value: &dyn Value) -> String {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue