Implement {has}
This commit is contained in:
parent
5278f3580c
commit
eb382c4126
|
|
@ -2,7 +2,7 @@ use core::fmt::Write;
|
|||
|
||||
use crate::Value;
|
||||
|
||||
use super::parse::{Ast, AstKind, For, Print, ValuePath};
|
||||
use super::parse::{Ast, AstKind, For, Has, Print, ValuePath};
|
||||
|
||||
pub fn execute(ast: &Ast, value: &dyn Value) -> String {
|
||||
let mut out = String::new();
|
||||
|
|
@ -17,6 +17,7 @@ pub fn execute_(out: &mut String, ast: &Ast, value: &dyn Value) {
|
|||
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),
|
||||
e => todo!("{e:?}"),
|
||||
}
|
||||
}
|
||||
|
|
@ -27,8 +28,6 @@ pub fn lookup<'a>(
|
|||
path: &ValuePath,
|
||||
value: &'a dyn Value,
|
||||
) -> Option<&'a dyn Value> {
|
||||
dbg!(value);
|
||||
dbg!(path);
|
||||
let mut value = value;
|
||||
for name in &path.0 {
|
||||
match value.lookup(name) {
|
||||
|
|
@ -65,3 +64,14 @@ pub fn for_(out: &mut String, f: &For, value: &dyn Value) {
|
|||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has(out: &mut String, f: &Has, value: &dyn Value) {
|
||||
let value = match lookup(out, &f.expr, value) {
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
};
|
||||
|
||||
while let Some(value) = value.has() {
|
||||
execute_(out, &f.body, value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ pub struct For {
|
|||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Has {
|
||||
expr: Box<Expr>,
|
||||
body: Ast,
|
||||
pub expr: Expr,
|
||||
pub body: Ast,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
|
|
@ -108,6 +108,8 @@ impl<'a> ParseState<'a> {
|
|||
self.maybe_parse_print();
|
||||
} else if remaining.starts_with("for") {
|
||||
self.maybe_parse_for();
|
||||
} else if remaining.starts_with("has") {
|
||||
self.maybe_parse_has();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -169,6 +171,47 @@ impl<'a> ParseState<'a> {
|
|||
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 parse_path(&mut self) -> Option<ValuePath> {
|
||||
assert!(self.remaining().starts_with('.'));
|
||||
self.current_byte_offset += 1;
|
||||
|
|
@ -225,7 +268,7 @@ pub fn parse(template: &str) -> Ast {
|
|||
mod test {
|
||||
use crate::internal::parse::{AstKind, Print, ValuePath};
|
||||
|
||||
use super::{parse, Ast, For};
|
||||
use super::{parse, Ast, For, Has};
|
||||
|
||||
fn go(tmpl: &str, rhs: Ast) {
|
||||
let ast = parse(tmpl);
|
||||
|
|
@ -272,6 +315,17 @@ mod test {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_has() {
|
||||
go(
|
||||
"{has .}{.}{/has}",
|
||||
vec![AstKind::Has(Has {
|
||||
expr: ValuePath(vec![]),
|
||||
body: vec![AstKind::Print(Print(ValuePath(vec![])))],
|
||||
})],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_for_2() {
|
||||
go(
|
||||
|
|
|
|||
41
src/lib.rs
41
src/lib.rs
|
|
@ -192,6 +192,37 @@ impl<const N: usize> Value for [&dyn Value; N] {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Value for Option<T>
|
||||
where
|
||||
T: Value,
|
||||
{
|
||||
fn print(&self, out: &mut String) {
|
||||
if let Some(v) = self {
|
||||
v.print(out)
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup(&self, _: &str) -> Option<&dyn Value> {
|
||||
None
|
||||
}
|
||||
|
||||
fn has(&self) -> Option<&dyn Value> {
|
||||
self.as_ref().map(|v| v as &dyn Value)
|
||||
}
|
||||
|
||||
fn if_(&self) -> bool {
|
||||
self.is_some()
|
||||
}
|
||||
|
||||
fn for_(&self, index: usize) -> Option<&dyn Value> {
|
||||
if index == 0 {
|
||||
self.has()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod build {}
|
||||
|
||||
pub struct Newt {
|
||||
|
|
@ -275,4 +306,14 @@ mod test {
|
|||
"Bob, Larry, ",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_has() {
|
||||
go("{has .}{.}{/has}", &Some("Bob"), "Bob");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_has_none() {
|
||||
go("{has .}{.}{/has}", &None::<&str>, "");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue