Merge pull request #257 from Kijewski/pr-smaller-span

parser: shrink the size of `WithSpan` to one register
This commit is contained in:
René Kijewski 2024-11-24 22:47:10 +01:00 committed by GitHub
commit 87f611e871
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 217 additions and 129 deletions

View File

@ -10,8 +10,8 @@ use parser::node::{
Whitespace, Ws,
};
use parser::{
CharLit, CharPrefix, Expr, Filter, FloatKind, IntKind, Node, Num, StrLit, StrPrefix, Target,
WithSpan,
CharLit, CharPrefix, Expr, Filter, FloatKind, IntKind, Node, Num, Span, StrLit, StrPrefix,
Target, WithSpan,
};
use rustc_hash::FxBuildHasher;
@ -263,7 +263,7 @@ impl<'a, 'h> Generator<'a, 'h> {
}
Node::BlockDef(ref b) => {
size_hint +=
self.write_block(ctx, buf, Some(b.name), Ws(b.ws1.0, b.ws2.1), b)?;
self.write_block(ctx, buf, Some(b.name), Ws(b.ws1.0, b.ws2.1), b.span())?;
}
Node::Include(ref i) => {
size_hint += self.handle_include(ctx, buf, i)?;
@ -276,9 +276,10 @@ impl<'a, 'h> Generator<'a, 'h> {
}
Node::Macro(ref m) => {
if level != AstLevel::Top {
return Err(
ctx.generate_error("macro blocks only allowed at the top level", m)
);
return Err(ctx.generate_error(
"macro blocks only allowed at the top level",
m.span(),
));
}
self.flush_ws(m.ws1);
self.prepare_ws(m.ws2);
@ -290,17 +291,19 @@ impl<'a, 'h> Generator<'a, 'h> {
}
Node::Import(ref i) => {
if level != AstLevel::Top {
return Err(
ctx.generate_error("import blocks only allowed at the top level", i)
);
return Err(ctx.generate_error(
"import blocks only allowed at the top level",
i.span(),
));
}
self.handle_ws(i.ws);
}
Node::Extends(ref e) => {
if level != AstLevel::Top {
return Err(
ctx.generate_error("extend blocks only allowed at the top level", e)
);
return Err(ctx.generate_error(
"extend blocks only allowed at the top level",
e.span(),
));
}
// No whitespace handling: child template top-level is not used,
// except for the blocks defined in it.
@ -746,26 +749,26 @@ impl<'a, 'h> Generator<'a, 'h> {
ref args,
} = **call;
if name == "super" {
return self.write_block(ctx, buf, None, ws, call);
return self.write_block(ctx, buf, None, ws, call.span());
}
let (def, own_ctx) = if let Some(s) = scope {
let path = ctx.imports.get(s).ok_or_else(|| {
ctx.generate_error(format_args!("no import found for scope {s:?}"), call)
ctx.generate_error(format_args!("no import found for scope {s:?}"), call.span())
})?;
let mctx = self.contexts.get(path).ok_or_else(|| {
ctx.generate_error(format_args!("context for {path:?} not found"), call)
ctx.generate_error(format_args!("context for {path:?} not found"), call.span())
})?;
let def = mctx.macros.get(name).ok_or_else(|| {
ctx.generate_error(
format_args!("macro {name:?} not found in scope {s:?}"),
call,
call.span(),
)
})?;
(def, mctx)
} else {
let def = ctx.macros.get(name).ok_or_else(|| {
ctx.generate_error(format_args!("macro {name:?} not found"), call)
ctx.generate_error(format_args!("macro {name:?} not found"), call.span())
})?;
(def, ctx)
};
@ -790,7 +793,7 @@ impl<'a, 'h> Generator<'a, 'h> {
if !def.args.iter().any(|(arg, _)| arg == arg_name) {
return Err(ctx.generate_error(
format_args!("no argument named `{arg_name}` in macro {name:?}"),
call,
call.span(),
));
}
named_arguments.insert(arg_name, (index, arg));
@ -824,7 +827,7 @@ impl<'a, 'h> Generator<'a, 'h> {
"cannot have unnamed argument (`{arg}`) after named argument \
in call to macro {name:?}"
),
call,
call.span(),
));
}
arg_expr
@ -833,14 +836,14 @@ impl<'a, 'h> Generator<'a, 'h> {
let Expr::NamedArgument(name, _) = **arg_expr else { unreachable!() };
return Err(ctx.generate_error(
format_args!("`{name}` is passed more than once"),
call,
call.span(),
));
}
_ => {
if let Some(default_value) = default_value {
default_value
} else {
return Err(ctx.generate_error(format_args!("missing `{arg}` argument"), call));
return Err(ctx.generate_error(format_args!("missing `{arg}` argument"), call.span()));
}
}
}
@ -932,7 +935,7 @@ impl<'a, 'h> Generator<'a, 'h> {
&mut filter_buf,
filter.filters.name,
&filter.filters.arguments,
filter,
filter.span(),
)?;
let filter_buf = match display_wrap {
DisplayWrap::Wrapped => filter_buf.into_string(),
@ -961,7 +964,9 @@ impl<'a, 'h> Generator<'a, 'h> {
) -> Result<usize, CompileError> {
self.flush_ws(i.ws);
self.write_buf_writable(ctx, buf)?;
let file_info = ctx.path.map(|path| FileInfo::of(i, path, ctx.parsed));
let file_info = ctx
.path
.map(|path| FileInfo::of(i.span(), path, ctx.parsed));
let path = self
.input
.config
@ -1006,11 +1011,11 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(size_hint)
}
fn is_shadowing_variable<T>(
fn is_shadowing_variable(
&self,
ctx: &Context<'_>,
var: &Target<'a>,
l: &WithSpan<'_, T>,
l: Span<'_>,
) -> Result<bool, CompileError> {
match var {
Target::Name(name) => {
@ -1078,7 +1083,7 @@ impl<'a, 'h> Generator<'a, 'h> {
let mut expr_buf = Buffer::new();
self.visit_expr(ctx, &mut expr_buf, val)?;
let shadowed = self.is_shadowing_variable(ctx, &l.var, l)?;
let shadowed = self.is_shadowing_variable(ctx, &l.var, l.span())?;
if shadowed {
// Need to flush the buffer if the variable is being shadowed,
// to ensure the old variable is used.
@ -1104,13 +1109,13 @@ impl<'a, 'h> Generator<'a, 'h> {
// If `name` is `Some`, this is a call to a block definition, and we have to find
// the first block for that name from the ancestry chain. If name is `None`, this
// is from a `super()` call, and we can get the name from `self.super_block`.
fn write_block<T>(
fn write_block(
&mut self,
ctx: &Context<'a>,
buf: &mut Buffer,
name: Option<&'a str>,
outer: Ws,
node: &WithSpan<'_, T>,
node: Span<'_>,
) -> Result<usize, CompileError> {
if self.is_in_filter_block > 0 {
return Err(ctx.generate_error("cannot have a block inside a filter block", node));
@ -1384,7 +1389,7 @@ impl<'a, 'h> Generator<'a, 'h> {
Expr::Filter(Filter {
name,
ref arguments,
}) => self.visit_filter(ctx, buf, name, arguments, expr)?,
}) => self.visit_filter(ctx, buf, name, arguments, expr.span())?,
Expr::Unary(op, ref inner) => self.visit_unary(ctx, buf, op, inner)?,
Expr::BinOp(op, ref left, ref right) => self.visit_binop(ctx, buf, op, left, right)?,
Expr::Range(op, ref left, ref right) => {
@ -1507,13 +1512,13 @@ impl<'a, 'h> Generator<'a, 'h> {
DisplayWrap::Unwrapped
}
fn visit_filter<T>(
fn visit_filter(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
name: &str,
args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>,
node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
let filter = match name {
"deref" => Self::_visit_deref_filter,
@ -1534,13 +1539,13 @@ impl<'a, 'h> Generator<'a, 'h> {
filter(self, ctx, buf, name, args, node)
}
fn _visit_custom_filter<T>(
fn _visit_custom_filter(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
name: &str,
args: &[WithSpan<'_, Expr<'_>>],
_node: &WithSpan<'_, T>,
_node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
buf.write(format_args!("filters::{name}("));
self._visit_args(ctx, buf, args)?;
@ -1548,13 +1553,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Unwrapped)
}
fn _visit_builtin_filter<T>(
fn _visit_builtin_filter(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
name: &str,
args: &[WithSpan<'_, Expr<'_>>],
_node: &WithSpan<'_, T>,
_node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
buf.write(format_args!("rinja::filters::{name}("));
self._visit_args(ctx, buf, args)?;
@ -1562,13 +1567,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Unwrapped)
}
fn _visit_urlencode<T>(
fn _visit_urlencode(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
name: &str,
args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>,
node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
if cfg!(not(feature = "urlencode")) {
return Err(ctx.generate_error(
@ -1586,13 +1591,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Unwrapped)
}
fn _visit_humansize<T>(
fn _visit_humansize(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
name: &str,
args: &[WithSpan<'_, Expr<'_>>],
_node: &WithSpan<'_, T>,
_node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
// All filters return numbers, and any default formatted number is HTML safe.
buf.write(format_args!(
@ -1604,28 +1609,24 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Unwrapped)
}
fn _visit_pluralize_filter<T>(
fn _visit_pluralize_filter(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
_name: &str,
args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>,
node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
const SINGULAR: &WithSpan<'static, Expr<'static>> = &WithSpan::new(
Expr::StrLit(StrLit {
const SINGULAR: &WithSpan<'static, Expr<'static>> =
&WithSpan::new_without_span(Expr::StrLit(StrLit {
prefix: None,
content: "",
}),
"",
);
const PLURAL: &WithSpan<'static, Expr<'static>> = &WithSpan::new(
Expr::StrLit(StrLit {
}));
const PLURAL: &WithSpan<'static, Expr<'static>> =
&WithSpan::new_without_span(Expr::StrLit(StrLit {
prefix: None,
content: "s",
}),
"",
);
}));
let (count, sg, pl) = match args {
[count] => (count, SINGULAR, PLURAL),
@ -1652,13 +1653,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Wrapped)
}
fn _visit_linebreaks_filter<T>(
fn _visit_linebreaks_filter(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
name: &str,
args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>,
node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
if args.len() != 1 {
return Err(ctx.generate_error(
@ -1676,13 +1677,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Unwrapped)
}
fn _visit_ref_filter<T>(
fn _visit_ref_filter(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
_name: &str,
args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>,
node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
let arg = match args {
[arg] => arg,
@ -1693,13 +1694,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Unwrapped)
}
fn _visit_deref_filter<T>(
fn _visit_deref_filter(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
_name: &str,
args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>,
node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
let arg = match args {
[arg] => arg,
@ -1710,13 +1711,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Unwrapped)
}
fn _visit_json_filter<T>(
fn _visit_json_filter(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
_name: &str,
args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>,
node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
if cfg!(not(feature = "serde_json")) {
return Err(ctx.generate_error(
@ -1737,13 +1738,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Unwrapped)
}
fn _visit_safe_filter<T>(
fn _visit_safe_filter(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
_name: &str,
args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>,
node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
if args.len() != 1 {
return Err(ctx.generate_error("unexpected argument(s) in `safe` filter", node));
@ -1754,13 +1755,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Wrapped)
}
fn _visit_escape_filter<T>(
fn _visit_escape_filter(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
_name: &str,
args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>,
node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
if args.len() > 2 {
return Err(ctx.generate_error("only two arguments allowed to escape filter", node));
@ -1777,7 +1778,7 @@ impl<'a, 'h> Generator<'a, 'h> {
format_args!(
"invalid escaper `b{content:?}`. Expected a string, found a {kind}"
),
&args[1],
args[1].span(),
));
}
Some(content)
@ -1815,13 +1816,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Wrapped)
}
fn _visit_format_filter<T>(
fn _visit_format_filter(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
_name: &str,
args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>,
node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
if !args.is_empty() {
if let Expr::StrLit(ref fmt) = *args[0] {
@ -1838,13 +1839,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Err(ctx.generate_error(r#"use filter format like `"a={} b={}"|format(a, b)`"#, node))
}
fn _visit_fmt_filter<T>(
fn _visit_fmt_filter(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
_name: &str,
args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>,
node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
if let [_, arg2] = args {
if let Expr::StrLit(ref fmt) = **arg2 {
@ -1860,13 +1861,13 @@ impl<'a, 'h> Generator<'a, 'h> {
}
// Force type coercion on first argument to `join` filter (see #39).
fn _visit_join_filter<T>(
fn _visit_join_filter(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
_name: &str,
args: &[WithSpan<'_, Expr<'_>>],
_node: &WithSpan<'_, T>,
_node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
buf.write("rinja::filters::join((&");
for (i, arg) in args.iter().enumerate() {
@ -1986,7 +1987,7 @@ impl<'a, 'h> Generator<'a, 'h> {
buf.write("_loop_item.last");
return Ok(DisplayWrap::Unwrapped);
} else {
return Err(ctx.generate_error("unknown loop variable", obj));
return Err(ctx.generate_error("unknown loop variable", obj.span()));
}
}
}
@ -2022,9 +2023,10 @@ impl<'a, 'h> Generator<'a, 'h> {
"cycle" => match args {
[arg] => {
if matches!(**arg, Expr::Array(ref arr) if arr.is_empty()) {
return Err(
ctx.generate_error("loop.cycle(…) cannot use an empty array", arg)
);
return Err(ctx.generate_error(
"loop.cycle(…) cannot use an empty array",
arg.span(),
));
}
buf.write(
"\
@ -2044,14 +2046,15 @@ impl<'a, 'h> Generator<'a, 'h> {
);
}
_ => {
return Err(
ctx.generate_error("loop.cycle(…) cannot use an empty array", left)
);
return Err(ctx.generate_error(
"loop.cycle(…) cannot use an empty array",
left.span(),
));
}
},
s => {
return Err(
ctx.generate_error(format_args!("unknown loop method: {s:?}"), left)
ctx.generate_error(format_args!("unknown loop method: {s:?}"), left.span())
);
}
},
@ -2421,7 +2424,7 @@ fn macro_call_ensure_arg_count(
if expected_args != 1 { "s" } else { "" },
call.args.len(),
),
call,
call.span(),
))
}

View File

@ -4,7 +4,7 @@ use std::path::Path;
use std::sync::Arc;
use parser::node::{BlockDef, Macro};
use parser::{Node, Parsed, WithSpan};
use parser::{Node, Parsed, Span};
use rustc_hash::FxBuildHasher;
use crate::config::Config;
@ -80,29 +80,29 @@ impl Context<'_> {
for n in nodes {
match n {
Node::Extends(e) => {
ensure_top(top, e, path, parsed, "extends")?;
ensure_top(top, e.span(), path, parsed, "extends")?;
if extends.is_some() {
return Err(CompileError::new(
"multiple extend blocks found",
Some(FileInfo::of(e, path, parsed)),
Some(FileInfo::of(e.span(), path, parsed)),
));
}
extends = Some(config.find_template(
e.path,
Some(path),
Some(FileInfo::of(e, path, parsed)),
Some(FileInfo::of(e.span(), path, parsed)),
)?);
}
Node::Macro(m) => {
ensure_top(top, m, path, parsed, "macro")?;
ensure_top(top, m.span(), path, parsed, "macro")?;
macros.insert(m.name, &**m);
}
Node::Import(import) => {
ensure_top(top, import, path, parsed, "import")?;
ensure_top(top, import.span(), path, parsed, "import")?;
let path = config.find_template(
import.path,
Some(path),
Some(FileInfo::of(import, path, parsed)),
Some(FileInfo::of(import.span(), path, parsed)),
)?;
imports.insert(import.scope, path);
}
@ -141,11 +141,7 @@ impl Context<'_> {
})
}
pub(crate) fn generate_error<T>(
&self,
msg: impl fmt::Display,
node: &WithSpan<'_, T>,
) -> CompileError {
pub(crate) fn generate_error(&self, msg: impl fmt::Display, node: Span<'_>) -> CompileError {
CompileError::new(
msg,
self.path.map(|path| FileInfo::of(node, path, self.parsed)),
@ -153,9 +149,9 @@ impl Context<'_> {
}
}
fn ensure_top<T>(
fn ensure_top(
top: bool,
node: &WithSpan<'_, T>,
node: Span<'_>,
path: &Path,
parsed: &Parsed,
kind: &str,

View File

@ -185,9 +185,14 @@ impl TemplateInput<'_> {
// Add a dummy entry to `map` in order to prevent adding `path`
// multiple times to `check`.
let new_path = e.key();
let source = parsed.source();
let source = get_template_source(
new_path,
Some((&path, parsed.source(), n.span())),
Some((
&path,
source,
n.span().as_suffix_of(source).unwrap_or_default(),
)),
)?;
check.push((new_path.clone(), source, Some(new_path.clone())));
e.insert(Arc::default());
@ -200,7 +205,7 @@ impl TemplateInput<'_> {
let extends = self.config.find_template(
extends.path,
Some(&path),
Some(FileInfo::of(extends, &path, &parsed)),
Some(FileInfo::of(extends.span(), &path, &parsed)),
)?;
let dependency_path = (path.clone(), extends.clone());
if path == extends {
@ -220,7 +225,7 @@ impl TemplateInput<'_> {
let import = self.config.find_template(
import.path,
Some(&path),
Some(FileInfo::of(import, &path, &parsed)),
Some(FileInfo::of(import.span(), &path, &parsed)),
)?;
add_to_check(import)?;
}
@ -231,7 +236,7 @@ impl TemplateInput<'_> {
let include = self.config.find_template(
include.path,
Some(&path),
Some(FileInfo::of(include, &path, &parsed)),
Some(FileInfo::of(include.span(), &path, &parsed)),
)?;
add_to_check(include)?;
}

View File

@ -23,7 +23,7 @@ use generator::template_to_string;
use heritage::{Context, Heritage};
use input::{Print, TemplateArgs, TemplateInput};
use integration::Buffer;
use parser::{Parsed, WithSpan, strip_common};
use parser::{Parsed, strip_common};
#[cfg(not(feature = "__standalone"))]
use proc_macro::TokenStream as TokenStream12;
#[cfg(feature = "__standalone")]
@ -294,11 +294,12 @@ impl<'a> FileInfo<'a> {
}
}
fn of<T>(node: &WithSpan<'a, T>, path: &'a Path, parsed: &'a Parsed) -> Self {
fn of(node: parser::Span<'a>, path: &'a Path, parsed: &'a Parsed) -> Self {
let source = parsed.source();
Self {
path,
source: Some(parsed.source()),
node_source: Some(node.span()),
source: Some(source),
node_source: node.as_suffix_of(source),
}
}
}

View File

@ -10,9 +10,9 @@ use winnow::combinator::{
use winnow::error::{ErrorKind, ParserError as _};
use crate::{
CharLit, ErrorContext, Level, Num, ParseErr, ParseResult, PathOrIdentifier, StrLit, WithSpan,
char_lit, filter, identifier, keyword, num_lit, path_or_identifier, skip_ws0, skip_ws1,
str_lit, ws,
CharLit, ErrorContext, Level, Num, ParseErr, ParseResult, PathOrIdentifier, Span, StrLit,
WithSpan, char_lit, filter, identifier, keyword, num_lit, path_or_identifier, skip_ws0,
skip_ws1, str_lit, ws,
};
macro_rules! expr_prec_layer {
@ -217,14 +217,14 @@ impl<'a> Expr<'a> {
allow_underscore: bool,
) -> ParseResult<'a, WithSpan<'a, Self>> {
let (_, level) = level.nest(i)?;
let start = i;
let start = Span::from(i);
let range_right =
move |i| (ws(alt(("..=", ".."))), opt(move |i| Self::or(i, level))).parse_next(i);
let (i, expr) = alt((
range_right.map(|(op, right)| {
range_right.map(move |(op, right)| {
WithSpan::new(Self::Range(op, None, right.map(Box::new)), start)
}),
(move |i| Self::or(i, level), opt(range_right)).map(|(left, right)| match right {
(move |i| Self::or(i, level), opt(range_right)).map(move |(left, right)| match right {
Some((op, right)) => WithSpan::new(
Self::Range(op, Some(Box::new(left)), right.map(Box::new)),
start,

View File

@ -114,11 +114,11 @@ impl<'a> Ast<'a> {
Ok(("", nodes)) => Ok(Self { nodes }),
Ok(_) | Err(winnow::error::ErrMode::Incomplete(_)) => unreachable!(),
Err(
winnow::error::ErrMode::Backtrack(ErrorContext { input, message, .. })
| winnow::error::ErrMode::Cut(ErrorContext { input, message, .. }),
winnow::error::ErrMode::Backtrack(ErrorContext { span, message, .. })
| winnow::error::ErrMode::Cut(ErrorContext { span, message, .. }),
) => Err(ParseError {
message,
offset: src.len() - input.len(),
offset: span.offset_from(src).unwrap_or_default(),
file_path,
}),
}
@ -134,19 +134,76 @@ impl<'a> Ast<'a> {
/// in the code generation.
pub struct WithSpan<'a, T> {
inner: T,
span: &'a str,
span: Span<'a>,
}
/// An location in `&'a str`
#[derive(Debug, Clone, Copy)]
pub struct Span<'a>(&'a [u8; 0]);
impl Default for Span<'static> {
#[inline]
fn default() -> Self {
Self::empty()
}
}
impl<'a> Span<'a> {
#[inline]
pub const fn empty() -> Self {
Self(&[])
}
pub fn offset_from(self, start: &'a str) -> Option<usize> {
let start_range = start.as_bytes().as_ptr_range();
let this_ptr = self.0.as_slice().as_ptr();
match start_range.contains(&this_ptr) {
// SAFETY: we just checked that `this_ptr` is inside `start_range`
true => Some(unsafe { this_ptr.offset_from(start_range.start) as usize }),
false => None,
}
}
pub fn as_suffix_of(self, start: &'a str) -> Option<&'a str> {
let offset = self.offset_from(start)?;
match start.is_char_boundary(offset) {
true => Some(&start[offset..]),
false => None,
}
}
}
impl<'a> From<&'a str> for Span<'a> {
#[inline]
fn from(value: &'a str) -> Self {
Self(value[..0].as_bytes().try_into().unwrap())
}
}
impl<'a, T> WithSpan<'a, T> {
pub const fn new(inner: T, span: &'a str) -> Self {
Self { inner, span }
#[inline]
pub fn new(inner: T, span: impl Into<Span<'a>>) -> Self {
Self {
inner,
span: span.into(),
}
}
pub fn span(&self) -> &'a str {
#[inline]
pub const fn new_without_span(inner: T) -> Self {
Self {
inner,
span: Span::empty(),
}
}
#[inline]
pub fn span(&self) -> Span<'a> {
self.span
}
pub fn deconstruct(self) -> (T, &'a str) {
#[inline]
pub fn deconstruct(self) -> (T, Span<'a>) {
let Self { inner, span } = self;
(inner, span)
}
@ -229,18 +286,18 @@ pub(crate) type ParseResult<'a, T = &'a str> = Result<(&'a str, T), ParseErr<'a>
/// `rinja`'s users experience less good (since this generic is only needed for `nom`).
#[derive(Debug)]
pub(crate) struct ErrorContext<'a> {
pub(crate) input: &'a str,
pub(crate) span: Span<'a>,
pub(crate) message: Option<Cow<'static, str>>,
}
impl<'a> ErrorContext<'a> {
fn unclosed(kind: &str, tag: &str, i: &'a str) -> Self {
Self::new(format!("unclosed {kind}, missing {tag:?}"), i)
fn unclosed(kind: &str, tag: &str, span: impl Into<Span<'a>>) -> Self {
Self::new(format!("unclosed {kind}, missing {tag:?}"), span)
}
fn new(message: impl Into<Cow<'static, str>>, input: &'a str) -> Self {
fn new(message: impl Into<Cow<'static, str>>, span: impl Into<Span<'a>>) -> Self {
Self {
input,
span: span.into(),
message: Some(message.into()),
}
}
@ -249,7 +306,7 @@ impl<'a> ErrorContext<'a> {
impl<'a> winnow::error::ParserError<&'a str> for ErrorContext<'a> {
fn from_error_kind(input: &'a str, _code: ErrorKind) -> Self {
Self {
input,
span: input.into(),
message: None,
}
}
@ -262,7 +319,7 @@ impl<'a> winnow::error::ParserError<&'a str> for ErrorContext<'a> {
impl<'a, E: std::fmt::Display> FromExternalError<&'a str, E> for ErrorContext<'a> {
fn from_external_error(input: &'a str, _kind: ErrorKind, e: E) -> Self {
Self {
input,
span: input.into(),
message: Some(Cow::Owned(e.to_string())),
}
}
@ -1213,3 +1270,11 @@ mod test {
assert!(str_lit.parse_next(r#"d"hello""#).is_err());
}
}
#[test]
fn assert_span_size() {
assert_eq!(
std::mem::size_of::<Span<'static>>(),
std::mem::size_of::<*const ()>()
);
}

View File

@ -9,8 +9,8 @@ use winnow::token::{any, tag};
use crate::memchr_splitter::{Splitter1, Splitter2, Splitter3};
use crate::{
ErrorContext, Expr, Filter, ParseResult, State, Target, WithSpan, filter, identifier, keyword,
skip_till, skip_ws0, str_lit_without_prefix, ws,
ErrorContext, Expr, Filter, ParseResult, Span, State, Target, WithSpan, filter, identifier,
keyword, skip_till, skip_ws0, str_lit_without_prefix, ws,
};
#[derive(Debug, PartialEq)]
@ -43,7 +43,9 @@ impl<'a> Node<'a> {
&err
{
if err.message.is_none() {
opt(|i| unexpected_tag(i, s)).parse_next(err.input)?;
if let Some(span) = err.span.as_suffix_of(i) {
opt(|i| unexpected_tag(i, s)).parse_next(span)?;
}
}
}
return Err(err);
@ -184,7 +186,7 @@ impl<'a> Node<'a> {
}
#[must_use]
pub fn span(&self) -> &str {
pub fn span(&self) -> Span<'a> {
match self {
Self::Lit(span) => span.span,
Self::Comment(span) => span.span,
@ -218,7 +220,9 @@ fn cut_node<'a, O>(
&result
{
if err.message.is_none() {
opt(|i| unexpected_raw_tag(kind, i)).parse_next(err.input)?;
if let Some(span) = err.span.as_suffix_of(i) {
opt(|i| unexpected_raw_tag(kind, i)).parse_next(span)?;
}
}
}
result

View File

@ -1,9 +1,14 @@
use crate::node::{Lit, Whitespace, Ws};
use crate::{Ast, Expr, Filter, InnerSyntax, Node, Num, StrLit, Syntax, SyntaxBuilder, WithSpan};
use crate::{
Ast, Expr, Filter, InnerSyntax, Node, Num, Span, StrLit, Syntax, SyntaxBuilder, WithSpan,
};
impl<T> WithSpan<'static, T> {
fn no_span(inner: T) -> Self {
Self { inner, span: "" }
Self {
inner,
span: Span::default(),
}
}
}
@ -1122,3 +1127,12 @@ fn extends_with_whitespace_control() {
}
}
}
#[test]
fn fuzzed_span_is_not_substring_of_source() {
let _: Result<Ast<'_>, crate::ParseError> = Ast::from_str(
include_str!("../tests/fuzzed_span_is_not_substring_of_source.bin"),
None,
&Syntax::default(),
);
}