Parse and execute print

This commit is contained in:
soup 2024-05-19 00:26:08 -04:00
parent 453d954063
commit 8dfbc70cee
No known key found for this signature in database
4 changed files with 427 additions and 61 deletions

33
src/internal/execute.rs Normal file
View file

@ -0,0 +1,33 @@
use core::fmt::Write;
use crate::Value;
use super::parse::{Ast, AstKind, Print};
pub fn execute(ast: &Ast, value: &dyn Value) -> String {
let mut out = String::new();
for node in ast {
match node {
AstKind::Text(t) => out.push_str(&t),
AstKind::Print(p) => print(&mut out, p, value),
e => todo!("{e:?}"),
}
}
out
}
pub fn print(out: &mut String, p: &Print, value: &dyn Value) {
let path = &p.0 .0;
let mut value = value;
for name in path {
match value.lookup(name) {
Some(p) => value = p,
None => {
let _ = write!(out, "'{name}' missing in current value");
},
}
}
value.print(out)
}

View file

@ -2,4 +2,5 @@
//! considered stable when it comes to semver. However, they are exposed for //! considered stable when it comes to semver. However, they are exposed for
//! advanced use cases. //! advanced use cases.
pub mod execute;
pub mod parse; pub mod parse;

View file

@ -1,26 +1,31 @@
use crate::Result; #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ValuePath(pub Vec<String>);
pub struct ValuePath {} pub type Expr = ValuePath;
pub enum Expr {
ValuePath(ValuePath),
}
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct If { pub struct If {
expr: Box<Expr>, expr: Expr,
then: Ast, then: Ast,
else_: Option<Ast>, else_: Option<Ast>,
} }
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct For { pub struct For {
expr: Box<Expr>, expr: Expr,
body: Ast, body: Ast,
} }
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Has { pub struct Has {
expr: Box<Expr>, expr: Box<Expr>,
body: Ast, body: Ast,
} }
pub struct Print {
expr: Box<Expr>, #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
} pub struct Print(pub Expr);
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum AstKind { pub enum AstKind {
Text(String), Text(String),
Print(Print), Print(Print),
@ -30,29 +35,188 @@ pub enum AstKind {
} }
pub type Ast = Vec<AstKind>; pub type Ast = Vec<AstKind>;
pub fn parse(template: &str) -> Result<Ast> { pub struct ParseState<'a> {
let mut out = Ast::new(); template: &'a str,
let mut remaining = template; last_byte_offset: usize,
while !remaining.is_empty() { speculative_text_starts_at: usize,
let (ast, r) = parse_one(remaining)?; speculative_text_ends_at: usize,
remaining = r; current_byte_offset: usize,
out.push(ast); ast: Ast,
}
Ok(out)
} }
impl<'a> ParseState<'a> {
pub fn out_of_input(&self) -> bool {
self.current_byte_offset >= self.last_byte_offset
}
pub fn parse_one(template: &str) -> Result<(AstKind, &str)> { pub fn has_input(&self) -> bool {
if template.starts_with("{{") { !self.out_of_input()
parse_text(template) }
} else if let Some(r) = template.strip_prefix('{') {
parse_block(r) pub fn remaining(&self) -> &str {
} else { &self.template[self.current_byte_offset..]
parse_text(template) }
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(),
));
}
pub fn text_might_end(&mut self) {
self.speculative_text_ends_at = self.current_byte_offset;
}
pub fn parse(&mut self) {
while self.has_input() {
let started_at = self.current_byte_offset;
let block_start = match self.remaining().find('{') {
Some(bs) => bs,
None => {
self.current_byte_offset = self.last_byte_offset;
self.add_current_text_to_ast();
break;
},
};
self.current_byte_offset += block_start;
self.text_might_end();
self.maybe_parse_block();
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();
if self.remaining().starts_with('.') {
self.maybe_parse_print();
}
}
pub fn maybe_parse_print(&mut self) {
assert!(self.remaining().starts_with('.'));
let path = self.parse_path();
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 parse_path(&mut self) -> 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;
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 + 1;
}
pub fn skip_whitespace(&mut self) {
while self.has_input() {
if self.remaining().starts_with(char::is_whitespace) {
continue;
}
break;
}
} }
} }
pub fn parse_text(template: &str) -> Result<(AstKind, &str)> { pub fn parse(template: &str) -> Ast {
assert!(template.starts_with("{{") || !template.starts_with('{')); let mut state = ParseState {
template,
speculative_text_starts_at: 0,
speculative_text_ends_at: 1,
current_byte_offset: 0,
last_byte_offset: template.len(),
ast: Ast::new(),
};
state.parse();
let template.trim_start_matches(|c| c != '{') state.ast
}
#[cfg(test)]
mod test {
use crate::internal::parse::{AstKind, Print, ValuePath};
use super::{parse, Ast, For};
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![])))],
})],
);
}
} }

View file

@ -1,27 +1,67 @@
use core::fmt::{Display, Write};
pub mod internal; pub mod internal;
use std::fmt::Write; use internal::{
execute::execute,
pub enum Error {} parse::{parse, Ast},
pub type Result<T> = core::result::Result<T, Error>; };
pub trait Value { pub trait Value {
fn print(&self, w: &mut dyn Write) -> core::fmt::Result; fn print(&self, out: &mut String);
fn has(&self) -> Option<&dyn Value> fn lookup(&self, name: &str) -> Option<&dyn Value>;
where fn has(&self) -> Option<&dyn Value>;
Self: Sized, fn if_(&self) -> bool;
{ fn for_(&self, index: usize) -> Option<&dyn Value>;
Some(self) }
impl<'a, T> Value for &'a T
where
T: Value,
{
fn print(&self, out: &mut String) {
(&**self).print(out)
}
fn lookup(&self, name: &str) -> Option<&dyn Value> {
(&**self).lookup(name)
}
fn has(&self) -> Option<&dyn Value> {
(&**self).has()
} }
fn if_(&self) -> bool { fn if_(&self) -> bool {
true (&**self).if_()
} }
fn for_(&self, index: usize) -> Option<&dyn Value> fn for_(&self, index: usize) -> Option<&dyn Value> {
where (&**self).for_(index)
Self: Sized, }
{ }
impl Value for &str {
fn print(&self, out: &mut String) {
out.push_str(self);
}
fn lookup(&self, _: &str) -> Option<&dyn Value> {
None
}
fn has(&self) -> Option<&dyn Value> {
if self.is_empty() {
None
} else {
Some(self)
}
}
fn if_(&self) -> bool {
self.is_empty()
}
fn for_(&self, index: usize) -> Option<&dyn Value> {
if index == 0 { if index == 0 {
Some(self) Some(self)
} else { } else {
@ -30,38 +70,166 @@ pub trait Value {
} }
} }
impl<'a, T> Value for &'a T impl Value for (&str, &dyn Value) {
where fn print(&self, out: &mut String) {
T: Value, out.push('(');
{ self.0.print(out);
fn print(&self, w: &mut dyn Write) -> core::fmt::Result { out.push_str(", ");
(&**self).print(w) self.1.print(out);
}
fn lookup(&self, name: &str) -> Option<&dyn Value> {
match name {
"0" => Some(&self.0),
"1" => Some(self.1),
_ => None,
}
}
fn has(&self) -> Option<&dyn Value> {
Some(self)
}
fn if_(&self) -> bool {
self.0.if_() && self.1.if_()
}
fn for_(&self, index: usize) -> Option<&dyn Value> {
match index {
0 => Some(&self.0),
1 => Some(self.1),
_ => None,
}
}
}
impl Value for (&dyn Value, &dyn Value) {
fn print(&self, out: &mut String) {
self.0.print(out);
self.1.print(out);
}
fn lookup(&self, name: &str) -> Option<&dyn Value> {
match name {
"0" => Some(self.0),
"1" => Some(self.1),
_ => None,
}
}
fn has(&self) -> Option<&dyn Value> {
Some(self)
}
fn if_(&self) -> bool {
self.0.if_() && self.1.if_()
}
fn for_(&self, index: usize) -> Option<&dyn Value> {
match index {
0 => Some(self.0),
1 => Some(self.1),
_ => None,
}
}
}
impl<const N: usize> Value for [(&str, &dyn Value); N] {
fn print(&self, w: &mut String) {
for (name, v) in self.iter() {
let _ = write!(w, "{name}");
v.print(w);
}
}
fn lookup(&self, name: &str) -> Option<&dyn Value> {
self.iter().find(|(n, _)| *n == name).map(|(_, v)| *v)
}
fn has(&self) -> Option<&dyn Value> {
if self.is_empty() {
None
} else {
Some(self)
}
}
fn if_(&self) -> bool {
self.is_empty()
}
fn for_(&self, index: usize) -> Option<&dyn Value> {
self.get(index).map(|v| v as &dyn Value)
}
}
impl<const N: usize> Value for [&dyn Value; N] {
fn print(&self, w: &mut String) {
for v in self.iter() {
v.print(w)
}
}
fn lookup(&self, name: &str) -> Option<&dyn Value> {
let idx: usize = name.parse().ok()?;
self.get(idx).copied()
}
fn has(&self) -> Option<&dyn Value> {
if self.is_empty() {
None
} else {
Some(self)
}
}
fn if_(&self) -> bool {
self.is_empty()
}
fn for_(&self, index: usize) -> Option<&dyn Value> {
self.get(index).map(|v| *v)
} }
} }
mod build {} mod build {}
pub struct Newt {} pub struct Newt {
ast: Ast,
}
impl Newt { impl Newt {
pub fn build(template: &str) -> Result<Self> { fn build(tmpl: &str) -> Self {
let ast = parse(tmpl);
Self { ast }
} }
pub fn build_and_execute( fn execute(&self, value: &dyn Value) -> String {
template: &str, execute(&self.ast, value)
value: &dyn Value,
) -> Result<String> {
let newt = Newt::build(template)?;
newt.execute(value)
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::{Newt, Value};
#[test] #[test]
fn wishlist() { fn simple_print() {
let tmpl = r#"Hello, {.}"#; let tmpl = r#"Hello, {.}"#;
let out = Newt::build_and_execute(tl, "World!").unwrap(); let out = Newt::build(tmpl).execute(&"World!");
assert_eq!(out, "Hello, World!"); assert_eq!(out, "Hello, World!");
} }
#[test]
fn print_lookup() {
let tmpl = "Hello, {.name}!";
let out = Newt::build(tmpl).execute(&[("name", &"Ted" as &dyn Value)]);
assert_eq!(out, "Hello, Ted!");
}
#[test]
fn print_for() {
let tmpl = "{for .}{.}, {/for}";
let out = Newt::build(tmpl).execute(&[&"Bob" as &dyn Value, &"Larry"]);
assert_eq!(out, "Bob, Larry");
}
} }