summaryrefslogtreecommitdiff
path: root/src/command_manager.cc
diff options
context:
space:
mode:
authorMaxime Coste <mawww@kakoune.org>2018-02-15 21:23:12 +1100
committerMaxime Coste <mawww@kakoune.org>2018-02-15 23:24:19 +1100
commitbde1f5349d218298de04ee453b304de3c053800e (patch)
treec3a3e50c2e0c1430ad96feed0eaeb6666b694ec5 /src/command_manager.cc
parentd95530b1715d959a08e899796149f6d152f543cf (diff)
CommandManager: refactor parsing of commands to iterate through tokens
Avoid storing a big vector of tokens, read them one by one, and store only the current command.
Diffstat (limited to 'src/command_manager.cc')
-rw-r--r--src/command_manager.cc291
1 files changed, 127 insertions, 164 deletions
diff --git a/src/command_manager.cc b/src/command_manager.cc
index e5854b7b..cbc18954 100644
--- a/src/command_manager.cc
+++ b/src/command_manager.cc
@@ -43,58 +43,23 @@ struct parse_error : runtime_error
: runtime_error{format("parse error: {}", error)} {}
};
-namespace
+Codepoint Reader::operator*() const
{
+ kak_assert(pos < str.end());
+ return utf8::codepoint(pos, str.end());
+}
-struct Reader
+Reader& Reader::operator++()
{
-public:
- Reader(StringView s) : str{s}, pos{s.begin()}, line_start{s.begin()}, line{} {}
-
- [[gnu::always_inline]]
- Codepoint operator*() const
- {
- kak_assert(pos < str.end());
- return utf8::codepoint(pos, str.end());
- }
-
- Reader& operator++()
- {
- kak_assert(pos < str.end());
- if (*pos == '\n')
- ++line;
- utf8::to_next(pos, str.end());
- return *this;
- }
-
- [[gnu::always_inline]]
- explicit operator bool() const { return pos < str.end(); }
-
- [[gnu::always_inline]]
- StringView substr_from(const char* start) const
- {
- kak_assert(start <= pos);
- return {start, pos};
- }
-
- Optional<Codepoint> peek_next() const
- {
- auto next = utf8::next(pos, str.end());
- if (next != str.end())
- return utf8::codepoint(next, str.end());
- return {};
- }
-
- BufferCoord coord() const
- {
- return {line, (int)(pos - line_start)};
- }
+ kak_assert(pos < str.end());
+ if (*pos == '\n')
+ ++line;
+ utf8::to_next(pos, str.end());
+ return *this;
+}
- StringView str;
- const char* pos;
- const char* line_start;
- LineCount line;
-};
+namespace
+{
bool is_command_separator(Codepoint c)
{
@@ -160,8 +125,7 @@ StringView get_until_closing_delimiter(Reader& reader, Codepoint opening_delimit
return reader.substr_from(start);
}
-template<bool throw_on_invalid>
-Token::Type token_type(StringView type_name)
+Token::Type token_type(StringView type_name, bool throw_on_invalid)
{
if (type_name == "")
return Token::Type::RawQuoted;
@@ -188,7 +152,8 @@ void skip_blanks_and_comments(Reader& reader)
const Codepoint c = *reader;
if (is_horizontal_blank(c))
++reader;
- else if (c == '\\' and reader.peek_next().value_or((Codepoint)'\0') == '\n')
+ else if (c == '\\' and reader.pos + 1 != reader.str.end() and
+ *(reader.pos + 1) == '\n')
++(++reader);
else if (c == '#')
{
@@ -200,8 +165,7 @@ void skip_blanks_and_comments(Reader& reader)
}
}
-template<bool throw_on_unterminated>
-Token parse_percent_token(Reader& reader)
+Token parse_percent_token(Reader& reader, bool throw_on_unterminated)
{
++reader;
const auto type_start = reader.pos;
@@ -217,7 +181,7 @@ Token parse_percent_token(Reader& reader)
return {};
}
- Token::Type type = token_type<throw_on_unterminated>(type_name);
+ Token::Type type = token_type(type_name, throw_on_unterminated);
constexpr struct CharPair { Codepoint opening; Codepoint closing; } matching_pairs[] = {
{ '(', ')' }, { '[', ']' }, { '{', '}' }, { '<', '>' }
@@ -242,7 +206,7 @@ Token parse_percent_token(Reader& reader)
coord.line, coord.column, type_name,
opening_delimiter, closing_delimiter)};
- return {type, start - str_beg, reader.pos - str_beg, coord, token.str()};
+ return {type, start - str_beg, coord, token.str()};
}
else
{
@@ -253,7 +217,7 @@ Token parse_percent_token(Reader& reader)
coord.line, coord.column, type_name,
opening_delimiter, opening_delimiter)};
- return {type, start - str_beg, reader.pos - str_beg, coord, std::move(token)};
+ return {type, start - str_beg, coord, std::move(token)};
}
}
@@ -278,7 +242,6 @@ String expand_token(const Token& token, const Context& context,
}
str.resize(str.length() - trailing_eol_count, 0);
return str;
-
}
case Token::Type::RegisterExpand:
return context.main_sel_register_value(content).str();
@@ -314,55 +277,51 @@ String expand_token(const Token& token, const Context& context,
}
-template<bool throw_on_unterminated>
-TokenList parse(StringView line)
-{
- TokenList result;
+CommandParser::CommandParser(StringView command_line) : m_reader{command_line} {}
- Reader reader{line};
- while (true)
- {
- skip_blanks_and_comments(reader);
- if (not reader)
- break;
-
- const char* start = reader.pos;
- auto coord = reader.coord();
-
- const Codepoint c = *reader;
- if (c == '"' or c == '\'')
- {
- start = (++reader).pos;
- String token = get_until_delimiter(reader, c);
- if (throw_on_unterminated and not reader)
- throw parse_error{format("unterminated string {0}...{0}", c)};
- result.push_back({c == '"' ? Token::Type::RawEval
- : Token::Type::RawQuoted,
- start - line.begin(), reader.pos - line.begin(), coord, std::move(token)});
- }
- else if (c == '%')
- result.push_back(
- parse_percent_token<throw_on_unterminated>(reader));
- else
- {
- String str = get_until_delimiter(reader, [](Codepoint c) {
- return is_command_separator(c) or is_horizontal_blank(c);
- });
+Optional<Token> CommandParser::read_token(bool throw_on_unterminated)
+{
+ skip_blanks_and_comments(m_reader);
+ if (not m_reader)
+ return {};
- if (not str.empty())
- result.push_back({Token::Type::Raw, start - line.begin(), reader.pos - line.begin(),
- coord, unescape(str, "%", '\\')});
+ const StringView line = m_reader.str;
+ const char* start = m_reader.pos;
+ auto coord = m_reader.coord();
- if (reader and is_command_separator(*reader))
- result.push_back({Token::Type::CommandSeparator,
- reader.pos - line.begin(), utf8::next(reader.pos, line.end()) - line.begin(), coord, {}});
- }
-
- if (not reader)
- break;
- ++reader;
+ const Codepoint c = *m_reader;
+ if (c == '"' or c == '\'')
+ {
+ start = (++m_reader).pos;
+ String token = get_until_delimiter(m_reader, c);
+ if (throw_on_unterminated and not m_reader)
+ throw parse_error{format("unterminated string {0}...{0}", c)};
+ ++m_reader;
+ return Token{c == '"' ? Token::Type::RawEval
+ : Token::Type::RawQuoted,
+ start - line.begin(), coord, std::move(token)};
+ }
+ else if (c == '%')
+ {
+ auto token = parse_percent_token(m_reader, throw_on_unterminated);
+ ++m_reader;
+ return token;
}
- return result;
+ else if (is_command_separator(*m_reader))
+ {
+ ++m_reader;
+ return Token{Token::Type::CommandSeparator,
+ m_reader.pos - line.begin(), coord, {}};
+ }
+ else
+ {
+ String str = get_until_delimiter(m_reader, [](Codepoint c) {
+ return is_command_separator(c) or is_horizontal_blank(c);
+ });
+ return Token{Token::Type::Raw, start - line.begin(),
+ coord, unescape(str, "%", '\\')};
+ }
+ return {};
}
template<typename Postprocess>
@@ -389,7 +348,7 @@ String expand_impl(StringView str, const Context& context,
else if (c == '%')
{
res += reader.substr_from(beg);
- res += postprocess(expand_token(parse_percent_token<true>(reader),
+ res += postprocess(expand_token(parse_percent_token(reader, true),
context, shell_context));
beg = (++reader).pos;
}
@@ -475,18 +434,29 @@ void CommandManager::execute_single_command(CommandParameters params,
void CommandManager::execute(StringView command_line,
Context& context, const ShellContext& shell_context)
{
- TokenList tokens = parse<true>(command_line);
- if (tokens.empty())
- return;
- // Tokens are going to be read as a stack
- std::reverse(tokens.begin(), tokens.end());
+ CommandParser parser(command_line);
+ struct ShellParser {
+ ShellParser(String&& str) : output{std::move(str)}, parser{output} {}
+ String output;
+ CommandParser parser;
+ };
+ Vector<ShellParser> shell_parser_stack;
+
+ auto next_token = [&] {
+ while (not shell_parser_stack.empty())
+ {
+ if (auto shell_token = shell_parser_stack.back().parser.read_token(true))
+ return shell_token;
+ shell_parser_stack.pop_back();
+ }
+ return parser.read_token(true);
+ };
BufferCoord command_coord;
Vector<String> params;
- while (not tokens.empty())
+ while (Optional<Token> token_opt = next_token())
{
- Token token = std::move(tokens.back());
- tokens.pop_back();
+ auto& token = *token_opt;
if (params.empty())
command_coord = token.coord;
@@ -497,13 +467,7 @@ void CommandManager::execute(StringView command_line,
}
// Shell expand are retokenized
else if (token.type == Token::Type::ShellExpand)
- {
- auto new_tokens = parse<true>(expand_token(token, context,
- shell_context));
- tokens.insert(tokens.end(),
- std::make_move_iterator(new_tokens.rbegin()),
- std::make_move_iterator(new_tokens.rend()));
- }
+ shell_parser_stack.emplace_back(expand_token(token, context, shell_context));
else if (token.type == Token::Type::ArgExpand and token.content == '@')
params.insert(params.end(), shell_context.params.begin(),
shell_context.params.end());
@@ -515,20 +479,22 @@ void CommandManager::execute(StringView command_line,
Optional<CommandInfo> CommandManager::command_info(const Context& context, StringView command_line) const
{
- TokenList tokens = parse<false>(command_line);
- size_t cmd_idx = 0;
- for (size_t i = 0; i < tokens.size(); ++i)
+ CommandParser parser{command_line};
+ Vector<Token> tokens;
+ while (auto token = parser.read_token(false))
{
- if (tokens[i].type == Token::Type::CommandSeparator)
- cmd_idx = i+1;
+ if (token->type == Token::Type::CommandSeparator)
+ tokens.clear();
+ else
+ tokens.push_back(std::move(*token));
}
- if (cmd_idx == tokens.size() or
- (tokens[cmd_idx].type != Token::Type::Raw and
- tokens[cmd_idx].type != Token::Type::RawQuoted))
+ if (tokens.empty() or
+ (tokens.front().type != Token::Type::Raw and
+ tokens.front().type != Token::Type::RawQuoted))
return {};
- auto cmd = find_command(context, tokens[cmd_idx].content);
+ auto cmd = find_command(context, tokens.front().content);
if (cmd == m_commands.end())
return {};
@@ -540,9 +506,7 @@ Optional<CommandInfo> CommandManager::command_info(const Context& context, Strin
if (cmd->value.helper)
{
Vector<String> params;
- for (auto it = tokens.begin() + cmd_idx + 1;
- it != tokens.end() and it->type != Token::Type::CommandSeparator;
- ++it)
+ for (auto it = tokens.begin() + 1; it != tokens.end(); ++it)
{
if (it->type == Token::Type::Raw or
it->type == Token::Type::RawQuoted or
@@ -560,7 +524,6 @@ Optional<CommandInfo> CommandManager::command_info(const Context& context, Strin
if (not aliases.empty())
res.info += format("Aliases:{}\n", aliases);
-
auto& switches = cmd->value.param_desc.switches;
if (not switches.empty())
res.info += format("Switches:\n{}", indent(generate_switches_doc(switches)));
@@ -582,66 +545,68 @@ Completions CommandManager::complete(const Context& context,
StringView command_line,
ByteCount cursor_pos)
{
- TokenList tokens = parse<false>(command_line);
+ CommandParser parser{command_line};
+ const char* cursor = command_line.begin() + (int)cursor_pos;
+ Vector<Token> tokens;
- size_t cmd_idx = 0;
- size_t tok_idx = tokens.size();
- for (size_t i = 0; i < tokens.size(); ++i)
+ bool is_last_token = true;
+ while (auto token = parser.read_token(false))
{
- if (tokens[i].type == Token::Type::CommandSeparator)
- cmd_idx = i+1;
+ if (token->type == Token::Type::CommandSeparator)
+ {
+ tokens.clear();
+ continue;
+ }
- if (tokens[i].begin <= cursor_pos and tokens[i].end >= cursor_pos)
+ tokens.push_back(std::move(*token));
+ if (parser.pos() >= cursor)
{
- tok_idx = i;
+ is_last_token = false;
break;
}
}
- const bool is_last_token = tok_idx == tokens.size();
+ if (is_last_token)
+ tokens.push_back({Token::Type::Raw, command_line.length(), parser.coord(), {}});
+ kak_assert(not tokens.empty());
+ const auto& token = tokens.back();
+
// command name completion
- if (tokens.empty() or
- (tok_idx == cmd_idx and (is_last_token or
- tokens[tok_idx].type == Token::Type::Raw or
- tokens[tok_idx].type == Token::Type::RawQuoted)))
+ if (tokens.size() == 1 and (token.type == Token::Type::Raw or
+ token.type == Token::Type::RawQuoted))
{
- auto cmd_start = is_last_token ? cursor_pos : tokens[tok_idx].begin;
+ auto cmd_start = token.pos;
StringView query = command_line.substr(cmd_start, cursor_pos - cmd_start);
return offset_pos(complete_command_name(context, query), cmd_start);
}
- kak_assert(not tokens.empty());
-
- ByteCount start = tok_idx < tokens.size() ?
- tokens[tok_idx].begin : cursor_pos;
- ByteCount cursor_pos_in_token = cursor_pos - start;
+ const ByteCount start = token.pos;
+ const ByteCount cursor_pos_in_token = cursor_pos - start;
- const Token::Type type = tok_idx < tokens.size() ?
- tokens[tok_idx].type : Token::Type::Raw;
- switch (type)
+ switch (token.type)
{
case Token::Type::OptionExpand:
return {start , cursor_pos,
GlobalScope::instance().option_registry().complete_option_name(
- tokens[tok_idx].content, cursor_pos_in_token) };
+ token.content, cursor_pos_in_token) };
case Token::Type::ShellExpand:
- return offset_pos(shell_complete(context, flags, tokens[tok_idx].content,
+ return offset_pos(shell_complete(context, flags, token.content,
cursor_pos_in_token), start);
case Token::Type::ValExpand:
return {start , cursor_pos,
ShellManager::instance().complete_env_var(
- tokens[tok_idx].content, cursor_pos_in_token) };
+ token.content, cursor_pos_in_token) };
case Token::Type::Raw:
case Token::Type::RawQuoted:
case Token::Type::RawEval:
{
- if (tokens[cmd_idx].type != Token::Type::Raw)
+ if (token.type != Token::Type::Raw)
return Completions{};
- StringView command_name = tokens[cmd_idx].content;
+ StringView command_name = tokens.front().content;
if (command_name != m_last_complete_command)
{
m_last_complete_command = command_name.str();
@@ -654,17 +619,15 @@ Completions CommandManager::complete(const Context& context,
return Completions();
Vector<String> params;
- for (auto it = tokens.begin() + cmd_idx + 1; it != tokens.end(); ++it)
+ for (auto it = tokens.begin() + 1; it != tokens.end(); ++it)
params.push_back(it->content);
- if (tok_idx == tokens.size())
- params.emplace_back("");
Completions completions = offset_pos(command_it->value.completer(
- context, flags, params, tok_idx - cmd_idx - 1,
+ context, flags, params, tokens.size() - 2,
cursor_pos_in_token), start);
- if (type != Token::Type::RawQuoted)
+ if (token.type != Token::Type::RawQuoted)
{
- StringView to_escape = type == Token::Type::Raw ? "% \t;" : "%";
+ StringView to_escape = token.type == Token::Type::Raw ? "% \t;" : "%";
for (auto& candidate : completions.candidates)
candidate = escape(candidate, to_escape, '\\');
}