diff --git a/src/parser_keywords.cpp b/src/parser_keywords.cpp index 4711d178d..7f1bdecc7 100644 --- a/src/parser_keywords.cpp +++ b/src/parser_keywords.cpp @@ -8,26 +8,82 @@ #include "fallback.h" // IWYU pragma: keep #include "parser_keywords.h" +typedef std::unordered_set string_set_t; + +static const wcstring skip_keywords[] { + L"else", + L"begin", +}; + +static const wcstring subcommand_keywords[] { + L"command", L"builtin", L"while", L"exec", + L"if", L"and", L"or", L"not" +}; + +static const string_set_t block_keywords = { + L"for", L"while", L"if", + L"function", L"switch", L"begin" +}; + +static const wcstring reserved_keywords[] = { + L"end", L"case", L"else", L"return", + L"continue", L"break", L"argparse", L"read", + L"set", L"status", L"test", L"[" +}; + +// 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. + +static size_t list_max_length(const string_set_t &list) { + size_t result = 0; + for (const auto &w: list) { + if (w.length() > result) { + result = w.length(); + } + } + return result; +} + bool parser_keywords_skip_arguments(const wcstring &cmd) { - static const wcstring el = L"else"; - static const wcstring beg = L"begin"; - return cmd == el || cmd == beg; + return cmd == skip_keywords[0] || cmd == skip_keywords[1]; } -static const std::unordered_set subcommand_keywords = {L"command", L"builtin", L"while", L"exec", - L"if", L"and", L"or", L"not"}; bool parser_keywords_is_subcommand(const wcstring &cmd) { - return parser_keywords_skip_arguments(cmd) || contains(subcommand_keywords, cmd); + const static string_set_t search_list = ([](){ + string_set_t results; + results.insert(std::begin(subcommand_keywords), std::end(subcommand_keywords)); + results.insert(std::begin(skip_keywords), std::end(skip_keywords)); + return results; + })(); + + const static auto max_len = list_max_length(search_list); + const static auto not_found = search_list.end(); + + // Everything above is executed only at startup, this is the actual optimized search routine: + return cmd.length() <= max_len && search_list.find(cmd) != not_found; } -static const std::unordered_set block_keywords = {L"for", L"while", L"if", - L"function", L"switch", L"begin"}; -bool parser_keywords_is_block(const wcstring &word) { return contains(block_keywords, word); } +bool parser_keywords_is_block(const wcstring &word) { + const static auto max_len = list_max_length(block_keywords); + const static auto not_found = block_keywords.end(); + + // Everything above is executed only at startup, this is the actual optimized search routine: + return word.length() <= max_len && block_keywords.find(word) != not_found; +} -static const std::unordered_set reserved_keywords = {L"end", L"case", L"else", L"return", - L"continue", L"break", L"argparse", L"read", - L"set", L"status", L"test", L"["}; bool parser_keywords_is_reserved(const wcstring &word) { - return parser_keywords_is_block(word) || parser_keywords_is_subcommand(word) || - contains(reserved_keywords, word); + const static string_set_t search_list = ([](){ + string_set_t results; + results.insert(std::begin(subcommand_keywords), std::end(subcommand_keywords)); + results.insert(std::begin(skip_keywords), std::end(skip_keywords)); + results.insert(std::begin(block_keywords), std::end(block_keywords)); + results.insert(std::begin(reserved_keywords), std::end(reserved_keywords)); + return results; + })(); + const static auto max_len = list_max_length(search_list); + const static auto not_found = search_list.end(); + + // Everything above is executed only at startup, this is the actual optimized search routine: + return word.length() <= max_len && search_list.find(word) != not_found; }