1#include "lexer.h"
  2#include "runtime.h"
  3#include "value.h"
  4#include "utils.h"
  5
  6#include <string>
  7#include <vector>
  8#include <memory>
  9#include <cmath>
 10
 11#define FILENAME "jinja-runtime"
 12
 13bool g_jinja_debug = false;
 14
 15namespace jinja {
 16
 17void enable_debug(bool enable) {
 18    g_jinja_debug = enable;
 19}
 20
 21static value_string exec_statements(const statements & stmts, context & ctx) {
 22    auto result = mk_val<value_array>();
 23    for (const auto & stmt : stmts) {
 24        JJ_DEBUG("Executing statement of type %s", stmt->type().c_str());
 25        result->push_back(stmt->execute(ctx));
 26    }
 27    // convert to string parts
 28    value_string str = mk_val<value_string>();
 29    gather_string_parts_recursive(result, str);
 30    return str;
 31}
 32
 33static std::string get_line_col(const std::string & source, size_t pos) {
 34    size_t line = 1;
 35    size_t col = 1;
 36    for (size_t i = 0; i < pos && i < source.size(); i++) {
 37        if (source[i] == '\n') {
 38            line++;
 39            col = 1;
 40        } else {
 41            col++;
 42        }
 43    }
 44    return "line " + std::to_string(line) + ", column " + std::to_string(col);
 45}
 46
 47static void ensure_key_type_allowed(const value & val) {
 48    if (!val->is_hashable()) {
 49        throw std::runtime_error("Type: " + val->type() + " is not allowed as object key");
 50    }
 51}
 52
 53// execute with error handling
 54value statement::execute(context & ctx) {
 55    try {
 56        return execute_impl(ctx);
 57    } catch (const continue_statement::signal & /* ex */) {
 58        throw;
 59    } catch (const break_statement::signal & /* ex */) {
 60        throw;
 61    } catch (const rethrown_exception & /* ex */) {
 62        throw;
 63    } catch (const not_implemented_exception & /* ex */) {
 64        throw;
 65    } catch (const std::exception & e) {
 66        const std::string & source = *ctx.src;
 67        if (source.empty()) {
 68            std::ostringstream oss;
 69            oss << "\nError executing " << type() << " at position " << pos << ": " << e.what();
 70            throw rethrown_exception(oss.str());
 71        } else {
 72            std::ostringstream oss;
 73            oss << "\n------------\n";
 74            oss << "While executing " << type() << " at " << get_line_col(source, pos) << " in source:\n";
 75            oss << peak_source(source, pos) << "\n";
 76            oss << "Error: " << e.what();
 77            // throw as another exception to avoid repeated formatting
 78            throw rethrown_exception(oss.str());
 79        }
 80    }
 81}
 82
 83value identifier::execute_impl(context & ctx) {
 84    auto it = ctx.get_val(val);
 85    auto builtins = global_builtins();
 86    if (!it->is_undefined()) {
 87        if (ctx.is_get_stats) {
 88            it->stats.used = true;
 89        }
 90        JJ_DEBUG("Identifier '%s' found, type = %s", val.c_str(), it->type().c_str());
 91        return it;
 92    } else if (builtins.find(val) != builtins.end()) {
 93        JJ_DEBUG("Identifier '%s' found in builtins", val.c_str());
 94        return mk_val<value_func>(val, builtins.at(val));
 95    } else {
 96        JJ_DEBUG("Identifier '%s' not found, returning undefined", val.c_str());
 97        return mk_val<value_undefined>(val);
 98    }
 99}
100
101value object_literal::execute_impl(context & ctx) {
102    auto obj = mk_val<value_object>();
103    for (const auto & pair : val) {
104        value key = pair.first->execute(ctx);
105        value val = pair.second->execute(ctx);
106        JJ_DEBUG("Object literal: setting key '%s' with value type %s", key->as_string().str().c_str(), val->type().c_str());
107        obj->insert(key, val);
108    }
109    return obj;
110}
111
112value binary_expression::execute_impl(context & ctx) {
113    value left_val = left->execute(ctx);
114
115    // Logical operators
116    if (op.value == "and") {
117        return left_val->as_bool() ? right->execute(ctx) : std::move(left_val);
118    } else if (op.value == "or") {
119        return left_val->as_bool() ? std::move(left_val) : right->execute(ctx);
120    }
121
122    // Equality operators
123    value right_val = right->execute(ctx);
124    JJ_DEBUG("Executing binary expression %s '%s' %s", left_val->type().c_str(), op.value.c_str(), right_val->type().c_str());
125    if (op.value == "==") {
126        return mk_val<value_bool>(*left_val == *right_val);
127    } else if (op.value == "!=") {
128        return mk_val<value_bool>(!(*left_val == *right_val));
129    }
130
131    auto workaround_concat_null_with_str = [&](value & res) -> bool {
132        bool is_left_null  = left_val->is_none()  || left_val->is_undefined();
133        bool is_right_null = right_val->is_none() || right_val->is_undefined();
134        bool is_left_str   = is_val<value_string>(left_val);
135        bool is_right_str  = is_val<value_string>(right_val);
136        if ((is_left_null && is_right_str) || (is_right_null && is_left_str)) {
137            JJ_DEBUG("%s", "Workaround: treating null/undefined as empty string for string concatenation");
138            string left_str  = is_left_null  ? string() : left_val->as_string();
139            string right_str = is_right_null ? string() : right_val->as_string();
140            auto output = left_str.append(right_str);
141            res = mk_val<value_string>(std::move(output));
142            return true;
143        }
144        return false;
145    };
146
147    auto test_is_in = [&]() -> bool {
148        func_args args(ctx);
149        args.push_back(left_val);
150        args.push_back(right_val);
151        return global_builtins().at("test_is_in")(args)->as_bool();
152    };
153
154    // Handle undefined and null values
155    if (is_val<value_undefined>(left_val) || is_val<value_undefined>(right_val)) {
156        if (is_val<value_undefined>(right_val) && (op.value == "in" || op.value == "not in")) {
157            // Special case: `anything in undefined` is `false` and `anything not in undefined` is `true`
158            return mk_val<value_bool>(op.value == "not in");
159        }
160        if (op.value == "+" || op.value == "~") {
161            value res = mk_val<value_undefined>();
162            if (workaround_concat_null_with_str(res)) {
163                return res;
164            }
165        }
166        throw std::runtime_error("Cannot perform operation " + op.value + " on undefined values");
167    } else if (is_val<value_none>(left_val) || is_val<value_none>(right_val)) {
168        if (op.value == "+" || op.value == "~") {
169            value res = mk_val<value_undefined>();
170            if (workaround_concat_null_with_str(res)) {
171                return res;
172            }
173        }
174        throw std::runtime_error("Cannot perform operation on null values");
175    }
176
177    // Float operations
178    if ((is_val<value_int>(left_val) || is_val<value_float>(left_val)) &&
179        (is_val<value_int>(right_val) || is_val<value_float>(right_val))) {
180        double a = left_val->as_float();
181        double b = right_val->as_float();
182        if (op.value == "+" || op.value == "-" || op.value == "*") {
183            double res = (op.value == "+") ? a + b : (op.value == "-") ? a - b : a * b;
184            JJ_DEBUG("Arithmetic operation: %f %s %f = %f", a, op.value.c_str(), b, res);
185            bool is_float = is_val<value_float>(left_val) || is_val<value_float>(right_val);
186            if (is_float) {
187                return mk_val<value_float>(res);
188            } else {
189                return mk_val<value_int>(static_cast<int64_t>(res));
190            }
191        } else if (op.value == "/") {
192            JJ_DEBUG("Division operation: %f / %f", a, b);
193            return mk_val<value_float>(a / b);
194        } else if (op.value == "%") {
195            double rem = std::fmod(a, b);
196            JJ_DEBUG("Modulo operation: %f %% %f = %f", a, b, rem);
197            bool is_float = is_val<value_float>(left_val) || is_val<value_float>(right_val);
198            if (is_float) {
199                return mk_val<value_float>(rem);
200            } else {
201                return mk_val<value_int>(static_cast<int64_t>(rem));
202            }
203        } else if (op.value == "<") {
204            JJ_DEBUG("Comparison operation: %f < %f is %d", a, b, a < b);
205            return mk_val<value_bool>(a < b);
206        } else if (op.value == ">") {
207            JJ_DEBUG("Comparison operation: %f > %f is %d", a, b, a > b);
208            return mk_val<value_bool>(a > b);
209        } else if (op.value == ">=") {
210            JJ_DEBUG("Comparison operation: %f >= %f is %d", a, b, a >= b);
211            return mk_val<value_bool>(a >= b);
212        } else if (op.value == "<=") {
213            JJ_DEBUG("Comparison operation: %f <= %f is %d", a, b, a <= b);
214            return mk_val<value_bool>(a <= b);
215        }
216    }
217
218    // Array operations
219    if (is_val<value_array>(left_val) && is_val<value_array>(right_val)) {
220        if (op.value == "+") {
221            auto & left_arr = left_val->as_array();
222            auto & right_arr = right_val->as_array();
223            auto result = mk_val<value_array>();
224            for (const auto & item : left_arr) {
225                result->push_back(item);
226            }
227            for (const auto & item : right_arr) {
228                result->push_back(item);
229            }
230            return result;
231        }
232    } else if (is_val<value_array>(right_val)) {
233        // case: 1 in [0, 1, 2]
234        bool member = test_is_in();
235        if (op.value == "in") {
236            return mk_val<value_bool>(member);
237        } else if (op.value == "not in") {
238            return mk_val<value_bool>(!member);
239        }
240    }
241
242    // String concatenation with ~ and +
243    if ((is_val<value_string>(left_val) || is_val<value_string>(right_val)) &&
244            (op.value == "~" || op.value == "+")) {
245        JJ_DEBUG("String concatenation with %s operator", op.value.c_str());
246        auto output = left_val->as_string().append(right_val->as_string());
247        auto res = mk_val<value_string>();
248        res->val_str = std::move(output);
249        return res;
250    }
251
252    // String membership
253    if (is_val<value_string>(left_val) && is_val<value_string>(right_val)) {
254        // case: "a" in "abc"
255        bool member = test_is_in();
256        if (op.value == "in") {
257            return mk_val<value_bool>(member);
258        } else if (op.value == "not in") {
259            return mk_val<value_bool>(!member);
260        }
261    }
262
263    // Value key in object
264    if (is_val<value_object>(right_val)) {
265        // case: key in {key: value}
266        bool member = test_is_in();
267        if (op.value == "in") {
268            return mk_val<value_bool>(member);
269        } else if (op.value == "not in") {
270            return mk_val<value_bool>(!member);
271        }
272    }
273
274    throw std::runtime_error("Unknown operator \"" + op.value + "\" between " + left_val->type() + " and " + right_val->type());
275}
276
277static value try_builtin_func(context & ctx, const std::string & name, value & input, bool undef_on_missing = false) {
278    JJ_DEBUG("Trying built-in function '%s' for type %s", name.c_str(), input->type().c_str());
279    if (ctx.is_get_stats) {
280        input->stats.used = true;
281        input->stats.ops.insert(name);
282    }
283    auto builtins = input->get_builtins();
284    auto it = builtins.find(name);
285    if (it != builtins.end()) {
286        JJ_DEBUG("Binding built-in '%s'", name.c_str());
287        return mk_val<value_func>(name, it->second, input);
288    }
289    if (undef_on_missing) {
290        return mk_val<value_undefined>(name);
291    }
292    throw std::runtime_error("Unknown (built-in) filter '" + name + "' for type " + input->type());
293}
294
295value filter_expression::execute_impl(context & ctx) {
296    value input = operand ? operand->execute(ctx) : val;
297
298    JJ_DEBUG("Applying filter to %s", input->type().c_str());
299
300    if (is_stmt<identifier>(filter)) {
301        auto filter_id = cast_stmt<identifier>(filter)->val;
302
303        if (filter_id == "trim") {
304            filter_id = "strip"; // alias
305        }
306        JJ_DEBUG("Applying filter '%s' to %s", filter_id.c_str(), input->type().c_str());
307        return try_builtin_func(ctx, filter_id, input)->invoke(func_args(ctx));
308
309    } else if (is_stmt<call_expression>(filter)) {
310        auto call = cast_stmt<call_expression>(filter);
311        if (!is_stmt<identifier>(call->callee)) {
312            throw std::runtime_error("Filter callee must be an identifier");
313        }
314        auto filter_id = cast_stmt<identifier>(call->callee)->val;
315
316        if (filter_id == "trim") {
317            filter_id = "strip"; // alias
318        }
319        JJ_DEBUG("Applying filter '%s' with arguments to %s", filter_id.c_str(), input->type().c_str());
320        func_args args(ctx);
321        for (const auto & arg_expr : call->args) {
322            args.push_back(arg_expr->execute(ctx));
323        }
324
325        return try_builtin_func(ctx, filter_id, input)->invoke(args);
326
327    } else {
328        throw std::runtime_error("Invalid filter expression");
329    }
330}
331
332value filter_statement::execute_impl(context & ctx) {
333    // eval body as string, then apply filter
334    auto body_val = exec_statements(body, ctx);
335    value_string parts = mk_val<value_string>();
336    gather_string_parts_recursive(body_val, parts);
337
338    JJ_DEBUG("FilterStatement: applying filter to body string of length %zu", parts->val_str.length());
339    filter_expression filter_expr(std::move(parts), std::move(filter));
340    value out = filter_expr.execute(ctx);
341
342    // this node can be reused later, make sure filter is preserved
343    this->filter = std::move(filter_expr.filter);
344    return out;
345}
346
347value test_expression::execute_impl(context & ctx) {
348    // NOTE: "value is something" translates to function call "test_is_something(value)"
349    const auto & builtins = global_builtins();
350
351    std::string test_id;
352    value input = operand->execute(ctx);
353
354    func_args args(ctx);
355    args.push_back(input);
356
357    if (is_stmt<identifier>(test)) {
358        test_id = cast_stmt<identifier>(test)->val;
359    } else if (is_stmt<call_expression>(test)) {
360        auto call = cast_stmt<call_expression>(test);
361        if (!is_stmt<identifier>(call->callee)) {
362            throw std::runtime_error("Test callee must be an identifier");
363        }
364        test_id = cast_stmt<identifier>(call->callee)->val;
365
366        JJ_DEBUG("Applying test '%s' with arguments to %s", test_id.c_str(), input->type().c_str());
367        for (const auto & arg_expr : call->args) {
368            args.push_back(arg_expr->execute(ctx));
369        }
370
371    } else {
372        throw std::runtime_error("Invalid test expression");
373    }
374
375    auto it = builtins.find("test_is_" + test_id);
376    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());
377    if (it == builtins.end()) {
378        throw std::runtime_error("Unknown test '" + test_id + "'");
379    }
380
381    auto res = it->second(args);
382
383    if (negate) {
384        return mk_val<value_bool>(!res->as_bool());
385    } else {
386        return res;
387    }
388}
389
390value unary_expression::execute_impl(context & ctx) {
391    value operand_val = argument->execute(ctx);
392    JJ_DEBUG("Executing unary expression with operator '%s'", op.value.c_str());
393
394    if (op.value == "not") {
395        return mk_val<value_bool>(!operand_val->as_bool());
396    } else if (op.value == "-") {
397        if (is_val<value_int>(operand_val)) {
398            return mk_val<value_int>(-operand_val->as_int());
399        } else if (is_val<value_float>(operand_val)) {
400            return mk_val<value_float>(-operand_val->as_float());
401        } else {
402            throw std::runtime_error("Unary - operator requires numeric operand");
403        }
404    }
405
406    throw std::runtime_error("Unknown unary operator '" + op.value + "'");
407}
408
409value if_statement::execute_impl(context & ctx) {
410    value test_val = test->execute(ctx);
411
412    auto out = mk_val<value_array>();
413    if (test_val->as_bool()) {
414        for (auto & stmt : body) {
415            JJ_DEBUG("IF --> Executing THEN body, current block: %s", stmt->type().c_str());
416            out->push_back(stmt->execute(ctx));
417        }
418    } else {
419        for (auto & stmt : alternate) {
420            JJ_DEBUG("IF --> Executing ELSE body, current block: %s", stmt->type().c_str());
421            out->push_back(stmt->execute(ctx));
422        }
423    }
424    // convert to string parts
425    value_string str = mk_val<value_string>();
426    gather_string_parts_recursive(out, str);
427    return str;
428}
429
430value for_statement::execute_impl(context & ctx) {
431    context scope(ctx); // new scope for loop variables
432
433    jinja::select_expression * select_expr = cast_stmt<select_expression>(iterable);
434    statement_ptr test_expr_nullptr;
435
436    statement_ptr & iter_expr = [&]() -> statement_ptr & {
437        auto tmp = cast_stmt<select_expression>(iterable);
438        return tmp ? tmp->lhs : iterable;
439    }();
440    statement_ptr & test_expr = [&]() -> statement_ptr & {
441        auto tmp = cast_stmt<select_expression>(iterable);
442        return tmp ? tmp->test : test_expr_nullptr;
443    }();
444
445    JJ_DEBUG("Executing for statement, iterable type: %s", iter_expr->type().c_str());
446
447    value iterable_val = iter_expr->execute(scope);
448
449    // mark the variable being iterated as used for stats
450    if (ctx.is_get_stats) {
451        iterable_val->stats.used = true;
452        iterable_val->stats.ops.insert("array_access");
453    }
454
455    if (iterable_val->is_undefined()) {
456        JJ_DEBUG("%s", "For loop iterable is undefined, skipping loop");
457        iterable_val = mk_val<value_array>();
458    }
459
460    if (!is_val<value_array>(iterable_val) && !is_val<value_object>(iterable_val)) {
461        throw std::runtime_error("Expected iterable or object type in for loop: got " + iterable_val->type());
462    }
463
464    std::vector<value> items;
465    if (is_val<value_object>(iterable_val)) {
466        JJ_DEBUG("%s", "For loop over object keys");
467        auto & obj = iterable_val->as_ordered_object();
468        for (auto & p : obj) {
469            auto tuple = mk_val<value_tuple>(p);
470            items.push_back(std::move(tuple));
471        }
472        if (ctx.is_get_stats) {
473            iterable_val->stats.used = true;
474            iterable_val->stats.ops.insert("object_access");
475        }
476    } else {
477        JJ_DEBUG("%s", "For loop over array items");
478        auto & arr = iterable_val->as_array();
479        for (const auto & item : arr) {
480            items.push_back(item);
481        }
482        if (ctx.is_get_stats) {
483            iterable_val->stats.used = true;
484            iterable_val->stats.ops.insert("array_access");
485        }
486    }
487
488    std::vector<std::function<void(context &)>> scope_update_fns;
489
490    std::vector<value> filtered_items;
491    for (size_t i = 0; i < items.size(); ++i) {
492        context loop_scope(scope);
493
494        value current = items[i];
495
496        std::function<void(context&)> scope_update_fn = [](context &) { /* no-op */};
497        if (is_stmt<identifier>(loopvar)) {
498            auto id = cast_stmt<identifier>(loopvar)->val;
499
500            if (is_val<value_object>(iterable_val)) {
501                // case example: {% for key in dict %}
502                current = items[i]->as_array()[0];
503                scope_update_fn = [id, &items, i](context & ctx) {
504                    ctx.set_val(id, items[i]->as_array()[0]);
505                };
506            } else {
507                // case example: {% for item in list %}
508                scope_update_fn = [id, &items, i](context & ctx) {
509                    ctx.set_val(id, items[i]);
510                };
511            }
512
513        } else if (is_stmt<tuple_literal>(loopvar)) {
514            // case example: {% for key, value in dict %}
515            auto tuple = cast_stmt<tuple_literal>(loopvar);
516            if (!is_val<value_array>(current)) {
517                throw std::runtime_error("Cannot unpack non-iterable type: " + current->type());
518            }
519            auto & c_arr = current->as_array();
520            if (tuple->val.size() != c_arr.size()) {
521                throw std::runtime_error(std::string("Too ") + (tuple->val.size() > c_arr.size() ? "few" : "many") + " items to unpack");
522            }
523            scope_update_fn = [tuple, &items, i](context & ctx) {
524                auto & c_arr = items[i]->as_array();
525                for (size_t j = 0; j < tuple->val.size(); ++j) {
526                    if (!is_stmt<identifier>(tuple->val[j])) {
527                        throw std::runtime_error("Cannot unpack non-identifier type: " + tuple->val[j]->type());
528                    }
529                    auto id = cast_stmt<identifier>(tuple->val[j])->val;
530                    ctx.set_val(id, c_arr[j]);
531                }
532            };
533
534        } else {
535            throw std::runtime_error("Invalid loop variable(s): " + loopvar->type());
536        }
537
538        if (select_expr && test_expr) {
539            scope_update_fn(loop_scope);
540            value test_val = test_expr->execute(loop_scope);
541            if (!test_val->as_bool()) {
542                continue;
543            }
544        }
545        JJ_DEBUG("For loop: adding item type %s at index %zu", current->type().c_str(), i);
546        filtered_items.push_back(current);
547        scope_update_fns.push_back(scope_update_fn);
548    }
549    JJ_DEBUG("For loop: %zu items after filtering", filtered_items.size());
550
551    auto result = mk_val<value_array>();
552
553    bool noIteration = true;
554    for (size_t i = 0; i < filtered_items.size(); i++) {
555        JJ_DEBUG("For loop iteration %zu/%zu", i + 1, filtered_items.size());
556        value_object loop_obj = mk_val<value_object>();
557        loop_obj->has_builtins = false; // loop object has no builtins
558        loop_obj->insert("index", mk_val<value_int>(i + 1));
559        loop_obj->insert("index0", mk_val<value_int>(i));
560        loop_obj->insert("revindex", mk_val<value_int>(filtered_items.size() - i));
561        loop_obj->insert("revindex0", mk_val<value_int>(filtered_items.size() - i - 1));
562        loop_obj->insert("first", mk_val<value_bool>(i == 0));
563        loop_obj->insert("last", mk_val<value_bool>(i == filtered_items.size() - 1));
564        loop_obj->insert("length", mk_val<value_int>(filtered_items.size()));
565        loop_obj->insert("previtem", i > 0 ? filtered_items[i - 1] : mk_val<value_undefined>("previtem"));
566        loop_obj->insert("nextitem", i < filtered_items.size() - 1 ? filtered_items[i + 1] : mk_val<value_undefined>("nextitem"));
567        scope.set_val("loop", loop_obj);
568        scope_update_fns[i](scope);
569        try {
570            for (auto & stmt : body) {
571                value val = stmt->execute(scope);
572                result->push_back(val);
573            }
574        } catch (const continue_statement::signal &) {
575            continue;
576        } catch (const break_statement::signal &) {
577            break;
578        }
579        noIteration = false;
580    }
581
582    JJ_DEBUG("For loop complete, total iterations: %zu", filtered_items.size());
583    if (noIteration) {
584        for (auto & stmt : default_block) {
585            value val = stmt->execute(ctx);
586            result->push_back(val);
587        }
588    }
589
590    // convert to string parts
591    value_string str = mk_val<value_string>();
592    gather_string_parts_recursive(result, str);
593    return str;
594}
595
596value set_statement::execute_impl(context & ctx) {
597    auto rhs = val ? val->execute(ctx) : exec_statements(body, ctx);
598
599    if (is_stmt<identifier>(assignee)) {
600        // case: {% set my_var = value %}
601        auto var_name = cast_stmt<identifier>(assignee)->val;
602        JJ_DEBUG("Setting global variable '%s' with value type %s", var_name.c_str(), rhs->type().c_str());
603        ctx.set_val(var_name, rhs);
604
605    } else if (is_stmt<tuple_literal>(assignee)) {
606        // case: {% set a, b = value %}
607        auto tuple = cast_stmt<tuple_literal>(assignee);
608        if (!is_val<value_array>(rhs)) {
609            throw std::runtime_error("Cannot unpack non-iterable type in set: " + rhs->type());
610        }
611        auto & arr = rhs->as_array();
612        if (arr.size() != tuple->val.size()) {
613            throw std::runtime_error(std::string("Too ") + (tuple->val.size() > arr.size() ? "few" : "many") + " items to unpack in set");
614        }
615        for (size_t i = 0; i < tuple->val.size(); ++i) {
616            auto & elem = tuple->val[i];
617            if (!is_stmt<identifier>(elem)) {
618                throw std::runtime_error("Cannot unpack to non-identifier in set: " + elem->type());
619            }
620            auto var_name = cast_stmt<identifier>(elem)->val;
621            ctx.set_val(var_name, arr[i]);
622        }
623
624    } else if (is_stmt<member_expression>(assignee)) {
625        // case: {% set ns.my_var = value %}
626        auto member = cast_stmt<member_expression>(assignee);
627        if (member->computed) {
628            throw std::runtime_error("Cannot assign to computed member");
629        }
630        if (!is_stmt<identifier>(member->property)) {
631            throw std::runtime_error("Cannot assign to member with non-identifier property");
632        }
633        auto prop_name = cast_stmt<identifier>(member->property)->val;
634
635        value object = member->object->execute(ctx);
636        if (!is_val<value_object>(object)) {
637            throw std::runtime_error("Cannot assign to member of non-object");
638        }
639        auto obj_ptr = cast_val<value_object>(object);
640        JJ_DEBUG("Setting object property '%s' with value type %s", prop_name.c_str(), rhs->type().c_str());
641        obj_ptr->insert(prop_name, rhs);
642
643    } else {
644        throw std::runtime_error("Invalid LHS inside assignment expression: " + assignee->type());
645    }
646    return mk_val<value_undefined>();
647}
648
649value macro_statement::execute_impl(context & ctx) {
650    if (!is_stmt<identifier>(this->name)) {
651        throw std::runtime_error("Macro name must be an identifier");
652    }
653    std::string name = cast_stmt<identifier>(this->name)->val;
654
655    const func_handler func = [this, name, &ctx](const func_args & args) -> value {
656        size_t expected_count = this->args.size();
657        size_t input_count = args.count();
658
659        JJ_DEBUG("Invoking macro '%s' with %zu input arguments (expected %zu)", name.c_str(), input_count, expected_count);
660        context macro_ctx(ctx); // new scope for macro execution
661
662        // bind parameters
663        for (size_t i = 0; i < expected_count; ++i) {
664            if (i < input_count) {
665                if (is_stmt<identifier>(this->args[i])) {
666                    // normal parameter
667                    std::string param_name = cast_stmt<identifier>(this->args[i])->val;
668                    JJ_DEBUG("  Binding parameter '%s' to argument of type %s", param_name.c_str(), args.get_pos(i)->type().c_str());
669                    macro_ctx.set_val(param_name, args.get_pos(i));
670                } else if (is_stmt<keyword_argument_expression>(this->args[i])) {
671                    // default argument used as normal parameter
672                    auto kwarg = cast_stmt<keyword_argument_expression>(this->args[i]);
673                    if (!is_stmt<identifier>(kwarg->key)) {
674                        throw std::runtime_error("Keyword argument key must be an identifier in macro '" + name + "'");
675                    }
676                    std::string param_name = cast_stmt<identifier>(kwarg->key)->val;
677                    JJ_DEBUG("  Binding parameter '%s' to argument of type %s", param_name.c_str(), args.get_pos(i)->type().c_str());
678                    macro_ctx.set_val(param_name, args.get_pos(i));
679                } else {
680                    throw std::runtime_error("Invalid parameter type in macro '" + name + "'");
681                }
682            } else {
683                auto & default_arg = this->args[i];
684                if (is_stmt<keyword_argument_expression>(default_arg)) {
685                    auto kwarg = cast_stmt<keyword_argument_expression>(default_arg);
686                    if (!is_stmt<identifier>(kwarg->key)) {
687                        throw std::runtime_error("Keyword argument key must be an identifier in macro '" + name + "'");
688                    }
689                    std::string param_name = cast_stmt<identifier>(kwarg->key)->val;
690                    JJ_DEBUG("  Binding parameter '%s' to default argument of type %s", param_name.c_str(), kwarg->val->type().c_str());
691                    macro_ctx.set_val(param_name, kwarg->val->execute(ctx));
692                } else {
693                    throw std::runtime_error("Not enough arguments provided to macro '" + name + "'");
694                }
695                //std::string param_name = cast_stmt<identifier>(default_args[i])->val;
696                //JJ_DEBUG("  Binding parameter '%s' to default", param_name.c_str());
697                //macro_ctx.var[param_name] = default_args[i]->execute(ctx);
698            }
699        }
700
701        // execute macro body
702        JJ_DEBUG("Executing macro '%s' body with %zu statements", name.c_str(), this->body.size());
703        auto res = exec_statements(this->body, macro_ctx);
704        JJ_DEBUG("Macro '%s' execution complete, result: %s", name.c_str(), res->val_str.str().c_str());
705        return res;
706    };
707
708    JJ_DEBUG("Defining macro '%s' with %zu parameters", name.c_str(), args.size());
709    ctx.set_val(name, mk_val<value_func>(name, func));
710    return mk_val<value_undefined>();
711}
712
713value member_expression::execute_impl(context & ctx) {
714    value object = this->object->execute(ctx);
715
716    value property;
717    if (this->computed) {
718        // syntax: obj[expr]
719        JJ_DEBUG("Member expression, computing property type %s", this->property->type().c_str());
720
721        int64_t arr_size = 0;
722        if (is_val<value_array>(object)) {
723            arr_size = object->as_array().size();
724        }
725
726        if (is_stmt<slice_expression>(this->property)) {
727            auto s = cast_stmt<slice_expression>(this->property);
728            value start_val = s->start_expr ? s->start_expr->execute(ctx) : mk_val<value_int>(0);
729            value stop_val  = s->stop_expr  ? s->stop_expr->execute(ctx)  : mk_val<value_int>(arr_size);
730            value step_val  = s->step_expr  ? s->step_expr->execute(ctx)  : mk_val<value_int>(1);
731
732            // translate to function call: obj.slice(start, stop, step)
733            JJ_DEBUG("Member expression is a slice: start %s, stop %s, step %s",
734                     start_val->as_repr().c_str(),
735                     stop_val->as_repr().c_str(),
736                     step_val->as_repr().c_str());
737            auto slice_func = try_builtin_func(ctx, "slice", object);
738            func_args args(ctx);
739            args.push_back(start_val);
740            args.push_back(stop_val);
741            args.push_back(step_val);
742            return slice_func->invoke(args);
743        } else {
744            property = this->property->execute(ctx);
745        }
746    } else {
747        // syntax: obj.prop
748        if (!is_stmt<identifier>(this->property)) {
749            throw std::runtime_error("Static member property must be an identifier");
750        }
751        property = mk_val<value_string>(cast_stmt<identifier>(this->property)->val);
752        std::string prop = property->as_string().str();
753        JJ_DEBUG("Member expression, object type %s, static property '%s'", object->type().c_str(), prop.c_str());
754
755        // behavior of jinja2: obj having prop as a built-in function AND 'prop', as an object key,
756        // then obj.prop returns the built-in function, not the property value.
757        // while obj['prop'] returns the property value.
758        // example: {"obj": {"items": 123}} -> obj.items is the built-in function, obj['items'] is 123
759
760        value val = try_builtin_func(ctx, prop, object, true);
761        if (!is_val<value_undefined>(val)) {
762            return val;
763        }
764        // else, fallthrough to normal property access below
765    }
766
767    JJ_DEBUG("Member expression on object type %s, property type %s", object->type().c_str(), property->type().c_str());
768    ensure_key_type_allowed(property);
769
770    value val = mk_val<value_undefined>("object_property");
771
772    if (is_val<value_undefined>(object)) {
773        JJ_DEBUG("%s", "Accessing property on undefined object, returning undefined");
774        return val;
775
776    } else if (is_val<value_object>(object)) {
777        auto key = property->as_string().str();
778        val = object->at(property, val);
779        if (is_val<value_undefined>(val)) {
780            val = try_builtin_func(ctx, key, object, true);
781        }
782        JJ_DEBUG("Accessed property '%s' value, got type: %s", key.c_str(), val->type().c_str());
783
784    } else if (is_val<value_array>(object) || is_val<value_string>(object)) {
785        if (is_val<value_int>(property)) {
786            int64_t index = property->as_int();
787            JJ_DEBUG("Accessing %s index %d", object->type().c_str(), (int)index);
788            if (is_val<value_array>(object)) {
789                auto & arr = object->as_array();
790                if (index < 0) {
791                    index += static_cast<int64_t>(arr.size());
792                }
793                if (index >= 0 && index < static_cast<int64_t>(arr.size())) {
794                    val = arr[index];
795                }
796            } else { // value_string
797                auto str = object->as_string().str();
798                if (index >= 0 && index < static_cast<int64_t>(str.size())) {
799                    val = mk_val<value_string>(std::string(1, str[index]));
800                }
801            }
802
803        } else if (is_val<value_string>(property)) {
804            auto key = property->as_string().str();
805            JJ_DEBUG("Accessing %s built-in '%s'", is_val<value_array>(object) ? "array" : "string", key.c_str());
806            val = try_builtin_func(ctx, key, object, true);
807
808        } else {
809            throw std::runtime_error("Cannot access property with non-string/non-number: got " + property->type());
810        }
811    } else {
812        if (!is_val<value_string>(property)) {
813            throw std::runtime_error("Cannot access property with non-string: got " + property->type());
814        }
815        auto key = property->as_string().str();
816        val = try_builtin_func(ctx, key, object, true);
817    }
818
819    if (ctx.is_get_stats && val && object && property) {
820        val->stats.used = true;
821        object->stats.used = true;
822        if (is_val<value_int>(property)) {
823            object->stats.ops.insert("array_access");
824        } else if (is_val<value_string>(property)) {
825            object->stats.ops.insert("object_access");
826        }
827    }
828
829    return val;
830}
831
832value call_expression::execute_impl(context & ctx) {
833    // gather arguments
834    func_args args(ctx);
835    for (auto & arg_stmt : this->args) {
836        auto arg_val = arg_stmt->execute(ctx);
837        JJ_DEBUG("  Argument type: %s", arg_val->type().c_str());
838        args.push_back(std::move(arg_val));
839    }
840    // execute callee
841    value callee_val = callee->execute(ctx);
842    if (!is_val<value_func>(callee_val)) {
843        throw std::runtime_error("Callee is not a function: got " + callee_val->type());
844    }
845    auto * callee_func = cast_val<value_func>(callee_val);
846    JJ_DEBUG("Calling function '%s' with %zu arguments", callee_func->name.c_str(), args.count());
847    return callee_func->invoke(args);
848}
849
850value keyword_argument_expression::execute_impl(context & ctx) {
851    if (!is_stmt<identifier>(key)) {
852        throw std::runtime_error("Keyword argument key must be identifiers");
853    }
854
855    std::string k = cast_stmt<identifier>(key)->val;
856    JJ_DEBUG("Keyword argument expression key: %s, value: %s", k.c_str(), val->type().c_str());
857
858    value v = val->execute(ctx);
859    JJ_DEBUG("Keyword argument value executed, type: %s", v->type().c_str());
860
861    return mk_val<value_kwarg>(k, v);
862}
863
864} // namespace jinja