newt/src/lib.rs
2024-05-30 18:37:59 -04:00

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");
}
}