mostly works
This commit is contained in:
parent
45df0c91e7
commit
9a8a45ff3b
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
namespace jinja {
|
||||
|
||||
static void string_replace_all(std::string & s, const std::string & search, const std::string & replace) {
|
||||
if (search.empty()) {
|
||||
return;
|
||||
}
|
||||
std::string builder;
|
||||
builder.reserve(s.length());
|
||||
size_t pos = 0;
|
||||
size_t last_pos = 0;
|
||||
while ((pos = s.find(search, last_pos)) != std::string::npos) {
|
||||
builder.append(s, last_pos, pos - last_pos);
|
||||
builder.append(replace);
|
||||
last_pos = pos + search.length();
|
||||
}
|
||||
builder.append(s, last_pos, std::string::npos);
|
||||
s = std::move(builder);
|
||||
}
|
||||
|
||||
} // namespace jinja
|
||||
|
|
@ -65,7 +65,7 @@ struct func_args {
|
|||
throw std::runtime_error("Expected " + std::to_string(count) + " arguments, got " + std::to_string(args.size()));
|
||||
}
|
||||
}
|
||||
// TODO: add support for get kwargs
|
||||
value get_kwarg(const std::string & key) const;
|
||||
// utility functions
|
||||
template<typename T> void ensure_vals() const {
|
||||
ensure_count(1);
|
||||
|
|
|
|||
|
|
@ -67,12 +67,14 @@ template<typename T>
|
|||
static value test_type_fn(const func_args & args) {
|
||||
args.ensure_count(1);
|
||||
bool is_type = is_val<T>(args.args[0]);
|
||||
JJ_DEBUG("test_type_fn: type=%s result=%d", typeid(T).name(), is_type ? 1 : 0);
|
||||
return mk_val<value_bool>(is_type);
|
||||
}
|
||||
template<typename T, typename U>
|
||||
static value test_type_fn(const func_args & args) {
|
||||
args.ensure_count(1);
|
||||
bool is_type = is_val<T>(args.args[0]) || is_val<U>(args.args[0]);
|
||||
JJ_DEBUG("test_type_fn: type=%s or %s result=%d", typeid(T).name(), typeid(U).name(), is_type ? 1 : 0);
|
||||
return mk_val<value_bool>(is_type);
|
||||
}
|
||||
|
||||
|
|
@ -95,6 +97,20 @@ const func_builtins & global_builtins() {
|
|||
}
|
||||
return out;
|
||||
}},
|
||||
{"strftime_now", [](const func_args & args) -> value {
|
||||
args.ensure_count(1);
|
||||
args.ensure_vals<value_string>();
|
||||
std::string format = args.args[0]->as_string().str();
|
||||
// get current time
|
||||
// TODO: make sure this is the same behavior as Python's strftime
|
||||
std::time_t t = std::time(nullptr);
|
||||
char buf[100];
|
||||
if (std::strftime(buf, sizeof(buf), format.c_str(), std::localtime(&t))) {
|
||||
return mk_val<value_string>(std::string(buf));
|
||||
} else {
|
||||
throw raised_exception("strftime_now: failed to format time");
|
||||
}
|
||||
}},
|
||||
|
||||
// tests
|
||||
{"test_is_boolean", test_type_fn<value_bool>},
|
||||
|
|
@ -296,6 +312,25 @@ const func_builtins & value_string_t::get_builtins() const {
|
|||
args.ensure_vals<value_string>();
|
||||
return mk_val<value_string>(args.args[0]->as_string());
|
||||
}},
|
||||
{"default", [](const func_args & args) -> value {
|
||||
value input = args.args[0];
|
||||
if (!is_val<value_string>(input)) {
|
||||
throw raised_exception("default() first argument must be a string");
|
||||
}
|
||||
value default_val = mk_val<value_string>("");
|
||||
if (args.args.size() > 1 && !args.args[1]->is_undefined()) {
|
||||
default_val = args.args[1];
|
||||
}
|
||||
value boolean_val = mk_val<value_bool>(false);
|
||||
if (args.args.size() > 1) {
|
||||
boolean_val = args.args[1];
|
||||
}
|
||||
if (input->is_undefined() || (boolean_val->as_bool() && !input->as_bool())) {
|
||||
return default_val;
|
||||
} else {
|
||||
return input;
|
||||
}
|
||||
}},
|
||||
{"indent", [](const func_args &) -> value {
|
||||
throw std::runtime_error("indent builtin not implemented");
|
||||
}},
|
||||
|
|
@ -380,6 +415,40 @@ const func_builtins & value_array_t::get_builtins() const {
|
|||
res->val_arr = std::move(arr);
|
||||
return res;
|
||||
}},
|
||||
{"selectattr", [](const func_args & args) -> value {
|
||||
value input = args.args[0];
|
||||
if (!is_val<value_array>(input)) {
|
||||
throw raised_exception("selectattr() first argument must be an array, got " + input->type());
|
||||
}
|
||||
std::vector<std::string> selected;
|
||||
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("selectattr() attributes must be strings, got " + v->type());
|
||||
}
|
||||
JJ_DEBUG("selectattr: selecting attribute '%s'", v->as_string().str().c_str());
|
||||
selected.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)) {
|
||||
continue;
|
||||
}
|
||||
const auto & obj = item->as_object();
|
||||
bool match = true;
|
||||
for (const auto & attr : selected) {
|
||||
auto it = obj.find(attr);
|
||||
if (it == obj.end() || it->second->is_undefined() || (is_val<value_bool>(it->second) && !it->second->as_bool())) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
result->push_back(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}},
|
||||
// TODO: reverse, sort, join, string, unique
|
||||
};
|
||||
return builtins;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "jinja-vm.h"
|
||||
#include "jinja-parser.h"
|
||||
#include "jinja-value.h"
|
||||
#include "jinja-utils.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
|
@ -14,6 +15,22 @@ bool g_jinja_debug = true;
|
|||
|
||||
namespace jinja {
|
||||
|
||||
// func_args method implementations
|
||||
|
||||
value func_args::get_kwarg(const std::string & key) const {
|
||||
for (const auto & arg : args) {
|
||||
if (is_val<value_kwarg>(arg)) {
|
||||
auto * kwarg = cast_val<value_kwarg>(arg);
|
||||
if (kwarg->key == key) {
|
||||
return kwarg->val;
|
||||
}
|
||||
}
|
||||
}
|
||||
return mk_val<value_undefined>();
|
||||
}
|
||||
|
||||
// utils
|
||||
|
||||
static value_array exec_statements(const statements & stmts, context & ctx) {
|
||||
auto result = mk_val<value_array>();
|
||||
for (const auto & stmt : stmts) {
|
||||
|
|
@ -23,23 +40,6 @@ static value_array exec_statements(const statements & stmts, context & ctx) {
|
|||
return result;
|
||||
}
|
||||
|
||||
static void string_replace_all(std::string & s, const std::string & search, const std::string & replace) {
|
||||
if (search.empty()) {
|
||||
return;
|
||||
}
|
||||
std::string builder;
|
||||
builder.reserve(s.length());
|
||||
size_t pos = 0;
|
||||
size_t last_pos = 0;
|
||||
while ((pos = s.find(search, last_pos)) != std::string::npos) {
|
||||
builder.append(s, last_pos, pos - last_pos);
|
||||
builder.append(replace);
|
||||
last_pos = pos + search.length();
|
||||
}
|
||||
builder.append(s, last_pos, std::string::npos);
|
||||
s = std::move(builder);
|
||||
}
|
||||
|
||||
// execute with error handling
|
||||
value statement::execute(context & ctx) {
|
||||
try {
|
||||
|
|
@ -138,6 +138,7 @@ value binary_expression::execute_impl(context & ctx) {
|
|||
return mk_val<value_int>(static_cast<int64_t>(res));
|
||||
}
|
||||
} else if (op.value == "/") {
|
||||
JJ_DEBUG("Division operation: %f / %f", a, b);
|
||||
return mk_val<value_float>(a / b);
|
||||
} else if (op.value == "%") {
|
||||
double rem = std::fmod(a, b);
|
||||
|
|
@ -149,12 +150,16 @@ value binary_expression::execute_impl(context & ctx) {
|
|||
return mk_val<value_int>(static_cast<int64_t>(rem));
|
||||
}
|
||||
} else if (op.value == "<") {
|
||||
JJ_DEBUG("Comparison operation: %f < %f is %d", a, b, a < b);
|
||||
return mk_val<value_bool>(a < b);
|
||||
} else if (op.value == ">") {
|
||||
JJ_DEBUG("Comparison operation: %f > %f is %d", a, b, a > b);
|
||||
return mk_val<value_bool>(a > b);
|
||||
} else if (op.value == ">=") {
|
||||
JJ_DEBUG("Comparison operation: %f >= %f is %d", a, b, a >= b);
|
||||
return mk_val<value_bool>(a >= b);
|
||||
} else if (op.value == "<=") {
|
||||
JJ_DEBUG("Comparison operation: %f <= %f is %d", a, b, a <= b);
|
||||
return mk_val<value_bool>(a <= b);
|
||||
}
|
||||
}
|
||||
|
|
@ -235,24 +240,33 @@ static value try_builtin_func(const std::string & name, const value & input, boo
|
|||
value filter_expression::execute_impl(context & ctx) {
|
||||
value input = operand->execute(ctx);
|
||||
|
||||
if (is_stmt<identifier>(filter)) {
|
||||
auto filter_val = cast_stmt<identifier>(filter)->val;
|
||||
JJ_DEBUG("Applying filter to %s", input->type().c_str());
|
||||
|
||||
if (filter_val == "to_json") {
|
||||
if (is_stmt<identifier>(filter)) {
|
||||
auto filter_id = cast_stmt<identifier>(filter)->val;
|
||||
|
||||
if (filter_id == "to_json") {
|
||||
// TODO: Implement to_json filter
|
||||
throw std::runtime_error("to_json filter not implemented");
|
||||
}
|
||||
|
||||
if (filter_val == "trim") {
|
||||
filter_val = "strip"; // alias
|
||||
if (filter_id == "trim") {
|
||||
filter_id = "strip"; // alias
|
||||
}
|
||||
JJ_DEBUG("Applying filter '%s' to %s", filter_val.c_str(), input->type().c_str());
|
||||
return try_builtin_func(filter_val, input)->invoke({});
|
||||
JJ_DEBUG("Applying filter '%s' to %s", filter_id.c_str(), input->type().c_str());
|
||||
return try_builtin_func(filter_id, input)->invoke({});
|
||||
|
||||
} else if (is_stmt<call_expression>(filter)) {
|
||||
// TODO
|
||||
// value filter_func = filter->execute(ctx);
|
||||
throw std::runtime_error("Filter with arguments not implemented");
|
||||
auto call = cast_stmt<call_expression>(filter);
|
||||
auto filter_id = cast_stmt<identifier>(call->callee)->val;
|
||||
|
||||
JJ_DEBUG("Applying filter '%s' with arguments to %s", filter_id.c_str(), input->type().c_str());
|
||||
func_args args;
|
||||
for (const auto & arg_expr : call->args) {
|
||||
args.args.push_back(arg_expr->execute(ctx));
|
||||
}
|
||||
|
||||
return try_builtin_func(filter_id, input)->invoke(args);
|
||||
|
||||
} else {
|
||||
throw std::runtime_error("Invalid filter expression");
|
||||
|
|
@ -268,7 +282,7 @@ value test_expression::execute_impl(context & ctx) {
|
|||
|
||||
auto test_id = cast_stmt<identifier>(test)->val;
|
||||
auto it = builtins.find("test_is_" + test_id);
|
||||
JJ_DEBUG("Test expression %s '%s' %s", operand->type().c_str(), test_id.c_str(), negate ? "(negate)" : "");
|
||||
JJ_DEBUG("Test expression %s '%s' %s (using function 'test_is_%s')", operand->type().c_str(), test_id.c_str(), negate ? "(negate)" : "", test_id.c_str());
|
||||
if (it == builtins.end()) {
|
||||
throw std::runtime_error("Unknown test '" + test_id + "'");
|
||||
}
|
||||
|
|
@ -336,6 +350,12 @@ value for_statement::execute_impl(context & ctx) {
|
|||
JJ_DEBUG("Executing for statement, iterable type: %s", iter_expr->type().c_str());
|
||||
|
||||
value iterable_val = iter_expr->execute(scope);
|
||||
|
||||
if (iterable_val->is_undefined()) {
|
||||
JJ_DEBUG("%s", "For loop iterable is undefined, skipping loop");
|
||||
iterable_val = mk_val<value_array>();
|
||||
}
|
||||
|
||||
if (!is_val<value_array>(iterable_val) && !is_val<value_object>(iterable_val)) {
|
||||
throw std::runtime_error("Expected iterable or object type in for loop: got " + iterable_val->type());
|
||||
}
|
||||
|
|
@ -555,7 +575,10 @@ value member_expression::execute_impl(context & ctx) {
|
|||
|
||||
value val = mk_val<value_undefined>();
|
||||
|
||||
if (is_val<value_object>(object)) {
|
||||
if (is_val<value_undefined>(object)) {
|
||||
JJ_DEBUG("%s", "Accessing property on undefined object, returning undefined");
|
||||
return val;
|
||||
} else if (is_val<value_object>(object)) {
|
||||
if (!is_val<value_string>(property)) {
|
||||
throw std::runtime_error("Cannot access object with non-string: got " + property->type());
|
||||
}
|
||||
|
|
@ -623,35 +646,39 @@ value call_expression::execute_impl(context & ctx) {
|
|||
|
||||
// compare operator for value_t
|
||||
bool value_compare(const value & a, const value & b) {
|
||||
JJ_DEBUG("Comparing types: %s and %s", a->type().c_str(), b->type().c_str());
|
||||
// compare numeric types
|
||||
if ((is_val<value_int>(a) || is_val<value_float>(a)) &&
|
||||
(is_val<value_int>(b) || is_val<value_float>(b))){
|
||||
try {
|
||||
return a->as_float() == b->as_float();
|
||||
} catch (...) {}
|
||||
}
|
||||
// compare string and number
|
||||
// TODO: not sure if this is the right behavior
|
||||
if ((is_val<value_string>(b) && (is_val<value_int>(a) || is_val<value_float>(a))) ||
|
||||
(is_val<value_string>(a) && (is_val<value_int>(b) || is_val<value_float>(b)))) {
|
||||
try {
|
||||
auto cmp = [&]() {
|
||||
// compare numeric types
|
||||
if ((is_val<value_int>(a) || is_val<value_float>(a)) &&
|
||||
(is_val<value_int>(b) || is_val<value_float>(b))){
|
||||
try {
|
||||
return a->as_float() == b->as_float();
|
||||
} catch (...) {}
|
||||
}
|
||||
// compare string and number
|
||||
// TODO: not sure if this is the right behavior
|
||||
if ((is_val<value_string>(b) && (is_val<value_int>(a) || is_val<value_float>(a))) ||
|
||||
(is_val<value_string>(a) && (is_val<value_int>(b) || is_val<value_float>(b)))) {
|
||||
try {
|
||||
return a->as_string().str() == b->as_string().str();
|
||||
} catch (...) {}
|
||||
}
|
||||
// compare boolean simple
|
||||
if (is_val<value_bool>(a) && is_val<value_bool>(b)) {
|
||||
return a->as_bool() == b->as_bool();
|
||||
}
|
||||
// compare string simple
|
||||
if (is_val<value_string>(a) && is_val<value_string>(b)) {
|
||||
return a->as_string().str() == b->as_string().str();
|
||||
} catch (...) {}
|
||||
}
|
||||
// compare boolean simple
|
||||
if (is_val<value_bool>(a) && is_val<value_bool>(b)) {
|
||||
return a->as_bool() == b->as_bool();
|
||||
}
|
||||
// compare string simple
|
||||
if (is_val<value_string>(a) && is_val<value_string>(b)) {
|
||||
return a->as_string().str() == b->as_string().str();
|
||||
}
|
||||
// compare by type
|
||||
if (a->type() != b->type()) {
|
||||
}
|
||||
// compare by type
|
||||
if (a->type() != b->type()) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
auto result = cmp();
|
||||
JJ_DEBUG("Comparing types: %s and %s result=%d", a->type().c_str(), b->type().c_str(), result);
|
||||
return result;
|
||||
}
|
||||
|
||||
value keyword_argument_expression::execute_impl(context & ctx) {
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ struct statement {
|
|||
// execute_impl must be overridden by derived classes
|
||||
virtual value execute_impl(context &) { throw std::runtime_error("cannot exec " + type()); }
|
||||
// execute is the public method to execute a statement with error handling
|
||||
virtual value execute(context &);
|
||||
value execute(context &);
|
||||
};
|
||||
|
||||
// Type Checking Utilities
|
||||
|
|
@ -288,13 +288,17 @@ struct array_literal : public expression {
|
|||
for (const auto& item : this->val) chk_type<expression>(item);
|
||||
}
|
||||
std::string type() const override { return "ArrayLiteral"; }
|
||||
value execute_impl(context & ctx) override {
|
||||
auto arr = mk_val<value_array>();
|
||||
for (const auto & item_stmt : val) {
|
||||
arr->push_back(item_stmt->execute(ctx));
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
};
|
||||
|
||||
struct tuple_literal : public expression {
|
||||
statements val;
|
||||
explicit tuple_literal(statements && val) : val(std::move(val)) {
|
||||
for (const auto & item : this->val) chk_type<expression>(item);
|
||||
}
|
||||
struct tuple_literal : public array_literal {
|
||||
explicit tuple_literal(statements && val) : array_literal(std::move(val)) {}
|
||||
std::string type() const override { return "TupleLiteral"; }
|
||||
};
|
||||
|
||||
|
|
@ -376,6 +380,13 @@ struct select_expression : public expression {
|
|||
chk_type<expression>(this->test);
|
||||
}
|
||||
std::string type() const override { return "SelectExpression"; }
|
||||
value execute_impl(context & ctx) override {
|
||||
auto predicate = test->execute_impl(ctx);
|
||||
if (!predicate->as_bool()) {
|
||||
return mk_val<value_undefined>();
|
||||
}
|
||||
return lhs->execute_impl(ctx);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -474,6 +485,14 @@ struct ternary_expression : public expression {
|
|||
chk_type<expression>(this->false_expr);
|
||||
}
|
||||
std::string type() const override { return "Ternary"; }
|
||||
value execute_impl(context & ctx) override {
|
||||
value cond_val = condition->execute(ctx);
|
||||
if (cond_val->as_bool()) {
|
||||
return true_expr->execute(ctx);
|
||||
} else {
|
||||
return false_expr->execute(ctx);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct raised_exception : public std::exception {
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ void run(std::string contents) {
|
|||
messages->push_back(std::move(msg2));
|
||||
|
||||
ctx.var["messages"] = std::move(messages);
|
||||
ctx.var["eos_token"] = jinja::mk_val<jinja::value_string>("</s>");
|
||||
// ctx.var["tools"] = jinja::mk_val<jinja::value_null>();
|
||||
|
||||
jinja::vm vm(ctx);
|
||||
const jinja::value results = vm.execute(ast);
|
||||
|
|
|
|||
Loading…
Reference in New Issue