Merge #188
188: Introduce `SyntaxErrorKind` and `TextRange` to `SyntaxError` r=matklad a=aochagavia Co-authored-by: Adolfo Ochagavía <aochagavia92@gmail.com> Co-authored-by: Adolfo Ochagavía <github@adolfo.ochagavia.xyz>
This commit is contained in:
commit
f605f6e70a
11 changed files with 197 additions and 72 deletions
|
@ -31,6 +31,7 @@ use ra_syntax::{
|
|||
algo::find_leaf_at_offset,
|
||||
ast::{self, AstNode, NameOwner},
|
||||
File,
|
||||
Location,
|
||||
SyntaxKind::{self, *},
|
||||
SyntaxNodeRef, TextRange, TextUnit,
|
||||
};
|
||||
|
@ -100,11 +101,18 @@ pub fn highlight(file: &File) -> Vec<HighlightedRange> {
|
|||
}
|
||||
|
||||
pub fn diagnostics(file: &File) -> Vec<Diagnostic> {
|
||||
fn location_to_range(location: Location) -> TextRange {
|
||||
match location {
|
||||
Location::Offset(offset) => TextRange::offset_len(offset, 1.into()),
|
||||
Location::Range(range) => range,
|
||||
}
|
||||
}
|
||||
|
||||
file.errors()
|
||||
.into_iter()
|
||||
.map(|err| Diagnostic {
|
||||
range: TextRange::offset_len(err.offset, 1.into()),
|
||||
msg: "Syntax Error: ".to_string() + &err.msg,
|
||||
range: location_to_range(err.location()),
|
||||
msg: format!("Syntax Error: {}", err),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ pub use crate::{
|
|||
rowan::{SmolStr, TextRange, TextUnit},
|
||||
syntax_kinds::SyntaxKind,
|
||||
yellow::{
|
||||
Direction, OwnedRoot, RefRoot, SyntaxError, SyntaxNode, SyntaxNodeRef, TreeRoot, WalkEvent,
|
||||
Direction, OwnedRoot, RefRoot, SyntaxError, SyntaxNode, SyntaxNodeRef, TreeRoot, WalkEvent, Location,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -13,6 +13,11 @@ use crate::{
|
|||
SmolStr,
|
||||
SyntaxKind::{self, *},
|
||||
TextRange, TextUnit,
|
||||
yellow::syntax_error::{
|
||||
ParseError,
|
||||
SyntaxError,
|
||||
SyntaxErrorKind,
|
||||
},
|
||||
};
|
||||
use std::mem;
|
||||
|
||||
|
@ -75,7 +80,7 @@ pub(crate) enum Event {
|
|||
},
|
||||
|
||||
Error {
|
||||
msg: String,
|
||||
msg: ParseError,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -157,7 +162,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(SyntaxError::new(
|
||||
SyntaxErrorKind::ParseError(msg),
|
||||
self.text_pos,
|
||||
)),
|
||||
}
|
||||
}
|
||||
self.sink
|
||||
|
|
|
@ -10,7 +10,11 @@ use crate::{
|
|||
event::{Event, EventProcessor},
|
||||
input::{InputPosition, ParserInput},
|
||||
},
|
||||
SmolStr, TextUnit,
|
||||
SmolStr,
|
||||
yellow::syntax_error::{
|
||||
ParseError,
|
||||
SyntaxError,
|
||||
},
|
||||
};
|
||||
|
||||
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, error: SyntaxError);
|
||||
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) {
|
||||
|
|
|
@ -165,20 +165,14 @@ fn merge_errors(
|
|||
) -> Vec<SyntaxError> {
|
||||
let mut res = Vec::new();
|
||||
for e in old_errors {
|
||||
if e.offset <= old_node.range().start() {
|
||||
if e.offset() <= old_node.range().start() {
|
||||
res.push(e)
|
||||
} else if e.offset >= old_node.range().end() {
|
||||
res.push(SyntaxError {
|
||||
msg: e.msg,
|
||||
offset: e.offset + TextUnit::of_str(&edit.insert) - edit.delete.len(),
|
||||
})
|
||||
} else if e.offset() >= old_node.range().end() {
|
||||
res.push(e.add_offset(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(),
|
||||
})
|
||||
res.push(e.add_offset(old_node.range().start()));
|
||||
}
|
||||
res
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.offset());
|
||||
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].offset() <= off {
|
||||
indent!();
|
||||
writeln!(buf, "err: `{}`", errors[err_pos].msg).unwrap();
|
||||
writeln!(buf, "err: `{}`", errors[err_pos]).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).unwrap();
|
||||
}
|
||||
|
||||
buf
|
||||
|
|
|
@ -1,40 +1,78 @@
|
|||
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::new(EmptyAsciiEscape, range));
|
||||
} else {
|
||||
let escape_code = text.chars().skip(1).next().unwrap();
|
||||
if !is_ascii_escape(escape_code) {
|
||||
errors.push(SyntaxError::new(InvalidAsciiEscape, 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::new(UnclosedChar, node.syntax().range()));
|
||||
}
|
||||
|
||||
if len == 0 {
|
||||
errors.push(SyntaxError::new(EmptyChar, node.syntax().range()));
|
||||
}
|
||||
|
||||
if len > 1 {
|
||||
errors.push(SyntaxError::new(LongChar, node.syntax().range()));
|
||||
}
|
||||
}
|
||||
|
||||
fn is_ascii_escape(code: char) -> bool {
|
||||
match code {
|
||||
'\'' | '"' | 'n' | 'r' | 't' | '0' => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
parser_impl::Sink,
|
||||
yellow::{GreenNode, RaTypes, SyntaxError},
|
||||
SmolStr, SyntaxKind, TextUnit,
|
||||
SmolStr, SyntaxKind,
|
||||
};
|
||||
use rowan::GreenNodeBuilder;
|
||||
|
||||
|
@ -34,11 +34,7 @@ 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, error: SyntaxError) {
|
||||
self.errors.push(error)
|
||||
}
|
||||
|
||||
|
|
|
@ -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, Location};
|
||||
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>>;
|
||||
|
|
89
crates/ra_syntax/src/yellow/syntax_error.rs
Normal file
89
crates/ra_syntax/src/yellow/syntax_error.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
use std::fmt;
|
||||
|
||||
use crate::{TextRange, TextUnit};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct SyntaxError {
|
||||
kind: SyntaxErrorKind,
|
||||
location: Location,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Location {
|
||||
Offset(TextUnit),
|
||||
Range(TextRange),
|
||||
}
|
||||
|
||||
impl Into<Location> for TextUnit {
|
||||
fn into(self) -> Location {
|
||||
Location::Offset(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Location> for TextRange {
|
||||
fn into(self) -> Location {
|
||||
Location::Range(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl SyntaxError {
|
||||
pub fn new<L: Into<Location>>(kind: SyntaxErrorKind, loc: L) -> SyntaxError {
|
||||
SyntaxError {
|
||||
kind,
|
||||
location: loc.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn location(&self) -> Location {
|
||||
self.location.clone()
|
||||
}
|
||||
|
||||
pub fn offset(&self) -> TextUnit {
|
||||
match self.location {
|
||||
Location::Offset(offset) => offset,
|
||||
Location::Range(range) => range.start(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_offset(mut self, plus_offset: TextUnit) -> SyntaxError {
|
||||
self.location = match self.location {
|
||||
Location::Range(range) => Location::Range(range + plus_offset),
|
||||
Location::Offset(offset) => Location::Offset(offset + plus_offset),
|
||||
};
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SyntaxError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.kind.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[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),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue