diff --git a/CHANGELOG.md b/CHANGELOG.md index d86b2af..41e0707 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [Unreleased](https://github.com/dalance/sv-parser/compare/v0.1.4...Unreleased) - ReleaseDate +* [Added] parse error position * [Fixed] spacing rule aroung text_macro_identifier * [Fixed] cond_predicate in cond_predicate * [Fixed] fixed_number priority in delay_value diff --git a/sv-parser-error/src/lib.rs b/sv-parser-error/src/lib.rs index 1cbe737..8808ca5 100644 --- a/sv-parser-error/src/lib.rs +++ b/sv-parser-error/src/lib.rs @@ -14,8 +14,8 @@ pub enum ErrorKind { File(PathBuf), #[fail(display = "Include error")] Include, - #[fail(display = "Parse error")] - Parse, + #[fail(display = "Parse error: {:?}", _0)] + Parse(Option<(PathBuf, usize)>), #[fail(display = "Preprocess error")] Preprocess, #[fail(display = "Define argument not found: {}", _0)] diff --git a/sv-parser-parser/Cargo.toml b/sv-parser-parser/Cargo.toml index 7fe5ca7..c8c73a4 100644 --- a/sv-parser-parser/Cargo.toml +++ b/sv-parser-parser/Cargo.toml @@ -20,9 +20,10 @@ trace = ["nom-tracable/trace"] [dependencies] nom = "5.0.0" nom_locate = "1.0.0" +nom-greedyerror = "0.1.0" nom-packrat = "0.3.0" nom-recursive = {version = "0.1.1", features = ["tracer128"]} -nom-tracable = "0.4.0" +nom-tracable = "0.4.1" str-concat = "0.1.4" sv-parser-macros = {version = "0.1.0", path = "../sv-parser-macros"} sv-parser-syntaxtree = {version = "0.1.0", path = "../sv-parser-syntaxtree"} diff --git a/sv-parser-parser/src/lib.rs b/sv-parser-parser/src/lib.rs index fd8d86d..4c2b7b9 100644 --- a/sv-parser-parser/src/lib.rs +++ b/sv-parser-parser/src/lib.rs @@ -31,10 +31,12 @@ pub(crate) use nom::branch::*; pub(crate) use nom::bytes::complete::*; pub(crate) use nom::character::complete::*; pub(crate) use nom::combinator::*; -pub(crate) use nom::error::{make_error, ErrorKind}; +pub(crate) use nom::error::{context, make_error, ErrorKind}; pub(crate) use nom::multi::*; pub(crate) use nom::sequence::*; -pub(crate) use nom::{Err, IResult}; +//pub(crate) use nom::{Err, IResult}; +pub(crate) use nom::Err; +pub(crate) use nom_greedyerror::GreedyError; pub(crate) use nom_packrat::{self, packrat_parser, HasExtraState}; pub(crate) use nom_recursive::{recursive_parser, HasRecursiveInfo, RecursiveInfo}; pub(crate) use nom_tracable::tracable_parser; @@ -52,6 +54,7 @@ pub struct SpanInfo { } pub type Span<'a> = nom_locate::LocatedSpanEx<&'a str, SpanInfo>; +pub type IResult = nom::IResult>; impl HasRecursiveInfo for SpanInfo { fn get_recursive_info(&self) -> RecursiveInfo { diff --git a/sv-parser-parser/src/source_text/configuration_source_text.rs b/sv-parser-parser/src/source_text/configuration_source_text.rs index 996ba1c..2a00ce5 100644 --- a/sv-parser-parser/src/source_text/configuration_source_text.rs +++ b/sv-parser-parser/src/source_text/configuration_source_text.rs @@ -5,7 +5,7 @@ use crate::*; #[tracable_parser] #[packrat_parser] pub(crate) fn config_declaration(s: Span) -> IResult { - let (s, a) = keyword("config")(s)?; + let (s, a) = context("config", keyword("config"))(s)?; let (s, b) = config_identifier(s)?; let (s, c) = symbol(";")(s)?; let (s, d) = many0(pair(local_parameter_declaration, symbol(";")))(s)?; diff --git a/sv-parser-parser/src/source_text/system_verilog_source_text.rs b/sv-parser-parser/src/source_text/system_verilog_source_text.rs index 2d65b43..84f0d31 100644 --- a/sv-parser-parser/src/source_text/system_verilog_source_text.rs +++ b/sv-parser-parser/src/source_text/system_verilog_source_text.rs @@ -7,8 +7,8 @@ use crate::*; pub(crate) fn source_text(s: Span) -> IResult { let (s, a) = many0(white_space)(s)?; let (s, b) = opt(timeunits_declaration)(s)?; - let (s, c) = many0(description)(s)?; - Ok((s, SourceText { nodes: (a, b, c) })) + let (s, c) = many_till(description, eof)(s)?; + Ok((s, SourceText { nodes: (a, b, c.0) })) } #[tracable_parser] diff --git a/sv-parser-parser/src/utils.rs b/sv-parser-parser/src/utils.rs index 2f2de25..2b6f022 100644 --- a/sv-parser-parser/src/utils.rs +++ b/sv-parser-parser/src/utils.rs @@ -51,8 +51,8 @@ pub(crate) fn symbol<'a>(t: &'a str) -> impl Fn(Span<'a>) -> IResult, S #[cfg(not(feature = "trace"))] pub(crate) fn symbol_exact<'a>(t: &'a str) -> impl Fn(Span<'a>) -> IResult, Symbol> { move |s: Span<'a>| { - let (s, x) = map(no_ws(map(tag(t.clone()), into_locate)), |x| { - Symbol { nodes: x } + let (s, x) = map(no_ws(map(tag(t.clone()), into_locate)), |x| Symbol { + nodes: x, })(s)?; Ok((s, x)) } @@ -63,8 +63,8 @@ pub(crate) fn symbol_exact<'a>(t: &'a str) -> impl Fn(Span<'a>) -> IResult| { let (depth, s) = nom_tracable::forward_trace(s, &format!("symbol(\"{}\")", t)); let body = || { - let (s, x) = map(no_ws(map(tag(t.clone()), into_locate)), |x| { - Symbol { nodes: x } + let (s, x) = map(no_ws(map(tag(t.clone()), into_locate)), |x| Symbol { + nodes: x, })(s)?; Ok((s, x)) }; @@ -79,10 +79,7 @@ pub(crate) fn keyword<'a>(t: &'a str) -> impl Fn(Span<'a>) -> IResult, let (s, x) = map( ws(alt(( all_consuming(map(tag(t.clone()), into_locate)), - terminated( - map(tag(t.clone()), into_locate), - peek(none_of(AZ09_)), - ), + terminated(map(tag(t.clone()), into_locate), peek(none_of(AZ09_))), ))), |x| Keyword { nodes: x }, )(s)?; @@ -98,10 +95,7 @@ pub(crate) fn keyword<'a>(t: &'a str) -> impl Fn(Span<'a>) -> IResult, let (s, x) = map( ws(alt(( all_consuming(map(tag(t.clone()), into_locate)), - terminated( - map(tag(t.clone()), into_locate), - peek(none_of(AZ09_)), - ), + terminated(map(tag(t.clone()), into_locate), peek(none_of(AZ09_))), ))), |x| Keyword { nodes: x }, )(s)?; @@ -315,6 +309,16 @@ where // ----------------------------------------------------------------------------- +pub(crate) fn eof(s: Span) -> IResult { + use nom::InputLength; + + if s.input_len() == 0 { + Ok((s, s)) + } else { + Err(Err::Error(make_error(s, ErrorKind::Eof))) + } +} + #[tracable_parser] #[packrat_parser] pub(crate) fn white_space(s: Span) -> IResult { diff --git a/sv-parser-pp/Cargo.toml b/sv-parser-pp/Cargo.toml index 868c5fe..fb00af4 100644 --- a/sv-parser-pp/Cargo.toml +++ b/sv-parser-pp/Cargo.toml @@ -20,6 +20,7 @@ trace = ["sv-parser-parser/trace"] [dependencies] failure = "0.1.5" nom = "5.0.0" +nom-greedyerror = "0.1.0" sv-parser-error = {version = "0.1.0", path = "../sv-parser-error"} sv-parser-parser = {version = "0.1.0", path = "../sv-parser-parser"} sv-parser-syntaxtree = {version = "0.1.0", path = "../sv-parser-syntaxtree"} diff --git a/sv-parser-pp/src/preprocess.rs b/sv-parser-pp/src/preprocess.rs index 828e778..72ace97 100644 --- a/sv-parser-pp/src/preprocess.rs +++ b/sv-parser-pp/src/preprocess.rs @@ -1,5 +1,6 @@ use crate::range::Range; use failure::ResultExt; +use nom_greedyerror::error_position; use std::collections::{BTreeMap, HashMap}; use std::convert::TryInto; use std::fs::File; @@ -107,7 +108,23 @@ fn preprocess_str, U: AsRef>( } let span = Span::new_extra(&s, SpanInfo::default()); - let (_, pp_text) = pp_parser(span).map_err(|_| ErrorKind::Parse)?; + let (_, pp_text) = pp_parser(span).map_err(|x| match x { + nom::Err::Incomplete(_) => ErrorKind::Parse(None), + nom::Err::Error(e) => { + if let Some(pos) = error_position(&e) { + ErrorKind::Parse(Some((PathBuf::from(path.as_ref()), pos))) + } else { + ErrorKind::Parse(None) + } + } + nom::Err::Failure(e) => { + if let Some(pos) = error_position(&e) { + ErrorKind::Parse(Some((PathBuf::from(path.as_ref()), pos))) + } else { + ErrorKind::Parse(None) + } + } + })?; let mut ret = PreprocessedText::new(); diff --git a/sv-parser/Cargo.toml b/sv-parser/Cargo.toml index 37da407..a9c1a60 100644 --- a/sv-parser/Cargo.toml +++ b/sv-parser/Cargo.toml @@ -14,8 +14,17 @@ edition = "2018" default = [] trace = ["sv-parser-parser/trace"] +[package.metadata.release] +pre-release-replacements = [ + {file = "../README.md", search = "sv-parser = \"[a-z0-9\\.-]+\"", replace = "sv-parser = \"{{version}}\""}, + {file = "../CHANGELOG.md", search = "Unreleased", replace = "v{{version}}"}, + {file = "../CHANGELOG.md", search = "ReleaseDate", replace = "{{date}}"}, + {file = "../CHANGELOG.md", search = "Change Log", replace = "Change Log\n\n## [Unreleased](https://github.com/dalance/sv-parser/compare/v{{version}}...Unreleased) - ReleaseDate"}, +] + [dependencies] nom = "5.0.0" +nom-greedyerror = "0.1.0" sv-parser-error = {version = "0.1.0", path = "../sv-parser-error"} sv-parser-parser = {version = "0.1.0", path = "../sv-parser-parser"} sv-parser-pp = {version = "0.1.0", path = "../sv-parser-pp"} diff --git a/sv-parser/examples/parse_sv.rs b/sv-parser/examples/parse_sv.rs index 9a55c82..4926bf8 100644 --- a/sv-parser/examples/parse_sv.rs +++ b/sv-parser/examples/parse_sv.rs @@ -1,8 +1,11 @@ use std::collections::HashMap; +use std::fs::File; +use std::io::Read; use std::path::PathBuf; -use std::process; +use std::{cmp, process}; use structopt::StructOpt; use sv_parser::parse_sv; +use sv_parser_error::ErrorKind; #[derive(StructOpt)] struct Opt { @@ -37,10 +40,79 @@ fn main() { } } Err(x) => { - println!("parse failed: {:?} ({})", path, x); + match x.kind() { + ErrorKind::Parse(Some((origin_path, origin_pos))) => { + println!("parse failed: {:?}", path); + print_parse_error(origin_path, origin_pos); + } + x => { + println!("parse failed: {:?} ({})", path, x); + } + } exit = 1; } } } process::exit(exit); } + +static CHAR_CR: u8 = 0x0d; +static CHAR_LF: u8 = 0x0a; + +fn print_parse_error(origin_path: &PathBuf, origin_pos: &usize) { + let mut f = File::open(&origin_path).unwrap(); + let mut s = String::new(); + let _ = f.read_to_string(&mut s); + + let mut pos = 0; + let mut column = 1; + let mut last_lf = None; + while pos < s.len() { + if s.as_bytes()[pos] == CHAR_LF { + column += 1; + last_lf = Some(pos); + } + pos += 1; + + if *origin_pos == pos { + let row = if let Some(last_lf) = last_lf { + pos - last_lf + } else { + pos + 1 + }; + let mut next_crlf = pos; + while next_crlf < s.len() { + if s.as_bytes()[next_crlf] == CHAR_CR || s.as_bytes()[next_crlf] == CHAR_LF { + break; + } + next_crlf += 1; + } + + let column_len = format!("{}", column).len(); + + print!(" {}:{}:{}\n", origin_path.to_string_lossy(), column, row); + + print!("{}|\n", " ".repeat(column_len + 1)); + + print!("{} |", column); + + let beg = if let Some(last_lf) = last_lf { + last_lf + 1 + } else { + 0 + }; + print!( + " {}\n", + String::from_utf8_lossy(&s.as_bytes()[beg..next_crlf]) + ); + + print!("{}|", " ".repeat(column_len + 1)); + + print!( + " {}{}\n", + " ".repeat(pos - beg), + "^".repeat(cmp::min(origin_pos + 1, next_crlf) - origin_pos) + ); + } + } +} diff --git a/sv-parser/src/lib.rs b/sv-parser/src/lib.rs index 69fad50..53d6dc5 100644 --- a/sv-parser/src/lib.rs +++ b/sv-parser/src/lib.rs @@ -1,6 +1,7 @@ #![recursion_limit = "256"] use nom::combinator::all_consuming; +use nom_greedyerror::error_position; use std::collections::HashMap; use std::fmt; use std::path::Path; @@ -79,7 +80,23 @@ pub fn parse_sv, U: AsRef>( }, defines, )), - Err(_) => Err(ErrorKind::Parse.into()), + Err(x) => { + let pos = match x { + nom::Err::Incomplete(_) => None, + nom::Err::Error(e) => error_position(&e), + nom::Err::Failure(e) => error_position(&e), + }; + let origin = if let Some(pos) = pos { + if let Some(origin) = text.origin(pos) { + Some((origin.0.clone(), origin.1)) + } else { + None + } + } else { + None + }; + Err(ErrorKind::Parse(origin).into()) + } } } @@ -99,7 +116,23 @@ pub fn parse_lib, U: AsRef>( }, defines, )), - Err(_) => Err(ErrorKind::Parse.into()), + Err(x) => { + let pos = match x { + nom::Err::Incomplete(_) => None, + nom::Err::Error(e) => error_position(&e), + nom::Err::Failure(e) => error_position(&e), + }; + let origin = if let Some(pos) = pos { + if let Some(origin) = text.origin(pos) { + Some((origin.0.clone(), origin.1)) + } else { + None + } + } else { + None + }; + Err(ErrorKind::Parse(origin).into()) + } } }