parser: a mode must end at a word boundary

A single-letter mode was stolen from the middle of a longer word, so `gen testG r
now` mis-parsed as name=r, mode=n (the 'n' of 'now'), leaving 'ow' dangling and
failing with a trailing-input error. Require a non-alphanumeric after a mode so 'now'
parses as the date it is. Adds a regression test.
This commit is contained in:
Oleksandr Kozachuk
2026-06-09 23:24:57 +02:00
parent 0dd173f113
commit 05613813de
2 changed files with 22 additions and 1 deletions
+22 -1
View File
@@ -85,7 +85,10 @@ peg::parser! {
_ => Err("unknown mode"), _ => Err("unknown mode"),
} }
} }
rule mode() -> Mode = m:(umode() / rmode()) { m } // A mode is a whole token: it must not be followed by another letter/digit, or
// a single letter would be stolen from a longer word (e.g. the `n` of `now`,
// making `name r now` mis-parse as name=`r`, mode=`n` instead of date=`now`).
rule mode() -> Mode = m:(umode() / rmode()) !['0'..='9' | 'a'..='z' | 'A'..='Z'] { m }
rule noop_cmd() -> Command<'input> = ("#" [' '..='~']*)? { Command::Noop } rule noop_cmd() -> Command<'input> = ("#" [' '..='~']*)? { Command::Noop }
rule help_cmd() -> Command<'input> = "help" { Command::Help } rule help_cmd() -> Command<'input> = "help" { Command::Help }
@@ -269,6 +272,24 @@ add t3 C 99 2022-12-14
assert_eq!(p3.comment, None); assert_eq!(p3.comment, None);
} }
#[test]
fn parse_mode_word_boundary_test() {
// `now` must be the date, not mode `n` + leftover `ow`: name=testG mode=Regular.
let g = command_parser::name("testG r now").unwrap();
assert_eq!(g.name, "testG");
assert_eq!(g.prefix, None);
assert_eq!(g.mode, Mode::Regular);
// the whole gen command parses with no trailing-input error (so `now` was
// consumed as the date, not as a mode that left `ow` dangling)
assert!(command_parser::cmd("gen testG r now").is_ok());
assert!(command_parser::cmd("gen testG R now").is_ok());
// a length+mode token still works right up to a word boundary
let h = command_parser::name("github 20R").unwrap();
assert_eq!(h.name, "github");
assert_eq!(h.length, Some(20));
assert_eq!(h.mode, Mode::Regular);
}
#[test] #[test]
fn parse_password_test() { fn parse_password_test() {
assert_eq!( assert_eq!(
Binary file not shown.