Add some docs

This commit is contained in:
soup 2024-05-31 22:44:35 -04:00
parent a6ea4009ff
commit c61c2dba37
No known key found for this signature in database
4 changed files with 130 additions and 38 deletions

View file

@ -1,2 +1,5 @@
//! NOTE: Changes to this module's public API are unlikely to result in semver
//! major version bumps.
pub mod parse; pub mod parse;
pub mod vm; pub mod vm;

View file

@ -1,3 +1,4 @@
/// The different types of AST nodes
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
pub enum AstNodeKind { pub enum AstNodeKind {
If(If), If(If),
@ -8,9 +9,11 @@ pub enum AstNodeKind {
Text(String), Text(String),
} }
/// A lookup path. `.foo.bar`
#[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>);
/// An if block. `{if .foo}Hello{else}Goodbye{/if}`
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
pub struct If { pub struct If {
pub path: Path, pub path: Path,
@ -18,12 +21,14 @@ pub struct If {
pub else_: Option<Vec<AstNodeKind>>, pub else_: Option<Vec<AstNodeKind>>,
} }
/// A for block. `{for .names}Hello, {.}! {/for}`
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
pub struct For { pub struct For {
pub path: Path, pub path: Path,
pub body: Vec<AstNodeKind>, pub body: Vec<AstNodeKind>,
} }
/// A has block. `{has .optional}{.}{/has}`
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
pub struct Has { pub struct Has {
pub path: Path, pub path: Path,
@ -31,17 +36,20 @@ pub struct Has {
pub else_: Option<Vec<AstNodeKind>>, pub else_: Option<Vec<AstNodeKind>>,
} }
/// A with block. `{with .name}Hello {.}!{/with}`
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
pub struct With { pub struct With {
pub path: Path, pub path: Path,
pub body: Vec<AstNodeKind>, pub body: Vec<AstNodeKind>,
} }
/// A print block. `{.foo.bar}`
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
pub struct Print { pub struct Print {
pub path: Path, pub path: Path,
} }
/// Parse a source string into a [Vec] of [AstNodeKind].
pub fn parse(source: &str) -> Vec<AstNodeKind> { pub fn parse(source: &str) -> Vec<AstNodeKind> {
parse_many_until_eof(source).1 parse_many_until_eof(source).1
} }

View file

@ -2,48 +2,73 @@ use std::fmt::Write;
use crate::Value; use crate::Value;
use self::ops::{Op, OpRef, Ops}; use self::ops::{Op, OpIdx, Ops};
pub(crate) mod ops { pub mod ops {
#[cfg(test)] #[cfg(test)]
use crate::go; use crate::go;
use crate::internal::parse::{AstNodeKind, Path}; use crate::internal::parse::{AstNodeKind, Path};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(super) struct OpRef(pub(super) u32); pub struct OpIdx(pub(super) u32);
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(super) enum Op { pub enum Op {
/// Do nothing.
Noop, Noop,
Jump(OpRef), /// Unconditionally set the program counter to the [`OpIdx`].
JumpFalse(OpRef), Jump(OpIdx),
/// Set the program counter to the [`OpIdx`] if cond register is false.
JumpFalse(OpIdx),
/// Exit the program
Exit, Exit,
/// Emit the contained string to the output string
Emit(String), Emit(String),
/// Duplicate the current value to the top of the value stack.
ValueDup, ValueDup,
/// Pop the top of the value stack and set the current value to it.
ValuePop, ValuePop,
/// Lookup the name in the current value
///
/// See also [`crate::Value::lookup`]
ValueLookup(String), ValueLookup(String),
/// Index the current value by the current index register.
///
/// See also [`crate::Value::index`]
ValueIndex, ValueIndex,
/// Print the current value to the output string.
///
/// See also [`crate::Value::print`]
ValuePrint, ValuePrint,
/// Check if the current value is truthy.
///
/// See also [`crate::Value::is_truthy`]
ValueTruthy, ValueTruthy,
/// Set the current value to the value returned from [`crate::Value::has`].
ValueHas, ValueHas,
CounterDup, /// Push the current counter register value to the top of the counter
/// stack, then set the register to zero.
CounterPushZero,
/// Pop the top of the counter stack and set the counter register to
/// the resulting value.
CounterPop, CounterPop,
/// Increment the current counter register
CounterIncrement, CounterIncrement,
} }
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct Ops(Vec<Op>); pub struct Ops(Vec<Op>);
impl Ops { impl Ops {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
Self(vec![]) Self(vec![])
} }
fn next_ref(&self) -> OpRef { fn next_idx(&self) -> OpIdx {
let next = OpRef( let next = OpIdx(
self.0 self.0
.len() .len()
.try_into() .try_into()
@ -53,23 +78,23 @@ pub(crate) mod ops {
next next
} }
fn emit(&mut self, op: Op) -> OpRef { fn emit(&mut self, op: Op) -> OpIdx {
let next = self.next_ref(); let next = self.next_idx();
self.0.push(op); self.0.push(op);
next next
} }
fn get_mut(&mut self, rf: OpRef) -> &mut Op { fn get_mut(&mut self, idx: OpIdx) -> &mut Op {
self.0.get_mut(rf.0 as usize).expect("OpRef out of bounds") self.0.get_mut(idx.0 as usize).expect("OpIdx out of bounds")
} }
pub(super) fn get(&self, rf: OpRef) -> &Op { pub(super) fn get(&self, idx: OpIdx) -> &Op {
self.0.get(rf.0 as usize).expect("OpRef out of bounds") self.0.get(idx.0 as usize).expect("OpIdx out of bounds")
} }
} }
pub(crate) fn lower_ast(ast: &[AstNodeKind]) -> Ops { pub fn lower_ast(ast: &[AstNodeKind]) -> Ops {
let mut ops = Ops::new(); let mut ops = Ops::new();
lower_many(&mut ops, ast); lower_many(&mut ops, ast);
ops.emit(Op::Exit); ops.emit(Op::Exit);
@ -94,7 +119,7 @@ pub(crate) mod ops {
}); });
}, },
AstNodeKind::For(f) => path_stack(ops, &f.path, |ops| { AstNodeKind::For(f) => path_stack(ops, &f.path, |ops| {
ops.emit(Op::CounterDup); ops.emit(Op::CounterPushZero);
let dup = ops.emit(Op::ValueDup); let dup = ops.emit(Op::ValueDup);
ops.emit(Op::ValueIndex); ops.emit(Op::ValueIndex);
let jump_end = ops.emit(Op::Noop); let jump_end = ops.emit(Op::Noop);
@ -118,26 +143,26 @@ pub(crate) mod ops {
lower_many(ops, &i.then); lower_many(ops, &i.then);
if let Some(else_) = i.else_.as_ref() { if let Some(else_) = i.else_.as_ref() {
let jump_end = ops.emit(Op::Noop); let jump_end = ops.emit(Op::Noop);
*ops.get_mut(jump_else) = Op::JumpFalse(ops.next_ref()); *ops.get_mut(jump_else) = Op::JumpFalse(ops.next_idx());
lower_many(ops, else_); lower_many(ops, else_);
*ops.get_mut(jump_end) = Op::Jump(ops.next_ref()); *ops.get_mut(jump_end) = Op::Jump(ops.next_idx());
} else { } else {
*ops.get_mut(jump_else) = Op::JumpFalse(ops.next_ref()); *ops.get_mut(jump_else) = Op::JumpFalse(ops.next_idx());
}; };
}, },
AstNodeKind::Has(h) => path_stack(ops, &h.path, |ops| { AstNodeKind::Has(h) => path_stack(ops, &h.path, |ops| {
ops.emit(Op::ValueHas); ops.emit(Op::ValueHas);
let jump_else = ops.emit(Op::Noop); let jump_else = ops.emit(Op::Noop);
lower_many(ops, &h.then); lower_many(ops, &h.then);
let next_ref = ops.next_ref(); let next_ref = ops.next_idx();
*ops.get_mut(jump_else) = Op::JumpFalse(next_ref); *ops.get_mut(jump_else) = Op::JumpFalse(next_ref);
if let Some(else_) = h.else_.as_ref() { if let Some(else_) = h.else_.as_ref() {
let jump_end = ops.emit(Op::Noop); let jump_end = ops.emit(Op::Noop);
*ops.get_mut(jump_else) = Op::JumpFalse(ops.next_ref()); *ops.get_mut(jump_else) = Op::JumpFalse(ops.next_idx());
lower_many(ops, else_); lower_many(ops, else_);
*ops.get_mut(jump_end) = Op::Jump(ops.next_ref()); *ops.get_mut(jump_end) = Op::Jump(ops.next_idx());
} else { } else {
*ops.get_mut(jump_else) = Op::JumpFalse(ops.next_ref()); *ops.get_mut(jump_else) = Op::JumpFalse(ops.next_idx());
}; };
}), }),
} }
@ -205,14 +230,14 @@ pub(crate) mod ops {
vec![ vec![
Op::ValueDup, Op::ValueDup,
Op::ValueLookup("test".to_string()), Op::ValueLookup("test".to_string()),
Op::CounterDup, Op::CounterPushZero,
Op::ValueDup, Op::ValueDup,
Op::ValueIndex, Op::ValueIndex,
Op::JumpFalse(OpRef(10)), Op::JumpFalse(OpIdx(10)),
Op::CounterIncrement, Op::CounterIncrement,
Op::ValuePrint, Op::ValuePrint,
Op::ValuePop, Op::ValuePop,
Op::Jump(OpRef(3)), Op::Jump(OpIdx(3)),
Op::CounterPop, Op::CounterPop,
Op::ValuePop, Op::ValuePop,
Op::ValuePop, Op::ValuePop,
@ -244,9 +269,9 @@ pub(crate) mod ops {
Op::ValueLookup("test".to_string()), Op::ValueLookup("test".to_string()),
Op::ValueTruthy, Op::ValueTruthy,
Op::ValuePop, Op::ValuePop,
Op::JumpFalse(OpRef(7)), Op::JumpFalse(OpIdx(7)),
Op::Emit("Then".to_string()), Op::Emit("Then".to_string()),
Op::Jump(OpRef(8)), Op::Jump(OpIdx(8)),
Op::Emit("Else".to_string()), Op::Emit("Else".to_string()),
Op::Exit, Op::Exit,
] ]
@ -262,7 +287,7 @@ pub(crate) mod ops {
Op::ValueLookup("test".to_string()), Op::ValueLookup("test".to_string()),
Op::ValueTruthy, Op::ValueTruthy,
Op::ValuePop, Op::ValuePop,
Op::JumpFalse(OpRef(6)), Op::JumpFalse(OpIdx(6)),
Op::Emit("Then".to_string()), Op::Emit("Then".to_string()),
Op::Exit, Op::Exit,
] ]
@ -271,13 +296,25 @@ pub(crate) mod ops {
} }
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct VmState<'a> { pub struct VmState<'a> {
/// The register used to check if the VM should jump.
///
/// See also [`ops::Op::Jump`] and [`ops::Op::JumpFalse`]
reg_cond: bool, reg_cond: bool,
/// The program counter.
reg_pc: u32, reg_pc: u32,
/// The current value.
///
/// See also the [`ops::Op`] variants that start with `Value`.
reg_value: &'a dyn Value, reg_value: &'a dyn Value,
/// The current loop counter.
///
/// See also the [`ops::Op`] variants that start with `Counter`.
reg_counter: usize, reg_counter: usize,
/// The stack for storing values.
stack_values: Vec<&'a dyn Value>, stack_values: Vec<&'a dyn Value>,
/// The stack for storing counters.
stack_counters: Vec<usize>, stack_counters: Vec<usize>,
output: String, output: String,
@ -285,7 +322,7 @@ pub(crate) struct VmState<'a> {
ops: &'a Ops, ops: &'a Ops,
} }
impl<'a> VmState<'a> { impl<'a> VmState<'a> {
pub(crate) fn new(ops: &'a Ops, value: &'a dyn Value) -> Self { pub fn new(ops: &'a Ops, value: &'a dyn Value) -> Self {
Self { Self {
reg_cond: false, reg_cond: false,
reg_pc: 0, reg_pc: 0,
@ -301,7 +338,7 @@ impl<'a> VmState<'a> {
} }
} }
pub(crate) fn execute(mut self) -> String { pub fn execute(mut self) -> String {
self.execute_loop(); self.execute_loop();
self.output self.output
} }
@ -316,7 +353,7 @@ impl<'a> VmState<'a> {
} }
fn execute_one(&mut self) -> bool { fn execute_one(&mut self) -> bool {
let op = self.ops.get(OpRef(self.reg_pc)); let op = self.ops.get(OpIdx(self.reg_pc));
let mut op_offset = 1; let mut op_offset = 1;
match op { match op {
Op::Exit => return true, Op::Exit => return true,
@ -378,8 +415,9 @@ impl<'a> VmState<'a> {
self.reg_cond = false; self.reg_cond = false;
} }
}, },
Op::CounterDup => { Op::CounterPushZero => {
self.stack_counters.push(self.reg_counter); self.stack_counters.push(self.reg_counter);
self.reg_counter = 0;
}, },
Op::CounterPop => { Op::CounterPop => {
self.reg_counter = self.reg_counter =

View file

@ -1,3 +1,13 @@
//! # Introduction
//! Newt is a small templating library for Rust. The templating language aims
//! to be simple to understand.
//!
//! See [`Newt`] for the primary entrypoint into the library. Additionally,
//! some parts of [`internal`] are exposed for advanced use cases.
//!
//! # A note on versioning
//! NOTE: Changes to modules in [`internal`] are unlikely to result in a semver
//! major version bump.
use core::fmt::Write; use core::fmt::Write;
pub mod internal; pub mod internal;
@ -10,11 +20,35 @@ use internal::{
}, },
}; };
/// The primary trait exposed by this crate. Implement this trait if you would
/// like to be able to render a type in a template.
pub trait Value: core::fmt::Debug { pub trait Value: core::fmt::Debug {
/// Print this value to the given string.
///
/// See also [`internal::parse::Print`] and [`internal::vm::ops::Op::ValuePrint`]
fn print(&self, out: &mut String); fn print(&self, out: &mut String);
/// Lookup a field name in this value. The returned [`Value`] will be set as
/// the current value inside the block.
///
/// See also [internal::vm::ops::Op::ValueLookup]
fn lookup(&self, name: &str) -> Option<&dyn Value>; fn lookup(&self, name: &str) -> Option<&dyn Value>;
/// Like [`Option::map`], but for a value. The returned [`Value`] will be set
/// as the current value inside the block.
///
/// See also [`internal::parse::Has`] and [`internal::vm::ops::Op::ValueHas`]
fn has(&self) -> Option<&dyn Value>; fn has(&self) -> Option<&dyn Value>;
/// Check if a value is truthy.
///
/// See also [`internal::parse::If`] and [`internal::vm::ops::Op::ValueTruthy`]
fn is_truthy(&self) -> bool; fn is_truthy(&self) -> bool;
/// Lookup the current index in this value. The returned [`Value`] will be
/// set as the current value inside the block.
///
/// See also [`internal::parse::For`] and [`internal::vm::ops::Op::ValueIndex`]
fn index(&self, index: usize) -> Option<&dyn Value>; fn index(&self, index: usize) -> Option<&dyn Value>;
} }
@ -205,8 +239,12 @@ where
} }
} }
/// A map from [`&str`] to `&dyn` [`Value`]s.
///
/// NOTE: This is best used when the number of keys is relatively small, since
/// internally this is a [`Vec`] of `(&str, &dyn Value)`.
#[derive(Debug)] #[derive(Debug)]
pub struct ValuesListMap<'a>(pub &'a [(&'a str, &'a dyn Value)]); pub struct ValuesListMap<'a>(&'a [(&'a str, &'a dyn Value)]);
impl<'a> Value for ValuesListMap<'a> { impl<'a> Value for ValuesListMap<'a> {
fn print(&self, w: &mut String) { fn print(&self, w: &mut String) {
for (name, v) in self.0 { for (name, v) in self.0 {
@ -374,11 +412,12 @@ impl Value for bool {
} }
} }
/// The primary entrypoint to this library.
pub struct Newt { pub struct Newt {
ops: Ops, ops: Ops,
} }
impl Newt { impl Newt {
#[must_use] /// Parse and compile the given template.
pub fn build(tmpl: &str) -> Self { pub fn build(tmpl: &str) -> Self {
let ast = parse(tmpl); let ast = parse(tmpl);
let ops = lower_ast(&ast); let ops = lower_ast(&ast);
@ -386,11 +425,14 @@ impl Newt {
Self { ops } Self { ops }
} }
/// Execute this template with `value` as the initial value.
pub fn execute(&self, value: &dyn Value) -> String { pub fn execute(&self, value: &dyn Value) -> String {
VmState::new(&self.ops, value).execute() VmState::new(&self.ops, value).execute()
} }
} }
/// A helper macro to construct a map of [&str] names to `&dyn` [`Value`]s.
/// See also [`ValuesListMap`].
#[macro_export] #[macro_export]
macro_rules! values_map { macro_rules! values_map {
($($key:path: $val:expr),* $(,)?) => {{ ($($key:path: $val:expr),* $(,)?) => {{
@ -404,6 +446,7 @@ macro_rules! values_map {
}} }}
} }
/// A helper macro to construct a list of `&dyn` [`Value`]s.
#[macro_export] #[macro_export]
macro_rules! values { macro_rules! values {
($($val:expr),* $(,)?) => {{ ($($val:expr),* $(,)?) => {{