From c61c2dba379280adf79c329efe56730c0b36c065 Mon Sep 17 00:00:00 2001 From: soup Date: Fri, 31 May 2024 22:44:35 -0400 Subject: [PATCH] Add some docs --- src/internal/mod.rs | 3 ++ src/internal/parse.rs | 8 +++ src/internal/vm.rs | 110 ++++++++++++++++++++++++++++-------------- src/lib.rs | 47 +++++++++++++++++- 4 files changed, 130 insertions(+), 38 deletions(-) diff --git a/src/internal/mod.rs b/src/internal/mod.rs index 1214935..c9b8713 100644 --- a/src/internal/mod.rs +++ b/src/internal/mod.rs @@ -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; diff --git a/src/internal/parse.rs b/src/internal/parse.rs index b785de7..d210c32 100644 --- a/src/internal/parse.rs +++ b/src/internal/parse.rs @@ -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); +/// 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>, } +/// A for block. `{for .names}Hello, {.}! {/for}` #[derive(Debug, PartialEq, Eq, Hash)] pub struct For { pub path: Path, pub body: Vec, } +/// 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>, } +/// A with block. `{with .name}Hello {.}!{/with}` #[derive(Debug, PartialEq, Eq, Hash)] pub struct With { pub path: Path, pub body: Vec, } +/// 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 { parse_many_until_eof(source).1 } diff --git a/src/internal/vm.rs b/src/internal/vm.rs index 892472a..70e3316 100644 --- a/src/internal/vm.rs +++ b/src/internal/vm.rs @@ -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); + pub struct Ops(Vec); 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, 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 = diff --git a/src/lib.rs b/src/lib.rs index 5712d5c..545bc13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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),* $(,)?) => {{