diff --git a/src/eval/mod.rs b/src/eval/mod.rs new file mode 100644 index 0000000..655eb9d --- /dev/null +++ b/src/eval/mod.rs @@ -0,0 +1,7 @@ +pub struct EvalStack { + stack: Vec, +} + +pub enum Evaluation { + AppendContent { content: String }, +} diff --git a/src/lib.rs b/src/lib.rs index bff388c..1ea1247 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ use serde::Serialize; use thiserror::Error; pub mod parser; +pub mod eval; #[derive(Debug, Error, Display)] pub enum TempleError { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 4757f5e..ff187f5 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8,13 +8,9 @@ use annotate_snippets::Snippet; use winnow::LocatingSlice; use winnow::Parser; use winnow::RecoverableParser; -use winnow::ascii::alpha1; -use winnow::ascii::multispace0; use winnow::ascii::multispace1; use winnow::combinator::alt; -use winnow::combinator::cut_err; use winnow::combinator::eof; -use winnow::combinator::opt; use winnow::combinator::peek; use winnow::combinator::repeat_till; use winnow::combinator::terminated; @@ -27,8 +23,10 @@ use winnow::stream::Location; use winnow::stream::Recoverable; use winnow::stream::Stream; use winnow::token::any; +use winnow::token::one_of; use winnow::token::rest; use winnow::token::take_until; +use winnow::token::take_while; type Input<'input> = Recoverable, ParseError>; type PResult<'input, T> = Result; @@ -53,35 +51,37 @@ impl ParseFailure { } pub fn to_report(&self) -> String { - let mut report = String::new(); + let reports = self + .errors + .iter() + .map(|error| { + Level::ERROR + .primary_title( + error + .message + .as_deref() + .unwrap_or("An error occurred while parsing"), + ) + .element( + Snippet::source(self.source.as_ref()).annotation( + AnnotationKind::Primary + .span(error.span.clone().map(|s| s.range).unwrap_or_else(|| 0..0)), + ), + ) + .elements(error.help.as_ref().map(|help| Level::HELP.message(help))) + }) + .collect::>(); - for error in &self.errors { - let rep = &[Level::ERROR - .primary_title( - error - .message - .as_deref() - .unwrap_or("An error occurred while parsing"), - ) - .element( - Snippet::source(self.source.as_ref()).annotation( - AnnotationKind::Primary - .span(error.span.clone().map(|s| s.range).unwrap_or_else(|| 0..0)), - ), - )]; - - let renderer = - Renderer::styled().decor_style(annotate_snippets::renderer::DecorStyle::Unicode); - report.push_str(&renderer.render(rep)); - } - - report + let renderer = + Renderer::styled().decor_style(annotate_snippets::renderer::DecorStyle::Unicode); + renderer.render(&reports) } } #[derive(Debug, Clone)] pub struct ParseError { pub(crate) message: Option, + pub(crate) help: Option, pub(crate) span: Option, is_fatal: bool, @@ -91,6 +91,7 @@ impl ParseError { fn ctx() -> Self { ParseError { message: None, + help: None, span: None, is_fatal: false, @@ -101,6 +102,11 @@ impl ParseError { self.message = Some(message.to_string()); self } + + fn help(mut self, help: &str) -> Self { + self.help = Some(help.to_string()); + self + } } impl ModalError for ParseError { @@ -138,6 +144,7 @@ impl<'input> AddContext, ParseError> for ParseError { context: ParseError, ) -> Self { self.message = context.message.or(self.message); + self.help = context.help.or(self.help); self } } @@ -157,11 +164,7 @@ impl<'input> ParserError> for ParseError { type Inner = ParseError; fn from_input(_input: &Input<'input>) -> Self { - ParseError { - message: None, - span: None, - is_fatal: false, - } + ParseError::ctx() } fn into_inner(self) -> winnow::Result { @@ -175,111 +178,131 @@ impl<'input> ParserError> for ParseError { #[derive(Debug)] pub struct ParsedTemplate<'input> { - content: Vec>, + tokens: Vec>, } -#[derive(Debug)] -pub enum TemplateChunk<'input> { +impl<'input> ParsedTemplate<'input> { + pub fn tokens(&self) -> &[TemplateToken<'input>] { + &self.tokens + } +} + +#[derive(Debug, Clone)] +pub enum TemplateToken<'input> { Content(&'input str), - Expression(InterpolateExpression<'input>), -} - -#[derive(Debug)] -pub struct InterpolateExpression<'input> { - pub left_delim: &'input str, - pub wants_output: Option<&'input str>, - pub value: Box>, - pub right_delim: &'input str, -} - -#[derive(Debug)] -pub struct TemplateExpression<'input> { - pub before_ws: &'input str, - pub expr: TemplateExpr<'input>, - pub after_ws: &'input str, -} - -#[derive(Debug)] -pub enum TemplateExpr<'input> { - Variable(&'input str), + LeftDelim(&'input str), + RightDelim(&'input str), + WantsOutput(&'input str), + Ident(&'input str), + Whitespace(&'input str), + Invalid(&'input str), } pub fn parse(input: &str) -> Result, ParseFailure> { - let (_remaining, val, errors) = parse_chunks.recoverable_parse(LocatingSlice::new(input)); - - dbg!(&val); + let (_remaining, val, errors) = parse_tokens.recoverable_parse(LocatingSlice::new(input)); if errors.is_empty() && let Some(val) = val { - Ok(ParsedTemplate { content: val }) + Ok(ParsedTemplate { tokens: val }) } else { Err(ParseFailure::from_errors(errors, input)) } } -fn parse_chunks<'input>(input: &mut Input<'input>) -> PResult<'input, Vec>> { +fn parse_tokens<'input>(input: &mut Input<'input>) -> PResult<'input, Vec>> { repeat_till(0.., alt((parse_interpolate, parse_content)), eof) - .map(|(v, _)| v) + .map(|(v, _): (Vec<_>, _)| v.into_iter().flatten().collect()) .parse_next(input) } -fn parse_content<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateChunk<'input>> { +fn parse_content<'input>(input: &mut Input<'input>) -> PResult<'input, Vec>> { alt((take_until(1.., "{{"), rest)) - .map(TemplateChunk::Content) + .map(TemplateToken::Content) + .map(|v| vec![v]) .parse_next(input) } -fn parse_interpolate<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateChunk<'input>> { - let left_delim = "{{".parse_next(input)?; - let (wants_output, value, right_delim) = - cut_err((opt("="), parse_value.map(Box::new), "}}")).parse_next(input)?; +fn parse_interpolate<'input>( + input: &mut Input<'input>, +) -> PResult<'input, Vec>> { + let left_delim = "{{".map(TemplateToken::LeftDelim).parse_next(input)?; - Ok(TemplateChunk::Expression(InterpolateExpression { - left_delim, - wants_output, - value, - right_delim, - })) + let get_tokens = repeat_till(1.., parse_interpolate_token, peek("}}")); + let recover = take_until(0.., "}}").void(); + + let (inside_tokens, _): (Vec<_>, _) = get_tokens + .resume_after(recover) + .with_taken() + .map(|(val, taken)| val.unwrap_or_else(|| (vec![TemplateToken::Invalid(taken)], ""))) + .parse_next(input)?; + + let right_delim = "}}".map(TemplateToken::RightDelim).parse_next(input)?; + + let mut tokens = vec![left_delim]; + tokens.extend(inside_tokens); + tokens.push(right_delim); + + Ok(tokens) } -fn parse_value<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateExpression<'input>> { - let before_ws = multispace0(input)?; - - let expr = trace("parse_value", alt((parse_variable,))).parse_next(input)?; - - let after_ws = multispace0(input)?; - - Ok(TemplateExpression { - before_ws, - expr, - after_ws, - }) +fn parse_interpolate_token<'input>( + input: &mut Input<'input>, +) -> PResult<'input, TemplateToken<'input>> { + trace( + "parse_interpolate_token", + alt((parse_ident, parse_whitespace)), + ) + .parse_next(input) } -fn parse_variable<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateExpr<'input>> { - terminated(alpha1, ident_terminator_check) - .map(TemplateExpr::Variable) - .context(ParseError::ctx().msg("valid variables are alpha")) +fn parse_whitespace<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken<'input>> { + trace( + "parse_whitespace", + multispace1.map(TemplateToken::Whitespace), + ) + .parse_next(input) +} + +fn parse_ident<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken<'input>> { + let ident = ident.map(TemplateToken::Ident).parse_next(input)?; + + ident_terminator_check + .context( + ParseError::ctx() + .msg("Invalid variable identifier") + .help("valid variable identifiers are alphanumeric"), + ) + .value(ident) .resume_after(bad_ident) - .map(|v| v.unwrap_or(TemplateExpr::Variable("BAD_VARIABLE"))) + .with_taken() + .map(|(val, taken)| val.unwrap_or(TemplateToken::Invalid(taken))) .parse_next(input) } -fn ident_terminator<'input>(input: &mut Input<'input>) -> PResult<'input, ()> { - alt((eof.void(), "{".void(), "}".void(), multispace1.void())).parse_next(input) -} - -fn ident_terminator_check<'input>(input: &mut Input<'input>) -> PResult<'input, ()> { - cut_err(peek(ident_terminator)).parse_next(input) +fn ident<'input>(input: &mut Input<'input>) -> PResult<'input, &'input str> { + take_while(1.., char::is_alphanumeric).parse_next(input) } fn bad_ident<'input>(input: &mut Input<'input>) -> PResult<'input, ()> { - repeat_till(1.., any, peek(ident_terminator)) + repeat_till(1.., any, ident_terminator_check) .map(|((), _)| ()) .parse_next(input) } +fn ident_terminator_check<'input>(input: &mut Input<'input>) -> PResult<'input, ()> { + peek(ident_terminator).parse_next(input) +} + +fn ident_terminator<'input>(input: &mut Input<'input>) -> PResult<'input, ()> { + alt(( + eof.void(), + one_of(('{', '}')).void(), + one_of((' ', '\t', '\r', '\n')).void(), + )) + .parse_next(input) +} + #[cfg(test)] mod tests { use crate::parser::parse; @@ -292,7 +315,7 @@ mod tests { insta::assert_debug_snapshot!(output, @r#" Ok( ParsedTemplate { - content: [ + tokens: [ Content( "Hello There", ), @@ -310,23 +333,24 @@ mod tests { insta::assert_debug_snapshot!(output, @r#" Ok( ParsedTemplate { - content: [ + tokens: [ Content( "Hello ", ), - Expression( - InterpolateExpression { - left_delim: "{{", - wants_output: None, - value: TemplateExpression { - before_ws: " ", - expr: Variable( - "there", - ), - after_ws: " ", - }, - right_delim: "}}", - }, + LeftDelim( + "{{", + ), + Whitespace( + " ", + ), + Ident( + "there", + ), + Whitespace( + " ", + ), + RightDelim( + "}}", ), ], }, @@ -336,24 +360,27 @@ mod tests { #[test] fn parse_interpolate_bad() { - let input = "Hello {{ the2re }}"; + let input = "Hello {{ the2re }} {{ the@re }}"; let output = parse(input); insta::assert_debug_snapshot!(output, @r#" Err( ParseFailure { - source: "Hello {{ the2re }}", + source: "Hello {{ the2re }} {{ the@re }}", errors: [ ParseError { message: Some( - "valid variables are alpha", + "Invalid variable identifier", + ), + help: Some( + "valid variable identifiers are alphanumeric", ), span: Some( SourceSpan { - range: 9..15, + range: 25..28, }, ), - is_fatal: true, + is_fatal: false, }, ], }, diff --git a/src/parser/snapshots/temple__parser__tests__parse_interpolate_bad-2.snap b/src/parser/snapshots/temple__parser__tests__parse_interpolate_bad-2.snap index 1e0c666..de2d41d 100644 --- a/src/parser/snapshots/temple__parser__tests__parse_interpolate_bad-2.snap +++ b/src/parser/snapshots/temple__parser__tests__parse_interpolate_bad-2.snap @@ -2,7 +2,9 @@ source: src/parser/mod.rs expression: error.to_report() --- -error: valid variables are alpha +error: Invalid variable identifier  ╭▸  -1 │ Hello {{ the2re }} - ╰╴ ━━━━━━ +1 │ Hello {{ the2re }} {{ the@re }} + │ ━━━ + │ + ╰ help: valid variable identifiers are alphanumeric