Refactor subcommand keyword detection

This file has bitrotted; for example commit bc66921ac9 (Optimize
keyword detection, 2019-04-03) removed use of SKIP_KEYWORDS but
confusingly it's still around (even after 0118eafee1 (Remove unused
functions, members (and a variable), 2022-04-09).

Also some keywords appear in multiple lists; the separate lists are
not really used today; there is a comment stating they may be useful
in future.

It would be great to add an optimization back but either way we should
present the set of reserved words in source code as a contiguous list,
to make it easy for humans to see all relevant information.
This commit is contained in:
Johannes Altmanninger
2025-01-12 08:20:07 +01:00
parent c7adb572d0
commit f5b2928cbb

View File

@@ -2,64 +2,77 @@
use crate::wchar::prelude::*;
const SKIP_KEYWORDS: &[&wstr] = &[L!("else"), L!("begin")];
const SUBCOMMAND_KEYWORDS: &[&wstr] = &[
L!("and"),
L!("begin"),
L!("builtin"),
L!("command"),
L!("exec"),
L!("if"),
L!("not"),
L!("or"),
L!("time"),
L!("while"),
];
const BLOCK_KEYWORDS: &[&wstr] = &[
L!("begin"),
L!("for"),
L!("function"),
L!("if"),
L!("switch"),
L!("while"),
];
struct ReservedWord {
text: &'static wstr,
is_super_command: bool,
}
macro_rules! rw {
( ( $text:literal ) ) => {
ReservedWord {
text: L!($text),
is_super_command: false,
}
};
( ( $text:literal, [subcommand] ) ) => {
ReservedWord {
text: L!($text),
is_super_command: true,
}
};
}
macro_rules! reserved_words {
( $( $reserved_word:tt , ) * ) => {
&[ $( rw!($reserved_word), )* ]
}
}
// Don't forget to add any new reserved keywords to the documentation
const RESERVED_KEYWORDS: &[&wstr] = &[
L!("["),
L!("_"),
L!("argparse"),
L!("break"),
L!("case"),
L!("continue"),
L!("else"),
L!("end"),
L!("eval"),
L!("read"),
L!("return"),
L!("set"),
L!("status"),
L!("string"),
L!("test"),
];
const RESERVED_WORDS: &[ReservedWord] = reserved_words!(
("["),
("_"),
("and", [subcommand]),
("argparse"),
("begin", [subcommand]),
("break"),
("builtin", [subcommand]),
("case"),
("command", [subcommand]),
("continue"),
("else"),
("end"),
("eval"),
("exec", [subcommand]),
("for"),
("function"),
("if", [subcommand]),
("not", [subcommand]),
("or", [subcommand]),
("read"),
("return"),
("set"),
("status"),
("string"),
("switch"),
("test"),
("time", [subcommand]),
("while", [subcommand]),
);
// The lists above are purposely implemented separately from the logic below, so that future
// maintainers may assume the contents of the list based off their names, and not off what the
// functions below require them to contain.
fn reserved_word(cmd: &wstr) -> Option<&'static ReservedWord> {
RESERVED_WORDS
.iter()
.find(|reserved_word| reserved_word.text == cmd)
}
/// Tests if the specified commands parameters should be interpreted as another command, which will
/// be true if the command is either 'command', 'exec', 'if', 'while', or 'builtin'. This does not
/// handle "else if" which is more complicated.
/// Tests if the specified command's parameters should be interpreted as another command.
pub fn parser_keywords_is_subcommand(cmd: &wstr) -> bool {
SUBCOMMAND_KEYWORDS.contains(&cmd) || SKIP_KEYWORDS.contains(&cmd)
reserved_word(cmd).is_some_and(|reserved_word| reserved_word.is_super_command)
}
/// Tests if the specified command is a reserved word, i.e. if it is the name of one of the builtin
/// functions that change the block or command scope, like 'for', 'end' or 'command' or 'exec'.
/// These functions may not be overloaded, so their names are reserved.
pub fn parser_keywords_is_reserved(word: &wstr) -> bool {
SUBCOMMAND_KEYWORDS.contains(&word)
|| SKIP_KEYWORDS.contains(&word)
|| BLOCK_KEYWORDS.contains(&word)
|| RESERVED_KEYWORDS.contains(&word)
reserved_word(word).is_some()
}