a lot of fixes

This commit is contained in:
Xuan Son Nguyen 2025-12-29 00:38:29 +01:00
parent 1784a57e7b
commit 2a31c9a30c
4 changed files with 202 additions and 31 deletions

View File

@ -127,6 +127,44 @@ const func_builtins & global_builtins() {
throw raised_exception("strftime_now: failed to format time");
}
}},
{"range", [](const func_args & args) -> value {
if (args.args.size() < 1 || args.args.size() > 3) {
throw raised_exception("slice() takes between 1 and 3 arguments");
}
int64_t arg0 = is_val<value_int>(args.args[0]) ? args.args[0]->as_int() : 0;
int64_t arg1 = is_val<value_int>(args.args[1]) ? args.args[1]->as_int() : -1;
int64_t arg2 = is_val<value_int>(args.args[2]) ? args.args[2]->as_int() : 1;
int64_t start, stop, step;
if (args.args.size() == 1) {
start = 0;
stop = arg0;
step = 1;
} else if (args.args.size() == 2) {
start = arg0;
stop = arg1;
step = 1;
} else {
start = arg0;
stop = arg1;
step = arg2;
}
auto out = mk_val<value_array>();
if (step == 0) {
throw raised_exception("range() step argument must not be zero");
}
if (step > 0) {
for (int64_t i = start; i < stop; i += step) {
out->push_back(mk_val<value_int>(i));
}
} else {
for (int64_t i = start; i > stop; i += step) {
out->push_back(mk_val<value_int>(i));
}
}
return out;
}},
// tests
{"test_is_boolean", test_type_fn<value_bool>},
@ -416,7 +454,9 @@ const func_builtins & value_array_t::get_builtins() const {
return mk_val<value_int>(static_cast<int64_t>(arr.size()));
}},
{"slice", [](const func_args & args) -> value {
args.ensure_count(4);
if (args.args.size() < 1 || args.args.size() > 4) {
throw raised_exception("slice() takes between 1 and 4 arguments");
}
int64_t start = is_val<value_int>(args.args[1]) ? args.args[1]->as_int() : 0;
int64_t stop = is_val<value_int>(args.args[2]) ? args.args[2]->as_int() : -1;
int64_t step = is_val<value_int>(args.args[3]) ? args.args[3]->as_int() : 1;
@ -465,7 +505,77 @@ const func_builtins & value_array_t::get_builtins() const {
}
return result;
}},
// TODO: reverse, sort, join, string, unique
{"rejectattr", [](const func_args & args) -> value {
value input = args.args[0];
if (!is_val<value_array>(input)) {
throw raised_exception("rejectattr() first argument must be an array, got " + input->type());
}
std::vector<std::string> rejected;
for (size_t i = 1; i < args.args.size(); ++i) {
const auto & v = args.args[i];
if (!is_val<value_string>(v)) {
throw raised_exception("rejectattr() attributes must be strings, got " + v->type());
}
JJ_DEBUG("rejectattr: rejecting attribute '%s'", v->as_string().str().c_str());
rejected.push_back(v->as_string().str());
}
auto result = mk_val<value_array>();
for (const auto & item : input->as_array()) {
if (!is_val<value_object>(item)) {
result->push_back(item);
continue;
}
const auto & obj = item->as_object();
bool match = false;
for (const auto & attr : rejected) {
auto it = obj.find(attr);
if (it != obj.end() && !it->second->is_undefined() && (!is_val<value_bool>(it->second) || it->second->as_bool())) {
match = true;
break;
}
}
if (!match) {
result->push_back(item);
}
}
return result;
}},
{"join", [](const func_args & args) -> value {
if (args.args.size() < 1 || args.args.size() > 2) {
throw raised_exception("join() takes one or two arguments");
}
if (!is_val<value_array>(args.args[0])) {
throw raised_exception("join() first argument must be an array");
}
const auto & arr = args.args[0]->as_array();
std::string delim = (args.args.size() > 1 && is_val<value_string>(args.args[1])) ? args.args[1]->as_string().str() : "";
std::string result;
for (size_t i = 0; i < arr.size(); ++i) {
if (!is_val<value_string>(arr[i])) {
throw raised_exception("join() can only join arrays of strings");
}
result += arr[i]->as_string().str();
if (i < arr.size() - 1) {
result += delim;
}
}
return mk_val<value_string>(result);
}},
{"string", [](const func_args & args) -> value {
args.ensure_vals<value_array>();
auto str = mk_val<value_string>();
gather_string_parts_recursive(args.args[0], str);
return str;
}},
{"sort", [](const func_args &) -> value {
throw std::runtime_error("Array sort builtin not implemented");
}},
{"reverse", [](const func_args &) -> value {
throw std::runtime_error("Array reverse builtin not implemented");
}},
{"unique", [](const func_args &) -> value {
throw std::runtime_error("Array unique builtin not implemented");
}},
};
return builtins;
}
@ -523,6 +633,26 @@ const func_builtins & value_object_t::get_builtins() const {
return builtins;
}
const func_builtins & value_null_t::get_builtins() const {
static const func_builtins builtins = {
{"list", [](const func_args &) -> value {
// fix for meetkai-functionary-medium-v3.1.jinja
// TODO: hide under a flag?
return mk_val<value_array>();
}},
{"selectattr", [](const func_args &) -> value {
// fix for meetkai-functionary-medium-v3.1.jinja
// TODO: hide under a flag?
return mk_val<value_array>();
}},
};
return builtins;
}
//////////////////////////////////
static value from_json(const nlohmann::json & j) {
if (j.is_null()) {
return mk_val<value_null>();

View File

@ -96,13 +96,14 @@ struct func_args {
std::vector<value> args;
context & ctx;
func_args(context & ctx) : ctx(ctx) {}
void ensure_count(size_t count) const {
if (args.size() != count) {
throw std::runtime_error("Expected " + std::to_string(count) + " arguments, got " + std::to_string(args.size()));
void ensure_count(size_t min, size_t max = 999) const {
if (args.size() < min || args.size() > max) {
throw std::runtime_error("Expected between " + std::to_string(min) + " and " + std::to_string(max) + " arguments, got " + std::to_string(args.size()));
}
}
value get_kwarg(const std::string & key) const;
// utility functions
// TODO: allow optional arguments
template<typename T> void ensure_vals() const {
ensure_count(1);
ensure_val<T>(args[0]);
@ -310,12 +311,15 @@ struct value_null_t : public value_t {
virtual bool is_null() const override { return true; }
virtual bool as_bool() const override { return false; }
virtual std::string as_repr() const override { return type(); }
virtual const func_builtins & get_builtins() const override;
};
using value_null = std::shared_ptr<value_null_t>;
struct value_undefined_t : public value_t {
virtual std::string type() const override { return "Undefined"; }
std::string hint; // for debugging, to indicate where undefined came from
value_undefined_t(const std::string & h = "") : hint(h) {}
virtual std::string type() const override { return hint.empty() ? "Undefined" : "Undefined (hint: '" + hint + "')"; }
virtual bool is_undefined() const override { return true; }
virtual bool as_bool() const override { return false; }
virtual std::string as_repr() const override { return type(); }

View File

@ -19,13 +19,16 @@ void enable_debug(bool enable) {
g_jinja_debug = enable;
}
static value_array exec_statements(const statements & stmts, context & ctx) {
static value_string exec_statements(const statements & stmts, context & ctx) {
auto result = mk_val<value_array>();
for (const auto & stmt : stmts) {
JJ_DEBUG("Executing statement of type %s", stmt->type().c_str());
result->push_back(stmt->execute(ctx));
}
return result;
// convert to string parts
value_string str = mk_val<value_string>();
gather_string_parts_recursive(result, str);
return str;
}
// execute with error handling
@ -66,7 +69,7 @@ value identifier::execute_impl(context & ctx) {
return mk_val<value_func>(builtins.at(val), val);
} else {
JJ_DEBUG("Identifier '%s' not found, returning undefined", val.c_str());
return mk_val<value_undefined>();
return mk_val<value_undefined>(val);
}
}
@ -83,7 +86,6 @@ value object_literal::execute_impl(context & ctx) {
value binary_expression::execute_impl(context & ctx) {
value left_val = left->execute(ctx);
JJ_DEBUG("Executing binary expression %s '%s' %s", left_val->type().c_str(), op.value.c_str(), right->type().c_str());
// Logical operators
if (op.value == "and") {
@ -94,6 +96,7 @@ value binary_expression::execute_impl(context & ctx) {
// Equality operators
value right_val = right->execute(ctx);
JJ_DEBUG("Executing binary expression %s '%s' %s", left_val->type().c_str(), op.value.c_str(), right_val->type().c_str());
if (op.value == "==") {
return mk_val<value_bool>(value_compare(left_val, right_val));
} else if (op.value == "!=") {
@ -168,10 +171,18 @@ value binary_expression::execute_impl(context & ctx) {
}
} else if (is_val<value_array>(right_val)) {
auto & arr = right_val->as_array();
bool member = std::find_if(arr.begin(), arr.end(), [&](const value& v) { return v == left_val; }) != arr.end();
bool member = false;
for (const auto & item : arr) {
if (value_compare(left_val, item)) {
member = true;
break;
}
}
if (op.value == "in") {
JJ_DEBUG("Checking membership: %s in Array is %d", left_val->type().c_str(), member);
return mk_val<value_bool>(member);
} else if (op.value == "not in") {
JJ_DEBUG("Checking non-membership: %s not in Array is %d", left_val->type().c_str(), !member);
return mk_val<value_bool>(!member);
}
}
@ -220,7 +231,7 @@ static value try_builtin_func(const std::string & name, const value & input, boo
return mk_val<value_func>(it->second, input, name);
}
if (undef_on_missing) {
return mk_val<value_undefined>();
return mk_val<value_undefined>(name);
}
throw std::runtime_error("Unknown (built-in) filter '" + name + "' for type " + input->type());
}
@ -330,7 +341,10 @@ value if_statement::execute_impl(context & ctx) {
out->push_back(stmt->execute(ctx));
}
}
return out;
// convert to string parts
value_string str = mk_val<value_string>();
gather_string_parts_recursive(out, str);
return str;
}
value for_statement::execute_impl(context & ctx) {
@ -437,8 +451,8 @@ value for_statement::execute_impl(context & ctx) {
loop_obj->insert("first", mk_val<value_bool>(i == 0));
loop_obj->insert("last", mk_val<value_bool>(i == filtered_items.size() - 1));
loop_obj->insert("length", mk_val<value_int>(filtered_items.size()));
loop_obj->insert("previtem", i > 0 ? filtered_items[i - 1] : mk_val<value_undefined>());
loop_obj->insert("nextitem", i < filtered_items.size() - 1 ? filtered_items[i + 1] : mk_val<value_undefined>());
loop_obj->insert("previtem", i > 0 ? filtered_items[i - 1] : mk_val<value_undefined>("previtem"));
loop_obj->insert("nextitem", i < filtered_items.size() - 1 ? filtered_items[i + 1] : mk_val<value_undefined>("nextitem"));
ctx.var["loop"] = loop_obj;
scope_update_fns[i](ctx);
try {
@ -460,7 +474,10 @@ value for_statement::execute_impl(context & ctx) {
}
}
return result;
// convert to string parts
value_string str = mk_val<value_string>();
gather_string_parts_recursive(result, str);
return str;
}
value set_statement::execute_impl(context & ctx) {
@ -515,24 +532,41 @@ value set_statement::execute_impl(context & ctx) {
value macro_statement::execute_impl(context & ctx) {
std::string name = cast_stmt<identifier>(this->name)->val;
const func_handler func = [this, &ctx, name](const func_args & args) -> value {
JJ_DEBUG("Invoking macro '%s' with %zu arguments", name.c_str(), args.args.size());
const func_handler func = [this, name, &ctx](const func_args & args) -> value {
size_t expected_count = this->args.size();
size_t input_count = args.args.size();
JJ_DEBUG("Invoking macro '%s' with %zu input arguments (expected %zu)", name.c_str(), input_count, expected_count);
context macro_ctx(ctx); // new scope for macro execution
// bind parameters
size_t param_count = this->args.size();
size_t arg_count = args.args.size();
for (size_t i = 0; i < param_count; ++i) {
std::string param_name = cast_stmt<identifier>(this->args[i])->val;
if (i < arg_count) {
for (size_t i = 0; i < expected_count; ++i) {
if (i < input_count) {
std::string param_name = cast_stmt<identifier>(this->args[i])->val;
JJ_DEBUG(" Binding parameter '%s' to argument of type %s", param_name.c_str(), args.args[i]->type().c_str());
macro_ctx.var[param_name] = args.args[i];
} else {
macro_ctx.var[param_name] = mk_val<value_undefined>();
auto & default_arg = this->args[i];
if (is_stmt<keyword_argument_expression>(default_arg)) {
auto kwarg = cast_stmt<keyword_argument_expression>(default_arg);
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.var[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
return exec_statements(this->body, macro_ctx);
JJ_DEBUG("Executing macro '%s' body with %zu statements", name.c_str(), this->body.size());
auto res = exec_statements(this->body, macro_ctx);
JJ_DEBUG("Macro '%s' execution complete, result: %s", name.c_str(), res->val_str.str().c_str());
return res;
};
JJ_DEBUG("Defining macro '%s' with %zu parameters", name.c_str(), args.size());
@ -548,9 +582,9 @@ value member_expression::execute_impl(context & ctx) {
JJ_DEBUG("Member expression, computing property type %s", this->property->type().c_str());
if (is_stmt<slice_expression>(this->property)) {
auto s = cast_stmt<slice_expression>(this->property);
value start_val = s->start_expr ? s->start_expr->execute(ctx) : mk_val<value_undefined>();
value stop_val = s->stop_expr ? s->stop_expr->execute(ctx) : mk_val<value_undefined>();
value step_val = s->step_expr ? s->step_expr->execute(ctx) : mk_val<value_undefined>();
value start_val = s->start_expr ? s->start_expr->execute(ctx) : mk_val<value_undefined>("start");
value stop_val = s->stop_expr ? s->stop_expr->execute(ctx) : mk_val<value_undefined>("stop");
value step_val = s->step_expr ? s->step_expr->execute(ctx) : mk_val<value_undefined>("step");
// translate to function call: obj.slice(start, stop, step)
JJ_DEBUG("Member expression is a slice: start %s, stop %s, step %s",
@ -572,7 +606,7 @@ value member_expression::execute_impl(context & ctx) {
JJ_DEBUG("Member expression on object type %s, property type %s", object->type().c_str(), property->type().c_str());
value val = mk_val<value_undefined>();
value val = mk_val<value_undefined>("object_property");
if (is_val<value_undefined>(object)) {
JJ_DEBUG("%s", "Accessing property on undefined object, returning undefined");

View File

@ -58,7 +58,7 @@ int main(void) {
void run(std::string contents) {
// jinja::enable_debug(true);
jinja::enable_debug(true);
jinja::lexer lexer;
jinja::preprocess_options options;
@ -90,7 +90,10 @@ void run(std::string contents) {
"content": {"__input__": "I am fine, thank you!"}
}
],
"eos_token": "</s>"
"bos_token": "<s>",
"eos_token": "</s>",
"functions": "",
"datetime": ""
})";
jinja::global_from_json(ctx, nlohmann::json::parse(json_inp));