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