refactor(location): deduplicated usage of Location::new with same value into default
This commit is contained in:
parent
567aa31c07
commit
64d93e9a27
5 changed files with 102 additions and 45 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue