bin ops works!

This commit is contained in:
Xuan Son Nguyen 2025-12-27 20:16:46 +01:00
parent 8d1e9a0d12
commit d8ef00e610
4 changed files with 206 additions and 46 deletions

View File

@ -15,12 +15,22 @@ struct value_t {
double val_flt;
std::string val_str;
bool val_bool;
std::vector<value> val_arr;
std::map<std::string, value> val_obj;
// array and object are stored as shared_ptr to allow reference access
// example:
// my_obj = {"a": 1, "b": 2}
// my_arr = [my_obj]
// my_obj["a"] = 3
// print(my_arr[0]["a"]) # should print 3
std::shared_ptr<std::vector<value>> val_arr;
std::shared_ptr<std::map<std::string, value>> val_obj;
value_t() = default;
value_t(const value_t &) = default;
virtual ~value_t() = default;
virtual std::string type() const { return ""; }
virtual ~value_t() = default;
virtual int64_t as_int() const { throw std::runtime_error("Not an int value"); }
virtual double as_float() const { throw std::runtime_error("Not a float value"); }
virtual std::string as_string() const { throw std::runtime_error("Not a string value"); }
@ -30,6 +40,10 @@ struct value_t {
virtual bool is_null() const { return false; }
virtual bool is_undefined() const { return false; }
virtual value clone() const {
return std::make_unique<value_t>(*this);
}
virtual bool operator==(const value & other) const {
// TODO
return false;
@ -44,6 +58,8 @@ struct value_int_t : public value_t {
virtual std::string type() const override { return "Integer"; }
virtual int64_t as_int() const override { return val_int; }
virtual double as_float() const override { return static_cast<double>(val_int); }
virtual std::string as_string() const override { return std::to_string(val_int); }
virtual value clone() const override { return std::make_unique<value_int_t>(*this); }
};
using value_int = std::unique_ptr<value_int_t>;
@ -52,6 +68,8 @@ struct value_float_t : public value_t {
virtual std::string type() const override { return "Float"; }
virtual double as_float() const override { return val_flt; }
virtual int64_t as_int() const override { return static_cast<int64_t>(val_flt); }
virtual std::string as_string() const override { return std::to_string(val_flt); }
virtual value clone() const override { return std::make_unique<value_float_t>(*this); }
};
using value_float = std::unique_ptr<value_float_t>;
@ -59,6 +77,7 @@ struct value_string_t : public value_t {
value_string_t(const std::string & v) { val_str = v; }
virtual std::string type() const override { return "String"; }
virtual std::string as_string() const override { return val_str; }
virtual value clone() const override { return std::make_unique<value_string_t>(*this); }
};
using value_string = std::unique_ptr<value_string_t>;
@ -66,32 +85,81 @@ struct value_bool_t : public value_t {
value_bool_t(bool v) { val_bool = v; }
virtual std::string type() const override { return "Boolean"; }
virtual bool as_bool() const override { return val_bool; }
virtual std::string as_string() const override { return val_bool ? "True" : "False"; }
virtual value clone() const override { return std::make_unique<value_bool_t>(*this); }
};
using value_bool = std::unique_ptr<value_bool_t>;
struct value_array_t : public value_t {
value_array_t(const std::vector<value> && v) { val_arr = std::move(v); }
value_array_t() {
val_arr = std::make_shared<std::vector<value>>();
}
value_array_t(value & v) {
// point to the same underlying data
val_arr = v->val_arr;
}
value_array_t(value_array_t & other, size_t start = 0, size_t end = -1) {
val_arr = std::make_shared<std::vector<value>>();
size_t sz = other.val_arr->size();
if (end == static_cast<size_t>(-1) || end > sz) {
end = sz;
}
if (start > end || start >= sz) {
return;
}
for (size_t i = start; i < end; i++) {
val_arr->push_back(other.val_arr->at(i)->clone());
}
}
virtual std::string type() const override { return "Array"; }
virtual const std::vector<value> & as_array() const override { return val_arr; }
virtual const std::vector<value> & as_array() const override { return *val_arr; }
virtual value clone() const override {
auto tmp = std::make_unique<value_array_t>();
tmp->val_arr = this->val_arr;
return tmp;
}
};
using value_array = std::unique_ptr<value_array_t>;
struct value_object_t : public value_t {
value_object_t(const std::map<std::string, value> & v) { val_obj = v; }
/*struct value_object_t : public value_t {
value_object_t() {
val_obj = std::make_shared<std::map<std::string, value>>();
}
value_object_t(value & v) {
// point to the same underlying data
val_obj = v->val_obj;
}
value_object_t(const std::map<std::string, value> & obj) {
val_obj = std::make_shared<std::map<std::string, value>>(obj);
}
virtual std::string type() const override { return "Object"; }
virtual const std::map<std::string, value> & as_object() const override { return val_obj; }
virtual const std::map<std::string, value> & as_object() const override { return *val_obj; }
virtual value clone() const override {
auto tmp = std::make_unique<value_object_t>();
tmp->val_obj = this->val_obj;
return tmp;
}
};
using value_object = std::unique_ptr<value_object_t>;*/
struct value_object_t : public value_t {
virtual std::string type() const override { return "TEST"; }
virtual bool is_null() const override { return true; }
virtual value clone() const override { return std::make_unique<value_object_t>(*this); }
};
using value_object = std::unique_ptr<value_object_t>;
struct value_null_t : public value_t {
virtual std::string type() const override { return "Null"; }
virtual bool is_null() const override { return true; }
virtual value clone() const override { return std::make_unique<value_null_t>(*this); }
};
using value_null = std::unique_ptr<value_null_t>;
struct value_undefined_t : public value_t {
virtual std::string type() const override { return "Undefined"; }
virtual bool is_undefined() const override { return true; }
virtual value clone() const override { return std::make_unique<value_undefined_t>(*this); }
};
using value_undefined = std::unique_ptr<value_undefined_t>;

View File

@ -9,22 +9,27 @@
namespace jinja {
// Helper to check type without asserting (useful for logic)
// Helper to extract the inner type if T is unique_ptr<U>, else T itself
template<typename T>
static bool is_type(const value & ptr) {
return dynamic_cast<const T*>(ptr.get()) != nullptr;
struct extract_pointee {
using type = T;
};
template<typename U>
struct extract_pointee<std::unique_ptr<U>> {
using type = U;
};
template<typename T>
static bool is_type(const value& ptr) {
using PointeeType = typename extract_pointee<T>::type;
return dynamic_cast<const PointeeType*>(ptr.get()) != nullptr;
}
struct vm {
context & ctx;
explicit vm(context & ctx) : ctx(ctx) {}
void execute(program & prog) {
for (auto & stmt : prog.body) {
stmt->execute(ctx);
}
}
};
template<typename T>
static bool is_stmt(const statement_ptr & ptr) {
return dynamic_cast<const T*>(ptr.get()) != nullptr;
}
value binary_expression::execute(context & ctx) {
value left_val = left->execute(ctx);
@ -97,13 +102,16 @@ value binary_expression::execute(context & ctx) {
// Array operations
if (is_type<value_array>(left_val) && is_type<value_array>(right_val)) {
if (op.value == "+") {
auto& left_arr = left_val->as_array();
auto& right_arr = right_val->as_array();
std::vector<value> result = left_arr;
for (auto & v : right_arr) {
result.push_back(std::move(v));
auto & left_arr = left_val->as_array();
auto & right_arr = right_val->as_array();
auto result = std::make_unique<value_array_t>();
for (const auto & item : left_arr) {
result->val_arr->push_back(item->clone());
}
return std::make_unique<value_array_t>(result);
for (const auto & item : right_arr) {
result->val_arr->push_back(item->clone());
}
return result;
}
} else if (is_type<value_array>(right_val)) {
auto & arr = right_val->as_array();
@ -148,4 +156,52 @@ value binary_expression::execute(context & ctx) {
throw std::runtime_error("Unknown operator \"" + op.value + "\" between " + left_val->type() + " and " + right_val->type());
}
value filter_expression::execute(context & ctx) {
value input = operand->execute(ctx);
value filter_func = filter->execute(ctx);
if (is_stmt<identifier>(filter)) {
auto filter_val = dynamic_cast<identifier*>(filter.get())->value;
if (filter_val == "to_json") {
// TODO: Implement to_json filter
throw std::runtime_error("to_json filter not implemented");
}
if (is_type<value_array>(input)) {
auto & arr = input->as_array();
if (filter_val == "list") {
return std::make_unique<value_array_t>(input);
} else if (filter_val == "first") {
if (arr.empty()) {
return std::make_unique<value_undefined_t>();
}
return arr[0]->clone();
} else if (filter_val == "last") {
if (arr.empty()) {
return std::make_unique<value_undefined_t>();
}
return arr[arr.size() - 1]->clone();
} else if (filter_val == "length") {
return std::make_unique<value_int_t>(static_cast<int64_t>(arr.size()));
} else {
// TODO: reverse, sort, join, string, unique
throw std::runtime_error("Unknown filter '" + filter_val + "' for array");
}
} else if (is_type<value_string>(input)) {
auto str = input->as_string();
// TODO
throw std::runtime_error("Unknown filter '" + filter_val + "' for string");
} else if (is_type<value_int>(input) || is_type<value_float>(input)) {
// TODO
throw std::runtime_error("Unknown filter '" + filter_val + "' for number");
} else {
throw std::runtime_error("Filters not supported for type " + input->type());
}
}
}
} // namespace jinja

View File

@ -23,7 +23,7 @@ struct context {
struct statement {
virtual ~statement() = default;
virtual std::string type() const { return "Statement"; }
virtual value execute(context & ctx) = 0;
virtual value execute(context & ctx) { throw std::runtime_error("cannot exec " + type()); };
};
using statement_ptr = std::unique_ptr<statement>;
@ -186,44 +186,53 @@ struct identifier : public expression {
// Literals
struct integer_literal : public expression {
int64_t value;
explicit integer_literal(int64_t value) : value(value) {}
int64_t val;
explicit integer_literal(int64_t val) : val(val) {}
std::string type() const override { return "IntegerLiteral"; }
value execute(context & ctx) override {
return std::make_unique<value_int_t>(val);
}
};
struct float_literal : public expression {
double value;
explicit float_literal(double value) : value(value) {}
double val;
explicit float_literal(double val) : val(val) {}
std::string type() const override { return "FloatLiteral"; }
value execute(context & ctx) override {
return std::make_unique<value_float_t>(val);
}
};
struct string_literal : public expression {
std::string value;
explicit string_literal(const std::string & value) : value(value) {}
std::string val;
explicit string_literal(const std::string & val) : val(val) {}
std::string type() const override { return "StringLiteral"; }
value execute(context & ctx) override {
return std::make_unique<value_string_t>(val);
}
};
struct array_literal : public expression {
statements value;
explicit array_literal(statements && value) : value(std::move(value)) {
for (const auto& item : this->value) chk_type<expression>(item);
statements val;
explicit array_literal(statements && val) : val(std::move(val)) {
for (const auto& item : this->val) chk_type<expression>(item);
}
std::string type() const override { return "ArrayLiteral"; }
};
struct tuple_literal : public expression {
statements value;
explicit tuple_literal(statements && value) : value(std::move(value)) {
for (const auto& item : this->value) chk_type<expression>(item);
statements val;
explicit tuple_literal(statements && val) : val(std::move(val)) {
for (const auto & item : this->val) chk_type<expression>(item);
}
std::string type() const override { return "TupleLiteral"; }
};
struct object_literal : public expression {
std::vector<std::pair<statement_ptr, statement_ptr>> value;
explicit object_literal(std::vector<std::pair<statement_ptr, statement_ptr>> && value)
: value(std::move(value)) {
for (const auto & pair : this->value) {
std::vector<std::pair<statement_ptr, statement_ptr>> val;
explicit object_literal(std::vector<std::pair<statement_ptr, statement_ptr>> && val)
: val(std::move(val)) {
for (const auto & pair : this->val) {
chk_type<expression>(pair.first);
chk_type<expression>(pair.second);
}
@ -391,4 +400,20 @@ struct ternary_expression : public expression {
std::string type() const override { return "Ternary"; }
};
//////////////////////
struct vm {
context & ctx;
explicit vm(context & ctx) : ctx(ctx) {}
std::vector<value> execute(program & prog) {
std::vector<value> results;
for (auto & stmt : prog.body) {
value res = stmt->execute(ctx);
results.push_back(std::move(res));
}
return results;
}
};
} // namespace jinja

View File

@ -11,7 +11,9 @@
#include "jinja/jinja-lexer.h"
int main(void) {
std::string contents = "{% if messages[0]['role'] == 'system' %}{{ raise_exception('System role not supported') }}{% endif %}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if (message['role'] == 'assistant') %}{% set role = 'model' %}{% else %}{% set role = message['role'] %}{% endif %}{{ '<start_of_turn>' + role + '\\n' + message['content'] | trim + '<end_of_turn>\\n' }}{% endfor %}{% if add_generation_prompt %}{{'<start_of_turn>model\\n'}}{% endif %}";
//std::string contents = "{% if messages[0]['role'] == 'system' %}{{ raise_exception('System role not supported') }}{% endif %}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if (message['role'] == 'assistant') %}{% set role = 'model' %}{% else %}{% set role = message['role'] %}{% endif %}{{ '<start_of_turn>' + role + '\\n' + message['content'] | trim + '<end_of_turn>\\n' }}{% endfor %}{% if add_generation_prompt %}{{'<start_of_turn>model\\n'}}{% endif %}";
std::string contents = "{{ 'hi' + 'fi' }}";
std::cout << "=== INPUT ===\n" << contents << "\n\n";
@ -24,11 +26,20 @@ int main(void) {
std::cout << "token: type=" << static_cast<int>(tok.t) << " text='" << tok.value << "'\n";
}
jinja::program ast = jinja::parse_from_tokens(tokens);
std::cout << "\n=== AST ===\n";
jinja::program ast = jinja::parse_from_tokens(tokens);
for (const auto & stmt : ast.body) {
std::cout << "stmt type: " << stmt->type() << "\n";
}
std::cout << "\n=== OUTPUT ===\n";
jinja::context ctx;
jinja::vm vm(ctx);
auto results = vm.execute(ast);
for (const auto & res : results) {
std::cout << "result type: " << res->type() << "\n";
std::cout << "result value: " << res->as_string() << "\n";
}
return 0;
}