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 ~.
This commit is contained in:
Maxime Coste 2013-06-27 19:07:26 +01:00
parent 938baacd05
commit b1f31d2e12
2 changed files with 46 additions and 29 deletions

View File

@ -291,17 +291,16 @@ if you want to give parameters with spaces, you should quote them.
Kakoune support three string syntax: Kakoune support three string syntax:
* +"strings" and \'strings\'+: classic strings, use \' or \" to escape the * +"strings" and \'strings\'+: classic strings, use \' or \" to escape the
separator. separator.
* +%\{strings\}+: these strings are very useful when entering commands * +%\{strings\}+: these strings are very useful when entering commands
- the '{' and '}' delimiter are configurable: you can use any non - the '{' and '}' delimiter are configurable: you can use any non
alphanumeric character. like %[string], %<string>, %(string), %~string~ alphanumeric character. like %[string], %<string>, %(string), %~string~
or %!string!... or %!string!...
- if the character following the % is one of {[(<, then - if the character following the % is one of {[(<, then the closing one is
the closing one is the matching }])>, and these delimiters in the string the matching }])> and the delimiters are not escapable but are nestable.
need not to be escaped if the contained delimiters are balanced. for example +%{ roger {}; }+ is a valid string, +%{ marcel \}+ as well.
for example +%{ roger {}; }+ is a valid string.
Highlighters Highlighters
------------ ------------

View File

@ -91,6 +91,26 @@ struct unknown_expand : parse_error
: parse_error{"unknown expand '" + name + "'"} {} : 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, TokenList parse(const String& line,
TokenPosList* opt_token_pos_info = nullptr) TokenPosList* opt_token_pos_info = nullptr)
{ {
@ -118,18 +138,15 @@ TokenList parse(const String& line,
ByteCount token_start = pos; ByteCount token_start = pos;
ByteCount start_pos = pos; ByteCount start_pos = pos;
Token::Type type = Token::Type::Raw;
if (line[pos] == '"' or line[pos] == '\'') if (line[pos] == '"' or line[pos] == '\'')
{ {
char delimiter = line[pos]; char delimiter = line[pos];
token_start = ++pos; token_start = ++pos;
String token = get_until_delimiter(line, pos, delimiter);
while ((line[pos] != delimiter or line[pos-1] == '\\') and result.emplace_back(Token::Type::Raw, std::move(token));
pos != length) if (opt_token_pos_info)
++pos; opt_token_pos_info->push_back({token_start, pos});
if (pos == length)
throw unterminated_string(String{delimiter}, String{delimiter});
} }
else if (line[pos] == '%') else if (line[pos] == '%')
{ {
@ -141,6 +158,7 @@ TokenList parse(const String& line,
if (pos == length) if (pos == length)
throw parse_error{"expected a string delimiter after '%" + type_name + "'"}; throw parse_error{"expected a string delimiter after '%" + type_name + "'"};
Token::Type type = Token::Type::Raw;
if (type_name == "sh") if (type_name == "sh")
type = Token::Type::ShellExpand; type = Token::Type::ShellExpand;
else if (type_name == "reg") else if (type_name == "reg")
@ -164,9 +182,9 @@ TokenList parse(const String& line,
int level = 0; int level = 0;
while (pos != length) while (pos != length)
{ {
if (line[pos-1] != '\\' and line[pos] == opening_delimiter) if (line[pos] == opening_delimiter)
++level; ++level;
if (line[pos-1] != '\\' and line[pos] == closing_delimiter) else if (line[pos] == closing_delimiter)
{ {
if (level > 0) if (level > 0)
--level; --level;
@ -178,32 +196,32 @@ TokenList parse(const String& line,
if (pos == length) if (pos == length)
throw unterminated_string("%" + type_name + opening_delimiter, throw unterminated_string("%" + type_name + opening_delimiter,
String{closing_delimiter}, level); String{closing_delimiter}, level);
result.emplace_back(type, line.substr(token_start, pos - token_start));
} }
else else
{ {
while (pos != length and String token = get_until_delimiter(line, pos, opening_delimiter);
(line[pos] != opening_delimiter or line[pos-1] == '\\')) result.emplace_back(type, std::move(token));
++pos;
if (pos == length)
throw unterminated_string("%" + type_name + opening_delimiter,
String{opening_delimiter});
} }
if (opt_token_pos_info)
opt_token_pos_info->push_back({token_start, pos});
} }
else else
{
while (pos != length and while (pos != length and
((not is_command_separator(line[pos]) and ((not is_command_separator(line[pos]) and
not is_horizontal_blank(line[pos])) not is_horizontal_blank(line[pos]))
or (pos != 0 and line[pos-1] == '\\'))) or (pos != 0 and line[pos-1] == '\\')))
++pos; ++pos;
if (start_pos != pos)
if (start_pos != pos) {
{ if (opt_token_pos_info)
if (opt_token_pos_info) opt_token_pos_info->push_back({token_start, pos});
opt_token_pos_info->push_back({token_start, pos}); auto token = line.substr(token_start, pos - token_start);
String token = line.substr(token_start, pos - token_start); static const Regex regex{R"(\\([ \t;\n]))"};
static const Regex regex{R"(\\([ \t;\n]))"}; result.emplace_back(Token::Type::Raw,
token = boost::regex_replace(token, regex, "\\1"); boost::regex_replace(token, regex, "\\1"));
result.push_back({type, token}); }
} }
if (is_command_separator(line[pos])) if (is_command_separator(line[pos]))