jinja : implement call statement (#24847)
* implement call statement * undo unintended change * de-lambda * simplify * move caller context inside function handler
This commit is contained in:
+89
-46
@@ -686,59 +686,62 @@ value set_statement::execute_impl(context & ctx) {
|
|||||||
return mk_val<value_undefined>();
|
return mk_val<value_undefined>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void bind_parameters(const std::string & name, const statements & this_args, const func_args & args, context & ctx) {
|
||||||
|
const size_t expected_count = this_args.size();
|
||||||
|
const size_t input_count = args.count();
|
||||||
|
|
||||||
|
JJ_DEBUG("Invoking '%s' with %zu input arguments (expected %zu)", name.c_str(), input_count, expected_count);
|
||||||
|
for (size_t i = 0; i < expected_count; ++i) {
|
||||||
|
if (i < input_count) {
|
||||||
|
if (is_stmt<identifier>(this_args[i])) {
|
||||||
|
// normal parameter
|
||||||
|
std::string param_name = cast_stmt<identifier>(this_args[i])->val;
|
||||||
|
value param_value = args.get_kwarg_or_pos(param_name, i);
|
||||||
|
JJ_DEBUG(" Binding parameter '%s' to argument of type %s", param_name.c_str(), param_value->type().c_str());
|
||||||
|
ctx.set_val(param_name, param_value);
|
||||||
|
} else if (is_stmt<keyword_argument_expression>(this_args[i])) {
|
||||||
|
// default argument used as normal parameter
|
||||||
|
auto kwarg = cast_stmt<keyword_argument_expression>(this_args[i]);
|
||||||
|
if (!is_stmt<identifier>(kwarg->key)) {
|
||||||
|
throw std::runtime_error("Keyword argument key must be an identifier in '" + name + "'");
|
||||||
|
}
|
||||||
|
std::string param_name = cast_stmt<identifier>(kwarg->key)->val;
|
||||||
|
value param_value = args.get_kwarg_or_pos(param_name, i);
|
||||||
|
JJ_DEBUG(" Binding parameter '%s' to argument of type %s", param_name.c_str(), param_value->type().c_str());
|
||||||
|
ctx.set_val(param_name, param_value);
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("Invalid parameter type in '" + name + "'");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto & default_arg = this_args[i];
|
||||||
|
if (is_stmt<keyword_argument_expression>(default_arg)) {
|
||||||
|
auto kwarg = cast_stmt<keyword_argument_expression>(default_arg);
|
||||||
|
if (!is_stmt<identifier>(kwarg->key)) {
|
||||||
|
throw std::runtime_error("Keyword argument key must be an identifier in '" + name + "'");
|
||||||
|
}
|
||||||
|
std::string param_name = cast_stmt<identifier>(kwarg->key)->val;
|
||||||
|
JJ_DEBUG(" Binding parameter '%s' to default argument of type %s", param_name.c_str(), kwarg->val->type().c_str());
|
||||||
|
ctx.set_val(param_name, kwarg->val->execute(args.ctx));
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("Not enough arguments provided to '" + name + "'");
|
||||||
|
}
|
||||||
|
//std::string param_name = cast_stmt<identifier>(default_args[i])->val;
|
||||||
|
//JJ_DEBUG(" Binding parameter '%s' to default", param_name.c_str());
|
||||||
|
//ctx.var[param_name] = default_args[i]->execute(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
value macro_statement::execute_impl(context & ctx) {
|
value macro_statement::execute_impl(context & ctx) {
|
||||||
if (!is_stmt<identifier>(this->name)) {
|
if (!is_stmt<identifier>(this->name)) {
|
||||||
throw std::runtime_error("Macro name must be an identifier");
|
throw std::runtime_error("Macro name must be an identifier");
|
||||||
}
|
}
|
||||||
std::string name = cast_stmt<identifier>(this->name)->val;
|
std::string name = cast_stmt<identifier>(this->name)->val;
|
||||||
|
|
||||||
const func_handler func = [this, name, &ctx](const func_args & args) -> value {
|
const func_handler func = [this, name](const func_args & args) -> value {
|
||||||
size_t expected_count = this->args.size();
|
context macro_ctx(args.ctx); // new scope for macro execution
|
||||||
size_t input_count = args.count();
|
|
||||||
|
|
||||||
JJ_DEBUG("Invoking macro '%s' with %zu input arguments (expected %zu)", name.c_str(), input_count, expected_count);
|
bind_parameters(name, this->args, args, macro_ctx);
|
||||||
context macro_ctx(ctx); // new scope for macro execution
|
|
||||||
|
|
||||||
// bind parameters
|
|
||||||
for (size_t i = 0; i < expected_count; ++i) {
|
|
||||||
if (i < input_count) {
|
|
||||||
if (is_stmt<identifier>(this->args[i])) {
|
|
||||||
// normal parameter
|
|
||||||
std::string param_name = cast_stmt<identifier>(this->args[i])->val;
|
|
||||||
value param_value = args.get_kwarg_or_pos(param_name, i);
|
|
||||||
JJ_DEBUG(" Binding parameter '%s' to argument of type %s", param_name.c_str(), param_value->type().c_str());
|
|
||||||
macro_ctx.set_val(param_name, param_value);
|
|
||||||
} else if (is_stmt<keyword_argument_expression>(this->args[i])) {
|
|
||||||
// default argument used as normal parameter
|
|
||||||
auto kwarg = cast_stmt<keyword_argument_expression>(this->args[i]);
|
|
||||||
if (!is_stmt<identifier>(kwarg->key)) {
|
|
||||||
throw std::runtime_error("Keyword argument key must be an identifier in macro '" + name + "'");
|
|
||||||
}
|
|
||||||
std::string param_name = cast_stmt<identifier>(kwarg->key)->val;
|
|
||||||
value param_value = args.get_kwarg_or_pos(param_name, i);
|
|
||||||
JJ_DEBUG(" Binding parameter '%s' to argument of type %s", param_name.c_str(), param_value->type().c_str());
|
|
||||||
macro_ctx.set_val(param_name, param_value);
|
|
||||||
} else {
|
|
||||||
throw std::runtime_error("Invalid parameter type in macro '" + name + "'");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
auto & default_arg = this->args[i];
|
|
||||||
if (is_stmt<keyword_argument_expression>(default_arg)) {
|
|
||||||
auto kwarg = cast_stmt<keyword_argument_expression>(default_arg);
|
|
||||||
if (!is_stmt<identifier>(kwarg->key)) {
|
|
||||||
throw std::runtime_error("Keyword argument key must be an identifier in macro '" + name + "'");
|
|
||||||
}
|
|
||||||
std::string param_name = cast_stmt<identifier>(kwarg->key)->val;
|
|
||||||
JJ_DEBUG(" Binding parameter '%s' to default argument of type %s", param_name.c_str(), kwarg->val->type().c_str());
|
|
||||||
macro_ctx.set_val(param_name, kwarg->val->execute(ctx));
|
|
||||||
} else {
|
|
||||||
throw std::runtime_error("Not enough arguments provided to macro '" + name + "'");
|
|
||||||
}
|
|
||||||
//std::string param_name = cast_stmt<identifier>(default_args[i])->val;
|
|
||||||
//JJ_DEBUG(" Binding parameter '%s' to default", param_name.c_str());
|
|
||||||
//macro_ctx.var[param_name] = default_args[i]->execute(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// execute macro body
|
// execute macro body
|
||||||
JJ_DEBUG("Executing macro '%s' body with %zu statements", name.c_str(), this->body.size());
|
JJ_DEBUG("Executing macro '%s' body with %zu statements", name.c_str(), this->body.size());
|
||||||
@@ -752,6 +755,46 @@ value macro_statement::execute_impl(context & ctx) {
|
|||||||
return mk_val<value_undefined>();
|
return mk_val<value_undefined>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
value call_statement::execute_impl(context & ctx) {
|
||||||
|
auto call_expr = cast_stmt<call_expression>(this->call);
|
||||||
|
if (!call_expr) {
|
||||||
|
throw std::runtime_error("Call statement requires a valid call expression");
|
||||||
|
}
|
||||||
|
|
||||||
|
value callee_val = call_expr->callee->execute(ctx);
|
||||||
|
if (!is_val<value_func>(callee_val)) {
|
||||||
|
throw std::runtime_error("Callee is not a function: got " + callee_val->type());
|
||||||
|
}
|
||||||
|
auto * callee_func = cast_val<value_func>(callee_val);
|
||||||
|
|
||||||
|
context caller_ctx(ctx); // new scope for caller execution
|
||||||
|
|
||||||
|
const func_handler func = [this, caller_ctx = std::move(caller_ctx)](const func_args & args) -> value {
|
||||||
|
context block_ctx(caller_ctx); // new scope for block execution
|
||||||
|
|
||||||
|
bind_parameters("caller", this->caller_args, args, block_ctx);
|
||||||
|
|
||||||
|
JJ_DEBUG("Executing call body with %zu statements", this->body.size());
|
||||||
|
auto res = exec_statements(this->body, block_ctx);
|
||||||
|
JJ_DEBUG("Call body execution complete, result: %s", res->val_str.str().c_str());
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
context call_ctx(ctx);
|
||||||
|
call_ctx.set_val("caller", mk_val<value_func>("caller", func));
|
||||||
|
|
||||||
|
func_args args(call_ctx);
|
||||||
|
|
||||||
|
for (const auto & arg_expr : call_expr->args) {
|
||||||
|
auto arg_val = arg_expr->execute(ctx);
|
||||||
|
JJ_DEBUG(" Argument type: %s", arg_val->type().c_str());
|
||||||
|
args.push_back(arg_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
JJ_DEBUG("Calling macro '%s' with %zu arguments", callee_func->name.c_str(), args.count());
|
||||||
|
return callee_func->invoke(args);
|
||||||
|
}
|
||||||
|
|
||||||
value member_expression::execute_impl(context & ctx) {
|
value member_expression::execute_impl(context & ctx) {
|
||||||
value object = this->object->execute(ctx);
|
value object = this->object->execute(ctx);
|
||||||
|
|
||||||
|
|||||||
@@ -552,6 +552,7 @@ struct call_statement : public statement {
|
|||||||
for (const auto & arg : this->caller_args) chk_type<expression>(arg);
|
for (const auto & arg : this->caller_args) chk_type<expression>(arg);
|
||||||
}
|
}
|
||||||
std::string type() const override { return "CallStatement"; }
|
std::string type() const override { return "CallStatement"; }
|
||||||
|
value execute_impl(context & ctx) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ternary_expression : public expression {
|
struct ternary_expression : public expression {
|
||||||
|
|||||||
@@ -995,6 +995,32 @@ static void test_macros(testing & t) {
|
|||||||
json::object(),
|
json::object(),
|
||||||
"Hello, John Smith,Hi, Jane Doe"
|
"Hello, John Smith,Hi, Jane Doe"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test_template(t, "macro with caller",
|
||||||
|
"\
|
||||||
|
{%- macro nest_dict(o, i, ff='') %}\n\
|
||||||
|
{{- caller(ff) }}\n\
|
||||||
|
{%- for k, v in o|items %}\n\
|
||||||
|
{{- i + k + ': ' }}\n\
|
||||||
|
{%- if v is mapping %}\n\
|
||||||
|
{{- '{' }}\n\
|
||||||
|
{% call(f) nest_dict(v, i + ' ') %}\n\
|
||||||
|
{{- 'fail' if ff is undefined }}\n\
|
||||||
|
{%- endcall %}\n\
|
||||||
|
{{- i + '}' }}\n\
|
||||||
|
{% else %}\n\
|
||||||
|
{{- v|string }}\n\
|
||||||
|
{% endif %}\n\
|
||||||
|
{%- endfor %}\n\
|
||||||
|
{%- endmacro %}\n\
|
||||||
|
{%- call(f) nest_dict({'root1': 1, 'root2': {'nest1': 1, 'nest2': {'nest3': 2}}}, ' ', 'Dict') %}\n\
|
||||||
|
{{- 'fail' if ff is defined }}\n\
|
||||||
|
{{- f + ' {' }}\n\
|
||||||
|
{% endcall %}\n\
|
||||||
|
{{- '}' }}",
|
||||||
|
json::object(),
|
||||||
|
"Dict {\n root1: 1\n root2: {\n nest1: 1\n nest2: {\n nest3: 2\n }\n }\n}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_namespace(testing & t) {
|
static void test_namespace(testing & t) {
|
||||||
|
|||||||
Reference in New Issue
Block a user