feat(grammar): meta-commands must be followed by end-of-file

This commit is contained in:
Khaïs COLIN 2025-06-03 22:00:52 +02:00
parent 64d93e9a27
commit 33c4edf91d
Signed by: logistic-bot
SSH key fingerprint: SHA256:RlpiqKeXpcPFZZ4y9Ou4xi2M8OhRJovIwDlbCaMsuAo
5 changed files with 90 additions and 44 deletions

View file

@ -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 ::= "+"

View file

@ -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

View file

@ -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]

View file

@ -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,
],
),
],
)

View file

@ -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,
),