#pragma once #include "jinja-lexer.h" #include "jinja-value.h" #include #include #include #include #include namespace jinja { struct context { std::map var; context() { var["true"] = mk_val(true); var["false"] = mk_val(false); var["none"] = mk_val(); } ~context() = default; context(const context & parent) { // inherit variables (for example, when entering a new scope) for (const auto & pair : parent.var) { var[pair.first] = pair.second->clone(); } } }; /** * Base class for all nodes in the AST. */ struct statement { virtual ~statement() = default; virtual std::string type() const { return "Statement"; } virtual value execute(context &) { throw std::runtime_error("cannot exec " + type()); } }; using statement_ptr = std::unique_ptr; using statements = std::vector; // Type Checking Utilities template static void chk_type(const statement_ptr & ptr) { if (!ptr) return; // Allow null for optional fields assert(dynamic_cast(ptr.get()) != nullptr); } template static void chk_type(const statement_ptr & ptr) { if (!ptr) return; assert(dynamic_cast(ptr.get()) != nullptr || dynamic_cast(ptr.get()) != nullptr); } // Base Types /** * Expressions will result in a value at runtime (unlike statements). */ struct expression : public statement { std::string type() const override { return "Expression"; } }; // Statements struct program : public statement { statements body; explicit program(statements && body) : body(std::move(body)) {} std::string type() const override { return "Program"; } value execute(context &) override { throw std::runtime_error("Cannot execute program directly, use jinja::vm instead"); } }; struct if_statement : public statement { statement_ptr test; statements body; statements alternate; if_statement(statement_ptr && test, statements && body, statements && alternate) : test(std::move(test)), body(std::move(body)), alternate(std::move(alternate)) { chk_type(this->test); } std::string type() const override { return "If"; } value execute(context & ctx) override; }; struct identifier; struct tuple_literal; /** * Loop over each item in a sequence * https://jinja.palletsprojects.com/en/3.0.x/templates/#for */ struct for_statement : public statement { statement_ptr loopvar; // Identifier | TupleLiteral statement_ptr iterable; statements body; statements default_block; // if no iteration took place for_statement(statement_ptr && loopvar, statement_ptr && iterable, statements && body, statements && default_block) : loopvar(std::move(loopvar)), iterable(std::move(iterable)), body(std::move(body)), default_block(std::move(default_block)) { chk_type(this->loopvar); chk_type(this->iterable); } std::string type() const override { return "For"; } value execute(context & ctx) override; }; struct break_statement : public statement { std::string type() const override { return "Break"; } struct exception : public std::exception { const char* what() const noexcept override { return "Break statement executed"; } }; value execute(context &) override { throw break_statement::exception(); } }; struct continue_statement : public statement { std::string type() const override { return "Continue"; } struct exception : public std::exception { const char* what() const noexcept override { return "Continue statement executed"; } }; value execute(context &) override { throw continue_statement::exception(); } }; struct set_statement : public statement { statement_ptr assignee; statement_ptr val; statements body; set_statement(statement_ptr && assignee, statement_ptr && value, statements && body) : assignee(std::move(assignee)), val(std::move(value)), body(std::move(body)) { chk_type(this->assignee); chk_type(this->val); } std::string type() const override { return "Set"; } value execute(context & ctx) override; }; struct macro_statement : public statement { statement_ptr name; statements args; statements body; macro_statement(statement_ptr && name, statements && args, statements && body) : name(std::move(name)), args(std::move(args)), body(std::move(body)) { chk_type(this->name); for (const auto& arg : this->args) chk_type(arg); } std::string type() const override { return "Macro"; } value execute(context & ctx) override; }; struct comment_statement : public statement { std::string val; explicit comment_statement(const std::string & v) : val(v) {} std::string type() const override { return "Comment"; } value execute(context &) override { return mk_val(); } }; // Expressions struct member_expression : public expression { statement_ptr object; statement_ptr property; bool computed; member_expression(statement_ptr && object, statement_ptr && property, bool computed) : object(std::move(object)), property(std::move(property)), computed(computed) { chk_type(this->object); chk_type(this->property); } std::string type() const override { return "MemberExpression"; } value execute(context & ctx) override; }; struct call_expression : public expression { statement_ptr callee; statements args; call_expression(statement_ptr && callee, statements && args) : callee(std::move(callee)), args(std::move(args)) { chk_type(this->callee); for (const auto& arg : this->args) chk_type(arg); } std::string type() const override { return "CallExpression"; } value execute(context & ctx) override; }; /** * Represents a user-defined variable or symbol in the template. */ struct identifier : public expression { std::string val; explicit identifier(const std::string & val) : val(val) {} std::string type() const override { return "Identifier"; } value execute(context & ctx) override; }; // Literals struct integer_literal : public expression { int64_t val; explicit integer_literal(int64_t val) : val(val) {} std::string type() const override { return "IntegerLiteral"; } value execute(context &) override { return std::make_unique(val); } }; struct float_literal : public expression { double val; explicit float_literal(double val) : val(val) {} std::string type() const override { return "FloatLiteral"; } value execute(context &) override { return std::make_unique(val); } }; struct string_literal : public expression { std::string val; explicit string_literal(const std::string & val) : val(val) {} std::string type() const override { return "StringLiteral"; } value execute(context &) override { return std::make_unique(val); } }; struct array_literal : public expression { statements val; explicit array_literal(statements && val) : val(std::move(val)) { for (const auto& item : this->val) chk_type(item); } std::string type() const override { return "ArrayLiteral"; } }; struct tuple_literal : public expression { statements val; explicit tuple_literal(statements && val) : val(std::move(val)) { for (const auto & item : this->val) chk_type(item); } std::string type() const override { return "TupleLiteral"; } }; struct object_literal : public expression { std::vector> val; explicit object_literal(std::vector> && val) : val(std::move(val)) { for (const auto & pair : this->val) { chk_type(pair.first); chk_type(pair.second); } } std::string type() const override { return "ObjectLiteral"; } }; // Complex Expressions /** * An operation with two sides, separated by an operator. * Note: Either side can be a Complex Expression, with order * of operations being determined by the operator. */ struct binary_expression : public expression { token op; statement_ptr left; statement_ptr right; binary_expression(token op, statement_ptr && left, statement_ptr && right) : op(op), left(std::move(left)), right(std::move(right)) { chk_type(this->left); chk_type(this->right); } std::string type() const override { return "BinaryExpression"; } value execute(context & ctx) override; }; /** * An operation with two sides, separated by the | operator. * Operator precedence: https://github.com/pallets/jinja/issues/379#issuecomment-168076202 */ struct filter_expression : public expression { statement_ptr operand; statement_ptr filter; filter_expression(statement_ptr && operand, statement_ptr && filter) : operand(std::move(operand)), filter(std::move(filter)) { chk_type(this->operand); chk_type(this->filter); } std::string type() const override { return "FilterExpression"; } value execute(context & ctx) override; }; struct filter_statement : public statement { statement_ptr filter; statements body; filter_statement(statement_ptr && filter, statements && body) : filter(std::move(filter)), body(std::move(body)) { chk_type(this->filter); } std::string type() const override { return "FilterStatement"; } }; /** * An operation which filters a sequence of objects by applying a test to each object, * and only selecting the objects with the test succeeding. * * It may also be used as a shortcut for a ternary operator. */ struct select_expression : public expression { statement_ptr lhs; statement_ptr test; select_expression(statement_ptr && lhs, statement_ptr && test) : lhs(std::move(lhs)), test(std::move(test)) { chk_type(this->lhs); chk_type(this->test); } std::string type() const override { return "SelectExpression"; } }; /** * An operation with two sides, separated by the "is" operator. * NOTE: "value is something" translates to function call "test_is_something(value)" */ struct test_expression : public expression { statement_ptr operand; bool negate; statement_ptr test; test_expression(statement_ptr && operand, bool negate, statement_ptr && test) : operand(std::move(operand)), negate(negate), test(std::move(test)) { chk_type(this->operand); chk_type(this->test); } std::string type() const override { return "TestExpression"; } value execute(context & ctx) override; }; /** * An operation with one side (operator on the left). */ struct unary_expression : public expression { token op; statement_ptr argument; unary_expression(token op, statement_ptr && argument) : op(std::move(op)), argument(std::move(argument)) { chk_type(this->argument); } std::string type() const override { return "UnaryExpression"; } value execute(context & ctx) override; }; struct slice_expression : public expression { statement_ptr start_expr; statement_ptr stop_expr; statement_ptr step_expr; slice_expression(statement_ptr && start_expr, statement_ptr && stop_expr, statement_ptr && step_expr) : start_expr(std::move(start_expr)), stop_expr(std::move(stop_expr)), step_expr(std::move(step_expr)) { chk_type(this->start_expr); chk_type(this->stop_expr); chk_type(this->step_expr); } std::string type() const override { return "SliceExpression"; } value execute(context &) override { throw std::runtime_error("must be handled by MemberExpression"); } }; struct keyword_argument_expression : public expression { statement_ptr key; statement_ptr val; keyword_argument_expression(statement_ptr && key, statement_ptr && val) : key(std::move(key)), val(std::move(val)) { chk_type(this->key); chk_type(this->val); } std::string type() const override { return "KeywordArgumentExpression"; } value execute(context & ctx) override; }; struct spread_expression : public expression { statement_ptr argument; explicit spread_expression(statement_ptr && argument) : argument(std::move(argument)) { chk_type(this->argument); } std::string type() const override { return "SpreadExpression"; } }; struct call_statement : public statement { statement_ptr call; statements caller_args; statements body; call_statement(statement_ptr && call, statements && caller_args, statements && body) : call(std::move(call)), caller_args(std::move(caller_args)), body(std::move(body)) { chk_type(this->call); for (const auto& arg : this->caller_args) chk_type(arg); } std::string type() const override { return "CallStatement"; } }; struct ternary_expression : public expression { statement_ptr condition; statement_ptr true_expr; statement_ptr false_expr; ternary_expression(statement_ptr && condition, statement_ptr && true_expr, statement_ptr && false_expr) : condition(std::move(condition)), true_expr(std::move(true_expr)), false_expr(std::move(false_expr)) { chk_type(this->condition); chk_type(this->true_expr); chk_type(this->false_expr); } std::string type() const override { return "Ternary"; } }; struct raised_exception : public std::exception { std::string message; raised_exception(const std::string & msg) : message(msg) {} const char* what() const noexcept override { return message.c_str(); } }; ////////////////////// struct vm { context & ctx; explicit vm(context & ctx) : ctx(ctx) {} value_array execute(program & prog) { value_array results = mk_val(); for (auto & stmt : prog.body) { value res = stmt->execute(ctx); results->val_arr->push_back(std::move(res)); } return results; } std::vector gather_string_parts(const value & val) { std::vector parts; gather_string_parts_recursive(val, parts); return parts; } void gather_string_parts_recursive(const value & val, std::vector & parts) { if (is_val(val)) { const auto & str_val = dynamic_cast(val.get())->val_str; for (const auto & part : str_val.parts) { parts.push_back(part); } } else if (is_val(val)) { auto items = dynamic_cast(val.get())->val_arr.get(); for (const auto & item : *items) { gather_string_parts_recursive(item, parts); } } } }; } // namespace jinja