From 36070dd429af69f1042f4ee56488a960c4d8e7ef Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Tue, 31 Jul 2012 14:22:57 +0200 Subject: CommandManager: rework command parser a new type of strings is supported inspired by the ruby strings. %content, if opening delimiter is one of ([{<, then closing delimiter is the matching )]}> and balanced delimiters in the string needs not to be escaped, else the closing delimiter is the same as the opening one. shell expansion is available through %shcommand syntax. Command flags have been removed, as these strings provide proper nesting support, so now, you can for example do: def command %{ echo %sh{ ls } } --- src/command_manager.cc | 108 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 75 insertions(+), 33 deletions(-) (limited to 'src/command_manager.cc') diff --git a/src/command_manager.cc b/src/command_manager.cc index e1cbdfdd..e4cde2ba 100644 --- a/src/command_manager.cc +++ b/src/command_manager.cc @@ -17,60 +17,110 @@ bool CommandManager::command_defined(const String& command_name) const void CommandManager::register_command(const String& command_name, Command command, - unsigned flags, const CommandCompleter& completer) { - m_commands[command_name] = CommandDescriptor { command, flags, completer }; + m_commands[command_name] = CommandDescriptor { command, completer }; } void CommandManager::register_commands(const memoryview& command_names, Command command, - unsigned flags, const CommandCompleter& completer) { for (auto command_name : command_names) - register_command(command_name, command, flags, completer); + register_command(command_name, command, completer); } -static bool is_blank(char c) +static bool is_horizontal_blank(char c) { - return c == ' ' or c == '\t' or c == '\n'; + return c == ' ' or c == '\t'; } using TokenList = std::vector; using TokenPosList = std::vector>; +static bool is_command_separator(Character c) +{ + return c == ';' or c == '\n'; +} + static TokenList parse(const String& line, TokenPosList* opt_token_pos_info = NULL) { TokenList result; + size_t length = line.length(); size_t pos = 0; - while (pos < line.length()) + while (pos < length) { - while(is_blank(line[pos]) and pos != line.length()) - ++pos; + while (pos != length) + { + if (is_horizontal_blank(line[pos])) + ++pos; + else if (line[pos] == '\\' and pos+1 < length and line[pos+1] == '\n') + pos += 2; + else + break; + } size_t token_start = pos; Token::Type type = Token::Type::Raw; - if (line[pos] == '"' or line[pos] == '\'' or line[pos] == '`') + if (line[pos] == '"' or line[pos] == '\'') { char delimiter = line[pos]; - if (delimiter == '`') - type = Token::Type::ShellExpand; - token_start = ++pos; while ((line[pos] != delimiter or line[pos-1] == '\\') and - pos != line.length()) + pos != length) ++pos; + } + else if (line[pos] == '%') + { + size_t type_start = ++pos; + while (isalpha(line[pos])) + ++pos; + String type_name = line.substr(type_start, pos - type_start); + + if (type_name == "sh") + type = Token::Type::ShellExpand; + static const std::unordered_map matching_delimiters = { + { '(', ')' }, { '[', ']' }, { '{', '}' }, { '<', '>' } + }; + + Character opening_delimiter = line[pos]; + token_start = ++pos; + + auto delim_it = matching_delimiters.find(opening_delimiter); + if (delim_it != matching_delimiters.end()) + { + Character closing_delimiter = delim_it->second; + int level = 0; + while (pos != length) + { + if (line[pos-1] != '\\' and line[pos] == opening_delimiter) + ++level; + if (line[pos-1] != '\\' and line[pos] == closing_delimiter) + { + if (level > 0) + --level; + else + break; + } + ++pos; + } + } + else + { + while ((line[pos] != opening_delimiter or line[pos-1] == '\\') and + pos != length) + ++pos; + } } else - while (not is_blank(line[pos]) and pos != line.length() and - (line[pos] != ';' or line[pos-1] == '\\')) + while (pos != length and not is_horizontal_blank(line[pos]) and + (not is_command_separator(line[pos]) or line[pos-1] == '\\')) ++pos; if (token_start != pos) @@ -80,7 +130,7 @@ static TokenList parse(const String& line, result.push_back({type, line.substr(token_start, pos - token_start)}); } - if (line[pos] == ';') + if (is_command_separator(line[pos])) { if (opt_token_pos_info) opt_token_pos_info->push_back({pos, pos+1}); @@ -140,24 +190,16 @@ void CommandManager::execute(const CommandParameters& params, if (command_it == m_commands.end()) throw command_not_found(begin->content()); - if (command_it->second.flags & IgnoreSemiColons) - end = params.end(); - - if (command_it->second.flags & DeferredShellEval) - command_it->second.command(CommandParameters(begin + 1, end), context); - else + TokenList expanded_tokens; + for (auto param = begin+1; param != end; ++param) { - TokenList expanded_tokens; - for (auto param = begin+1; param != end; ++param) - { - if (param->type() == Token::Type::ShellExpand) - shell_eval(expanded_tokens, param->content(), - context, env_vars); - else - expanded_tokens.push_back(*param); - } - command_it->second.command(expanded_tokens, context); + if (param->type() == Token::Type::ShellExpand) + shell_eval(expanded_tokens, param->content(), + context, env_vars); + else + expanded_tokens.push_back(*param); } + command_it->second.command(expanded_tokens, context); } if (end == params.end()) -- cgit v1.2.3