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 vm;

View file

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

View file

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