Use custom Arc backed input

Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
Marcel Müller 2026-03-06 12:40:02 +01:00
parent 1ea15f0e49
commit 1ee7611981
6 changed files with 283 additions and 77 deletions

View file

@ -24,8 +24,8 @@ pub struct TemplateAst<'input> {
root: Vec<TemplateAstExpr<'input>>, root: Vec<TemplateAstExpr<'input>>,
} }
impl<'input> TemplateAst<'input> { impl TemplateAst<'_> {
pub fn root(&self) -> &[TemplateAstExpr<'input>] { pub fn root(&self) -> &[TemplateAstExpr<'_>] {
&self.root &self.root
} }
} }
@ -73,22 +73,22 @@ impl ModalError for AstError {
} }
} }
impl<'input> FromRecoverableError<Input<'input>, AstError> for AstError { impl FromRecoverableError<Input<'_>, AstError> for AstError {
fn from_recoverable_error( fn from_recoverable_error(
token_start: &<Input<'input> as winnow::stream::Stream>::Checkpoint, token_start: &<Input as winnow::stream::Stream>::Checkpoint,
_err_start: &<Input<'input> as winnow::stream::Stream>::Checkpoint, _err_start: &<Input as winnow::stream::Stream>::Checkpoint,
input: &Input<'input>, input: &Input,
mut e: AstError, mut e: AstError,
) -> Self { ) -> Self {
e e
} }
} }
impl<'input> AddContext<Input<'input>, AstError> for AstError { impl AddContext<Input<'_>, AstError> for AstError {
fn add_context( fn add_context(
mut self, mut self,
_input: &Input<'input>, _input: &Input,
_token_start: &<Input<'input> as Stream>::Checkpoint, _token_start: &<Input as Stream>::Checkpoint,
context: AstError, context: AstError,
) -> Self { ) -> Self {
self.message = context.message.or(self.message); self.message = context.message.or(self.message);
@ -97,10 +97,10 @@ impl<'input> AddContext<Input<'input>, AstError> for AstError {
} }
} }
impl<'input> ParserError<Input<'input>> for AstError { impl ParserError<Input<'_>> for AstError {
type Inner = AstError; type Inner = AstError;
fn from_input(_input: &Input<'input>) -> Self { fn from_input(_input: &Input<'_>) -> Self {
AstError::ctx() AstError::ctx()
} }
@ -117,24 +117,22 @@ impl<'input> ParserError<Input<'input>> for AstError {
pub struct AstFailure {} pub struct AstFailure {}
impl AstFailure { impl AstFailure {
fn from_errors(_errors: Vec<AstError>, _input: &[TemplateToken<'_>]) -> AstFailure { fn from_errors(_errors: Vec<AstError>, _input: &[TemplateToken]) -> AstFailure {
AstFailure {} AstFailure {}
} }
} }
type Input<'input> = Recoverable<TokenSlice<'input, TemplateToken<'input>>, AstError>; type Input<'input> = Recoverable<TokenSlice<'input, TemplateToken>, AstError>;
impl<'i> Parser<Input<'i>, TemplateToken<'i>, AstError> for TokenKind { impl<'input> Parser<Input<'input>, TemplateToken, AstError> for TokenKind {
fn parse_next(&mut self, input: &mut Input<'i>) -> winnow::Result<TemplateToken<'i>, AstError> { fn parse_next(&mut self, input: &mut Input<'input>) -> winnow::Result<TemplateToken, AstError> {
winnow::token::literal(*self) winnow::token::literal(*self)
.parse_next(input) .parse_next(input)
.map(|t| t[0].clone()) .map(|t| t[0].clone())
} }
} }
pub fn parse<'input>( pub fn parse(input: &[TemplateToken]) -> Result<TemplateAst<'_>, AstFailure> {
input: &'input [TemplateToken<'input>],
) -> Result<TemplateAst<'input>, AstFailure> {
let (_remaining, val, errors) = parse_ast.recoverable_parse(TokenSlice::new(input)); let (_remaining, val, errors) = parse_ast.recoverable_parse(TokenSlice::new(input));
if errors.is_empty() if errors.is_empty()
@ -148,15 +146,15 @@ pub fn parse<'input>(
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum TemplateAstExpr<'input> { pub enum TemplateAstExpr<'input> {
StaticContent(TemplateToken<'input>), StaticContent(TemplateToken),
Interpolation { Interpolation {
prev_whitespace: Option<TemplateToken<'input>>, prev_whitespace: Option<TemplateToken>,
wants_output: Option<TemplateToken<'input>>, wants_output: Option<TemplateToken>,
expression: Box<TemplateAstExpr<'input>>, expression: Box<TemplateAstExpr<'input>>,
post_whitespace: Option<TemplateToken<'input>>, post_whitespace: Option<TemplateToken>,
}, },
VariableAccess(TemplateToken<'input>), VariableAccess(TemplateToken),
Invalid(&'input [TemplateToken<'input>]), Invalid(&'input [TemplateToken]),
} }
fn parse_ast<'input>(input: &mut Input<'input>) -> Result<Vec<TemplateAstExpr<'input>>, AstError> { fn parse_ast<'input>(input: &mut Input<'input>) -> Result<Vec<TemplateAstExpr<'input>>, AstError> {

View file

@ -1,4 +1,5 @@
use crate::ast::TemplateAstExpr; use crate::ast::TemplateAstExpr;
use crate::input::TempleInput;
pub struct EmitMachine { pub struct EmitMachine {
current_index: usize, current_index: usize,
@ -23,10 +24,19 @@ pub struct VariableSlot {
#[derive(Debug)] #[derive(Debug)]
pub enum Instruction { pub enum Instruction {
AppendContent { content: String }, AppendContent {
LoadFromContextToSlot { name: String, slot: VariableSlot }, content: TempleInput,
EmitFromSlot { slot: VariableSlot }, },
PushScope { inherit_parent: bool }, LoadFromContextToSlot {
name: TempleInput,
slot: VariableSlot,
},
EmitFromSlot {
slot: VariableSlot,
},
PushScope {
inherit_parent: bool,
},
Abort, Abort,
} }
@ -42,11 +52,15 @@ pub fn emit_machine(input: crate::ast::TemplateAst<'_>) -> Vec<Instruction> {
eval eval
} }
fn emit_ast_expr(machine: &mut EmitMachine, eval: &mut Vec<Instruction>, ast: &TemplateAstExpr<'_>) { fn emit_ast_expr(
machine: &mut EmitMachine,
eval: &mut Vec<Instruction>,
ast: &TemplateAstExpr<'_>,
) {
match ast { match ast {
TemplateAstExpr::StaticContent(template_token) => { TemplateAstExpr::StaticContent(template_token) => {
eval.push(Instruction::AppendContent { eval.push(Instruction::AppendContent {
content: template_token.source().to_string(), content: template_token.source().clone(),
}); });
} }
TemplateAstExpr::Interpolation { TemplateAstExpr::Interpolation {
@ -57,7 +71,7 @@ fn emit_ast_expr(machine: &mut EmitMachine, eval: &mut Vec<Instruction>, ast: &T
} => { } => {
if let Some(ws) = prev_whitespace { if let Some(ws) = prev_whitespace {
eval.push(Instruction::AppendContent { eval.push(Instruction::AppendContent {
content: ws.source().to_string(), content: ws.source().clone(),
}); });
} }
@ -70,7 +84,7 @@ fn emit_ast_expr(machine: &mut EmitMachine, eval: &mut Vec<Instruction>, ast: &T
if let Some(ws) = post_whitespace { if let Some(ws) = post_whitespace {
eval.push(Instruction::AppendContent { eval.push(Instruction::AppendContent {
content: ws.source().to_string(), content: ws.source().clone(),
}); });
} }
} }
@ -89,7 +103,7 @@ fn emit_expr(
match expression { match expression {
TemplateAstExpr::VariableAccess(template_token) => { TemplateAstExpr::VariableAccess(template_token) => {
eval.push(Instruction::LoadFromContextToSlot { eval.push(Instruction::LoadFromContextToSlot {
name: template_token.source().to_string(), name: template_token.source().clone(),
slot: emit_slot, slot: emit_slot,
}); });
} }

View file

@ -5,11 +5,12 @@ use thiserror::Error;
use crate::Context; use crate::Context;
use crate::emit::Instruction; use crate::emit::Instruction;
use crate::input::TempleInput;
#[derive(Debug, Error, Display)] #[derive(Debug, Error, Display)]
enum EvalError { enum EvalError {
/// An unknown variable was encountered: .0 /// An unknown variable was encountered: .0
UnknownVariable(String), UnknownVariable(TempleInput),
/// An explicit abort was requested /// An explicit abort was requested
ExplicitAbort, ExplicitAbort,
} }
@ -25,7 +26,7 @@ fn execute(instructions: &[Instruction], global_context: &Context) -> Result<Str
Instruction::LoadFromContextToSlot { name, slot } => { Instruction::LoadFromContextToSlot { name, slot } => {
let value = global_context let value = global_context
.values .values
.get(name) .get(name.as_str())
.ok_or(EvalError::UnknownVariable(name.clone()))?; .ok_or(EvalError::UnknownVariable(name.clone()))?;
scopes.insert(*slot, value.clone()); scopes.insert(*slot, value.clone());

194
src/input.rs Normal file
View file

@ -0,0 +1,194 @@
use std::ops::Deref;
use std::ops::Range;
use std::sync::Arc;
use winnow::stream::Compare;
use winnow::stream::FindSlice;
use winnow::stream::Offset;
use winnow::stream::Stream;
use winnow::stream::StreamIsPartial;
#[derive(Clone, PartialEq, Eq)]
pub struct TempleInput {
backing: Arc<str>,
range: Range<usize>,
}
impl std::fmt::Debug for TempleInput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"{}\"", self.as_str())
}
}
impl From<&str> for TempleInput {
fn from(value: &str) -> Self {
let backing = Arc::from(value.to_string());
let range = 0..value.len();
TempleInput { backing, range }
}
}
impl FindSlice<&str> for TempleInput {
fn find_slice(&self, substr: &str) -> Option<core::ops::Range<usize>> {
self.as_str().find_slice(substr)
}
}
impl Compare<&str> for TempleInput {
fn compare(&self, t: &str) -> winnow::stream::CompareResult {
self.as_str().compare(t)
}
}
impl Deref for TempleInput {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.backing[self.range.clone()]
}
}
impl TempleInput {
pub fn as_str(&self) -> &str {
self.deref()
}
}
impl Offset for TempleInput {
fn offset_from(&self, start: &Self) -> usize {
self.as_str().offset_from(&start.as_str())
}
}
pub struct TempleInputIter {
idx: usize,
input: TempleInput,
}
impl Iterator for TempleInputIter {
type Item = (usize, char);
fn next(&mut self) -> Option<Self::Item> {
let val = self.input.as_str().char_indices().nth(self.idx);
self.idx += 1;
val
}
}
impl StreamIsPartial for TempleInput {
type PartialState = ();
fn complete(&mut self) -> Self::PartialState {
// Always complete
}
fn restore_partial(&mut self, _state: Self::PartialState) {}
fn is_partial_supported() -> bool {
false
}
}
impl Stream for TempleInput {
type Token = char;
type Slice = TempleInput;
type IterOffsets = TempleInputIter;
type Checkpoint = TempleInput;
fn iter_offsets(&self) -> Self::IterOffsets {
TempleInputIter {
idx: 0,
input: self.clone(),
}
}
fn eof_offset(&self) -> usize {
self.len()
}
fn next_token(&mut self) -> Option<Self::Token> {
let c = self.chars().next()?;
self.range.start += c.len_utf8();
Some(c)
}
fn peek_token(&self) -> Option<Self::Token> {
self.chars().next()
}
fn offset_for<P>(&self, predicate: P) -> Option<usize>
where
P: Fn(Self::Token) -> bool,
{
self.as_str().offset_for(predicate)
}
fn offset_at(&self, tokens: usize) -> Result<usize, winnow::error::Needed> {
self.as_str().offset_at(tokens)
}
fn next_slice(&mut self, offset: usize) -> Self::Slice {
let mut next = self.clone();
self.range.start += offset;
next.range.end = self.range.start;
next
}
fn peek_slice(&self, offset: usize) -> Self::Slice {
let mut next = self.clone();
next.range.end = self.range.start + offset;
next
}
fn checkpoint(&self) -> Self::Checkpoint {
self.clone()
}
fn reset(&mut self, checkpoint: &Self::Checkpoint) {
self.range = checkpoint.range.clone();
}
fn raw(&self) -> &dyn core::fmt::Debug {
self
}
fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
use winnow::stream::Stream;
use crate::input::TempleInput;
#[test]
fn check_stream_impl() {
let mut stream = TempleInput::from("checking");
let checkpoint = stream.checkpoint();
assert_eq!(stream.peek_token(), Some('c'));
assert_eq!(stream.next_token(), Some('c'));
let next_slice = stream.next_slice(4);
assert_eq!(next_slice.as_str(), "heck");
assert_eq!(stream.peek_token(), Some('i'));
stream.reset(&checkpoint);
assert_eq!(stream.peek_token(), Some('c'));
let peek = stream.peek_slice(4);
assert_eq!(peek.as_str(), "chec");
let eof_offset = stream.eof_offset();
assert_eq!(eof_offset, 8);
}
}

View file

@ -9,6 +9,7 @@ pub mod ast;
pub mod emit; pub mod emit;
pub mod eval; pub mod eval;
pub mod parser; pub mod parser;
mod input;
#[derive(Debug, Error, Display)] #[derive(Debug, Error, Display)]
pub enum TempleError { pub enum TempleError {

View file

@ -31,9 +31,10 @@ use winnow::token::take_until;
use winnow::token::take_while; use winnow::token::take_while;
use crate::SourceSpan; use crate::SourceSpan;
use crate::input::TempleInput;
use crate::resume_after_cut; use crate::resume_after_cut;
type Input<'input> = Recoverable<LocatingSlice<&'input str>, ParseError>; type Input<'input> = Recoverable<LocatingSlice<TempleInput>, ParseError>;
type PResult<'input, T> = Result<T, ParseError>; type PResult<'input, T> = Result<T, ParseError>;
#[derive(Debug)] #[derive(Debug)]
@ -177,12 +178,12 @@ impl<'input> ParserError<Input<'input>> for ParseError {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct ParsedTemplate<'input> { pub struct ParsedTemplate {
tokens: Vec<TemplateToken<'input>>, tokens: Vec<TemplateToken>,
} }
impl<'input> ParsedTemplate<'input> { impl ParsedTemplate {
pub fn tokens(&self) -> &[TemplateToken<'input>] { pub fn tokens(&self) -> &[TemplateToken] {
&self.tokens &self.tokens
} }
} }
@ -198,88 +199,86 @@ pub enum TokenKind {
Invalid, Invalid,
} }
impl PartialEq<TokenKind> for TemplateToken<'_> { impl PartialEq<TokenKind> for TemplateToken {
fn eq(&self, other: &TokenKind) -> bool { fn eq(&self, other: &TokenKind) -> bool {
self.kind == *other self.kind == *other
} }
} }
impl winnow::stream::ContainsToken<&'_ TemplateToken<'_>> for TokenKind { impl winnow::stream::ContainsToken<&'_ TemplateToken> for TokenKind {
fn contains_token(&self, token: &'_ TemplateToken<'_>) -> bool { fn contains_token(&self, token: &'_ TemplateToken) -> bool {
*self == token.kind *self == token.kind
} }
} }
impl winnow::stream::ContainsToken<&'_ TemplateToken<'_>> for &'_ [TokenKind] { impl winnow::stream::ContainsToken<&'_ TemplateToken> for &'_ [TokenKind] {
fn contains_token(&self, token: &'_ TemplateToken<'_>) -> bool { fn contains_token(&self, token: &'_ TemplateToken) -> bool {
self.contains(&token.kind) self.contains(&token.kind)
} }
} }
impl<const LEN: usize> winnow::stream::ContainsToken<&'_ TemplateToken<'_>> impl<const LEN: usize> winnow::stream::ContainsToken<&'_ TemplateToken> for &'_ [TokenKind; LEN] {
for &'_ [TokenKind; LEN] fn contains_token(&self, token: &'_ TemplateToken) -> bool {
{
fn contains_token(&self, token: &'_ TemplateToken<'_>) -> bool {
self.contains(&token.kind) self.contains(&token.kind)
} }
} }
impl<const LEN: usize> winnow::stream::ContainsToken<&'_ TemplateToken<'_>> for [TokenKind; LEN] { impl<const LEN: usize> winnow::stream::ContainsToken<&'_ TemplateToken> for [TokenKind; LEN] {
fn contains_token(&self, token: &'_ TemplateToken<'_>) -> bool { fn contains_token(&self, token: &'_ TemplateToken) -> bool {
self.contains(&token.kind) self.contains(&token.kind)
} }
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct TemplateToken<'input> { pub struct TemplateToken {
kind: TokenKind, kind: TokenKind,
source: &'input str, source: TempleInput,
} }
impl<'input> TemplateToken<'input> { impl TemplateToken {
fn content(source: &'input str) -> Self { fn content(source: TempleInput) -> Self {
TemplateToken { TemplateToken {
kind: TokenKind::Content, kind: TokenKind::Content,
source, source,
} }
} }
fn left_delim(source: &'input str) -> Self { fn left_delim(source: TempleInput) -> Self {
TemplateToken { TemplateToken {
kind: TokenKind::LeftDelim, kind: TokenKind::LeftDelim,
source, source,
} }
} }
fn right_delim(source: &'input str) -> Self { fn right_delim(source: TempleInput) -> Self {
TemplateToken { TemplateToken {
kind: TokenKind::RightDelim, kind: TokenKind::RightDelim,
source, source,
} }
} }
fn wants_output(source: &'input str) -> Self { fn wants_output(source: TempleInput) -> Self {
TemplateToken { TemplateToken {
kind: TokenKind::WantsOutput, kind: TokenKind::WantsOutput,
source, source,
} }
} }
fn ident(source: &'input str) -> Self { fn ident(source: TempleInput) -> Self {
TemplateToken { TemplateToken {
kind: TokenKind::Ident, kind: TokenKind::Ident,
source, source,
} }
} }
fn whitespace(source: &'input str) -> Self { fn whitespace(source: TempleInput) -> Self {
TemplateToken { TemplateToken {
kind: TokenKind::Whitespace, kind: TokenKind::Whitespace,
source, source,
} }
} }
fn invalid(source: &'input str) -> Self { fn invalid(source: TempleInput) -> Self {
TemplateToken { TemplateToken {
kind: TokenKind::Invalid, kind: TokenKind::Invalid,
source, source,
@ -290,13 +289,14 @@ impl<'input> TemplateToken<'input> {
self.kind self.kind
} }
pub fn source(&self) -> &'input str { pub fn source(&self) -> TempleInput {
self.source self.source.clone()
} }
} }
pub fn parse(input: &str) -> Result<ParsedTemplate<'_>, ParseFailure> { pub fn parse(input: &str) -> Result<ParsedTemplate, ParseFailure> {
let (_remaining, val, errors) = parse_tokens.recoverable_parse(LocatingSlice::new(input)); let (_remaining, val, errors) =
parse_tokens.recoverable_parse(LocatingSlice::new(TempleInput::from(input)));
if errors.is_empty() if errors.is_empty()
&& let Some(val) = val && let Some(val) = val
@ -307,13 +307,13 @@ pub fn parse(input: &str) -> Result<ParsedTemplate<'_>, ParseFailure> {
} }
} }
fn parse_tokens<'input>(input: &mut Input<'input>) -> PResult<'input, Vec<TemplateToken<'input>>> { fn parse_tokens<'input>(input: &mut Input<'input>) -> PResult<'input, Vec<TemplateToken>> {
repeat_till(0.., alt((parse_interpolate, parse_content)), eof) repeat_till(0.., alt((parse_interpolate, parse_content)), eof)
.map(|(v, _): (Vec<_>, _)| v.into_iter().flatten().collect()) .map(|(v, _): (Vec<_>, _)| v.into_iter().flatten().collect())
.parse_next(input) .parse_next(input)
} }
fn parse_content<'input>(input: &mut Input<'input>) -> PResult<'input, Vec<TemplateToken<'input>>> { fn parse_content<'input>(input: &mut Input<'input>) -> PResult<'input, Vec<TemplateToken>> {
alt(( alt((
repeat_till(1.., any, peek((multispace0, "{{"))).map(|((), _)| ()), repeat_till(1.., any, peek((multispace0, "{{"))).map(|((), _)| ()),
rest.void(), rest.void(),
@ -324,9 +324,7 @@ fn parse_content<'input>(input: &mut Input<'input>) -> PResult<'input, Vec<Templ
.parse_next(input) .parse_next(input)
} }
fn parse_interpolate<'input>( fn parse_interpolate<'input>(input: &mut Input<'input>) -> PResult<'input, Vec<TemplateToken>> {
input: &mut Input<'input>,
) -> PResult<'input, Vec<TemplateToken<'input>>> {
let prev_whitespace = opt(parse_whitespace).parse_next(input)?; let prev_whitespace = opt(parse_whitespace).parse_next(input)?;
let left_delim = "{{".map(TemplateToken::left_delim).parse_next(input)?; let left_delim = "{{".map(TemplateToken::left_delim).parse_next(input)?;
let wants_output = opt("=".map(TemplateToken::wants_output)).parse_next(input)?; let wants_output = opt("=".map(TemplateToken::wants_output)).parse_next(input)?;
@ -337,7 +335,9 @@ fn parse_interpolate<'input>(
let (inside_tokens, _): (Vec<_>, _) = get_tokens let (inside_tokens, _): (Vec<_>, _) = get_tokens
.resume_after(recover) .resume_after(recover)
.with_taken() .with_taken()
.map(|(val, taken)| val.unwrap_or_else(|| (vec![TemplateToken::invalid(taken)], ""))) .map(|(val, taken)| {
val.unwrap_or_else(|| (vec![TemplateToken::invalid(taken)], TempleInput::from("")))
})
.parse_next(input)?; .parse_next(input)?;
let right_delim = "}}".map(TemplateToken::right_delim).parse_next(input)?; let right_delim = "}}".map(TemplateToken::right_delim).parse_next(input)?;
@ -354,9 +354,7 @@ fn parse_interpolate<'input>(
Ok(tokens) Ok(tokens)
} }
fn parse_interpolate_token<'input>( fn parse_interpolate_token<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
input: &mut Input<'input>,
) -> PResult<'input, TemplateToken<'input>> {
trace( trace(
"parse_interpolate_token", "parse_interpolate_token",
alt((parse_ident, parse_whitespace)), alt((parse_ident, parse_whitespace)),
@ -364,7 +362,7 @@ fn parse_interpolate_token<'input>(
.parse_next(input) .parse_next(input)
} }
fn parse_whitespace<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken<'input>> { fn parse_whitespace<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
trace( trace(
"parse_whitespace", "parse_whitespace",
multispace1.map(TemplateToken::whitespace), multispace1.map(TemplateToken::whitespace),
@ -372,7 +370,7 @@ fn parse_whitespace<'input>(input: &mut Input<'input>) -> PResult<'input, Templa
.parse_next(input) .parse_next(input)
} }
fn parse_ident<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken<'input>> { fn parse_ident<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
resume_after_cut( resume_after_cut(
terminated( terminated(
ident.map(TemplateToken::ident), ident.map(TemplateToken::ident),
@ -390,7 +388,7 @@ fn parse_ident<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateTok
.parse_next(input) .parse_next(input)
} }
fn ident<'input>(input: &mut Input<'input>) -> PResult<'input, &'input str> { fn ident<'input>(input: &mut Input<'input>) -> PResult<'input, TempleInput> {
take_while(1.., char::is_alphanumeric).parse_next(input) take_while(1.., char::is_alphanumeric).parse_next(input)
} }