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
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 project code documentation
* TODO project usage documentation

View file

@ -1,6 +1,6 @@
use crate::meta_commands::{MetaCommand, MetaCommandExecuteResult};
use crate::statements::{Statement, StatementExecuteResult};
use crate::tokens::{ScanError, Token};
use crate::tokens::{Location, ScanError, Token};
#[derive(Debug)]
pub enum Command {
@ -88,6 +88,7 @@ impl std::fmt::Display for ExpectedToken {
pub enum CommandParseError {
Scan(ScanError),
UnexpectedToken(Token, &'static [ExpectedToken]),
UnexpectedEndOfFile(Location, &'static [ExpectedToken]),
}
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};
pub trait OSDBError {
@ -22,33 +25,66 @@ impl OSDBError for CommandParseError {
.with_message(format!("found {token}")),
);
// If we have expected tokens, show an example for the first one
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 = add_expected_tokens_to_report(report, expected_tokens);
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 {

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

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 {
/// ```
/// use osdb::tokens::Location;