Skip to content

Commit

Permalink
improved parser error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
timbod7 committed Dec 7, 2022
1 parent 0805b16 commit e43f444
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 62 deletions.
171 changes: 111 additions & 60 deletions rust/compiler/src/parser/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::adlgen::sys::adlast2 as adlast;
use crate::adlgen::sys::adlast2::Spanned;
use anyhow::anyhow;
use std::collections::HashMap;
use std::{collections::HashMap, iter::repeat};

use crate::adlrt::custom::sys::types::map::Map;
use crate::adlrt::custom::sys::types::maybe::Maybe;
Expand All @@ -12,7 +11,7 @@ use nom::{
character::complete::{alpha1, alphanumeric1},
character::complete::{digit1, satisfy},
combinator::{cut, map, opt, recognize, value},
error::{context, VerboseError},
error::{context, VerboseError, VerboseErrorKind},
multi::{many0, many0_count, separated_list0},
number::complete::double,
sequence::{delimited, pair, preceded, terminated},
Expand All @@ -24,7 +23,7 @@ use nom_locate::{position, LocatedSpan};
#[cfg(test)]
mod tests;

type Res<T, U> = IResult<T, U, VerboseError<T>>;
type Res<I, T> = IResult<I, T, VerboseError<I>>;

type Input<'a> = LocatedSpan<&'a str>;

Expand Down Expand Up @@ -162,8 +161,11 @@ pub fn scoped_name(i: Input) -> Res<Input, adlast::ScopedName> {
};
Ok((i, scoped_name))
}

pub fn raw_module(i: Input) -> Res<Input, RawModule> {
context("module", raw_module0)(i)
}

pub fn raw_module0(i: Input) -> Res<Input, RawModule> {
let (i, annotations) = many0(prefix_annotation)(i)?;
let (i, _) = ws(tag("module"))(i)?;
let (i, name) = ws(spanned(module_name))(i)?;
Expand Down Expand Up @@ -297,59 +299,68 @@ pub fn decl_type(i: Input) -> Res<Input, (Spanned<&str>, adlast::DeclType<TypeEx
))(i)
}


pub fn struct_(i: Input) -> Res<Input, (Spanned<&str>, adlast::Struct<TypeExpr0>)> {
let (i, _) = ws(tag("struct"))(i)?;
let (i, name) = ws(spanned(ident0))(i)?;
let (i, _) = oversion(i)?;
let (i, type_params) = type_params(i)?;
let (i, fields) = delimited(wtag("{"), many0(terminated(field, wtag(";"))), wtag("}"))(i)?;
let struct_ = adlast::Struct {
fields,
type_params,
};
Ok((i, (name, struct_)))
cut( |i| {
let (i, name) = ws(spanned(ident0))(i)?;
let (i, _) = oversion(i)?;
let (i, type_params) = type_params(i)?;
let (i, fields) = delimited(wtag("{"), many0(terminated(field, wtag(";"))), wtag("}"))(i)?;
let struct_ = adlast::Struct {
fields,
type_params,
};
Ok((i, (name, struct_)))
})(i)
}

pub fn union(i: Input) -> Res<Input, (Spanned<&str>, adlast::Union<TypeExpr0>)> {
let (i, _) = wtag("union")(i)?;
let (i, name) = ws(spanned(ident0))(i)?;
let (i, _) = oversion(i)?;
let (i, type_params) = type_params(i)?;
let (i, fields) = delimited(wtag("{"), many0(terminated(field, wtag(";"))), wtag("}"))(i)?;
let union = adlast::Union {
fields,
type_params,
};
Ok((i, (name, union)))
cut( |i| {
let (i, name) = ws(spanned(ident0))(i)?;
let (i, _) = oversion(i)?;
let (i, type_params) = type_params(i)?;
let (i, fields) = delimited(wtag("{"), many0(terminated(field, wtag(";"))), wtag("}"))(i)?;
let union = adlast::Union {
fields,
type_params,
};
Ok((i, (name, union)))
})(i)
}

pub fn typedef(i: Input) -> Res<Input, (Spanned<&str>, adlast::TypeDef<TypeExpr0>)> {
let (i, _) = wtag("type")(i)?;
let (i, name) = ws(spanned(ident0))(i)?;
let (i, _) = oversion(i)?;
let (i, type_params) = type_params(i)?;
let (i, type_expr) = preceded(wtag("="), type_expr)(i)?;
let typedef = adlast::TypeDef {
type_params,
type_expr,
};
Ok((i, (name, typedef)))
cut( |i| {
let (i, name) = ws(spanned(ident0))(i)?;
let (i, _) = oversion(i)?;
let (i, type_params) = type_params(i)?;
let (i, type_expr) = preceded(wtag("="), type_expr)(i)?;
let typedef = adlast::TypeDef {
type_params,
type_expr,
};
Ok((i, (name, typedef)))
})(i)
}

pub fn newtype(i: Input) -> Res<Input, (Spanned<&str>, adlast::NewType<TypeExpr0>)> {
let (i, _) = ws(tag("newtype"))(i)?;
let (i, name) = ws(spanned(ident0))(i)?;
let (i, _) = oversion(i)?;
let (i, type_params) = type_params(i)?;
let (i, type_expr) = preceded(wtag("="), type_expr)(i)?;
let (i, default) = opt(preceded(wtag("="), json))(i)?;

let newtype = adlast::NewType {
type_params,
type_expr,
default: maybe_from_option(default),
};
Ok((i, (name, newtype)))
cut( |i| {
let (i, name) = ws(spanned(ident0))(i)?;
let (i, _) = oversion(i)?;
let (i, type_params) = type_params(i)?;
let (i, type_expr) = preceded(wtag("="), type_expr)(i)?;
let (i, default) = opt(preceded(wtag("="), json))(i)?;

let newtype = adlast::NewType {
type_params,
type_expr,
default: maybe_from_option(default),
};
Ok((i, (name, newtype)))
})(i)
}

fn oversion(i: Input) -> Res<Input, Option<u64>> {
Expand Down Expand Up @@ -575,20 +586,60 @@ where
Spanned::new(f(sa.value), sa.span)
}

// Turn the error into something readable
pub fn process_parse_error(e: Err<VerboseError<Input>>) -> anyhow::Error {
match e {
Err::Incomplete(_) => return anyhow!("incomplete input"),
Err::Error(e) => process_parse_error1( e),
Err::Failure(e) => process_parse_error1(e),
// Lifted from nom source, but with our custom input type.
pub fn convert_error(input: Input, e: VerboseError<Input>) -> String {
let lines: Vec<_> = input.lines().map(String::from).collect();

let mut result = String::new();

for (i, (substring, kind)) in e.errors.iter().enumerate() {
let mut offset = input.offset(substring);

let mut line = 0;
let mut column = 0;

for (j, l) in lines.iter().enumerate() {
if offset <= l.len() {
line = j;
column = offset;
break;
} else {
offset = offset - l.len() - 1;
}
}

match kind {
VerboseErrorKind::Char(c) => {
result += &format!("{}: at line {}:\n", i, line);
result += &lines[line];
result += "\n";

if column > 0 {
result += &repeat(' ').take(column).collect::<String>();
}
result += "^\n";
result += &format!("expected '{}', found {}\n\n", c, substring.chars().next().unwrap());
}
VerboseErrorKind::Context(s) => {
result += &format!("{}: at line {}, in {}:\n", i, line, s);
result += &lines[line];
result += "\n";
if column > 0 {
result += &repeat(' ').take(column).collect::<String>();
}
result += "^\n\n";
},
VerboseErrorKind::Nom(e) => {
result += &format!("{}: at line {}, in {:?}:\n", i, line, e);
result += &lines[line];
result += "\n";
if column > 0 {
result += &repeat(' ').take(column).collect::<String>();
}
result += "^\n\n";
}
}
}
}

fn process_parse_error1(e: VerboseError<Input>) -> anyhow::Error {
let messages: Vec<_> = e
.errors
.into_iter()
.map(|(input, error)| format!("line: {}, column: {}, error: {:?}",input.location_line(), input.get_column(), error))
.collect();
anyhow!("parse error: {:#?}", messages)
}

result
}
7 changes: 5 additions & 2 deletions rust/compiler/src/processing/loader.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use nom_locate::LocatedSpan;
use nom::Finish;
use std::fs;
use std::io::ErrorKind;
use std::path::PathBuf;
use anyhow::anyhow;


use crate::adlgen::sys::adlast2 as adlast;
use crate::parser::{process_parse_error, raw_module};
use crate::parser::{convert_error, raw_module};
use crate::processing::annotations::apply_explicit_annotations;

use super::Module0;
Expand Down Expand Up @@ -67,7 +70,7 @@ impl AdlLoader for DirTreeLoader {

fn parse(content: &str) -> Result<Module0, anyhow::Error> {
let inp = LocatedSpan::new(content);
let (_, raw_module) = raw_module(inp).map_err(process_parse_error)?;
let (_, raw_module) = raw_module(inp).finish().map_err(|e| anyhow!(convert_error(inp,e)))?;
match apply_explicit_annotations(raw_module) {
Ok(module0) => Ok(module0),
Err(err) => Err(anyhow::Error::from(err)),
Expand Down

0 comments on commit e43f444

Please sign in to comment.