feat(grammar): meta-commands must be followed by end-of-file
This commit is contained in:
parent
64d93e9a27
commit
33c4edf91d
5 changed files with 90 additions and 44 deletions
|
|
@ -17,9 +17,10 @@ insert ::= "insert"
|
|||
select ::= "select"
|
||||
semicolon ::= ";"
|
||||
|
||||
meta-command ::= "." "exit"
|
||||
| "about"
|
||||
| "version"
|
||||
meta-command ::= meta-command-verb end-of-file
|
||||
meta-command-verb ::= ".exit"
|
||||
| ".about"
|
||||
| ".version"
|
||||
|
||||
int ::= sign? digit+
|
||||
sign ::= "+"
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ Error: unexpected token
|
|||
|
||||
This will make errors like "expected semicolon" more helpful by showing "expected semicolon (example: ;)".
|
||||
* DONE correct all instances of <unknown> in locations
|
||||
* TODO meta-commands must be followed by end-of-file
|
||||
* DONE meta-commands must be followed by end-of-file
|
||||
* TODO project code documentation
|
||||
* TODO project usage documentation
|
||||
* DONE in case of parse error, skip until next semicolon to better recover
|
||||
|
|
|
|||
|
|
@ -91,8 +91,13 @@ pub fn parse(file: String, input: String) -> Result<Vec<Command>, Vec<CommandPar
|
|||
}
|
||||
},
|
||||
TokenData::MetaCommand(meta_command) => {
|
||||
// Meta commands don't require semicolons per grammar
|
||||
cmds.push(Command::MetaCommand(meta_command));
|
||||
match parse_meta_command(meta_command, &mut tokens) {
|
||||
Ok(cmd) => cmds.push(cmd),
|
||||
Err(err) => {
|
||||
errs.push(err);
|
||||
skip_to_next_statement(&mut tokens); // Skip to next statement for error recovery
|
||||
}
|
||||
}
|
||||
}
|
||||
TokenData::Semicolon => {
|
||||
// Empty statement (just a semicolon) - ignore it
|
||||
|
|
@ -126,6 +131,24 @@ pub fn parse(file: String, input: String) -> Result<Vec<Command>, Vec<CommandPar
|
|||
if errs.is_empty() { Ok(cmds) } else { Err(errs) }
|
||||
}
|
||||
|
||||
fn parse_meta_command(
|
||||
meta_command: crate::meta_commands::MetaCommand,
|
||||
tokens: &mut VecDeque<Token>,
|
||||
) -> Result<Command, CommandParseError> {
|
||||
if let Some(token) = tokens.pop_front() {
|
||||
if matches!(token.data, TokenData::EndOfFile) {
|
||||
Ok(Command::MetaCommand(meta_command))
|
||||
} else {
|
||||
Err(CommandParseError::UnexpectedToken(
|
||||
token,
|
||||
&[ExpectedToken::EndOfFile],
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Ok(Command::MetaCommand(meta_command))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_insert_command(
|
||||
tokens: &mut VecDeque<crate::tokens::Token>,
|
||||
) -> Result<Command, CommandParseError> {
|
||||
|
|
@ -263,10 +286,13 @@ mod tests {
|
|||
#[test]
|
||||
fn test_parse_multiple_correct() {
|
||||
let file = String::from("<stdin>");
|
||||
assert_debug_snapshot!(parse(
|
||||
file.clone(),
|
||||
String::from(".exit select; select; select;")
|
||||
));
|
||||
assert_debug_snapshot!(parse(file.clone(), String::from("select; select; select;")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_meta_command_require_eof() {
|
||||
let file = String::from("<stdin>");
|
||||
assert_debug_snapshot!(parse(file.clone(), String::from(".exit select; select;")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
source: src/parser.rs
|
||||
expression: "parse(file.clone(), String::from(\".exit select; select;\"))"
|
||||
---
|
||||
Err(
|
||||
[
|
||||
UnexpectedToken(
|
||||
Token {
|
||||
location: Location {
|
||||
file: "<stdin>",
|
||||
offset: 6,
|
||||
length: 6,
|
||||
},
|
||||
data: Select,
|
||||
lexeme: "select",
|
||||
},
|
||||
[
|
||||
EndOfFile,
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
---
|
||||
source: src/parser.rs
|
||||
expression: "parse(file.clone(), String::from(\".exit select select select\"))"
|
||||
expression: "parse(file.clone(), String::from(\"select; select; select;\"))"
|
||||
---
|
||||
Ok(
|
||||
[
|
||||
MetaCommand(
|
||||
Exit,
|
||||
),
|
||||
Statement(
|
||||
Select,
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue