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"
|
select ::= "select"
|
||||||
semicolon ::= ";"
|
semicolon ::= ";"
|
||||||
|
|
||||||
meta-command ::= "." "exit"
|
meta-command ::= meta-command-verb end-of-file
|
||||||
| "about"
|
meta-command-verb ::= ".exit"
|
||||||
| "version"
|
| ".about"
|
||||||
|
| ".version"
|
||||||
|
|
||||||
int ::= sign? digit+
|
int ::= sign? digit+
|
||||||
sign ::= "+"
|
sign ::= "+"
|
||||||
|
|
|
||||||
|
|
@ -267,7 +267,7 @@ Error: unexpected token
|
||||||
|
|
||||||
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: ;)".
|
||||||
* DONE 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
|
* DONE 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
|
||||||
* DONE in case of parse error, skip until next semicolon to better recover
|
* 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) => {
|
TokenData::MetaCommand(meta_command) => {
|
||||||
// Meta commands don't require semicolons per grammar
|
match parse_meta_command(meta_command, &mut tokens) {
|
||||||
cmds.push(Command::MetaCommand(meta_command));
|
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 => {
|
TokenData::Semicolon => {
|
||||||
// Empty statement (just a semicolon) - ignore it
|
// 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) }
|
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(
|
fn parse_insert_command(
|
||||||
tokens: &mut VecDeque<crate::tokens::Token>,
|
tokens: &mut VecDeque<crate::tokens::Token>,
|
||||||
) -> Result<Command, CommandParseError> {
|
) -> Result<Command, CommandParseError> {
|
||||||
|
|
@ -263,10 +286,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_multiple_correct() {
|
fn test_parse_multiple_correct() {
|
||||||
let file = String::from("<stdin>");
|
let file = String::from("<stdin>");
|
||||||
assert_debug_snapshot!(parse(
|
assert_debug_snapshot!(parse(file.clone(), String::from("select; select; select;")));
|
||||||
file.clone(),
|
}
|
||||||
String::from(".exit 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]
|
#[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
|
source: src/parser.rs
|
||||||
expression: "parse(file.clone(), String::from(\".exit select select select\"))"
|
expression: "parse(file.clone(), String::from(\"select; select; select;\"))"
|
||||||
---
|
---
|
||||||
Ok(
|
Ok(
|
||||||
[
|
[
|
||||||
MetaCommand(
|
|
||||||
Exit,
|
|
||||||
),
|
|
||||||
Statement(
|
Statement(
|
||||||
Select,
|
Select,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue