Add %rec{...} string that recursively expand %block inside
the result is then used as one token
This commit is contained in:
parent
c4e694fb7b
commit
3c40ac50af
|
@ -53,6 +53,7 @@ struct Token
|
||||||
enum class Type
|
enum class Type
|
||||||
{
|
{
|
||||||
Raw,
|
Raw,
|
||||||
|
RawEval,
|
||||||
ShellExpand,
|
ShellExpand,
|
||||||
RegisterExpand,
|
RegisterExpand,
|
||||||
OptionExpand,
|
OptionExpand,
|
||||||
|
@ -152,6 +153,8 @@ Token::Type token_type(const String& type_name)
|
||||||
return Token::Type::RegisterExpand;
|
return Token::Type::RegisterExpand;
|
||||||
else if (type_name == "opt")
|
else if (type_name == "opt")
|
||||||
return Token::Type::OptionExpand;
|
return Token::Type::OptionExpand;
|
||||||
|
else if (type_name == "rec")
|
||||||
|
return Token::Type::RawEval;
|
||||||
else if (throw_on_invalid)
|
else if (throw_on_invalid)
|
||||||
throw unknown_expand{type_name};
|
throw unknown_expand{type_name};
|
||||||
else
|
else
|
||||||
|
@ -177,12 +180,50 @@ void skip_blanks_and_comments(const String& base, ByteCount& pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<bool throw_on_unterminated>
|
||||||
|
Token parse_percent_token(const String& line, ByteCount& pos)
|
||||||
|
{
|
||||||
|
const ByteCount length = line.length();
|
||||||
|
const ByteCount type_start = ++pos;
|
||||||
|
while (isalpha(line[pos]))
|
||||||
|
++pos;
|
||||||
|
String type_name = line.substr(type_start, pos - type_start);
|
||||||
|
|
||||||
|
if (throw_on_unterminated and pos == length)
|
||||||
|
throw parse_error{"expected a string delimiter after '%" + type_name + "'"};
|
||||||
|
|
||||||
|
Token::Type type = token_type<throw_on_unterminated>(type_name);
|
||||||
|
static const std::unordered_map<char, char> matching_delimiters = {
|
||||||
|
{ '(', ')' }, { '[', ']' }, { '{', '}' }, { '<', '>' }
|
||||||
|
};
|
||||||
|
|
||||||
|
char opening_delimiter = line[pos];
|
||||||
|
ByteCount token_start = ++pos;
|
||||||
|
|
||||||
|
auto delim_it = matching_delimiters.find(opening_delimiter);
|
||||||
|
if (delim_it != matching_delimiters.end())
|
||||||
|
{
|
||||||
|
char closing_delimiter = delim_it->second;
|
||||||
|
String token = get_until_delimiter(line, pos, opening_delimiter,
|
||||||
|
closing_delimiter);
|
||||||
|
if (throw_on_unterminated and pos == length)
|
||||||
|
throw unterminated_string("%" + type_name + opening_delimiter,
|
||||||
|
String{closing_delimiter}, 0);
|
||||||
|
return {type, token_start, pos, std::move(token)};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
String token = get_until_delimiter(line, pos, opening_delimiter);
|
||||||
|
return {type, token_start, pos, std::move(token)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template<bool throw_on_unterminated>
|
template<bool throw_on_unterminated>
|
||||||
TokenList parse(const String& line)
|
TokenList parse(const String& line)
|
||||||
{
|
{
|
||||||
TokenList result;
|
TokenList result;
|
||||||
|
|
||||||
ByteCount length = line.length();
|
const ByteCount length = line.length();
|
||||||
ByteCount pos = 0;
|
ByteCount pos = 0;
|
||||||
while (pos < length)
|
while (pos < length)
|
||||||
{
|
{
|
||||||
|
@ -199,42 +240,11 @@ TokenList parse(const String& line)
|
||||||
String token = get_until_delimiter(line, pos, delimiter);
|
String token = get_until_delimiter(line, pos, delimiter);
|
||||||
if (throw_on_unterminated and pos == length)
|
if (throw_on_unterminated and pos == length)
|
||||||
throw unterminated_string(String{delimiter}, String{delimiter});
|
throw unterminated_string(String{delimiter}, String{delimiter});
|
||||||
result.emplace_back(Token::Type::Raw, token_start, pos, std::move(token));
|
result.emplace_back(Token::Type::Raw, token_start,
|
||||||
|
pos, std::move(token));
|
||||||
}
|
}
|
||||||
else if (line[pos] == '%')
|
else if (line[pos] == '%')
|
||||||
{
|
result.push_back(parse_percent_token<throw_on_unterminated>(line, pos));
|
||||||
ByteCount type_start = ++pos;
|
|
||||||
while (isalpha(line[pos]))
|
|
||||||
++pos;
|
|
||||||
String type_name = line.substr(type_start, pos - type_start);
|
|
||||||
|
|
||||||
if (throw_on_unterminated and pos == length)
|
|
||||||
throw parse_error{"expected a string delimiter after '%" + type_name + "'"};
|
|
||||||
|
|
||||||
Token::Type type = token_type<throw_on_unterminated>(type_name);
|
|
||||||
static const std::unordered_map<char, char> matching_delimiters = {
|
|
||||||
{ '(', ')' }, { '[', ']' }, { '{', '}' }, { '<', '>' }
|
|
||||||
};
|
|
||||||
|
|
||||||
char opening_delimiter = line[pos];
|
|
||||||
token_start = ++pos;
|
|
||||||
|
|
||||||
auto delim_it = matching_delimiters.find(opening_delimiter);
|
|
||||||
if (delim_it != matching_delimiters.end())
|
|
||||||
{
|
|
||||||
char closing_delimiter = delim_it->second;
|
|
||||||
String token = get_until_delimiter(line, pos, opening_delimiter, closing_delimiter);
|
|
||||||
if (throw_on_unterminated and pos == length)
|
|
||||||
throw unterminated_string("%" + type_name + opening_delimiter,
|
|
||||||
String{closing_delimiter}, 0);
|
|
||||||
result.emplace_back(type, token_start, pos, std::move(token));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
String token = get_until_delimiter(line, pos, opening_delimiter);
|
|
||||||
result.emplace_back(type, token_start, pos, std::move(token));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
while (pos != length and
|
while (pos != length and
|
||||||
|
@ -259,6 +269,62 @@ TokenList parse(const String& line)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String eval_token(const Token& token, Context& context,
|
||||||
|
memoryview<String> shell_params,
|
||||||
|
const EnvVarMap& env_vars);
|
||||||
|
|
||||||
|
String eval(const String& str, Context& context,
|
||||||
|
memoryview<String> shell_params,
|
||||||
|
const EnvVarMap& env_vars)
|
||||||
|
{
|
||||||
|
String res;
|
||||||
|
auto pos = 0_byte;
|
||||||
|
auto length = str.length();
|
||||||
|
while (pos < length)
|
||||||
|
{
|
||||||
|
if (str[pos] == '\\')
|
||||||
|
{
|
||||||
|
char c = str[++pos];
|
||||||
|
if (c != '%' and c != '\\')
|
||||||
|
res += '\\';
|
||||||
|
res += c;
|
||||||
|
}
|
||||||
|
else if (str[pos] == '%')
|
||||||
|
{
|
||||||
|
Token token = parse_percent_token<true>(str, pos);
|
||||||
|
res += eval_token(token, context, shell_params, env_vars);
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
res += str[pos++];
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
String eval_token(const Token& token, Context& context,
|
||||||
|
memoryview<String> shell_params,
|
||||||
|
const EnvVarMap& env_vars)
|
||||||
|
{
|
||||||
|
switch (token.type())
|
||||||
|
{
|
||||||
|
case Token::Type::ShellExpand:
|
||||||
|
return ShellManager::instance().eval(token.content(), context,
|
||||||
|
shell_params, env_vars);
|
||||||
|
case Token::Type::RegisterExpand:
|
||||||
|
if (token.content().length() != 1)
|
||||||
|
throw runtime_error("wrong register name: " + token.content());
|
||||||
|
return RegisterManager::instance()[token.content()[0]].values(context)[0];
|
||||||
|
case Token::Type::OptionExpand:
|
||||||
|
return context.options()[token.content()].get_as_string();
|
||||||
|
case Token::Type::RawEval:
|
||||||
|
return eval(token.content(), context, shell_params, env_vars);
|
||||||
|
case Token::Type::Raw:
|
||||||
|
return token.content();
|
||||||
|
default: kak_assert(false);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct command_not_found : runtime_error
|
struct command_not_found : runtime_error
|
||||||
|
@ -300,40 +366,27 @@ void CommandManager::execute(const String& command_line,
|
||||||
std::vector<String> params;
|
std::vector<String> params;
|
||||||
for (auto it = tokens.begin(); it != tokens.end(); ++it)
|
for (auto it = tokens.begin(); it != tokens.end(); ++it)
|
||||||
{
|
{
|
||||||
if (it->type() == Token::Type::ShellExpand)
|
|
||||||
{
|
|
||||||
String output = ShellManager::instance().eval(it->content(),
|
|
||||||
context, shell_params,
|
|
||||||
env_vars);
|
|
||||||
TokenList shell_tokens = parse<true>(output);
|
|
||||||
it = tokens.erase(it);
|
|
||||||
for (auto& token : shell_tokens)
|
|
||||||
it = ++tokens.insert(it, std::move(token));
|
|
||||||
it -= shell_tokens.size();
|
|
||||||
|
|
||||||
// when last token is a ShellExpand which produces no output
|
|
||||||
if (it == tokens.end())
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (it->type() == Token::Type::RegisterExpand)
|
|
||||||
{
|
|
||||||
if (it->content().length() != 1)
|
|
||||||
throw runtime_error("wrong register name: " + it->content());
|
|
||||||
Register& reg = RegisterManager::instance()[it->content()[0]];
|
|
||||||
params.push_back(reg.values(context)[0]);
|
|
||||||
}
|
|
||||||
if (it->type() == Token::Type::OptionExpand)
|
|
||||||
{
|
|
||||||
const Option& option = context.options()[it->content()];
|
|
||||||
params.push_back(option.get_as_string());
|
|
||||||
}
|
|
||||||
if (it->type() == Token::Type::CommandSeparator)
|
if (it->type() == Token::Type::CommandSeparator)
|
||||||
{
|
{
|
||||||
execute_single_command(params, context);
|
execute_single_command(params, context);
|
||||||
params.clear();
|
params.clear();
|
||||||
}
|
}
|
||||||
if (it->type() == Token::Type::Raw)
|
// Shell expand are retokenized
|
||||||
params.push_back(it->content());
|
else if (it->type() == Token::Type::ShellExpand)
|
||||||
|
{
|
||||||
|
auto shell_tokens = parse<true>(eval_token(*it, context,
|
||||||
|
shell_params, env_vars));
|
||||||
|
it = tokens.erase(it);
|
||||||
|
for (auto& token : shell_tokens)
|
||||||
|
it = ++tokens.insert(it, std::move(token));
|
||||||
|
|
||||||
|
if (tokens.empty())
|
||||||
|
break;
|
||||||
|
|
||||||
|
it -= shell_tokens.size() + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
params.push_back(eval_token(*it, context, shell_params, env_vars));
|
||||||
}
|
}
|
||||||
execute_single_command(params, context);
|
execute_single_command(params, context);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user