diff --git a/grammar.ebnf b/grammar.ebnf index 2f39fd3..ff4bd2e 100644 --- a/grammar.ebnf +++ b/grammar.ebnf @@ -1,42 +1,43 @@ /* token is first stage of parsing */ -token ::= insert - | select - | meta-command - | int - | string - | semicolon - | end-of-file +token ::= insert + | select + | meta-command + | int + | string + | semicolon + | end-of-file /* command is second stage of parsing */ -command ::= cmd-insert semicolon - | cmd-select semicolon -cmd-insert ::= insert int string string -cmd-select ::= select +command ::= cmd-insert semicolon + | cmd-select semicolon +cmd-insert ::= insert int string string +cmd-select ::= select -insert ::= "insert" -select ::= "select" -semicolon ::= ";" +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 ::= "+" - | "-" -digit ::= "0" - | "1" - | "2" - | "3" - | "4" - | "5" - | "6" - | "7" - | "8" - | "9" +int ::= sign? digit+ +sign ::= "+" + | "-" +digit ::= "0" + | "1" + | "2" + | "3" + | "4" + | "5" + | "6" + | "7" + | "8" + | "9" -string ::= '"' string-char* '"' -string-char ::= '\' utf8-char - | utf8-char-not-dbl-quote +string ::= '"' string-char* '"' +string-char ::= '\' utf8-char + | utf8-char-not-dbl-quote diff --git a/notes.org b/notes.org index 64f3976..bf78fcf 100644 --- a/notes.org +++ b/notes.org @@ -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 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 diff --git a/src/parser.rs b/src/parser.rs index 3aa5ba3..181f3e3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -91,8 +91,13 @@ pub fn parse(file: String, input: String) -> Result, Vec { - // 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, +) -> Result { + 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, ) -> Result { @@ -263,10 +286,13 @@ mod tests { #[test] fn test_parse_multiple_correct() { let file = String::from(""); - 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(""); + assert_debug_snapshot!(parse(file.clone(), String::from(".exit select; select;"))); } #[test] diff --git a/src/snapshots/osdb__parser__tests__meta_command_require_eof.snap b/src/snapshots/osdb__parser__tests__meta_command_require_eof.snap new file mode 100644 index 0000000..fb9fffe --- /dev/null +++ b/src/snapshots/osdb__parser__tests__meta_command_require_eof.snap @@ -0,0 +1,22 @@ +--- +source: src/parser.rs +expression: "parse(file.clone(), String::from(\".exit select; select;\"))" +--- +Err( + [ + UnexpectedToken( + Token { + location: Location { + file: "", + offset: 6, + length: 6, + }, + data: Select, + lexeme: "select", + }, + [ + EndOfFile, + ], + ), + ], +) diff --git a/src/snapshots/osdb__parser__tests__parse_multiple_correct.snap b/src/snapshots/osdb__parser__tests__parse_multiple_correct.snap index 4642731..e3f4512 100644 --- a/src/snapshots/osdb__parser__tests__parse_multiple_correct.snap +++ b/src/snapshots/osdb__parser__tests__parse_multiple_correct.snap @@ -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, ),