Skip to content

Commit

Permalink
Proper errors from the parser.
Browse files Browse the repository at this point in the history
Use nom "verbose" error handling to build proper parse errors.
  • Loading branch information
kaj committed Oct 2, 2024
1 parent b66d263 commit a2b0923
Show file tree
Hide file tree
Showing 49 changed files with 285 additions and 279 deletions.
41 changes: 34 additions & 7 deletions rsass/src/parser/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{PResult, Span};
use crate::input::SourcePos;
use nom::Finish;
use nom::{character::complete::one_of, error::VerboseErrorKind, Finish};
use std::fmt;

/// An error encountered when parsing sass.
Expand Down Expand Up @@ -44,15 +44,42 @@ impl ParseError {
}
}

impl From<nom::error::Error<Span<'_>>> for ParseError {
fn from(err: nom::error::Error<Span>) -> Self {
Self::new(
format!("Parse error: {:?}", err.code),
err.input.up_to(&err.input).to_owned(),
)
impl From<nom::error::VerboseError<Span<'_>>> for ParseError {
fn from(value: nom::error::VerboseError<Span<'_>>) -> Self {
let (msg, pos) = find_relevant(&value.errors).unwrap_or_else(|| {
let (pos, kind) = value.errors.first().unwrap();
if pos.is_at_end() {
("expected more input.".to_string(), pos)
} else if let PResult::Ok((_, b)) = one_of(")}]")(*pos) {
(format!("unmatched \"{b}\"."), pos)
} else {
(format!("Parse error: {kind:?}"), pos)
}
});
Self::new(msg, pos.up_to(pos).to_owned())
}
}

fn find_relevant<'a>(
errors: &'a [(Span<'a>, VerboseErrorKind)],
) -> Option<(String, &'a Span<'a>)> {
for (pos, kind) in errors {
match kind {
VerboseErrorKind::Context(ctx) => {
return Some((ctx.to_string(), pos));
}
VerboseErrorKind::Char(ch) => {
return Some((
format!("expected {:?}.", ch.to_string()),
pos,
));
}
VerboseErrorKind::Nom(_) => (), // Try the next one!
}
}
None
}

impl fmt::Display for ParseError {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
writeln!(out, "{}", self.msg)?;
Expand Down
31 changes: 18 additions & 13 deletions rsass/src/parser/formalargs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@ use super::value::space_list;
use super::{PResult, Span};
use crate::sass::{CallArgs, FormalArgs, Name};
use nom::bytes::complete::tag;
use nom::combinator::{map, map_res, opt};
use nom::character::complete::char;
use nom::combinator::{cut, map, map_res, opt};
use nom::error::context;
use nom::multi::separated_list0;
use nom::sequence::{delimited, pair, preceded, terminated};

pub fn formal_args(input: Span) -> PResult<FormalArgs> {
let (input, _) = terminated(tag("("), opt_spacelike)(input)?;
let (input, _) = terminated(char('('), opt_spacelike)(input)?;
let (input, v) = separated_list0(
preceded(tag(","), opt_spacelike),
map(
pair(
delimited(tag("$"), name, opt_spacelike),
opt(delimited(
terminated(tag(":"), opt_spacelike),
space_list,
cut(context("Expected expression.", space_list)),
opt_spacelike,
)),
),
Expand All @@ -26,7 +28,7 @@ pub fn formal_args(input: Span) -> PResult<FormalArgs> {
)(input)?;
let (input, _) = terminated(opt(tag(",")), opt_spacelike)(input)?;
let (input, va) = terminated(opt(tag("...")), opt_spacelike)(input)?;
let (input, _) = tag(")")(input)?;
let (input, _) = char(')')(input)?;
Ok((
input,
if va.is_none() {
Expand All @@ -39,28 +41,31 @@ pub fn formal_args(input: Span) -> PResult<FormalArgs> {

pub fn call_args(input: Span) -> PResult<CallArgs> {
delimited(
terminated(tag("("), opt_spacelike),
terminated(char('('), opt_spacelike),
map_res(
pair(
separated_list0(
terminated(tag(","), opt_spacelike),
pair(
opt(delimited(
tag("$"),
map(name, Name::from),
opt(map(
delimited(
ignore_comments,
tag(":"),
opt_spacelike,
tag("$"),
name,
delimited(
ignore_comments,
char(':'),
opt_spacelike,
),
),
Name::from,
)),
terminated(space_list, opt_spacelike),
),
),
opt(terminated(tag(","), opt_spacelike)),
opt(terminated(char(','), opt_spacelike)),
),
|(args, trail)| CallArgs::new(args, trail.is_some()),
),
tag(")"),
cut(char(')')),
)(input)
}
53 changes: 30 additions & 23 deletions rsass/src/parser/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use super::{media, position, PResult, Span};
use crate::sass::{Expose, Item, Name, SassString, UseAs, Value};
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::combinator::{map, opt, value};
use nom::character::complete::char;
use nom::combinator::{cut, map, opt, value};
use nom::error::context;
use nom::multi::{separated_list0, separated_list1};
use nom::sequence::{delimited, pair, preceded, terminated, tuple};
use std::collections::BTreeSet;
Expand Down Expand Up @@ -43,12 +45,15 @@ pub fn use2<'a>(start: Span, input: Span<'a>) -> PResult<'a, Item> {
map(
terminated(
tuple((
terminated(quoted_sass_string, opt_spacelike),
context(
"Expected string.",
terminated(quoted_sass_string, ignore_comments),
),
opt(preceded(
terminated(tag("with"), opt_spacelike),
terminated(tag("with"), ignore_comments),
with_arg,
)),
opt(preceded(terminated(tag("as"), opt_spacelike), as_arg)),
opt(preceded(terminated(tag("as"), ignore_comments), as_arg)),
position,
)),
semi_or_end,
Expand All @@ -65,24 +70,28 @@ pub fn use2<'a>(start: Span, input: Span<'a>) -> PResult<'a, Item> {
}

pub fn forward2<'a>(start: Span, input: Span<'a>) -> PResult<'a, Item> {
let (mut end, path) =
terminated(quoted_sass_string, opt_spacelike)(input)?;
let (mut end, path) = context(
"Expected string.",
terminated(quoted_sass_string, opt_spacelike),
)(input)?;
let mut found_as = None;
let mut expose = Expose::All;
let mut found_with = None;
while let Ok((rest, arg)) = terminated(name, opt_spacelike)(end) {
while let Ok((rest, arg)) =
delimited(ignore_comments, name, ignore_comments)(end)
{
end = match arg.as_ref() {
"as" if found_as.is_none() => {
let (i, a) = as_arg(rest)?;
"as" if found_as.is_none() && found_with.is_none() => {
let (i, a) = context("Expected identifier.", as_arg)(rest)?;
found_as = Some(a);
i
}
"hide" if expose == Expose::All => {
"hide" if expose == Expose::All && found_with.is_none() => {
let (i, (funs, vars)) = exposed_names(rest)?;
expose = Expose::Hide(funs, vars);
i
}
"show" if expose == Expose::All => {
"show" if expose == Expose::All && found_with.is_none() => {
let (i, (funs, vars)) = exposed_names(rest)?;
expose = Expose::Show(funs, vars);
i
Expand All @@ -92,12 +101,7 @@ pub fn forward2<'a>(start: Span, input: Span<'a>) -> PResult<'a, Item> {
found_with = Some(w);
i
}
_ => {
return Err(nom::Err::Error(nom::error::Error::new(
end,
nom::error::ErrorKind::MapRes,
)));
}
_ => break,
};
}
let (rest, ()) = semi_or_end(end)?;
Expand All @@ -119,7 +123,10 @@ fn exposed_names(input: Span) -> PResult<(BTreeSet<Name>, BTreeSet<Name>)> {
terminated(tag(","), opt_spacelike),
pair(
map(opt(tag("$")), |v| v.is_some()),
map(terminated(name, opt_spacelike), Name::from),
cut(context(
"Expected variable, mixin, or function name",
map(terminated(name, opt_spacelike), Name::from),
)),
),
),
|items| {
Expand Down Expand Up @@ -148,22 +155,22 @@ fn as_arg(input: Span) -> PResult<UseAs> {

fn with_arg(input: Span) -> PResult<Vec<(Name, Value, bool)>> {
delimited(
terminated(tag("("), opt_spacelike),
terminated(char('('), ignore_comments),
separated_list0(
comma,
tuple((
delimited(
tag("$"),
map(name, Name::from),
delimited(opt_spacelike, tag(":"), opt_spacelike),
delimited(ignore_comments, char(':'), ignore_comments),
),
terminated(space_list, opt_spacelike),
terminated(space_list, ignore_comments),
map(opt(terminated(tag("!default"), opt_spacelike)), |o| {
o.is_some()
}),
)),
),
delimited(opt(comma), tag(")"), opt_spacelike),
delimited(opt(comma), char(')'), opt_spacelike),
)(input)
}

Expand All @@ -172,5 +179,5 @@ fn quoted_sass_string(input: Span) -> PResult<SassString> {
}

fn comma(input: Span) -> PResult<()> {
map(terminated(tag(","), ignore_comments), |_| ())(input)
delimited(ignore_comments, map(tag(","), |_| ()), ignore_comments)(input)
}
4 changes: 2 additions & 2 deletions rsass/src/parser/media.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use super::strings::{sass_string_dq, sass_string_sq};
use super::util::{ignore_comments, opt_spacelike, semi_or_end};
use super::value::{
self, any_additive_expr, any_product, bracket_list, dictionary,
function_call_or_string, variable,
function_call_or_string_rulearg, variable,
};
use super::{body_block, list_or_single, PResult};
use crate::sass::{BinOp, Item, Value};
Expand Down Expand Up @@ -53,7 +53,7 @@ pub fn args(input: Span) -> PResult<Value> {
bracket_list,
into(value::numeric),
variable,
map(function_call_or_string, |s| match s {
map(function_call_or_string_rulearg, |s| match s {
Value::Literal(s) => Value::Literal({
let lower = s
.single_raw()
Expand Down
Loading

0 comments on commit a2b0923

Please sign in to comment.