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 unicode_segmentation::UnicodeSegmentation;
use unscanny::Scanner; use unscanny::Scanner;
use crate::SyntaxKind; use crate::{SyntaxError, SyntaxKind};
/// Splits up a string of source code into tokens. /// Splits up a string of source code into tokens.
#[derive(Clone)] #[derive(Clone)]
@ -19,7 +19,7 @@ pub(super) struct Lexer<'s> {
/// The state held by raw line lexing. /// The state held by raw line lexing.
raw: Vec<(SyntaxKind, usize)>, raw: Vec<(SyntaxKind, usize)>,
/// An error for the last token. /// An error for the last token.
error: Option<EcoString>, error: Option<SyntaxError>,
} }
/// What kind of tokens to emit. /// What kind of tokens to emit.
@ -75,7 +75,7 @@ impl<'s> Lexer<'s> {
} }
/// Take out the last error, if any. /// 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() self.error.take()
} }
} }
@ -83,9 +83,16 @@ impl<'s> Lexer<'s> {
impl Lexer<'_> { impl Lexer<'_> {
/// Construct a full-positioned syntax error. /// Construct a full-positioned syntax error.
fn error(&mut self, message: impl Into<EcoString>) -> SyntaxKind { fn error(&mut self, message: impl Into<EcoString>) -> SyntaxKind {
self.error = Some(message.into()); self.error = Some(SyntaxError::new(message));
SyntaxKind::Error 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`]. /// 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.line_comment(),
Some('/') if self.s.eat_if('*') => self.block_comment(), Some('/') if self.s.eat_if('*') => self.block_comment(),
Some('*') if self.s.eat_if('/') => { 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 { Some(c) => match self.mode {

View File

@ -35,8 +35,8 @@ impl SyntaxNode {
} }
/// Create a new error node. /// Create a new error node.
pub fn error(message: impl Into<EcoString>, text: impl Into<EcoString>) -> Self { pub fn error(error: SyntaxError, text: impl Into<EcoString>) -> Self {
Self(Repr::Error(Arc::new(ErrorNode::new(message, text)))) Self(Repr::Error(Arc::new(ErrorNode::new(error, text))))
} }
/// Create a dummy node of the given kind. /// 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>) { pub(super) fn convert_to_error(&mut self, message: impl Into<EcoString>) {
if !self.kind().is_error() { if !self.kind().is_error() {
let text = std::mem::take(self).into_text(); 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 { impl ErrorNode {
/// Create new error node. /// Create new error node.
fn new(message: impl Into<EcoString>, text: impl Into<EcoString>) -> Self { fn new(error: SyntaxError, text: impl Into<EcoString>) -> Self {
Self { Self { text: text.into(), error }
text: text.into(),
error: SyntaxError {
span: Span::detached(),
message: message.into(),
hints: eco_vec![],
},
}
} }
/// The byte length of the node in the source text. /// The byte length of the node in the source text.
@ -674,6 +667,15 @@ pub struct SyntaxError {
} }
impl 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. /// Whether the two errors are the same apart from spans.
fn spanless_eq(&self, other: &Self) -> bool { fn spanless_eq(&self, other: &Self) -> bool {
self.message == other.message && self.hints == other.hints 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 unicode_math_class::MathClass;
use crate::set::SyntaxSet; 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. /// Parses a source file.
pub fn parse(text: &str) -> SyntaxNode { pub fn parse(text: &str) -> SyntaxNode {
@ -1760,8 +1762,8 @@ impl<'s> Parser<'s> {
fn save(&mut self) { fn save(&mut self) {
let text = self.current_text(); let text = self.current_text();
if self.at(SyntaxKind::Error) { if self.at(SyntaxKind::Error) {
let message = self.lexer.take_error().unwrap(); let error = self.lexer.take_error().unwrap();
self.nodes.push(SyntaxNode::error(message, text)); self.nodes.push(SyntaxNode::error(error, text));
} else { } else {
self.nodes.push(SyntaxNode::leaf(self.current, text)); 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 /// Produce an error that the given `thing` was expected at the position
/// of the marker `m`. /// of the marker `m`.
fn expected_at(&mut self, m: Marker, thing: &str) { 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); self.nodes.insert(m.0, error);
} }

View File

@ -22,6 +22,7 @@
--- array-bad-token --- --- array-bad-token ---
// Error: 4-6 unexpected end of block comment // 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) #(1*/2)
--- array-bad-number-suffix --- --- array-bad-number-suffix ---

View File

@ -80,6 +80,7 @@
--- call-args-bad-token --- --- call-args-bad-token ---
// Error: 10-12 unexpected end of block comment // 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*/) #func(a:1*/)
--- call-args-missing-comma --- --- call-args-missing-comma ---

View File

@ -37,6 +37,7 @@ Second part
--- comment-block-unclosed --- --- comment-block-unclosed ---
// End should not appear without start. // End should not appear without start.
// Error: 7-9 unexpected end of block comment // 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. // Unterminated is okay.