aboutsummaryrefslogtreecommitdiffstats
path: root/askama_parser/src/lib.rs
diff options
context:
space:
mode:
authorLibravatar Guillaume Gomez <guillaume1.gomez@gmail.com>2023-10-23 20:43:45 +0200
committerLibravatar Dirkjan Ochtman <dirkjan@ochtman.nl>2023-10-25 14:30:35 +0200
commit6dae920a451407913ab188fd507a2cfc2cd03018 (patch)
tree21614616db993375a0d67e06ffa74fd1f7c31614 /askama_parser/src/lib.rs
parente0574d3092dc71336ef6a1b6ef2db242aa19732d (diff)
downloadaskama-6dae920a451407913ab188fd507a2cfc2cd03018.tar.gz
askama-6dae920a451407913ab188fd507a2cfc2cd03018.tar.bz2
askama-6dae920a451407913ab188fd507a2cfc2cd03018.zip
Create `ErrorContext` type
Diffstat (limited to 'askama_parser/src/lib.rs')
-rw-r--r--askama_parser/src/lib.rs128
1 files changed, 97 insertions, 31 deletions
diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs
index 78b0807..ba5f6a4 100644
--- a/askama_parser/src/lib.rs
+++ b/askama_parser/src/lib.rs
@@ -1,6 +1,7 @@
#![deny(unreachable_pub)]
#![deny(elided_lifetimes_in_paths)]
+use std::borrow::Cow;
use std::cell::Cell;
use std::{fmt, str};
@@ -9,7 +10,7 @@ use nom::bytes::complete::{escaped, is_not, tag, take_till};
use nom::character::complete::char;
use nom::character::complete::{anychar, digit1};
use nom::combinator::{cut, eof, map, opt, recognize};
-use nom::error::ErrorKind;
+use nom::error::{Error, ErrorKind, FromExternalError};
use nom::multi::many1;
use nom::sequence::{delimited, pair, preceded, terminated, tuple};
use nom::{error_position, AsChar, IResult, InputTakeAtPosition};
@@ -76,14 +77,16 @@ pub struct Ast<'a> {
impl<'a> Ast<'a> {
pub fn from_str(src: &'a str, syntax: &Syntax<'_>) -> Result<Self, ParseError> {
let parse = |i: &'a str| Node::many(i, &State::new(syntax));
- let err = match terminated(parse, cut(eof))(src) {
+ let (input, message) = match terminated(parse, cut(eof))(src) {
Ok(("", nodes)) => return Ok(Self { nodes }),
Ok(_) => unreachable!("eof() is not eof?"),
- Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => err,
+ Err(
+ nom::Err::Error(ErrorContext { input, message, .. })
+ | nom::Err::Failure(ErrorContext { input, message, .. }),
+ ) => (input, message),
Err(nom::Err::Incomplete(_)) => return Err(ParseError("parsing incomplete".into())),
};
- let nom::error::Error { input, .. } = err;
let offset = src.len() - input.len();
let (source_before, source_after) = src.split_at(offset);
@@ -96,7 +99,12 @@ impl<'a> Ast<'a> {
let column = last_line.chars().count();
let msg = format!(
- "problems parsing template source at row {}, column {} near:\n{}",
+ "{}problems parsing template source at row {}, column {} near:\n{}",
+ if let Some(message) = message {
+ format!("error: {message}\n")
+ } else {
+ String::new()
+ },
row + 1,
column,
source_after,
@@ -121,6 +129,55 @@ impl fmt::Display for ParseError {
}
}
+/// This type is used to handle `nom` errors and in particular to add custom error messages.
+/// It used to generate `ParserError`.
+///
+/// It cannot be used to replace `ParseError` because it expects a generic, which would make
+/// `askama`'s users experience less good (since this generic is only needed for `nom`).
+#[derive(Debug)]
+pub(crate) struct ErrorContext<I> {
+ pub(crate) input: I,
+ pub(crate) message: Option<Cow<'static, str>>,
+}
+
+impl<I> nom::error::ParseError<I> for ErrorContext<I> {
+ fn from_error_kind(input: I, _code: ErrorKind) -> Self {
+ Self {
+ input,
+ message: None,
+ }
+ }
+
+ fn append(_: I, _: ErrorKind, other: Self) -> Self {
+ other
+ }
+}
+
+impl<I, E: std::fmt::Display> FromExternalError<I, E> for ErrorContext<I> {
+ fn from_external_error(input: I, _kind: ErrorKind, e: E) -> Self {
+ Self {
+ input,
+ message: Some(Cow::Owned(e.to_string())),
+ }
+ }
+}
+
+impl<I> ErrorContext<I> {
+ pub(crate) fn from_err(error: nom::Err<Error<I>>) -> nom::Err<Self> {
+ match error {
+ nom::Err::Incomplete(i) => nom::Err::Incomplete(i),
+ nom::Err::Failure(Error { input, .. }) => nom::Err::Failure(Self {
+ input,
+ message: None,
+ }),
+ nom::Err::Error(Error { input, .. }) => nom::Err::Error(Self {
+ input,
+ message: None,
+ }),
+ }
+ }
+}
+
fn is_ws(c: char) -> bool {
matches!(c, ' ' | '\t' | '\r' | '\n')
}
@@ -130,16 +187,16 @@ fn not_ws(c: char) -> bool {
}
fn ws<'a, O>(
- inner: impl FnMut(&'a str) -> IResult<&'a str, O>,
-) -> impl FnMut(&'a str) -> IResult<&'a str, O> {
+ inner: impl FnMut(&'a str) -> IResult<&'a str, O, ErrorContext<&'a str>>,
+) -> impl FnMut(&'a str) -> IResult<&'a str, O, ErrorContext<&'a str>> {
delimited(take_till(not_ws), inner, take_till(not_ws))
}
/// Skips input until `end` was found, but does not consume it.
/// Returns tuple that would be returned when parsing `end`.
fn skip_till<'a, O>(
- end: impl FnMut(&'a str) -> IResult<&'a str, O>,
-) -> impl FnMut(&'a str) -> IResult<&'a str, (&'a str, O)> {
+ end: impl FnMut(&'a str) -> IResult<&'a str, O, ErrorContext<&'a str>>,
+) -> impl FnMut(&'a str) -> IResult<&'a str, (&'a str, O), ErrorContext<&'a str>> {
enum Next<O> {
IsEnd(O),
NotEnd(char),
@@ -157,8 +214,10 @@ fn skip_till<'a, O>(
}
}
-fn keyword<'a>(k: &'a str) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str> {
- move |i: &'a str| -> IResult<&'a str, &'a str> {
+fn keyword<'a>(
+ k: &'a str,
+) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str, ErrorContext<&'a str>> {
+ move |i: &'a str| -> IResult<&'a str, &'a str, ErrorContext<&'a str>> {
let (j, v) = identifier(i)?;
if k == v {
Ok((j, v))
@@ -168,15 +227,15 @@ fn keyword<'a>(k: &'a str) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str> {
}
}
-fn identifier(input: &str) -> IResult<&str, &str> {
- fn start(s: &str) -> IResult<&str, &str> {
+fn identifier(input: &str) -> IResult<&str, &str, ErrorContext<&str>> {
+ fn start(s: &str) -> IResult<&str, &str, ErrorContext<&str>> {
s.split_at_position1_complete(
|c| !(c.is_alpha() || c == '_' || c >= '\u{0080}'),
nom::error::ErrorKind::Alpha,
)
}
- fn tail(s: &str) -> IResult<&str, &str> {
+ fn tail(s: &str) -> IResult<&str, &str, ErrorContext<&str>> {
s.split_at_position1_complete(
|c| !(c.is_alphanum() || c == '_' || c >= '\u{0080}'),
nom::error::ErrorKind::Alpha,
@@ -186,11 +245,11 @@ fn identifier(input: &str) -> IResult<&str, &str> {
recognize(pair(start, opt(tail)))(input)
}
-fn bool_lit(i: &str) -> IResult<&str, &str> {
+fn bool_lit(i: &str) -> IResult<&str, &str, ErrorContext<&str>> {
alt((keyword("false"), keyword("true")))(i)
}
-fn num_lit(i: &str) -> IResult<&str, &str> {
+fn num_lit(i: &str) -> IResult<&str, &str, ErrorContext<&str>> {
recognize(tuple((
opt(char('-')),
digit1,
@@ -198,7 +257,7 @@ fn num_lit(i: &str) -> IResult<&str, &str> {
)))(i)
}
-fn str_lit(i: &str) -> IResult<&str, &str> {
+fn str_lit(i: &str) -> IResult<&str, &str, ErrorContext<&str>> {
let (i, s) = delimited(
char('"'),
opt(escaped(is_not("\\\""), '\\', anychar)),
@@ -207,7 +266,7 @@ fn str_lit(i: &str) -> IResult<&str, &str> {
Ok((i, s.unwrap_or_default()))
}
-fn char_lit(i: &str) -> IResult<&str, &str> {
+fn char_lit(i: &str) -> IResult<&str, &str, ErrorContext<&str>> {
let (i, s) = delimited(
char('\''),
opt(escaped(is_not("\\\'"), '\\', anychar)),
@@ -221,7 +280,7 @@ enum PathOrIdentifier<'a> {
Identifier(&'a str),
}
-fn path_or_identifier(i: &str) -> IResult<&str, PathOrIdentifier<'_>> {
+fn path_or_identifier(i: &str) -> IResult<&str, PathOrIdentifier<'_>, ErrorContext<&str>> {
let root = ws(opt(tag("::")));
let tail = opt(many1(preceded(ws(tag("::")), identifier)));
@@ -267,36 +326,40 @@ impl<'a> State<'a> {
}
}
- fn nest<'b>(&self, i: &'b str) -> Result<(), nom::Err<nom::error::Error<&'b str>>> {
- self.level.set(self.level.get().nest(i)?);
- Ok(())
+ fn nest<'b>(&self, i: &'b str) -> IResult<&'b str, (), ErrorContext<&'b str>> {
+ let (_, level) = self.level.get().nest(i)?;
+ self.level.set(level);
+ Ok((i, ()))
}
fn leave(&self) {
self.level.set(self.level.get().leave());
}
- fn tag_block_start<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> {
+ fn tag_block_start<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str, ErrorContext<&'i str>> {
tag(self.syntax.block_start)(i)
}
- fn tag_block_end<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> {
+ fn tag_block_end<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str, ErrorContext<&'i str>> {
tag(self.syntax.block_end)(i)
}
- fn tag_comment_start<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> {
+ fn tag_comment_start<'i>(
+ &self,
+ i: &'i str,
+ ) -> IResult<&'i str, &'i str, ErrorContext<&'i str>> {
tag(self.syntax.comment_start)(i)
}
- fn tag_comment_end<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> {
+ fn tag_comment_end<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str, ErrorContext<&'i str>> {
tag(self.syntax.comment_end)(i)
}
- fn tag_expr_start<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> {
+ fn tag_expr_start<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str, ErrorContext<&'i str>> {
tag(self.syntax.expr_start)(i)
}
- fn tag_expr_end<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> {
+ fn tag_expr_end<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str, ErrorContext<&'i str>> {
tag(self.syntax.expr_end)(i)
}
@@ -340,12 +403,15 @@ impl Default for Syntax<'static> {
pub(crate) struct Level(u8);
impl Level {
- fn nest(self, i: &str) -> Result<Level, nom::Err<nom::error::Error<&str>>> {
+ fn nest(self, i: &str) -> IResult<&str, Level, ErrorContext<&str>> {
if self.0 >= Self::MAX_DEPTH {
- return Err(nom::Err::Failure(error_position!(i, ErrorKind::TooLarge)));
+ return Err(ErrorContext::from_err(nom::Err::Failure(error_position!(
+ i,
+ ErrorKind::TooLarge
+ ))));
}
- Ok(Level(self.0 + 1))
+ Ok((i, Level(self.0 + 1)))
}
fn leave(&self) -> Self {