439 lines
6.8 KiB
Rust
439 lines
6.8 KiB
Rust
use core::fmt::Write;
|
|
|
|
pub mod internal;
|
|
|
|
use internal::{
|
|
execute::execute,
|
|
parse::{parse, Ast},
|
|
};
|
|
|
|
pub trait Value {
|
|
fn print(&self, out: &mut String);
|
|
fn lookup(&self, name: &str) -> Option<&dyn Value>;
|
|
fn has(&self) -> Option<&dyn Value>;
|
|
fn if_(&self) -> bool;
|
|
fn for_(&self, index: usize) -> Option<&dyn Value>;
|
|
}
|
|
|
|
macro_rules! value_for_int {
|
|
($tp:ty) => {
|
|
impl Value for $tp {
|
|
fn print(&self, out: &mut String) {
|
|
let _ = write!(out, "{self}");
|
|
}
|
|
|
|
fn lookup(&self, _: &str) -> Option<&dyn Value> {
|
|
None
|
|
}
|
|
|
|
fn has(&self) -> Option<&dyn Value> {
|
|
if self.if_() {
|
|
Some(self)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn if_(&self) -> bool {
|
|
*self != 0
|
|
}
|
|
|
|
fn for_(&self, _: usize) -> Option<&dyn Value> {
|
|
None
|
|
}
|
|
}
|
|
};
|
|
}
|
|
value_for_int!(u8);
|
|
value_for_int!(u16);
|
|
value_for_int!(u32);
|
|
value_for_int!(u64);
|
|
value_for_int!(u128);
|
|
value_for_int!(usize);
|
|
value_for_int!(i8);
|
|
value_for_int!(i16);
|
|
value_for_int!(i32);
|
|
value_for_int!(i64);
|
|
value_for_int!(i128);
|
|
value_for_int!(isize);
|
|
|
|
impl<'a, T> Value for &'a T
|
|
where
|
|
T: Value + ?Sized,
|
|
{
|
|
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 {
|
|
(&**self).if_()
|
|
}
|
|
|
|
fn for_(&self, index: usize) -> Option<&dyn Value> {
|
|
(&**self).for_(index)
|
|
}
|
|
}
|
|
|
|
impl<'a, T> Value for &'a mut T
|
|
where
|
|
T: Value + ?Sized,
|
|
{
|
|
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 {
|
|
(&**self).if_()
|
|
}
|
|
|
|
fn for_(&self, index: usize) -> Option<&dyn Value> {
|
|
(&**self).for_(index)
|
|
}
|
|
}
|
|
|
|
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.if_() {
|
|
None
|
|
} else {
|
|
Some(self)
|
|
}
|
|
}
|
|
|
|
fn if_(&self) -> bool {
|
|
!self.is_empty()
|
|
}
|
|
|
|
fn for_(&self, index: usize) -> Option<&dyn Value> {
|
|
if index == 0 {
|
|
Some(self)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Value for String {
|
|
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.if_() {
|
|
None
|
|
} else {
|
|
Some(self)
|
|
}
|
|
}
|
|
|
|
fn if_(&self) -> bool {
|
|
!self.is_empty()
|
|
}
|
|
|
|
fn for_(&self, index: usize) -> Option<&dyn Value> {
|
|
if index == 0 {
|
|
Some(self)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<A, B> Value for (A, B)
|
|
where
|
|
A: Value,
|
|
B: 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> {
|
|
None
|
|
}
|
|
|
|
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,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct ValuesListMap<'a>(pub &'a [(&'a str, &'a dyn Value)]);
|
|
impl<'a> Value for ValuesListMap<'a> {
|
|
fn print(&self, w: &mut String) {
|
|
for (name, v) in self.0.iter() {
|
|
let _ = write!(w, "{name}");
|
|
v.print(w);
|
|
}
|
|
}
|
|
|
|
fn lookup(&self, name: &str) -> Option<&dyn Value> {
|
|
self.0.iter().find(|(n, _)| *n == name).map(|(_, v)| *v)
|
|
}
|
|
|
|
fn has(&self) -> Option<&dyn Value> {
|
|
if self.0.is_empty() {
|
|
None
|
|
} else {
|
|
Some(self)
|
|
}
|
|
}
|
|
|
|
fn if_(&self) -> bool {
|
|
!self.0.is_empty()
|
|
}
|
|
|
|
fn for_(&self, index: usize) -> Option<&dyn Value> {
|
|
self.0.get(index).map(|v| v as &dyn Value)
|
|
}
|
|
}
|
|
|
|
impl<const N: usize, T> Value for [T; N]
|
|
where
|
|
T: Value,
|
|
{
|
|
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).map(|v| v as &dyn Value)
|
|
}
|
|
|
|
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<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
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> Value for Vec<T>
|
|
where
|
|
T: Value,
|
|
{
|
|
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).map(|v| v as &dyn Value)
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
pub struct Newt {
|
|
ast: Ast,
|
|
}
|
|
impl Newt {
|
|
pub fn build(tmpl: &str) -> Self {
|
|
let ast = parse(tmpl);
|
|
Self { ast: todo!() }
|
|
}
|
|
|
|
pub fn execute(&self, value: &dyn Value) -> String {
|
|
execute(&self.ast, value)
|
|
}
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! values_list_map {
|
|
($($key:literal: $val:expr),* $(,)?) => {{
|
|
&$crate::ValuesListMap(
|
|
&[
|
|
$(
|
|
($key, $val as &dyn Value)
|
|
)*,
|
|
]
|
|
)
|
|
}}
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! values {
|
|
($($val:expr),* $(,)?) => {{
|
|
&[
|
|
$(
|
|
&$val as &dyn Value,
|
|
)*
|
|
]
|
|
}}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use crate::{Newt, Value};
|
|
|
|
fn go(tmpl: &str, value: &dyn Value, expected: &str) {
|
|
assert_eq!(Newt::build(tmpl).execute(value), expected)
|
|
}
|
|
|
|
#[test]
|
|
fn simple_print() {
|
|
go("Hello, {.}!", &"World", "Hello, World!")
|
|
}
|
|
|
|
#[test]
|
|
fn print_lookup() {
|
|
go(
|
|
"Hello, {.name}!",
|
|
values_list_map! {
|
|
"name": &"Ted",
|
|
},
|
|
"Hello, Ted!",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn print_for() {
|
|
go(
|
|
"{for .}{.}, {/for}",
|
|
values!["Bob", "Larry"],
|
|
"Bob, Larry, ",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn print_for_nested() {
|
|
go(
|
|
"{for .items}{for .names}{.}, {/for}{/for}",
|
|
values_list_map! {
|
|
"items": values![values_list_map! {
|
|
"names": values!["Bob", "Larry", 0],
|
|
}]
|
|
},
|
|
"Bob, Larry, 0, ",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn print_has() {
|
|
go("{has .}{.}{/has}", &Some("Bob"), "Bob");
|
|
}
|
|
|
|
#[test]
|
|
fn print_has_none() {
|
|
go("{has .}{.}{/has}", &None::<&str>, "");
|
|
}
|
|
|
|
#[test]
|
|
fn print_if() {
|
|
go("{if .}{.}{/if}", &"Bob", "Bob");
|
|
}
|
|
|
|
#[test]
|
|
fn print_if_false() {
|
|
go("{if .}{.}{/if}", &"", "");
|
|
}
|
|
|
|
#[test]
|
|
fn print_if_false_else() {
|
|
go("{if .}{.}{else}Frank{/if}", &"", "Frank");
|
|
}
|
|
}
|