Introduce SyntaxErrorKind and TextRange in SyntaxError

This commit is contained in:
Adolfo Ochagavía 2018-11-04 16:45:22 +01:00
parent 576b9a0727
commit 3b42ddae60
10 changed files with 159 additions and 68 deletions

View File

@ -103,8 +103,8 @@ pub fn diagnostics(file: &File) -> Vec<Diagnostic> {
file.errors()
.into_iter()
.map(|err| Diagnostic {
range: TextRange::offset_len(err.offset, 1.into()),
msg: "Syntax Error: ".to_string() + &err.msg,
range: err.range,
msg: format!("Syntax Error: {}", err.kind),
})
.collect()
}

View File

@ -13,6 +13,10 @@ use crate::{
SmolStr,
SyntaxKind::{self, *},
TextRange, TextUnit,
yellow::syntax_error::{
ParseError,
SyntaxErrorKind,
},
};
use std::mem;
@ -75,7 +79,7 @@ pub(crate) enum Event {
},
Error {
msg: String,
msg: ParseError,
},
}
@ -157,7 +161,10 @@ impl<'a, S: Sink> EventProcessor<'a, S> {
.sum::<TextUnit>();
self.leaf(kind, len, n_raw_tokens);
}
Event::Error { msg } => self.sink.error(msg, self.text_pos),
Event::Error { msg } => self.sink.error(
SyntaxErrorKind::ParseError(msg),
TextRange::offset_len(self.text_pos, 1.into()),
),
}
}
self.sink

View File

@ -10,7 +10,11 @@ use crate::{
event::{Event, EventProcessor},
input::{InputPosition, ParserInput},
},
SmolStr, TextUnit,
SmolStr, TextRange,
yellow::syntax_error::{
ParseError,
SyntaxErrorKind,
},
};
use crate::SyntaxKind::{self, EOF, TOMBSTONE};
@ -21,7 +25,7 @@ pub(crate) trait Sink {
fn leaf(&mut self, kind: SyntaxKind, text: SmolStr);
fn start_internal(&mut self, kind: SyntaxKind);
fn finish_internal(&mut self);
fn error(&mut self, message: String, offset: TextUnit);
fn error(&mut self, kind: SyntaxErrorKind, offset: TextRange);
fn finish(self) -> Self::Tree;
}
@ -144,7 +148,9 @@ impl<'t> ParserImpl<'t> {
}
pub(super) fn error(&mut self, msg: String) {
self.event(Event::Error { msg })
self.event(Event::Error {
msg: ParseError(msg),
})
}
pub(super) fn complete(&mut self, pos: u32, kind: SyntaxKind) {

View File

@ -165,19 +165,19 @@ fn merge_errors(
) -> Vec<SyntaxError> {
let mut res = Vec::new();
for e in old_errors {
if e.offset <= old_node.range().start() {
if e.range.start() <= old_node.range().start() {
res.push(e)
} else if e.offset >= old_node.range().end() {
} else if e.range.start() >= old_node.range().end() {
res.push(SyntaxError {
msg: e.msg,
offset: e.offset + TextUnit::of_str(&edit.insert) - edit.delete.len(),
kind: e.kind,
range: e.range + TextUnit::of_str(&edit.insert) - edit.delete.len(),
})
}
}
for e in new_errors {
res.push(SyntaxError {
msg: e.msg,
offset: e.offset + old_node.range().start(),
kind: e.kind,
range: e.range + old_node.range().start(),
})
}
res

View File

@ -100,10 +100,6 @@ impl<'a> Parser<'a> {
// Char parsing methods
fn parse_unicode_escape(&mut self, start: TextUnit) -> CharComponent {
// Note: validation of UnicodeEscape will be done elsewhere:
// * Only hex digits or underscores allowed
// * Max 6 chars
// * Within allowed range (must be at most 10FFFF)
match self.peek() {
Some('{') => {
self.advance();
@ -127,9 +123,6 @@ impl<'a> Parser<'a> {
}
fn parse_ascii_code_escape(&mut self, start: TextUnit) -> CharComponent {
// Note: validation of AsciiCodeEscape will be done elsewhere:
// * First digit is octal
// * Second digit is hex
let code_start = self.get_pos();
while let Some(next) = self.peek() {
if next == '\'' || (self.get_pos() - code_start == 2.into()) {
@ -144,9 +137,6 @@ impl<'a> Parser<'a> {
}
fn parse_escape(&mut self, start: TextUnit) -> CharComponent {
// Note: validation of AsciiEscape will be done elsewhere:
// * The escape sequence is non-empty
// * The escape sequence is valid
if self.peek().is_none() {
return CharComponent::new(TextRange::from_to(start, start), AsciiEscape);
}

View File

@ -4,7 +4,7 @@ use std::fmt::Write;
/// Parse a file and create a string representation of the resulting parse tree.
pub fn dump_tree(syntax: SyntaxNodeRef) -> String {
let mut errors: Vec<_> = syntax.root_data().to_vec();
errors.sort_by_key(|e| e.offset);
errors.sort_by_key(|e| e.range.start());
let mut err_pos = 0;
let mut level = 0;
let mut buf = String::new();
@ -23,9 +23,9 @@ pub fn dump_tree(syntax: SyntaxNodeRef) -> String {
writeln!(buf, "{:?}", node).unwrap();
if node.first_child().is_none() {
let off = node.range().end();
while err_pos < errors.len() && errors[err_pos].offset <= off {
while err_pos < errors.len() && errors[err_pos].range.start() <= off {
indent!();
writeln!(buf, "err: `{}`", errors[err_pos].msg).unwrap();
writeln!(buf, "err: `{}`", errors[err_pos].kind).unwrap();
err_pos += 1;
}
}
@ -37,7 +37,7 @@ pub fn dump_tree(syntax: SyntaxNodeRef) -> String {
assert_eq!(level, 0);
for err in errors[err_pos..].iter() {
writeln!(buf, "err: `{}`", err.msg).unwrap();
writeln!(buf, "err: `{}`", err.kind).unwrap();
}
buf

View File

@ -1,40 +1,93 @@
use crate::{
algo::visit::{visitor_ctx, VisitorCtx},
ast::{self, AstNode},
File,
string_lexing,
string_lexing::{self, CharComponentKind},
yellow::{
SyntaxError,
SyntaxErrorKind::*,
},
};
pub(crate) fn validate(file: &File) -> Vec<SyntaxError> {
let mut errors = Vec::new();
for d in file.root.borrowed().descendants() {
if let Some(c) = ast::Char::cast(d) {
let components = &mut string_lexing::parse_char_literal(c.text());
let len = components.count();
if !components.has_closing_quote {
errors.push(SyntaxError {
msg: "Unclosed char literal".to_string(),
offset: d.range().start(),
});
}
if len == 0 {
errors.push(SyntaxError {
msg: "Empty char literal".to_string(),
offset: d.range().start(),
});
}
if len > 1 {
errors.push(SyntaxError {
msg: "Character literal should be only one character long".to_string(),
offset: d.range().start(),
});
}
}
for node in file.root.borrowed().descendants() {
let _ = visitor_ctx(&mut errors)
.visit::<ast::Char, _>(validate_char)
.accept(node);
}
errors
}
fn validate_char(node: ast::Char, errors: &mut Vec<SyntaxError>) {
let mut components = string_lexing::parse_char_literal(node.text());
let mut len = 0;
for component in &mut components {
len += 1;
// Validate escapes
let text = &node.text()[component.range];
let range = component.range + node.syntax().range().start();
use self::CharComponentKind::*;
match component.kind {
AsciiEscape => {
if text.len() == 1 {
// Escape sequence consists only of leading `\`
errors.push(SyntaxError {
kind: EmptyAsciiEscape,
range: range,
});
} else {
let escape_code = text.chars().skip(1).next().unwrap();
if !is_ascii_escape(escape_code) {
errors.push(SyntaxError {
kind: InvalidAsciiEscape,
range: range,
});
}
}
}
AsciiCodeEscape => {
// TODO:
// * First digit is octal
// * Second digit is hex
}
UnicodeEscape => {
// TODO:
// * Only hex digits or underscores allowed
// * Max 6 chars
// * Within allowed range (must be at most 10FFFF)
}
// Code points are always valid
CodePoint => (),
}
}
if !components.has_closing_quote {
errors.push(SyntaxError {
kind: UnclosedChar,
range: node.syntax().range(),
});
}
if len == 0 {
errors.push(SyntaxError {
kind: EmptyChar,
range: node.syntax().range(),
});
}
if len > 1 {
errors.push(SyntaxError {
kind: LongChar,
range: node.syntax().range(),
});
}
}
fn is_ascii_escape(code: char) -> bool {
match code {
'\'' | '"' | 'n' | 'r' | 't' | '0' => true,
_ => false,
}
}

View File

@ -1,7 +1,7 @@
use crate::{
parser_impl::Sink,
yellow::{GreenNode, RaTypes, SyntaxError},
SmolStr, SyntaxKind, TextUnit,
yellow::{GreenNode, RaTypes, SyntaxError, SyntaxErrorKind},
SmolStr, SyntaxKind, TextRange,
};
use rowan::GreenNodeBuilder;
@ -34,11 +34,8 @@ impl Sink for GreenBuilder {
self.inner.finish_internal();
}
fn error(&mut self, message: String, offset: TextUnit) {
let error = SyntaxError {
msg: message,
offset,
};
fn error(&mut self, kind: SyntaxErrorKind, range: TextRange) {
let error = SyntaxError { kind, range };
self.errors.push(error)
}

View File

@ -1,8 +1,9 @@
mod builder;
pub mod syntax_error;
mod syntax_text;
use self::syntax_text::SyntaxText;
use crate::{SmolStr, SyntaxKind, TextRange, TextUnit};
use crate::{SmolStr, SyntaxKind, TextRange};
use rowan::Types;
use std::{
fmt,
@ -10,6 +11,7 @@ use std::{
};
pub(crate) use self::builder::GreenBuilder;
pub use self::syntax_error::{SyntaxError, SyntaxErrorKind};
pub use rowan::{TreeRoot, WalkEvent};
#[derive(Debug, Clone, Copy)]
@ -24,12 +26,6 @@ pub type RefRoot<'a> = ::rowan::RefRoot<'a, RaTypes>;
pub type GreenNode = ::rowan::GreenNode<RaTypes>;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct SyntaxError {
pub msg: String,
pub offset: TextUnit,
}
#[derive(Clone, Copy)]
pub struct SyntaxNode<R: TreeRoot<RaTypes> = OwnedRoot>(::rowan::SyntaxNode<RaTypes, R>);
pub type SyntaxNodeRef<'a> = SyntaxNode<RefRoot<'a>>;

View File

@ -0,0 +1,42 @@
use std::fmt;
use crate::TextRange;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SyntaxError {
pub kind: SyntaxErrorKind,
pub range: TextRange,
}
impl SyntaxError {
pub fn new(kind: SyntaxErrorKind, range: TextRange) -> SyntaxError {
SyntaxError { kind, range }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SyntaxErrorKind {
ParseError(ParseError),
EmptyChar,
UnclosedChar,
LongChar,
EmptyAsciiEscape,
InvalidAsciiEscape,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ParseError(pub String);
impl fmt::Display for SyntaxErrorKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::SyntaxErrorKind::*;
match self {
EmptyAsciiEscape => write!(f, "Empty escape sequence"),
InvalidAsciiEscape => write!(f, "Invalid escape sequence"),
EmptyChar => write!(f, "Empty char literal"),
UnclosedChar => write!(f, "Unclosed char literal"),
LongChar => write!(f, "Char literal should be one character long"),
ParseError(msg) => write!(f, "{}", msg.0),
}
}
}