Lexer hint (#4346)

Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
astrale-sharp 2024-06-12 14:00:22 +02:00 committed by GitHub
parent 20b8d2c121
commit ad4ef68a11
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 41 additions and 21 deletions

View File

@ -4,7 +4,7 @@ use unicode_script::{Script, UnicodeScript};
use unicode_segmentation::UnicodeSegmentation;
use unscanny::Scanner;
use crate::SyntaxKind;
use crate::{SyntaxError, SyntaxKind};
/// Splits up a string of source code into tokens.
#[derive(Clone)]
@ -19,7 +19,7 @@ pub(super) struct Lexer<'s> {
/// The state held by raw line lexing.
raw: Vec<(SyntaxKind, usize)>,
/// An error for the last token.
error: Option<EcoString>,
error: Option<SyntaxError>,
}
/// What kind of tokens to emit.
@ -75,7 +75,7 @@ impl<'s> Lexer<'s> {
}
/// Take out the last error, if any.
pub fn take_error(&mut self) -> Option<EcoString> {
pub fn take_error(&mut self) -> Option<SyntaxError> {
self.error.take()
}
}
@ -83,9 +83,16 @@ impl<'s> Lexer<'s> {
impl Lexer<'_> {
/// Construct a full-positioned syntax error.
fn error(&mut self, message: impl Into<EcoString>) -> SyntaxKind {
self.error = Some(message.into());
self.error = Some(SyntaxError::new(message));
SyntaxKind::Error
}
/// If the current node is an error, adds a hint.
fn hint(&mut self, message: impl Into<EcoString>) {
if let Some(error) = &mut self.error {
error.hints.push(message.into());
}
}
}
/// Shared methods with all [`LexMode`].
@ -109,7 +116,12 @@ impl Lexer<'_> {
Some('/') if self.s.eat_if('/') => self.line_comment(),
Some('/') if self.s.eat_if('*') => self.block_comment(),
Some('*') if self.s.eat_if('/') => {
self.error("unexpected end of block comment")
let kind = self.error("unexpected end of block comment");
self.hint(
"consider escaping the `*` with a backslash or \
opening the block comment with `/*`",
);
kind
}
Some(c) => match self.mode {

View File

@ -35,8 +35,8 @@ impl SyntaxNode {
}
/// Create a new error node.
pub fn error(message: impl Into<EcoString>, text: impl Into<EcoString>) -> Self {
Self(Repr::Error(Arc::new(ErrorNode::new(message, text))))
pub fn error(error: SyntaxError, text: impl Into<EcoString>) -> Self {
Self(Repr::Error(Arc::new(ErrorNode::new(error, text))))
}
/// Create a dummy node of the given kind.
@ -209,7 +209,7 @@ impl SyntaxNode {
pub(super) fn convert_to_error(&mut self, message: impl Into<EcoString>) {
if !self.kind().is_error() {
let text = std::mem::take(self).into_text();
*self = SyntaxNode::error(message, text);
*self = SyntaxNode::error(SyntaxError::new(message), text);
}
}
@ -628,15 +628,8 @@ struct ErrorNode {
impl ErrorNode {
/// Create new error node.
fn new(message: impl Into<EcoString>, text: impl Into<EcoString>) -> Self {
Self {
text: text.into(),
error: SyntaxError {
span: Span::detached(),
message: message.into(),
hints: eco_vec![],
},
}
fn new(error: SyntaxError, text: impl Into<EcoString>) -> Self {
Self { text: text.into(), error }
}
/// The byte length of the node in the source text.
@ -674,6 +667,15 @@ pub struct SyntaxError {
}
impl SyntaxError {
/// Create a new detached syntax error.
pub fn new(message: impl Into<EcoString>) -> Self {
Self {
span: Span::detached(),
message: message.into(),
hints: eco_vec![],
}
}
/// Whether the two errors are the same apart from spans.
fn spanless_eq(&self, other: &Self) -> bool {
self.message == other.message && self.hints == other.hints

View File

@ -6,7 +6,9 @@ use ecow::{eco_format, EcoString};
use unicode_math_class::MathClass;
use crate::set::SyntaxSet;
use crate::{ast, is_ident, is_newline, set, LexMode, Lexer, SyntaxKind, SyntaxNode};
use crate::{
ast, is_ident, is_newline, set, LexMode, Lexer, SyntaxError, SyntaxKind, SyntaxNode,
};
/// Parses a source file.
pub fn parse(text: &str) -> SyntaxNode {
@ -1760,8 +1762,8 @@ impl<'s> Parser<'s> {
fn save(&mut self) {
let text = self.current_text();
if self.at(SyntaxKind::Error) {
let message = self.lexer.take_error().unwrap();
self.nodes.push(SyntaxNode::error(message, text));
let error = self.lexer.take_error().unwrap();
self.nodes.push(SyntaxNode::error(error, text));
} else {
self.nodes.push(SyntaxNode::leaf(self.current, text));
}
@ -1838,7 +1840,8 @@ impl<'s> Parser<'s> {
/// Produce an error that the given `thing` was expected at the position
/// of the marker `m`.
fn expected_at(&mut self, m: Marker, thing: &str) {
let error = SyntaxNode::error(eco_format!("expected {thing}"), "");
let error =
SyntaxNode::error(SyntaxError::new(eco_format!("expected {thing}")), "");
self.nodes.insert(m.0, error);
}

View File

@ -22,6 +22,7 @@
--- array-bad-token ---
// Error: 4-6 unexpected end of block comment
// Hint: 4-6 consider escaping the `*` with a backslash or opening the block comment with `/*`
#(1*/2)
--- array-bad-number-suffix ---

View File

@ -80,6 +80,7 @@
--- call-args-bad-token ---
// Error: 10-12 unexpected end of block comment
// Hint: 10-12 consider escaping the `*` with a backslash or opening the block comment with `/*`
#func(a:1*/)
--- call-args-missing-comma ---

View File

@ -37,6 +37,7 @@ Second part
--- comment-block-unclosed ---
// End should not appear without start.
// Error: 7-9 unexpected end of block comment
// Hint: 7-9 consider escaping the `*` with a backslash or opening the block comment with `/*`
/* */ */
// Unterminated is okay.