From b1f31d2e1285b6586f22f1b0616a3767148377b9 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Thu, 27 Jun 2013 19:07:26 +0100 Subject: [PATCH] change string parsing behaviour in command manager * single and doubly quoted strings correctly remove the \ when their delimiter is quoted ('\'' is interpreted as ', not \') * %{} strings do not support quoting when using matching pairs, so %{\} is interpreted as \, however when using same ending character as delimiter quoting still works: %~\~~ is interpreted as ~. --- README.asciidoc | 9 +++--- src/command_manager.cc | 66 +++++++++++++++++++++++++++--------------- 2 files changed, 46 insertions(+), 29 deletions(-) diff --git a/README.asciidoc b/README.asciidoc index dc55d8f0..f1e760f3 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -291,17 +291,16 @@ if you want to give parameters with spaces, you should quote them. Kakoune support three string syntax: * +"strings" and \'strings\'+: classic strings, use \' or \" to escape the - separator. + separator. * +%\{strings\}+: these strings are very useful when entering commands - the '{' and '}' delimiter are configurable: you can use any non alphanumeric character. like %[string], %, %(string), %~string~ or %!string!... - - if the character following the % is one of {[(<, then - the closing one is the matching }])>, and these delimiters in the string - need not to be escaped if the contained delimiters are balanced. - for example +%{ roger {}; }+ is a valid string. + - if the character following the % is one of {[(<, then the closing one is + the matching }])> and the delimiters are not escapable but are nestable. + for example +%{ roger {}; }+ is a valid string, +%{ marcel \}+ as well. Highlighters ------------ diff --git a/src/command_manager.cc b/src/command_manager.cc index 7de7209a..1f199fd0 100644 --- a/src/command_manager.cc +++ b/src/command_manager.cc @@ -91,6 +91,26 @@ struct unknown_expand : parse_error : parse_error{"unknown expand '" + name + "'"} {} }; +static String get_until_delimiter(const String& base, ByteCount& pos, char delimiter) +{ + const ByteCount length = base.length(); + String str; + while (true) + { + char c = base[pos]; + if (c == delimiter) + { + if (base[pos-1] != '\\') + return str; + str.back() = delimiter; + } + else + str += c; + if (++pos == length) + throw unterminated_string(String{delimiter}, String{delimiter}); + } +} + TokenList parse(const String& line, TokenPosList* opt_token_pos_info = nullptr) { @@ -118,18 +138,15 @@ TokenList parse(const String& line, ByteCount token_start = pos; ByteCount start_pos = pos; - Token::Type type = Token::Type::Raw; if (line[pos] == '"' or line[pos] == '\'') { char delimiter = line[pos]; token_start = ++pos; - - while ((line[pos] != delimiter or line[pos-1] == '\\') and - pos != length) - ++pos; - if (pos == length) - throw unterminated_string(String{delimiter}, String{delimiter}); + String token = get_until_delimiter(line, pos, delimiter); + result.emplace_back(Token::Type::Raw, std::move(token)); + if (opt_token_pos_info) + opt_token_pos_info->push_back({token_start, pos}); } else if (line[pos] == '%') { @@ -141,6 +158,7 @@ TokenList parse(const String& line, if (pos == length) throw parse_error{"expected a string delimiter after '%" + type_name + "'"}; + Token::Type type = Token::Type::Raw; if (type_name == "sh") type = Token::Type::ShellExpand; else if (type_name == "reg") @@ -164,9 +182,9 @@ TokenList parse(const String& line, int level = 0; while (pos != length) { - if (line[pos-1] != '\\' and line[pos] == opening_delimiter) + if (line[pos] == opening_delimiter) ++level; - if (line[pos-1] != '\\' and line[pos] == closing_delimiter) + else if (line[pos] == closing_delimiter) { if (level > 0) --level; @@ -178,32 +196,32 @@ TokenList parse(const String& line, if (pos == length) throw unterminated_string("%" + type_name + opening_delimiter, String{closing_delimiter}, level); + result.emplace_back(type, line.substr(token_start, pos - token_start)); } else { - while (pos != length and - (line[pos] != opening_delimiter or line[pos-1] == '\\')) - ++pos; - if (pos == length) - throw unterminated_string("%" + type_name + opening_delimiter, - String{opening_delimiter}); + String token = get_until_delimiter(line, pos, opening_delimiter); + result.emplace_back(type, std::move(token)); } + if (opt_token_pos_info) + opt_token_pos_info->push_back({token_start, pos}); } else + { while (pos != length and ((not is_command_separator(line[pos]) and not is_horizontal_blank(line[pos])) or (pos != 0 and line[pos-1] == '\\'))) ++pos; - - if (start_pos != pos) - { - if (opt_token_pos_info) - opt_token_pos_info->push_back({token_start, pos}); - String token = line.substr(token_start, pos - token_start); - static const Regex regex{R"(\\([ \t;\n]))"}; - token = boost::regex_replace(token, regex, "\\1"); - result.push_back({type, token}); + if (start_pos != pos) + { + if (opt_token_pos_info) + opt_token_pos_info->push_back({token_start, pos}); + auto token = line.substr(token_start, pos - token_start); + static const Regex regex{R"(\\([ \t;\n]))"}; + result.emplace_back(Token::Type::Raw, + boost::regex_replace(token, regex, "\\1")); + } } if (is_command_separator(line[pos]))