From 64d93e9a27e5df54658beaa589f33e72119ffe5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kha=C3=AFs=20COLIN?= Date: Tue, 3 Jun 2025 20:09:22 +0200 Subject: [PATCH] refactor(location): deduplicated usage of Location::new with same value into default --- notes.org | 2 +- src/command.rs | 3 +- src/error_display.rs | 82 +++++++++++++++++++++++++++++++------------- src/parser.rs | 54 ++++++++++++++++++----------- src/tokens.rs | 6 ++++ 5 files changed, 102 insertions(+), 45 deletions(-) diff --git a/notes.org b/notes.org index 6ea2e37..64f3976 100644 --- a/notes.org +++ b/notes.org @@ -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 in locations +* DONE correct all instances of in locations * TODO meta-commands must be followed by end-of-file * TODO project code documentation * TODO project usage documentation diff --git a/src/command.rs b/src/command.rs index 43204a6..10b1a97 100644 --- a/src/command.rs +++ b/src/command.rs @@ -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 for Command { diff --git a/src/error_display.rs b/src/error_display.rs index 92c8936..6466248 100644 --- a/src/error_display.rs +++ b/src/error_display.rs @@ -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,35 +25,68 @@ 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::>::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)>; + +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 { + 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 { + 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 { fn display(&self, file: &str, input: &str) { let location = (file, Into::>::into(&self.location)); diff --git a/src/parser.rs b/src/parser.rs index 912aba1..3aa5ba3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -44,10 +44,9 @@ fn expect_semicolon(tokens: &mut VecDeque) -> 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(""), 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 { 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(""), 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(""), 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(""), 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 diff --git a/src/tokens.rs b/src/tokens.rs index ed2f23b..88610d2 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -30,6 +30,12 @@ impl From<&Location> for std::ops::Range { } } +impl Default for Location { + fn default() -> Self { + Self::new(String::from(""), 0, 0) + } +} + impl Location { /// ``` /// use osdb::tokens::Location;