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>>,
}
impl<'input> TemplateAst<'input> {
pub fn root(&self) -> &[TemplateAstExpr<'input>] {
impl TemplateAst<'_> {
pub fn root(&self) -> &[TemplateAstExpr<'_>] {
&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(
token_start: &<Input<'input> as winnow::stream::Stream>::Checkpoint,
_err_start: &<Input<'input> as winnow::stream::Stream>::Checkpoint,
input: &Input<'input>,
token_start: &<Input as winnow::stream::Stream>::Checkpoint,
_err_start: &<Input as winnow::stream::Stream>::Checkpoint,
input: &Input,
mut e: AstError,
) -> Self {
e
}
}
impl<'input> AddContext<Input<'input>, AstError> for AstError {
impl AddContext<Input<'_>, AstError> for AstError {
fn add_context(
mut self,
_input: &Input<'input>,
_token_start: &<Input<'input> as Stream>::Checkpoint,
_input: &Input,
_token_start: &<Input as Stream>::Checkpoint,
context: AstError,
) -> Self {
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;
fn from_input(_input: &Input<'input>) -> Self {
fn from_input(_input: &Input<'_>) -> Self {
AstError::ctx()
}
@ -117,24 +117,22 @@ impl<'input> ParserError<Input<'input>> for AstError {
pub struct AstFailure {}
impl AstFailure {
fn from_errors(_errors: Vec<AstError>, _input: &[TemplateToken<'_>]) -> AstFailure {
fn from_errors(_errors: Vec<AstError>, _input: &[TemplateToken]) -> 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 {
fn parse_next(&mut self, input: &mut Input<'i>) -> winnow::Result<TemplateToken<'i>, AstError> {
impl<'input> Parser<Input<'input>, TemplateToken, AstError> for TokenKind {
fn parse_next(&mut self, input: &mut Input<'input>) -> winnow::Result<TemplateToken, AstError> {
winnow::token::literal(*self)
.parse_next(input)
.map(|t| t[0].clone())
}
}
pub fn parse<'input>(
input: &'input [TemplateToken<'input>],
) -> Result<TemplateAst<'input>, AstFailure> {
pub fn parse(input: &[TemplateToken]) -> Result<TemplateAst<'_>, AstFailure> {
let (_remaining, val, errors) = parse_ast.recoverable_parse(TokenSlice::new(input));
if errors.is_empty()
@ -148,15 +146,15 @@ pub fn parse<'input>(
#[derive(Debug, Clone)]
pub enum TemplateAstExpr<'input> {
StaticContent(TemplateToken<'input>),
StaticContent(TemplateToken),
Interpolation {
prev_whitespace: Option<TemplateToken<'input>>,
wants_output: Option<TemplateToken<'input>>,
prev_whitespace: Option<TemplateToken>,
wants_output: Option<TemplateToken>,
expression: Box<TemplateAstExpr<'input>>,
post_whitespace: Option<TemplateToken<'input>>,
post_whitespace: Option<TemplateToken>,
},
VariableAccess(TemplateToken<'input>),
Invalid(&'input [TemplateToken<'input>]),
VariableAccess(TemplateToken),
Invalid(&'input [TemplateToken]),
}
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::input::TempleInput;
pub struct EmitMachine {
current_index: usize,
@ -23,10 +24,19 @@ pub struct VariableSlot {
#[derive(Debug)]
pub enum Instruction {
AppendContent { content: String },
LoadFromContextToSlot { name: String, slot: VariableSlot },
EmitFromSlot { slot: VariableSlot },
PushScope { inherit_parent: bool },
AppendContent {
content: TempleInput,
},
LoadFromContextToSlot {
name: TempleInput,
slot: VariableSlot,
},
EmitFromSlot {
slot: VariableSlot,
},
PushScope {
inherit_parent: bool,
},
Abort,
}
@ -42,11 +52,15 @@ pub fn emit_machine(input: crate::ast::TemplateAst<'_>) -> Vec<Instruction> {
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 {
TemplateAstExpr::StaticContent(template_token) => {
eval.push(Instruction::AppendContent {
content: template_token.source().to_string(),
content: template_token.source().clone(),
});
}
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 {
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 {
eval.push(Instruction::AppendContent {
content: ws.source().to_string(),
content: ws.source().clone(),
});
}
}
@ -89,7 +103,7 @@ fn emit_expr(
match expression {
TemplateAstExpr::VariableAccess(template_token) => {
eval.push(Instruction::LoadFromContextToSlot {
name: template_token.source().to_string(),
name: template_token.source().clone(),
slot: emit_slot,
});
}

View file

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

View file

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