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
|
- 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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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,33 +25,66 @@ 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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue