refactor(location): deduplicated usage of Location::new with same value into default

This commit is contained in:
Khaïs COLIN 2025-06-03 20:09:22 +02:00
parent 567aa31c07
commit 64d93e9a27
Signed by: logistic-bot
SSH key fingerprint: SHA256:RlpiqKeXpcPFZZ4y9Ou4xi2M8OhRJovIwDlbCaMsuAo
5 changed files with 102 additions and 45 deletions

View file

@ -266,7 +266,7 @@ Error: unexpected token
- Ensure examples are contextually appropriate - Ensure examples are contextually appropriate
This will make errors like "expected semicolon" more helpful by showing "expected semicolon (example: ;)". This will make errors like "expected semicolon" more helpful by showing "expected semicolon (example: ;)".
TODO * TODO correct all instances of <unknown> in locations * DONE correct all instances of <unknown> in locations
* TODO meta-commands must be followed by end-of-file * TODO meta-commands must be followed by end-of-file
* TODO project code documentation * TODO project code documentation
* TODO project usage documentation * TODO project usage documentation

View file

@ -1,6 +1,6 @@
use crate::meta_commands::{MetaCommand, MetaCommandExecuteResult}; use crate::meta_commands::{MetaCommand, MetaCommandExecuteResult};
use crate::statements::{Statement, StatementExecuteResult}; use crate::statements::{Statement, StatementExecuteResult};
use crate::tokens::{ScanError, Token}; use crate::tokens::{Location, ScanError, Token};
#[derive(Debug)] #[derive(Debug)]
pub enum Command { pub enum Command {
@ -88,6 +88,7 @@ impl std::fmt::Display for ExpectedToken {
pub enum CommandParseError { pub enum CommandParseError {
Scan(ScanError), Scan(ScanError),
UnexpectedToken(Token, &'static [ExpectedToken]), UnexpectedToken(Token, &'static [ExpectedToken]),
UnexpectedEndOfFile(Location, &'static [ExpectedToken]),
} }
impl From<MetaCommand> for Command { impl From<MetaCommand> for Command {

View file

@ -1,4 +1,7 @@
use crate::{command::CommandParseError, tokens::ScanError}; use crate::{
command::{CommandParseError, ExpectedToken},
tokens::ScanError,
};
use ariadne::{Color, Label, Report, ReportKind, Source}; use ariadne::{Color, Label, Report, ReportKind, Source};
pub trait OSDBError { pub trait OSDBError {
@ -22,34 +25,67 @@ impl OSDBError for CommandParseError {
.with_message(format!("found {token}")), .with_message(format!("found {token}")),
); );
// If we have expected tokens, show an example for the first one report = add_expected_tokens_to_report(report, expected_tokens);
if let Some(first_expected) = expected_tokens.get(0) {
let example = first_expected.example();
// Add a note with all expected types
let expected_types: Vec<_> = expected_tokens
.iter()
.map(|t| format!("{}", t))
.collect();
// Use singular form when there's only one expected token type
match expected_types.as_slice() {
[single_type] => {
report = report.with_note(format!("expected: {}", single_type));
},
_ => {
report = report.with_note(format!("expected one of: {}", expected_types.join(", ")));
}
}
report =
report.with_help(format!("try a token of the expected type: {example}"))
}
report.finish().eprint((file, Source::from(input))).unwrap() report.finish().eprint((file, Source::from(input))).unwrap()
} }
CommandParseError::UnexpectedEndOfFile(location, expected_tokens) => {
let location = (file, Into::<std::ops::Range<usize>>::into(location));
let report = Report::build(ReportKind::Error, location)
.with_message("unexpected end of file");
let report = add_expected_tokens_to_report(report, expected_tokens);
report.finish().eprint((file, Source::from(input))).unwrap()
} }
} }
} }
}
type OSDBReport<'a> = ariadne::ReportBuilder<'a, (&'a str, std::ops::Range<usize>)>;
fn add_expected_tokens_to_report<'a>(
mut report: OSDBReport<'a>,
expected_tokens: &'static [ExpectedToken],
) -> OSDBReport<'a> {
// If we have expected tokens, show an example for the first one
if let Some(help) = expected_token_example_msg(expected_tokens) {
report = report.with_help(help)
}
// If we have at least one expected token, show a message showing what type was expected
if let Some(note) = expected_token_msg(expected_tokens) {
report = report.with_note(note);
}
report
}
fn expected_token_msg(expected_tokens: &'static [ExpectedToken]) -> Option<String> {
if !expected_tokens.is_empty() {
// Add a note with all expected types
let expected_types: Vec<_> = expected_tokens.iter().map(|t| format!("{}", t)).collect();
// Use singular form when there's only one expected token type
Some(match expected_types.as_slice() {
[single_type] => {
format!("expected: {}", single_type)
}
_ => {
format!("expected one of: {}", expected_types.join(", "))
}
})
} else {
// there are no expected tokens
None
}
}
fn expected_token_example_msg(expected_tokens: &'static [ExpectedToken]) -> Option<String> {
if let Some(first_expected) = expected_tokens.first() {
let example = first_expected.example();
Some(format!("try a token of the expected type: {example}"))
} else {
None
}
}
impl OSDBError for ScanError { impl OSDBError for ScanError {
fn display(&self, file: &str, input: &str) { fn display(&self, file: &str, input: &str) {

View file

@ -44,10 +44,9 @@ fn expect_semicolon(tokens: &mut VecDeque<crate::tokens::Token>) -> Result<(), C
// Even at the end of input, we need a semicolon // Even at the end of input, we need a semicolon
Err(CommandParseError::UnexpectedToken( Err(CommandParseError::UnexpectedToken(
Token { Token {
location: tokens.back().map_or_else( location: tokens
|| Location::new(String::from("<unknown>"), 0, 0), .back()
|t| t.location.clone(), .map_or_else(Location::default, |t| t.location.clone()),
),
data: TokenData::EndOfFile, data: TokenData::EndOfFile,
lexeme: String::new(), lexeme: String::new(),
}, },
@ -101,14 +100,22 @@ pub fn parse(file: String, input: String) -> Result<Vec<Command>, Vec<CommandPar
TokenData::Int(_) => { TokenData::Int(_) => {
errs.push(CommandParseError::UnexpectedToken( errs.push(CommandParseError::UnexpectedToken(
token, token,
&[ExpectedToken::Statement, ExpectedToken::MetaCommand, ExpectedToken::EndOfFile], &[
ExpectedToken::Statement,
ExpectedToken::MetaCommand,
ExpectedToken::EndOfFile,
],
)); ));
skip_to_next_statement(&mut tokens); skip_to_next_statement(&mut tokens);
} }
TokenData::String(_) => { TokenData::String(_) => {
errs.push(CommandParseError::UnexpectedToken( errs.push(CommandParseError::UnexpectedToken(
token, token,
&[ExpectedToken::Statement, ExpectedToken::MetaCommand, ExpectedToken::EndOfFile], &[
ExpectedToken::Statement,
ExpectedToken::MetaCommand,
ExpectedToken::EndOfFile,
],
)); ));
skip_to_next_statement(&mut tokens); skip_to_next_statement(&mut tokens);
} }
@ -128,10 +135,9 @@ fn parse_insert_command(
let id_token = tokens.pop_front().ok_or_else(|| { let id_token = tokens.pop_front().ok_or_else(|| {
CommandParseError::UnexpectedToken( CommandParseError::UnexpectedToken(
Token { Token {
location: tokens.back().map_or_else( location: tokens
|| Location::new(String::from("<unknown>"), 0, 0), .back()
|t| t.location.clone(), .map_or_else(Location::default, |t| t.location.clone()),
),
data: TokenData::EndOfFile, data: TokenData::EndOfFile,
lexeme: String::new(), lexeme: String::new(),
}, },
@ -141,17 +147,21 @@ fn parse_insert_command(
let id = match id_token.data { let id = match id_token.data {
TokenData::Int(id) => id, TokenData::Int(id) => id,
_ => return Err(CommandParseError::UnexpectedToken(id_token, &[ExpectedToken::Integer])), _ => {
return Err(CommandParseError::UnexpectedToken(
id_token,
&[ExpectedToken::Integer],
));
}
}; };
// Parse the username (string) // Parse the username (string)
let username_token = tokens.pop_front().ok_or_else(|| { let username_token = tokens.pop_front().ok_or_else(|| {
CommandParseError::UnexpectedToken( CommandParseError::UnexpectedToken(
Token { Token {
location: tokens.back().map_or_else( location: tokens
|| Location::new(String::from("<unknown>"), 0, 0), .back()
|t| t.location.clone(), .map_or_else(Location::default, |t| t.location.clone()),
),
data: TokenData::EndOfFile, data: TokenData::EndOfFile,
lexeme: String::new(), lexeme: String::new(),
}, },
@ -173,10 +183,9 @@ fn parse_insert_command(
let email_token = tokens.pop_front().ok_or_else(|| { let email_token = tokens.pop_front().ok_or_else(|| {
CommandParseError::UnexpectedToken( CommandParseError::UnexpectedToken(
Token { Token {
location: tokens.back().map_or_else( location: tokens
|| Location::new(String::from("<unknown>"), 0, 0), .back()
|t| t.location.clone(), .map_or_else(Location::default, |t| t.location.clone()),
),
data: TokenData::EndOfFile, data: TokenData::EndOfFile,
lexeme: String::new(), lexeme: String::new(),
}, },
@ -186,7 +195,12 @@ fn parse_insert_command(
let email = match email_token.data { let email = match email_token.data {
TokenData::String(email) => email, TokenData::String(email) => email,
_ => return Err(CommandParseError::UnexpectedToken(email_token, &[ExpectedToken::String])), _ => {
return Err(CommandParseError::UnexpectedToken(
email_token,
&[ExpectedToken::String],
));
}
}; };
// Check for semicolon after the insert command // Check for semicolon after the insert command

View file

@ -30,6 +30,12 @@ impl From<&Location> for std::ops::Range<usize> {
} }
} }
impl Default for Location {
fn default() -> Self {
Self::new(String::from("<unknown>"), 0, 0)
}
}
impl Location { impl Location {
/// ``` /// ```
/// use osdb::tokens::Location; /// use osdb::tokens::Location;