1/*
2 Copyright 2024 Google LLC
3
4 Use of this source code is governed by an MIT-style
5 license that can be found in the LICENSE file or at
6 https://opensource.org/licenses/MIT.
7*/
8// SPDX-License-Identifier: MIT
9#pragma once
10
11#include <algorithm>
12#include <cctype>
13#include <cstddef>
14#include <cstdint>
15#include <cmath>
16#include <exception>
17#include <functional>
18#include <iostream>
19#include <iterator>
20#include <limits>
21#include <map>
22#include <memory>
23#include <regex>
24#include <sstream>
25#include <string>
26#include <stdexcept>
27#include <unordered_map>
28#include <unordered_set>
29#include <utility>
30#include <vector>
31
32#include <nlohmann/json.hpp>
33
34using json = nlohmann::ordered_json;
35
36namespace minja {
37
38class Context;
39
40struct Options {
41 bool trim_blocks; // removes the first newline after a block
42 bool lstrip_blocks; // removes leading whitespace on the line of the block
43 bool keep_trailing_newline; // don't remove last newline
44};
45
46struct ArgumentsValue;
47
48inline std::string normalize_newlines(const std::string & s) {
49#ifdef _WIN32
50 static const std::regex nl_regex("\r\n");
51 return std::regex_replace(s, nl_regex, "\n");
52#else
53 return s;
54#endif
55}
56
57/* Values that behave roughly like in Python. */
58class Value {
59public:
60 using CallableType = std::function<Value(const std::shared_ptr<Context> &, ArgumentsValue &)>;
61 using FilterType = std::function<Value(const std::shared_ptr<Context> &, ArgumentsValue &)>;
62
63private:
64 using ObjectType = nlohmann::ordered_map<json, Value>; // Only contains primitive keys
65 using ArrayType = std::vector<Value>;
66
67 std::shared_ptr<ArrayType> array_;
68 std::shared_ptr<ObjectType> object_;
69 std::shared_ptr<CallableType> callable_;
70 json primitive_;
71
72 Value(const std::shared_ptr<ArrayType> & array) : array_(array) {}
73 Value(const std::shared_ptr<ObjectType> & object) : object_(object) {}
74 Value(const std::shared_ptr<CallableType> & callable) : object_(std::make_shared<ObjectType>()), callable_(callable) {}
75
76 /* Python-style string repr */
77 static void dump_string(const json & primitive, std::ostringstream & out, char string_quote = '\'') {
78 if (!primitive.is_string()) throw std::runtime_error("Value is not a string: " + primitive.dump());
79 auto s = primitive.dump();
80 if (string_quote == '"' || s.find(c: '\'') != std::string::npos) {
81 out << s;
82 return;
83 }
84 // Reuse json dump, just changing string quotes
85 out << string_quote;
86 for (size_t i = 1, n = s.size() - 1; i < n; ++i) {
87 if (s[i] == '\\' && s[i + 1] == '"') {
88 out << '"';
89 i++;
90 } else if (s[i] == string_quote) {
91 out << '\\' << string_quote;
92 } else {
93 out << s[i];
94 }
95 }
96 out << string_quote;
97 }
98 void dump(std::ostringstream & out, int indent = -1, int level = 0, bool to_json = false) const {
99 auto print_indent = [&](int level) {
100 if (indent > 0) {
101 out << "\n";
102 for (int i = 0, n = level * indent; i < n; ++i) out << ' ';
103 }
104 };
105 auto print_sub_sep = [&]() {
106 out << ',';
107 if (indent < 0) out << ' ';
108 else print_indent(level + 1);
109 };
110
111 auto string_quote = to_json ? '"' : '\'';
112
113 if (is_null()) out << "null";
114 else if (array_) {
115 out << "[";
116 print_indent(level + 1);
117 for (size_t i = 0; i < array_->size(); ++i) {
118 if (i) print_sub_sep();
119 (*array_)[i].dump(out, indent, level: level + 1, to_json);
120 }
121 print_indent(level);
122 out << "]";
123 } else if (object_) {
124 out << "{";
125 print_indent(level + 1);
126 for (auto begin = object_->begin(), it = begin; it != object_->end(); ++it) {
127 if (it != begin) print_sub_sep();
128 if (it->first.is_string()) {
129 dump_string(primitive: it->first, out, string_quote);
130 } else {
131 out << string_quote << it->first.dump() << string_quote;
132 }
133 out << ": ";
134 it->second.dump(out, indent, level: level + 1, to_json);
135 }
136 print_indent(level);
137 out << "}";
138 } else if (callable_) {
139 throw std::runtime_error("Cannot dump callable to JSON");
140 } else if (is_boolean() && !to_json) {
141 out << (this->to_bool() ? "True" : "False");
142 } else if (is_string() && !to_json) {
143 dump_string(primitive: primitive_, out, string_quote);
144 } else {
145 out << primitive_.dump();
146 }
147 }
148
149public:
150 Value() {}
151 Value(const bool& v) : primitive_(v) {}
152 Value(const int64_t & v) : primitive_(v) {}
153 Value(const double& v) : primitive_(v) {}
154 Value(const std::nullptr_t &) {}
155 Value(const std::string & v) : primitive_(v) {}
156 Value(const char * v) : primitive_(std::string(v)) {}
157
158 Value(const json & v) {
159 if (v.is_object()) {
160 auto object = std::make_shared<ObjectType>();
161 object->reserve(n: v.size());
162 for (auto it = v.begin(); it != v.end(); ++it) {
163 object->emplace_back(args: it.key(), args: Value(it.value()));
164 }
165 object_ = std::move(object);
166 } else if (v.is_array()) {
167 auto array = std::make_shared<ArrayType>();
168 array->reserve(n: v.size());
169 for (const auto& item : v) {
170 array->push_back(x: Value(item));
171 }
172 array_ = array;
173 } else {
174 primitive_ = v;
175 }
176 }
177
178 std::vector<Value> keys() {
179 if (!object_) throw std::runtime_error("Value is not an object: " + dump());
180 std::vector<Value> res;
181 for (const auto& item : *object_) {
182 res.push_back(x: item.first);
183 }
184 return res;
185 }
186
187 size_t size() const {
188 if (is_object()) return object_->size();
189 if (is_array()) return array_->size();
190 if (is_string()) return primitive_.get<std::string>().length();
191 throw std::runtime_error("Value is not an array or object: " + dump());
192 }
193
194 static Value array(const std::vector<Value> values = {}) {
195 auto array = std::make_shared<ArrayType>();
196 for (const auto& item : values) {
197 array->push_back(x: item);
198 }
199 return Value(array);
200 }
201 static Value object(const std::shared_ptr<ObjectType> object = std::make_shared<ObjectType>()) {
202 return Value(object);
203 }
204 static Value callable(const CallableType & callable) {
205 return Value(std::make_shared<CallableType>(args: callable));
206 }
207
208 void insert(size_t index, const Value& v) {
209 if (!array_)
210 throw std::runtime_error("Value is not an array: " + dump());
211 array_->insert(position: array_->begin() + index, x: v);
212 }
213 void push_back(const Value& v) {
214 if (!array_)
215 throw std::runtime_error("Value is not an array: " + dump());
216 array_->push_back(x: v);
217 }
218 Value pop(const Value& index) {
219 if (is_array()) {
220 if (array_->empty())
221 throw std::runtime_error("pop from empty list");
222 if (index.is_null()) {
223 auto ret = array_->back();
224 array_->pop_back();
225 return ret;
226 } else if (!index.is_number_integer()) {
227 throw std::runtime_error("pop index must be an integer: " + index.dump());
228 } else {
229 auto i = index.get<int>();
230 if (i < 0 || i >= static_cast<int>(array_->size()))
231 throw std::runtime_error("pop index out of range: " + index.dump());
232 auto it = array_->begin() + (i < 0 ? array_->size() + i : i);
233 auto ret = *it;
234 array_->erase(position: it);
235 return ret;
236 }
237 } else if (is_object()) {
238 if (!index.is_hashable())
239 throw std::runtime_error("Unhashable type: " + index.dump());
240 auto it = object_->find(key: index.primitive_);
241 if (it == object_->end())
242 throw std::runtime_error("Key not found: " + index.dump());
243 auto ret = it->second;
244 object_->erase(pos: it);
245 return ret;
246 } else {
247 throw std::runtime_error("Value is not an array or object: " + dump());
248 }
249 }
250 Value get(const Value& key) {
251 if (array_) {
252 if (!key.is_number_integer()) {
253 return Value();
254 }
255 auto index = key.get<int>();
256 return array_->at(n: index < 0 ? array_->size() + index : index);
257 } else if (object_) {
258 if (!key.is_hashable()) throw std::runtime_error("Unhashable type: " + dump());
259 auto it = object_->find(key: key.primitive_);
260 if (it == object_->end()) return Value();
261 return it->second;
262 }
263 return Value();
264 }
265 void set(const Value& key, const Value& value) {
266 if (!object_) throw std::runtime_error("Value is not an object: " + dump());
267 if (!key.is_hashable()) throw std::runtime_error("Unhashable type: " + dump());
268 (*object_)[key.primitive_] = value;
269 }
270 Value call(const std::shared_ptr<Context> & context, ArgumentsValue & args) const {
271 if (!callable_) throw std::runtime_error("Value is not callable: " + dump());
272 return (*callable_)(context, args);
273 }
274
275 bool is_object() const { return !!object_; }
276 bool is_array() const { return !!array_; }
277 bool is_callable() const { return !!callable_; }
278 bool is_null() const { return !object_ && !array_ && primitive_.is_null() && !callable_; }
279 bool is_boolean() const { return primitive_.is_boolean(); }
280 bool is_number_integer() const { return primitive_.is_number_integer(); }
281 bool is_number_float() const { return primitive_.is_number_float(); }
282 bool is_number() const { return primitive_.is_number(); }
283 bool is_string() const { return primitive_.is_string(); }
284 bool is_iterable() const { return is_array() || is_object() || is_string(); }
285
286 bool is_primitive() const { return !array_ && !object_ && !callable_; }
287 bool is_hashable() const { return is_primitive(); }
288
289 bool empty() const {
290 if (is_null())
291 throw std::runtime_error("Undefined value or reference");
292 if (is_string()) return primitive_.empty();
293 if (is_array()) return array_->empty();
294 if (is_object()) return object_->empty();
295 return false;
296 }
297
298 void for_each(const std::function<void(Value &)> & callback) const {
299 if (is_null())
300 throw std::runtime_error("Undefined value or reference");
301 if (array_) {
302 for (auto& item : *array_) {
303 callback(item);
304 }
305 } else if (object_) {
306 for (auto & item : *object_) {
307 Value key(item.first);
308 callback(key);
309 }
310 } else if (is_string()) {
311 for (char c : primitive_.get<std::string>()) {
312 auto val = Value(std::string(1, c));
313 callback(val);
314 }
315 } else {
316 throw std::runtime_error("Value is not iterable: " + dump());
317 }
318 }
319
320 bool to_bool() const {
321 if (is_null()) return false;
322 if (is_boolean()) return get<bool>();
323 if (is_number()) return get<double>() != 0;
324 if (is_string()) return !get<std::string>().empty();
325 if (is_array()) return !empty();
326 return true;
327 }
328
329 int64_t to_int() const {
330 if (is_null()) return 0;
331 if (is_boolean()) return get<bool>() ? 1 : 0;
332 if (is_number()) return static_cast<int64_t>(get<double>());
333 if (is_string()) {
334 try {
335 return std::stol(str: get<std::string>());
336 } catch (const std::exception &) {
337 return 0;
338 }
339 }
340 return 0;
341 }
342
343 bool operator<(const Value & other) const {
344 if (is_null())
345 throw std::runtime_error("Undefined value or reference");
346 if (is_number() && other.is_number()) return get<double>() < other.get<double>();
347 if (is_string() && other.is_string()) return get<std::string>() < other.get<std::string>();
348 throw std::runtime_error("Cannot compare values: " + dump() + " < " + other.dump());
349 }
350 bool operator>=(const Value & other) const { return !(*this < other); }
351
352 bool operator>(const Value & other) const {
353 if (is_null())
354 throw std::runtime_error("Undefined value or reference");
355 if (is_number() && other.is_number()) return get<double>() > other.get<double>();
356 if (is_string() && other.is_string()) return get<std::string>() > other.get<std::string>();
357 throw std::runtime_error("Cannot compare values: " + dump() + " > " + other.dump());
358 }
359 bool operator<=(const Value & other) const { return !(*this > other); }
360
361 bool operator==(const Value & other) const {
362 if (callable_ || other.callable_) {
363 if (callable_.get() != other.callable_.get()) return false;
364 }
365 if (array_) {
366 if (!other.array_) return false;
367 if (array_->size() != other.array_->size()) return false;
368 for (size_t i = 0; i < array_->size(); ++i) {
369 if (!(*array_)[i].to_bool() || !(*other.array_)[i].to_bool() || (*array_)[i] != (*other.array_)[i]) return false;
370 }
371 return true;
372 } else if (object_) {
373 if (!other.object_) return false;
374 if (object_->size() != other.object_->size()) return false;
375 for (const auto& item : *object_) {
376 if (!item.second.to_bool() || !other.object_->count(key: item.first) || item.second != other.object_->at(key: item.first)) return false;
377 }
378 return true;
379 } else {
380 return primitive_ == other.primitive_;
381 }
382 }
383 bool operator!=(const Value & other) const { return !(*this == other); }
384
385 bool contains(const char * key) const { return contains(key: std::string(key)); }
386 bool contains(const std::string & key) const {
387 if (array_) {
388 return false;
389 } else if (object_) {
390 return object_->find(key) != object_->end();
391 } else {
392 throw std::runtime_error("contains can only be called on arrays and objects: " + dump());
393 }
394 }
395 bool contains(const Value & value) const {
396 if (is_null())
397 throw std::runtime_error("Undefined value or reference");
398 if (array_) {
399 for (const auto& item : *array_) {
400 if (item.to_bool() && item == value) return true;
401 }
402 return false;
403 } else if (object_) {
404 if (!value.is_hashable()) throw std::runtime_error("Unhashable type: " + value.dump());
405 return object_->find(key: value.primitive_) != object_->end();
406 } else {
407 throw std::runtime_error("contains can only be called on arrays and objects: " + dump());
408 }
409 }
410 void erase(size_t index) {
411 if (!array_) throw std::runtime_error("Value is not an array: " + dump());
412 array_->erase(position: array_->begin() + index);
413 }
414 void erase(const std::string & key) {
415 if (!object_) throw std::runtime_error("Value is not an object: " + dump());
416 object_->erase(key);
417 }
418 const Value& at(const Value & index) const {
419 return const_cast<Value*>(this)->at(index);
420 }
421 Value& at(const Value & index) {
422 if (!index.is_hashable()) throw std::runtime_error("Unhashable type: " + dump());
423 if (is_array()) return array_->at(n: index.get<int>());
424 if (is_object()) return object_->at(key: index.primitive_);
425 throw std::runtime_error("Value is not an array or object: " + dump());
426 }
427 const Value& at(size_t index) const {
428 return const_cast<Value*>(this)->at(index);
429 }
430 Value& at(size_t index) {
431 if (is_null())
432 throw std::runtime_error("Undefined value or reference");
433 if (is_array()) return array_->at(n: index);
434 if (is_object()) return object_->at(key&: index);
435 throw std::runtime_error("Value is not an array or object: " + dump());
436 }
437
438 template <typename T>
439 T get(const std::string & key, T default_value) const {
440 if (!contains(key)) return default_value;
441 return at(index: key).get<T>();
442 }
443
444 template <typename T>
445 T get() const {
446 if (is_primitive()) return primitive_.get<T>();
447 throw std::runtime_error("get<T> not defined for this value type: " + dump());
448 }
449
450 std::string dump(int indent=-1, bool to_json=false) const {
451 std::ostringstream out;
452 dump(out, indent, level: 0, to_json);
453 return out.str();
454 }
455
456 Value operator-() const {
457 if (is_number_integer())
458 return -get<int64_t>();
459 else
460 return -get<double>();
461 }
462 std::string to_str() const {
463 if (is_string()) return get<std::string>();
464 if (is_number_integer()) return std::to_string(val: get<int64_t>());
465 if (is_number_float()) return std::to_string(val: get<double>());
466 if (is_boolean()) return get<bool>() ? "True" : "False";
467 if (is_null()) return "None";
468 return dump();
469 }
470 Value operator+(const Value& rhs) const {
471 if (is_string() || rhs.is_string()) {
472 return to_str() + rhs.to_str();
473 } else if (is_number_integer() && rhs.is_number_integer()) {
474 return get<int64_t>() + rhs.get<int64_t>();
475 } else if (is_array() && rhs.is_array()) {
476 auto res = Value::array();
477 for (const auto& item : *array_) res.push_back(v: item);
478 for (const auto& item : *rhs.array_) res.push_back(v: item);
479 return res;
480 } else {
481 return get<double>() + rhs.get<double>();
482 }
483 }
484 Value operator-(const Value& rhs) const {
485 if (is_number_integer() && rhs.is_number_integer())
486 return get<int64_t>() - rhs.get<int64_t>();
487 else
488 return get<double>() - rhs.get<double>();
489 }
490 Value operator*(const Value& rhs) const {
491 if (is_string() && rhs.is_number_integer()) {
492 std::ostringstream out;
493 for (int64_t i = 0, n = rhs.get<int64_t>(); i < n; ++i) {
494 out << to_str();
495 }
496 return out.str();
497 }
498 else if (is_number_integer() && rhs.is_number_integer())
499 return get<int64_t>() * rhs.get<int64_t>();
500 else
501 return get<double>() * rhs.get<double>();
502 }
503 Value operator/(const Value& rhs) const {
504 if (is_number_integer() && rhs.is_number_integer())
505 return get<int64_t>() / rhs.get<int64_t>();
506 else
507 return get<double>() / rhs.get<double>();
508 }
509 Value operator%(const Value& rhs) const {
510 return get<int64_t>() % rhs.get<int64_t>();
511 }
512};
513
514struct ArgumentsValue {
515 std::vector<Value> args;
516 std::vector<std::pair<std::string, Value>> kwargs;
517
518 bool has_named(const std::string & name) {
519 for (const auto & p : kwargs) {
520 if (p.first == name) return true;
521 }
522 return false;
523 }
524
525 Value get_named(const std::string & name) {
526 for (const auto & [key, value] : kwargs) {
527 if (key == name) return value;
528 }
529 return Value();
530 }
531
532 bool empty() {
533 return args.empty() && kwargs.empty();
534 }
535
536 void expectArgs(const std::string & method_name, const std::pair<size_t, size_t> & pos_count, const std::pair<size_t, size_t> & kw_count) {
537 if (args.size() < pos_count.first || args.size() > pos_count.second || kwargs.size() < kw_count.first || kwargs.size() > kw_count.second) {
538 std::ostringstream out;
539 out << method_name << " must have between " << pos_count.first << " and " << pos_count.second << " positional arguments and between " << kw_count.first << " and " << kw_count.second << " keyword arguments";
540 throw std::runtime_error(out.str());
541 }
542 }
543};
544
545template <>
546inline json Value::get<json>() const {
547 if (is_primitive()) return primitive_;
548 if (is_null()) return json();
549 if (array_) {
550 std::vector<json> res;
551 for (const auto& item : *array_) {
552 res.push_back(x: item.get<json>());
553 }
554 return res;
555 }
556 if (object_) {
557 json res = json::object();
558 for (const auto& [key, value] : *object_) {
559 if (key.is_string()) {
560 res[key.get<std::string>()] = value.get<json>();
561 } else if (key.is_primitive()) {
562 res[key.dump()] = value.get<json>();
563 } else {
564 throw std::runtime_error("Invalid key type for conversion to JSON: " + key.dump());
565 }
566 }
567 if (is_callable()) {
568 res["__callable__"] = true;
569 }
570 return res;
571 }
572 throw std::runtime_error("get<json> not defined for this value type: " + dump());
573}
574
575} // namespace minja
576
577namespace std {
578 template <>
579 struct hash<minja::Value> {
580 size_t operator()(const minja::Value & v) const {
581 if (!v.is_hashable())
582 throw std::runtime_error("Unsupported type for hashing: " + v.dump());
583 return std::hash<json>()(v.get<json>());
584 }
585 };
586} // namespace std
587
588namespace minja {
589
590static std::string error_location_suffix(const std::string & source, size_t pos) {
591 auto get_line = [&](size_t line) {
592 auto start = source.begin();
593 for (size_t i = 1; i < line; ++i) {
594 start = std::find(first: start, last: source.end(), val: '\n') + 1;
595 }
596 auto end = std::find(first: start, last: source.end(), val: '\n');
597 return std::string(start, end);
598 };
599 auto start = source.begin();
600 auto end = source.end();
601 auto it = start + pos;
602 auto line = std::count(first: start, last: it, value: '\n') + 1;
603 auto max_line = std::count(first: start, last: end, value: '\n') + 1;
604 auto col = pos - std::string(start, it).rfind(c: '\n');
605 std::ostringstream out;
606 out << " at row " << line << ", column " << col << ":\n";
607 if (line > 1) out << get_line(line - 1) << "\n";
608 out << get_line(line) << "\n";
609 out << std::string(col - 1, ' ') << "^\n";
610 if (line < max_line) out << get_line(line + 1) << "\n";
611
612 return out.str();
613}
614
615class Context {
616 protected:
617 Value values_;
618 std::shared_ptr<Context> parent_;
619 public:
620 Context(Value && values, const std::shared_ptr<Context> & parent = nullptr) : values_(std::move(values)), parent_(parent) {
621 if (!values_.is_object()) throw std::runtime_error("Context values must be an object: " + values_.dump());
622 }
623 virtual ~Context() {}
624
625 static std::shared_ptr<Context> builtins();
626 static std::shared_ptr<Context> make(Value && values, const std::shared_ptr<Context> & parent = builtins());
627
628 std::vector<Value> keys() {
629 return values_.keys();
630 }
631 virtual Value get(const Value & key) {
632 if (values_.contains(value: key)) return values_.at(index: key);
633 if (parent_) return parent_->get(key);
634 return Value();
635 }
636 virtual Value & at(const Value & key) {
637 if (values_.contains(value: key)) return values_.at(index: key);
638 if (parent_) return parent_->at(key);
639 throw std::runtime_error("Undefined variable: " + key.dump());
640 }
641 virtual bool contains(const Value & key) {
642 if (values_.contains(value: key)) return true;
643 if (parent_) return parent_->contains(key);
644 return false;
645 }
646 virtual void set(const Value & key, const Value & value) {
647 values_.set(key, value);
648 }
649};
650
651struct Location {
652 std::shared_ptr<std::string> source;
653 size_t pos;
654};
655
656class Expression {
657protected:
658 virtual Value do_evaluate(const std::shared_ptr<Context> & context) const = 0;
659public:
660 using Parameters = std::vector<std::pair<std::string, std::shared_ptr<Expression>>>;
661
662 Location location;
663
664 Expression(const Location & location) : location(location) {}
665 virtual ~Expression() = default;
666
667 Value evaluate(const std::shared_ptr<Context> & context) const {
668 try {
669 return do_evaluate(context);
670 } catch (const std::exception & e) {
671 std::ostringstream out;
672 out << e.what();
673 if (location.source) out << error_location_suffix(source: *location.source, pos: location.pos);
674 throw std::runtime_error(out.str());
675 }
676 }
677};
678
679class VariableExpr : public Expression {
680 std::string name;
681public:
682 VariableExpr(const Location & loc, const std::string& n)
683 : Expression(loc), name(n) {}
684 std::string get_name() const { return name; }
685 Value do_evaluate(const std::shared_ptr<Context> & context) const override {
686 if (!context->contains(key: name)) {
687 return Value();
688 }
689 return context->at(key: name);
690 }
691};
692
693static void destructuring_assign(const std::vector<std::string> & var_names, const std::shared_ptr<Context> & context, Value& item) {
694 if (var_names.size() == 1) {
695 Value name(var_names[0]);
696 context->set(key: name, value: item);
697 } else {
698 if (!item.is_array() || item.size() != var_names.size()) {
699 throw std::runtime_error("Mismatched number of variables and items in destructuring assignment");
700 }
701 for (size_t i = 0; i < var_names.size(); ++i) {
702 context->set(key: var_names[i], value: item.at(index: i));
703 }
704 }
705}
706
707enum SpaceHandling { Keep, Strip, StripSpaces, StripNewline };
708
709class TemplateToken {
710public:
711 enum class Type { Text, Expression, If, Else, Elif, EndIf, For, EndFor, Generation, EndGeneration, Set, EndSet, Comment, Macro, EndMacro, Filter, EndFilter, Break, Continue, Call, EndCall };
712
713 static std::string typeToString(Type t) {
714 switch (t) {
715 case Type::Text: return "text";
716 case Type::Expression: return "expression";
717 case Type::If: return "if";
718 case Type::Else: return "else";
719 case Type::Elif: return "elif";
720 case Type::EndIf: return "endif";
721 case Type::For: return "for";
722 case Type::EndFor: return "endfor";
723 case Type::Set: return "set";
724 case Type::EndSet: return "endset";
725 case Type::Comment: return "comment";
726 case Type::Macro: return "macro";
727 case Type::EndMacro: return "endmacro";
728 case Type::Filter: return "filter";
729 case Type::EndFilter: return "endfilter";
730 case Type::Generation: return "generation";
731 case Type::EndGeneration: return "endgeneration";
732 case Type::Break: return "break";
733 case Type::Continue: return "continue";
734 case Type::Call: return "call";
735 case Type::EndCall: return "endcall";
736 }
737 return "Unknown";
738 }
739
740 TemplateToken(Type type, const Location & location, SpaceHandling pre, SpaceHandling post) : type(type), location(location), pre_space(pre), post_space(post) {}
741 virtual ~TemplateToken() = default;
742
743 Type type;
744 Location location;
745 SpaceHandling pre_space = SpaceHandling::Keep;
746 SpaceHandling post_space = SpaceHandling::Keep;
747};
748
749struct TextTemplateToken : public TemplateToken {
750 std::string text;
751 TextTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post, const std::string& t) : TemplateToken(Type::Text, loc, pre, post), text(t) {}
752};
753
754struct ExpressionTemplateToken : public TemplateToken {
755 std::shared_ptr<Expression> expr;
756 ExpressionTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post, std::shared_ptr<Expression> && e) : TemplateToken(Type::Expression, loc, pre, post), expr(std::move(e)) {}
757};
758
759struct IfTemplateToken : public TemplateToken {
760 std::shared_ptr<Expression> condition;
761 IfTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post, std::shared_ptr<Expression> && c) : TemplateToken(Type::If, loc, pre, post), condition(std::move(c)) {}
762};
763
764struct ElifTemplateToken : public TemplateToken {
765 std::shared_ptr<Expression> condition;
766 ElifTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post, std::shared_ptr<Expression> && c) : TemplateToken(Type::Elif, loc, pre, post), condition(std::move(c)) {}
767};
768
769struct ElseTemplateToken : public TemplateToken {
770 ElseTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post) : TemplateToken(Type::Else, loc, pre, post) {}
771};
772
773struct EndIfTemplateToken : public TemplateToken {
774 EndIfTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post) : TemplateToken(Type::EndIf, loc, pre, post) {}
775};
776
777struct MacroTemplateToken : public TemplateToken {
778 std::shared_ptr<VariableExpr> name;
779 Expression::Parameters params;
780 MacroTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post, std::shared_ptr<VariableExpr> && n, Expression::Parameters && p)
781 : TemplateToken(Type::Macro, loc, pre, post), name(std::move(n)), params(std::move(p)) {}
782};
783
784struct EndMacroTemplateToken : public TemplateToken {
785 EndMacroTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post) : TemplateToken(Type::EndMacro, loc, pre, post) {}
786};
787
788struct FilterTemplateToken : public TemplateToken {
789 std::shared_ptr<Expression> filter;
790 FilterTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post, std::shared_ptr<Expression> && filter)
791 : TemplateToken(Type::Filter, loc, pre, post), filter(std::move(filter)) {}
792};
793
794struct EndFilterTemplateToken : public TemplateToken {
795 EndFilterTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post) : TemplateToken(Type::EndFilter, loc, pre, post) {}
796};
797
798struct ForTemplateToken : public TemplateToken {
799 std::vector<std::string> var_names;
800 std::shared_ptr<Expression> iterable;
801 std::shared_ptr<Expression> condition;
802 bool recursive;
803 ForTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post, const std::vector<std::string> & vns, std::shared_ptr<Expression> && iter,
804 std::shared_ptr<Expression> && c, bool r)
805 : TemplateToken(Type::For, loc, pre, post), var_names(vns), iterable(std::move(iter)), condition(std::move(c)), recursive(r) {}
806};
807
808struct EndForTemplateToken : public TemplateToken {
809 EndForTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post) : TemplateToken(Type::EndFor, loc, pre, post) {}
810};
811
812struct GenerationTemplateToken : public TemplateToken {
813 GenerationTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post) : TemplateToken(Type::Generation, loc, pre, post) {}
814};
815
816struct EndGenerationTemplateToken : public TemplateToken {
817 EndGenerationTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post) : TemplateToken(Type::EndGeneration, loc, pre, post) {}
818};
819
820struct SetTemplateToken : public TemplateToken {
821 std::string ns;
822 std::vector<std::string> var_names;
823 std::shared_ptr<Expression> value;
824 SetTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post, const std::string & ns, const std::vector<std::string> & vns, std::shared_ptr<Expression> && v)
825 : TemplateToken(Type::Set, loc, pre, post), ns(ns), var_names(vns), value(std::move(v)) {}
826};
827
828struct EndSetTemplateToken : public TemplateToken {
829 EndSetTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post) : TemplateToken(Type::EndSet, loc, pre, post) {}
830};
831
832struct CommentTemplateToken : public TemplateToken {
833 std::string text;
834 CommentTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post, const std::string& t) : TemplateToken(Type::Comment, loc, pre, post), text(t) {}
835};
836
837enum class LoopControlType { Break, Continue };
838
839class LoopControlException : public std::runtime_error {
840public:
841 LoopControlType control_type;
842 LoopControlException(const std::string & message, LoopControlType control_type) : std::runtime_error(message), control_type(control_type) {}
843 LoopControlException(LoopControlType control_type)
844 : std::runtime_error((control_type == LoopControlType::Continue ? "continue" : "break") + std::string(" outside of a loop")),
845 control_type(control_type) {}
846};
847
848struct LoopControlTemplateToken : public TemplateToken {
849 LoopControlType control_type;
850 LoopControlTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post, LoopControlType control_type) : TemplateToken(Type::Break, loc, pre, post), control_type(control_type) {}
851};
852
853struct CallTemplateToken : public TemplateToken {
854 std::shared_ptr<Expression> expr;
855 CallTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post, std::shared_ptr<Expression> && e)
856 : TemplateToken(Type::Call, loc, pre, post), expr(std::move(e)) {}
857};
858
859struct EndCallTemplateToken : public TemplateToken {
860 EndCallTemplateToken(const Location & loc, SpaceHandling pre, SpaceHandling post)
861 : TemplateToken(Type::EndCall, loc, pre, post) {}
862};
863
864class TemplateNode {
865 Location location_;
866protected:
867 virtual void do_render(std::ostringstream & out, const std::shared_ptr<Context> & context) const = 0;
868
869public:
870 TemplateNode(const Location & location) : location_(location) {}
871 void render(std::ostringstream & out, const std::shared_ptr<Context> & context) const {
872 try {
873 do_render(out, context);
874 } catch (const LoopControlException & e) {
875 // TODO: make stack creation lazy. Only needed if it was thrown outside of a loop.
876 std::ostringstream err;
877 err << e.what();
878 if (location_.source) err << error_location_suffix(source: *location_.source, pos: location_.pos);
879 throw LoopControlException(err.str(), e.control_type);
880 } catch (const std::exception & e) {
881 std::ostringstream err;
882 err << e.what();
883 if (location_.source) err << error_location_suffix(source: *location_.source, pos: location_.pos);
884 throw std::runtime_error(err.str());
885 }
886 }
887 const Location & location() const { return location_; }
888 virtual ~TemplateNode() = default;
889 std::string render(const std::shared_ptr<Context> & context) const {
890 std::ostringstream out;
891 render(out, context);
892 return out.str();
893 }
894};
895
896class SequenceNode : public TemplateNode {
897 std::vector<std::shared_ptr<TemplateNode>> children;
898public:
899 SequenceNode(const Location & loc, std::vector<std::shared_ptr<TemplateNode>> && c)
900 : TemplateNode(loc), children(std::move(c)) {}
901 void do_render(std::ostringstream & out, const std::shared_ptr<Context> & context) const override {
902 for (const auto& child : children) child->render(out, context);
903 }
904};
905
906class TextNode : public TemplateNode {
907 std::string text;
908public:
909 TextNode(const Location & loc, const std::string& t) : TemplateNode(loc), text(t) {}
910 void do_render(std::ostringstream & out, const std::shared_ptr<Context> &) const override {
911 out << text;
912 }
913};
914
915class ExpressionNode : public TemplateNode {
916 std::shared_ptr<Expression> expr;
917public:
918 ExpressionNode(const Location & loc, std::shared_ptr<Expression> && e) : TemplateNode(loc), expr(std::move(e)) {}
919 void do_render(std::ostringstream & out, const std::shared_ptr<Context> & context) const override {
920 if (!expr) throw std::runtime_error("ExpressionNode.expr is null");
921 auto result = expr->evaluate(context);
922 if (result.is_string()) {
923 out << result.get<std::string>();
924 } else if (result.is_boolean()) {
925 out << (result.get<bool>() ? "True" : "False");
926 } else if (!result.is_null()) {
927 out << result.dump();
928 }
929 }
930};
931
932class IfNode : public TemplateNode {
933 std::vector<std::pair<std::shared_ptr<Expression>, std::shared_ptr<TemplateNode>>> cascade;
934public:
935 IfNode(const Location & loc, std::vector<std::pair<std::shared_ptr<Expression>, std::shared_ptr<TemplateNode>>> && c)
936 : TemplateNode(loc), cascade(std::move(c)) {}
937 void do_render(std::ostringstream & out, const std::shared_ptr<Context> & context) const override {
938 for (const auto& branch : cascade) {
939 auto enter_branch = true;
940 if (branch.first) {
941 enter_branch = branch.first->evaluate(context).to_bool();
942 }
943 if (enter_branch) {
944 if (!branch.second) throw std::runtime_error("IfNode.cascade.second is null");
945 branch.second->render(out, context);
946 return;
947 }
948 }
949 }
950};
951
952class LoopControlNode : public TemplateNode {
953 LoopControlType control_type_;
954 public:
955 LoopControlNode(const Location & loc, LoopControlType control_type) : TemplateNode(loc), control_type_(control_type) {}
956 void do_render(std::ostringstream &, const std::shared_ptr<Context> &) const override {
957 throw LoopControlException(control_type_);
958 }
959};
960
961class ForNode : public TemplateNode {
962 std::vector<std::string> var_names;
963 std::shared_ptr<Expression> iterable;
964 std::shared_ptr<Expression> condition;
965 std::shared_ptr<TemplateNode> body;
966 bool recursive;
967 std::shared_ptr<TemplateNode> else_body;
968public:
969 ForNode(const Location & loc, std::vector<std::string> && var_names, std::shared_ptr<Expression> && iterable,
970 std::shared_ptr<Expression> && condition, std::shared_ptr<TemplateNode> && body, bool recursive, std::shared_ptr<TemplateNode> && else_body)
971 : TemplateNode(loc), var_names(var_names), iterable(std::move(iterable)), condition(std::move(condition)), body(std::move(body)), recursive(recursive), else_body(std::move(else_body)) {}
972
973 void do_render(std::ostringstream & out, const std::shared_ptr<Context> & context) const override {
974 // https://jinja.palletsprojects.com/en/3.0.x/templates/#for
975 if (!iterable) throw std::runtime_error("ForNode.iterable is null");
976 if (!body) throw std::runtime_error("ForNode.body is null");
977
978 auto iterable_value = iterable->evaluate(context);
979 Value::CallableType loop_function;
980
981 std::function<void(Value&)> visit = [&](Value& iter) {
982 auto filtered_items = Value::array();
983 if (!iter.is_null()) {
984 if (!iterable_value.is_iterable()) {
985 throw std::runtime_error("For loop iterable must be iterable: " + iterable_value.dump());
986 }
987 iterable_value.for_each(callback: [&](Value & item) {
988 destructuring_assign(var_names, context, item);
989 if (!condition || condition->evaluate(context).to_bool()) {
990 filtered_items.push_back(v: item);
991 }
992 });
993 }
994 if (filtered_items.empty()) {
995 if (else_body) {
996 else_body->render(out, context);
997 }
998 } else {
999 auto loop = recursive ? Value::callable(callable: loop_function) : Value::object();
1000 loop.set(key: "length", value: (int64_t) filtered_items.size());
1001
1002 size_t cycle_index = 0;
1003 loop.set(key: "cycle", value: Value::callable(callable: [&](const std::shared_ptr<Context> &, ArgumentsValue & args) {
1004 if (args.args.empty() || !args.kwargs.empty()) {
1005 throw std::runtime_error("cycle() expects at least 1 positional argument and no named arg");
1006 }
1007 auto item = args.args[cycle_index];
1008 cycle_index = (cycle_index + 1) % args.args.size();
1009 return item;
1010 }));
1011 auto loop_context = Context::make(values: Value::object(), parent: context);
1012 loop_context->set(key: "loop", value: loop);
1013 for (size_t i = 0, n = filtered_items.size(); i < n; ++i) {
1014 auto & item = filtered_items.at(index: i);
1015 destructuring_assign(var_names, context: loop_context, item);
1016 loop.set(key: "index", value: (int64_t) i + 1);
1017 loop.set(key: "index0", value: (int64_t) i);
1018 loop.set(key: "revindex", value: (int64_t) (n - i));
1019 loop.set(key: "revindex0", value: (int64_t) (n - i - 1));
1020 loop.set(key: "length", value: (int64_t) n);
1021 loop.set(key: "first", value: i == 0);
1022 loop.set(key: "last", value: i == (n - 1));
1023 loop.set(key: "previtem", value: i > 0 ? filtered_items.at(index: i - 1) : Value());
1024 loop.set(key: "nextitem", value: i < n - 1 ? filtered_items.at(index: i + 1) : Value());
1025 try {
1026 body->render(out, context: loop_context);
1027 } catch (const LoopControlException & e) {
1028 if (e.control_type == LoopControlType::Break) break;
1029 if (e.control_type == LoopControlType::Continue) continue;
1030 }
1031 }
1032 }
1033 };
1034
1035 if (recursive) {
1036 loop_function = [&](const std::shared_ptr<Context> &, ArgumentsValue & args) {
1037 if (args.args.size() != 1 || !args.kwargs.empty() || !args.args[0].is_array()) {
1038 throw std::runtime_error("loop() expects exactly 1 positional iterable argument");
1039 }
1040 auto & items = args.args[0];
1041 visit(items);
1042 return Value();
1043 };
1044 }
1045
1046 visit(iterable_value);
1047 }
1048};
1049
1050class MacroNode : public TemplateNode {
1051 std::shared_ptr<VariableExpr> name;
1052 Expression::Parameters params;
1053 std::shared_ptr<TemplateNode> body;
1054 std::unordered_map<std::string, size_t> named_param_positions;
1055public:
1056 MacroNode(const Location & loc, std::shared_ptr<VariableExpr> && n, Expression::Parameters && p, std::shared_ptr<TemplateNode> && b)
1057 : TemplateNode(loc), name(std::move(n)), params(std::move(p)), body(std::move(b)) {
1058 for (size_t i = 0; i < params.size(); ++i) {
1059 const auto & name = params[i].first;
1060 if (!name.empty()) {
1061 named_param_positions[name] = i;
1062 }
1063 }
1064 }
1065 void do_render(std::ostringstream &, const std::shared_ptr<Context> & context) const override {
1066 if (!name) throw std::runtime_error("MacroNode.name is null");
1067 if (!body) throw std::runtime_error("MacroNode.body is null");
1068
1069 // Use init-capture to avoid dangling 'this' pointer and circular references
1070 auto callable = Value::callable(callable: [weak_context = std::weak_ptr<Context>(context),
1071 name = name, params = params, body = body,
1072 named_param_positions = named_param_positions]
1073 (const std::shared_ptr<Context> & call_context, ArgumentsValue & args) {
1074 auto context_locked = weak_context.lock();
1075 if (!context_locked) throw std::runtime_error("Macro context no longer valid");
1076 auto execution_context = Context::make(values: Value::object(), parent: context_locked);
1077
1078 if (call_context->contains(key: "caller")) {
1079 execution_context->set(key: "caller", value: call_context->get(key: "caller"));
1080 }
1081
1082 std::vector<bool> param_set(params.size(), false);
1083 for (size_t i = 0, n = args.args.size(); i < n; i++) {
1084 auto & arg = args.args[i];
1085 if (i >= params.size()) throw std::runtime_error("Too many positional arguments for macro " + name->get_name());
1086 param_set[i] = true;
1087 const auto & param_name = params[i].first;
1088 execution_context->set(key: param_name, value: arg);
1089 }
1090 for (auto & [arg_name, value] : args.kwargs) {
1091 auto it = named_param_positions.find(x: arg_name);
1092 if (it == named_param_positions.end()) throw std::runtime_error("Unknown parameter name for macro " + name->get_name() + ": " + arg_name);
1093
1094 execution_context->set(key: arg_name, value);
1095 param_set[it->second] = true;
1096 }
1097 // Set default values for parameters that were not passed
1098 for (size_t i = 0, n = params.size(); i < n; i++) {
1099 if (!param_set[i] && params[i].second != nullptr) {
1100 auto val = params[i].second->evaluate(context: call_context);
1101 execution_context->set(key: params[i].first, value: val);
1102 }
1103 }
1104 return body->render(context: execution_context);
1105 });
1106 context->set(key: name->get_name(), value: callable);
1107 }
1108};
1109
1110class FilterNode : public TemplateNode {
1111 std::shared_ptr<Expression> filter;
1112 std::shared_ptr<TemplateNode> body;
1113
1114public:
1115 FilterNode(const Location & loc, std::shared_ptr<Expression> && f, std::shared_ptr<TemplateNode> && b)
1116 : TemplateNode(loc), filter(std::move(f)), body(std::move(b)) {}
1117
1118 void do_render(std::ostringstream & out, const std::shared_ptr<Context> & context) const override {
1119 if (!filter) throw std::runtime_error("FilterNode.filter is null");
1120 if (!body) throw std::runtime_error("FilterNode.body is null");
1121 auto filter_value = filter->evaluate(context);
1122 if (!filter_value.is_callable()) {
1123 throw std::runtime_error("Filter must be a callable: " + filter_value.dump());
1124 }
1125 std::string rendered_body = body->render(context);
1126
1127 ArgumentsValue filter_args = {.args: {Value(rendered_body)}, .kwargs: {}};
1128 auto result = filter_value.call(context, args&: filter_args);
1129 out << result.to_str();
1130 }
1131};
1132
1133class SetNode : public TemplateNode {
1134 std::string ns;
1135 std::vector<std::string> var_names;
1136 std::shared_ptr<Expression> value;
1137public:
1138 SetNode(const Location & loc, const std::string & ns, const std::vector<std::string> & vns, std::shared_ptr<Expression> && v)
1139 : TemplateNode(loc), ns(ns), var_names(vns), value(std::move(v)) {}
1140 void do_render(std::ostringstream &, const std::shared_ptr<Context> & context) const override {
1141 if (!value) throw std::runtime_error("SetNode.value is null");
1142 if (!ns.empty()) {
1143 if (var_names.size() != 1) {
1144 throw std::runtime_error("Namespaced set only supports a single variable name");
1145 }
1146 auto & name = var_names[0];
1147 auto ns_value = context->get(key: ns);
1148 if (!ns_value.is_object()) throw std::runtime_error("Namespace '" + ns + "' is not an object");
1149 ns_value.set(key: name, value: this->value->evaluate(context));
1150 } else {
1151 auto val = value->evaluate(context);
1152 destructuring_assign(var_names, context, item&: val);
1153 }
1154 }
1155};
1156
1157class SetTemplateNode : public TemplateNode {
1158 std::string name;
1159 std::shared_ptr<TemplateNode> template_value;
1160public:
1161 SetTemplateNode(const Location & loc, const std::string & name, std::shared_ptr<TemplateNode> && tv)
1162 : TemplateNode(loc), name(name), template_value(std::move(tv)) {}
1163 void do_render(std::ostringstream &, const std::shared_ptr<Context> & context) const override {
1164 if (!template_value) throw std::runtime_error("SetTemplateNode.template_value is null");
1165 Value value { template_value->render(context) };
1166 context->set(key: name, value);
1167 }
1168};
1169
1170class IfExpr : public Expression {
1171 std::shared_ptr<Expression> condition;
1172 std::shared_ptr<Expression> then_expr;
1173 std::shared_ptr<Expression> else_expr;
1174public:
1175 IfExpr(const Location & loc, std::shared_ptr<Expression> && c, std::shared_ptr<Expression> && t, std::shared_ptr<Expression> && e)
1176 : Expression(loc), condition(std::move(c)), then_expr(std::move(t)), else_expr(std::move(e)) {}
1177 Value do_evaluate(const std::shared_ptr<Context> & context) const override {
1178 if (!condition) throw std::runtime_error("IfExpr.condition is null");
1179 if (!then_expr) throw std::runtime_error("IfExpr.then_expr is null");
1180 if (condition->evaluate(context).to_bool()) {
1181 return then_expr->evaluate(context);
1182 }
1183 if (else_expr) {
1184 return else_expr->evaluate(context);
1185 }
1186 return nullptr;
1187 }
1188};
1189
1190class LiteralExpr : public Expression {
1191 Value value;
1192public:
1193 LiteralExpr(const Location & loc, const Value& v)
1194 : Expression(loc), value(v) {}
1195 Value do_evaluate(const std::shared_ptr<Context> &) const override { return value; }
1196};
1197
1198class ArrayExpr : public Expression {
1199 std::vector<std::shared_ptr<Expression>> elements;
1200public:
1201 ArrayExpr(const Location & loc, std::vector<std::shared_ptr<Expression>> && e)
1202 : Expression(loc), elements(std::move(e)) {}
1203 Value do_evaluate(const std::shared_ptr<Context> & context) const override {
1204 auto result = Value::array();
1205 for (const auto& e : elements) {
1206 if (!e) throw std::runtime_error("Array element is null");
1207 result.push_back(v: e->evaluate(context));
1208 }
1209 return result;
1210 }
1211};
1212
1213class DictExpr : public Expression {
1214 std::vector<std::pair<std::shared_ptr<Expression>, std::shared_ptr<Expression>>> elements;
1215public:
1216 DictExpr(const Location & loc, std::vector<std::pair<std::shared_ptr<Expression>, std::shared_ptr<Expression>>> && e)
1217 : Expression(loc), elements(std::move(e)) {}
1218 Value do_evaluate(const std::shared_ptr<Context> & context) const override {
1219 auto result = Value::object();
1220 for (const auto& [key, value] : elements) {
1221 if (!key) throw std::runtime_error("Dict key is null");
1222 if (!value) throw std::runtime_error("Dict value is null");
1223 result.set(key: key->evaluate(context), value: value->evaluate(context));
1224 }
1225 return result;
1226 }
1227};
1228
1229class SliceExpr : public Expression {
1230public:
1231 std::shared_ptr<Expression> start, end, step;
1232 SliceExpr(const Location & loc, std::shared_ptr<Expression> && s, std::shared_ptr<Expression> && e, std::shared_ptr<Expression> && st = nullptr)
1233 : Expression(loc), start(std::move(s)), end(std::move(e)), step(std::move(st)) {}
1234 Value do_evaluate(const std::shared_ptr<Context> &) const override {
1235 throw std::runtime_error("SliceExpr not implemented");
1236 }
1237};
1238
1239class SubscriptExpr : public Expression {
1240 std::shared_ptr<Expression> base;
1241 std::shared_ptr<Expression> index;
1242public:
1243 SubscriptExpr(const Location & loc, std::shared_ptr<Expression> && b, std::shared_ptr<Expression> && i)
1244 : Expression(loc), base(std::move(b)), index(std::move(i)) {}
1245 Value do_evaluate(const std::shared_ptr<Context> & context) const override {
1246 if (!base) throw std::runtime_error("SubscriptExpr.base is null");
1247 if (!index) throw std::runtime_error("SubscriptExpr.index is null");
1248 auto target_value = base->evaluate(context);
1249 if (auto slice = dynamic_cast<SliceExpr*>(index.get())) {
1250 auto len = target_value.size();
1251 auto wrap = [len](int64_t i) -> int64_t {
1252 if (i < 0) {
1253 return i + len;
1254 }
1255 return i;
1256 };
1257 int64_t step = slice->step ? slice->step->evaluate(context).get<int64_t>() : 1;
1258 if (!step) {
1259 throw std::runtime_error("slice step cannot be zero");
1260 }
1261 int64_t start = slice->start ? wrap(slice->start->evaluate(context).get<int64_t>()) : (step < 0 ? len - 1 : 0);
1262 int64_t end = slice->end ? wrap(slice->end->evaluate(context).get<int64_t>()) : (step < 0 ? -1 : len);
1263 if (target_value.is_string()) {
1264 std::string s = target_value.get<std::string>();
1265
1266 std::string result;
1267 if (start < end && step == 1) {
1268 result = s.substr(pos: start, n: end - start);
1269 } else {
1270 for (int64_t i = start; step > 0 ? i < end : i > end; i += step) {
1271 result += s[i];
1272 }
1273 }
1274 return result;
1275
1276 } else if (target_value.is_array()) {
1277 auto result = Value::array();
1278 for (int64_t i = start; step > 0 ? i < end : i > end; i += step) {
1279 result.push_back(v: target_value.at(index: i));
1280 }
1281 return result;
1282 } else {
1283 throw std::runtime_error(target_value.is_null() ? "Cannot subscript null" : "Subscripting only supported on arrays and strings");
1284 }
1285 } else {
1286 auto index_value = index->evaluate(context);
1287 if (target_value.is_null()) {
1288 if (auto t = dynamic_cast<VariableExpr*>(base.get())) {
1289 throw std::runtime_error("'" + t->get_name() + "' is " + (context->contains(key: t->get_name()) ? "null" : "not defined"));
1290 }
1291 throw std::runtime_error("Trying to access property '" + index_value.dump() + "' on null!");
1292 }
1293 return target_value.get(key: index_value);
1294 }
1295 }
1296};
1297
1298class UnaryOpExpr : public Expression {
1299public:
1300 enum class Op { Plus, Minus, LogicalNot, Expansion, ExpansionDict };
1301 std::shared_ptr<Expression> expr;
1302 Op op;
1303 UnaryOpExpr(const Location & loc, std::shared_ptr<Expression> && e, Op o)
1304 : Expression(loc), expr(std::move(e)), op(o) {}
1305 Value do_evaluate(const std::shared_ptr<Context> & context) const override {
1306 if (!expr) throw std::runtime_error("UnaryOpExpr.expr is null");
1307 auto e = expr->evaluate(context);
1308 switch (op) {
1309 case Op::Plus: return e;
1310 case Op::Minus: return -e;
1311 case Op::LogicalNot: return !e.to_bool();
1312 case Op::Expansion:
1313 case Op::ExpansionDict:
1314 throw std::runtime_error("Expansion operator is only supported in function calls and collections");
1315
1316 }
1317 throw std::runtime_error("Unknown unary operator");
1318 }
1319};
1320
1321static bool in(const Value & value, const Value & container) {
1322 return (((container.is_array() || container.is_object()) && container.contains(value)) ||
1323 (value.is_string() && container.is_string() &&
1324 container.to_str().find(str: value.to_str()) != std::string::npos));
1325}
1326
1327class BinaryOpExpr : public Expression {
1328public:
1329 enum class Op { StrConcat, Add, Sub, Mul, MulMul, Div, DivDiv, Mod, Eq, Ne, Lt, Gt, Le, Ge, And, Or, In, NotIn, Is, IsNot };
1330private:
1331 std::shared_ptr<Expression> left;
1332 std::shared_ptr<Expression> right;
1333 Op op;
1334public:
1335 BinaryOpExpr(const Location & loc, std::shared_ptr<Expression> && l, std::shared_ptr<Expression> && r, Op o)
1336 : Expression(loc), left(std::move(l)), right(std::move(r)), op(o) {}
1337 Value do_evaluate(const std::shared_ptr<Context> & context) const override {
1338 if (!left) throw std::runtime_error("BinaryOpExpr.left is null");
1339 if (!right) throw std::runtime_error("BinaryOpExpr.right is null");
1340 auto l = left->evaluate(context);
1341
1342 auto do_eval = [&](const Value & l) -> Value {
1343 if (op == Op::Is || op == Op::IsNot) {
1344 auto t = dynamic_cast<VariableExpr*>(right.get());
1345 if (!t) throw std::runtime_error("Right side of 'is' operator must be a variable");
1346
1347 auto eval = [&]() {
1348 const auto & name = t->get_name();
1349 if (name == "none") return l.is_null();
1350 if (name == "boolean") return l.is_boolean();
1351 if (name == "integer") return l.is_number_integer();
1352 if (name == "float") return l.is_number_float();
1353 if (name == "number") return l.is_number();
1354 if (name == "string") return l.is_string();
1355 if (name == "mapping") return l.is_object();
1356 if (name == "iterable") return l.is_iterable();
1357 if (name == "sequence") return l.is_array();
1358 if (name == "defined") return !l.is_null();
1359 if (name == "true") return l.to_bool();
1360 if (name == "false") return !l.to_bool();
1361 throw std::runtime_error("Unknown type for 'is' operator: " + name);
1362 };
1363 auto value = eval();
1364 return Value(op == Op::Is ? value : !value);
1365 }
1366
1367 if (op == Op::And) {
1368 if (!l.to_bool()) return Value(false);
1369 return right->evaluate(context).to_bool();
1370 } else if (op == Op::Or) {
1371 if (l.to_bool()) return l;
1372 return right->evaluate(context);
1373 }
1374
1375 auto r = right->evaluate(context);
1376 switch (op) {
1377 case Op::StrConcat: return l.to_str() + r.to_str();
1378 case Op::Add: return l + r;
1379 case Op::Sub: return l - r;
1380 case Op::Mul: return l * r;
1381 case Op::Div: return l / r;
1382 case Op::MulMul: return std::pow(x: l.get<double>(), y: r.get<double>());
1383 case Op::DivDiv: return l.get<int64_t>() / r.get<int64_t>();
1384 case Op::Mod: return l.get<int64_t>() % r.get<int64_t>();
1385 case Op::Eq: return l == r;
1386 case Op::Ne: return l != r;
1387 case Op::Lt: return l < r;
1388 case Op::Gt: return l > r;
1389 case Op::Le: return l <= r;
1390 case Op::Ge: return l >= r;
1391 case Op::In: return in(value: l, container: r);
1392 case Op::NotIn: return !in(value: l, container: r);
1393 default: break;
1394 }
1395 throw std::runtime_error("Unknown binary operator");
1396 };
1397
1398 if (l.is_callable()) {
1399 return Value::callable(callable: [l, do_eval](const std::shared_ptr<Context> & context, ArgumentsValue & args) {
1400 auto ll = l.call(context, args);
1401 return do_eval(ll); //args[0].second);
1402 });
1403 } else {
1404 return do_eval(l);
1405 }
1406 }
1407};
1408
1409struct ArgumentsExpression {
1410 std::vector<std::shared_ptr<Expression>> args;
1411 std::vector<std::pair<std::string, std::shared_ptr<Expression>>> kwargs;
1412
1413 ArgumentsValue evaluate(const std::shared_ptr<Context> & context) const {
1414 ArgumentsValue vargs;
1415 for (const auto& arg : this->args) {
1416 if (auto un_expr = std::dynamic_pointer_cast<UnaryOpExpr>(r: arg)) {
1417 if (un_expr->op == UnaryOpExpr::Op::Expansion) {
1418 auto array = un_expr->expr->evaluate(context);
1419 if (!array.is_array()) {
1420 throw std::runtime_error("Expansion operator only supported on arrays");
1421 }
1422 array.for_each(callback: [&](Value & value) {
1423 vargs.args.push_back(x: value);
1424 });
1425 continue;
1426 } else if (un_expr->op == UnaryOpExpr::Op::ExpansionDict) {
1427 auto dict = un_expr->expr->evaluate(context);
1428 if (!dict.is_object()) {
1429 throw std::runtime_error("ExpansionDict operator only supported on objects");
1430 }
1431 dict.for_each(callback: [&](const Value & key) {
1432 vargs.kwargs.push_back(x: {key.get<std::string>(), dict.at(index: key)});
1433 });
1434 continue;
1435 }
1436 }
1437 vargs.args.push_back(x: arg->evaluate(context));
1438 }
1439 for (const auto& [name, value] : this->kwargs) {
1440 vargs.kwargs.push_back(x: {name, value->evaluate(context)});
1441 }
1442 return vargs;
1443 }
1444};
1445
1446static std::string strip(const std::string & s, const std::string & chars = "", bool left = true, bool right = true) {
1447 auto charset = chars.empty() ? " \t\n\r" : chars;
1448 auto start = left ? s.find_first_not_of(str: charset) : 0;
1449 if (start == std::string::npos) return "";
1450 auto end = right ? s.find_last_not_of(str: charset) : s.size() - 1;
1451 return s.substr(pos: start, n: end - start + 1);
1452}
1453
1454static std::vector<std::string> split(const std::string & s, const std::string & sep) {
1455 std::vector<std::string> result;
1456 size_t start = 0;
1457 size_t end = s.find(str: sep);
1458 while (end != std::string::npos) {
1459 result.push_back(x: s.substr(pos: start, n: end - start));
1460 start = end + sep.length();
1461 end = s.find(str: sep, pos: start);
1462 }
1463 result.push_back(x: s.substr(pos: start));
1464 return result;
1465}
1466
1467static std::string capitalize(const std::string & s) {
1468 if (s.empty()) return s;
1469 auto result = s;
1470 result[0] = std::toupper(c: result[0]);
1471 return result;
1472}
1473
1474static std::string html_escape(const std::string & s) {
1475 std::string result;
1476 result.reserve(res_arg: s.size());
1477 for (const auto & c : s) {
1478 switch (c) {
1479 case '&': result += "&amp;"; break;
1480 case '<': result += "&lt;"; break;
1481 case '>': result += "&gt;"; break;
1482 case '"': result += "&#34;"; break;
1483 case '\'': result += "&apos;"; break;
1484 default: result += c; break;
1485 }
1486 }
1487 return result;
1488}
1489
1490class MethodCallExpr : public Expression {
1491 std::shared_ptr<Expression> object;
1492 std::shared_ptr<VariableExpr> method;
1493 ArgumentsExpression args;
1494public:
1495 MethodCallExpr(const Location & loc, std::shared_ptr<Expression> && obj, std::shared_ptr<VariableExpr> && m, ArgumentsExpression && a)
1496 : Expression(loc), object(std::move(obj)), method(std::move(m)), args(std::move(a)) {}
1497 Value do_evaluate(const std::shared_ptr<Context> & context) const override {
1498 if (!object) throw std::runtime_error("MethodCallExpr.object is null");
1499 if (!method) throw std::runtime_error("MethodCallExpr.method is null");
1500 auto obj = object->evaluate(context);
1501 auto vargs = args.evaluate(context);
1502 if (obj.is_null()) {
1503 throw std::runtime_error("Trying to call method '" + method->get_name() + "' on null");
1504 }
1505 if (obj.is_array()) {
1506 if (method->get_name() == "append") {
1507 vargs.expectArgs(method_name: "append method", pos_count: {1, 1}, kw_count: {0, 0});
1508 obj.push_back(v: vargs.args[0]);
1509 return Value();
1510 } else if (method->get_name() == "pop") {
1511 vargs.expectArgs(method_name: "pop method", pos_count: {0, 1}, kw_count: {0, 0});
1512 return obj.pop(index: vargs.args.empty() ? Value() : vargs.args[0]);
1513 } else if (method->get_name() == "insert") {
1514 vargs.expectArgs(method_name: "insert method", pos_count: {2, 2}, kw_count: {0, 0});
1515 auto index = vargs.args[0].get<int64_t>();
1516 if (index < 0 || index > (int64_t) obj.size()) throw std::runtime_error("Index out of range for insert method");
1517 obj.insert(index, v: vargs.args[1]);
1518 return Value();
1519 }
1520 } else if (obj.is_object()) {
1521 if (method->get_name() == "items") {
1522 vargs.expectArgs(method_name: "items method", pos_count: {0, 0}, kw_count: {0, 0});
1523 auto result = Value::array();
1524 for (const auto& key : obj.keys()) {
1525 result.push_back(v: Value::array(values: {key, obj.at(index: key)}));
1526 }
1527 return result;
1528 } else if (method->get_name() == "pop") {
1529 vargs.expectArgs(method_name: "pop method", pos_count: {1, 1}, kw_count: {0, 0});
1530 return obj.pop(index: vargs.args[0]);
1531 } else if (method->get_name() == "keys") {
1532 vargs.expectArgs(method_name: "keys method", pos_count: {0, 0}, kw_count: {0, 0});
1533 auto result = Value::array();
1534 for (const auto& key : obj.keys()) {
1535 result.push_back(v: Value(key));
1536 }
1537 return result;
1538 } else if (method->get_name() == "get") {
1539 vargs.expectArgs(method_name: "get method", pos_count: {1, 2}, kw_count: {0, 0});
1540 auto key = vargs.args[0];
1541 if (vargs.args.size() == 1) {
1542 return obj.contains(value: key) ? obj.at(index: key) : Value();
1543 } else {
1544 return obj.contains(value: key) ? obj.at(index: key) : vargs.args[1];
1545 }
1546 } else if (obj.contains(key: method->get_name())) {
1547 auto callable = obj.at(index: method->get_name());
1548 if (!callable.is_callable()) {
1549 throw std::runtime_error("Property '" + method->get_name() + "' is not callable");
1550 }
1551 return callable.call(context, args&: vargs);
1552 }
1553 } else if (obj.is_string()) {
1554 auto str = obj.get<std::string>();
1555 if (method->get_name() == "strip") {
1556 vargs.expectArgs(method_name: "strip method", pos_count: {0, 1}, kw_count: {0, 0});
1557 auto chars = vargs.args.empty() ? "" : vargs.args[0].get<std::string>();
1558 return Value(strip(s: str, chars));
1559 } else if (method->get_name() == "lstrip") {
1560 vargs.expectArgs(method_name: "lstrip method", pos_count: {0, 1}, kw_count: {0, 0});
1561 auto chars = vargs.args.empty() ? "" : vargs.args[0].get<std::string>();
1562 return Value(strip(s: str, chars, /* left= */ left: true, /* right= */ right: false));
1563 } else if (method->get_name() == "rstrip") {
1564 vargs.expectArgs(method_name: "rstrip method", pos_count: {0, 1}, kw_count: {0, 0});
1565 auto chars = vargs.args.empty() ? "" : vargs.args[0].get<std::string>();
1566 return Value(strip(s: str, chars, /* left= */ left: false, /* right= */ right: true));
1567 } else if (method->get_name() == "split") {
1568 vargs.expectArgs(method_name: "split method", pos_count: {1, 1}, kw_count: {0, 0});
1569 auto sep = vargs.args[0].get<std::string>();
1570 auto parts = split(s: str, sep);
1571 Value result = Value::array();
1572 for (const auto& part : parts) {
1573 result.push_back(v: Value(part));
1574 }
1575 return result;
1576 } else if (method->get_name() == "capitalize") {
1577 vargs.expectArgs(method_name: "capitalize method", pos_count: {0, 0}, kw_count: {0, 0});
1578 return Value(capitalize(s: str));
1579 } else if (method->get_name() == "upper") {
1580 vargs.expectArgs(method_name: "upper method", pos_count: {0, 0}, kw_count: {0, 0});
1581 auto result = str;
1582 std::transform(first: result.begin(), last: result.end(), result: result.begin(), unary_op: ::toupper);
1583 return Value(result);
1584 } else if (method->get_name() == "lower") {
1585 vargs.expectArgs(method_name: "lower method", pos_count: {0, 0}, kw_count: {0, 0});
1586 auto result = str;
1587 std::transform(first: result.begin(), last: result.end(), result: result.begin(), unary_op: ::tolower);
1588 return Value(result);
1589 } else if (method->get_name() == "endswith") {
1590 vargs.expectArgs(method_name: "endswith method", pos_count: {1, 1}, kw_count: {0, 0});
1591 auto suffix = vargs.args[0].get<std::string>();
1592 return suffix.length() <= str.length() && std::equal(first1: suffix.rbegin(), last1: suffix.rend(), first2: str.rbegin());
1593 } else if (method->get_name() == "startswith") {
1594 vargs.expectArgs(method_name: "startswith method", pos_count: {1, 1}, kw_count: {0, 0});
1595 auto prefix = vargs.args[0].get<std::string>();
1596 return prefix.length() <= str.length() && std::equal(first1: prefix.begin(), last1: prefix.end(), first2: str.begin());
1597 } else if (method->get_name() == "title") {
1598 vargs.expectArgs(method_name: "title method", pos_count: {0, 0}, kw_count: {0, 0});
1599 auto res = str;
1600 for (size_t i = 0, n = res.size(); i < n; ++i) {
1601 if (i == 0 || std::isspace(res[i - 1])) res[i] = std::toupper(c: res[i]);
1602 else res[i] = std::tolower(c: res[i]);
1603 }
1604 return res;
1605 } else if (method->get_name() == "replace") {
1606 vargs.expectArgs(method_name: "replace method", pos_count: {2, 3}, kw_count: {0, 0});
1607 auto before = vargs.args[0].get<std::string>();
1608 auto after = vargs.args[1].get<std::string>();
1609 auto count = vargs.args.size() == 3 ? vargs.args[2].get<int64_t>()
1610 : str.length();
1611 size_t start_pos = 0;
1612 while ((start_pos = str.find(str: before, pos: start_pos)) != std::string::npos &&
1613 count-- > 0) {
1614 str.replace(pos: start_pos, n: before.length(), str: after);
1615 start_pos += after.length();
1616 }
1617 return str;
1618 }
1619 }
1620 throw std::runtime_error("Unknown method: " + method->get_name());
1621 }
1622};
1623
1624class CallExpr : public Expression {
1625public:
1626 std::shared_ptr<Expression> object;
1627 ArgumentsExpression args;
1628 CallExpr(const Location & loc, std::shared_ptr<Expression> && obj, ArgumentsExpression && a)
1629 : Expression(loc), object(std::move(obj)), args(std::move(a)) {}
1630 Value do_evaluate(const std::shared_ptr<Context> & context) const override {
1631 if (!object) throw std::runtime_error("CallExpr.object is null");
1632 auto obj = object->evaluate(context);
1633 if (!obj.is_callable()) {
1634 throw std::runtime_error("Object is not callable: " + obj.dump(indent: 2));
1635 }
1636 auto vargs = args.evaluate(context);
1637 return obj.call(context, args&: vargs);
1638 }
1639};
1640
1641class CallNode : public TemplateNode {
1642 std::shared_ptr<Expression> expr;
1643 std::shared_ptr<TemplateNode> body;
1644
1645public:
1646 CallNode(const Location & loc, std::shared_ptr<Expression> && e, std::shared_ptr<TemplateNode> && b)
1647 : TemplateNode(loc), expr(std::move(e)), body(std::move(b)) {}
1648
1649 void do_render(std::ostringstream & out, const std::shared_ptr<Context> & context) const override {
1650 if (!expr) throw std::runtime_error("CallNode.expr is null");
1651 if (!body) throw std::runtime_error("CallNode.body is null");
1652
1653 // Use init-capture to avoid dangling 'this' pointer and circular references
1654 auto caller = Value::callable(callable: [weak_context = std::weak_ptr<Context>(context), body=body]
1655 (const std::shared_ptr<Context> &, ArgumentsValue &) -> Value {
1656 auto context_locked = weak_context.lock();
1657 if (!context_locked) throw std::runtime_error("Caller context no longer valid");
1658 return Value(body->render(context: context_locked));
1659 });
1660
1661 context->set(key: "caller", value: caller);
1662
1663 auto call_expr = dynamic_cast<CallExpr*>(expr.get());
1664 if (!call_expr) {
1665 throw std::runtime_error("Invalid call block syntax - expected function call");
1666 }
1667
1668 Value function = call_expr->object->evaluate(context);
1669 if (!function.is_callable()) {
1670 throw std::runtime_error("Call target must be callable: " + function.dump());
1671 }
1672 ArgumentsValue args = call_expr->args.evaluate(context);
1673
1674 Value result = function.call(context, args);
1675 out << result.to_str();
1676 }
1677};
1678
1679class FilterExpr : public Expression {
1680 std::vector<std::shared_ptr<Expression>> parts;
1681public:
1682 FilterExpr(const Location & loc, std::vector<std::shared_ptr<Expression>> && p)
1683 : Expression(loc), parts(std::move(p)) {}
1684 Value do_evaluate(const std::shared_ptr<Context> & context) const override {
1685 Value result;
1686 bool first = true;
1687 for (const auto& part : parts) {
1688 if (!part) throw std::runtime_error("FilterExpr.part is null");
1689 if (first) {
1690 first = false;
1691 result = part->evaluate(context);
1692 } else {
1693 if (auto ce = dynamic_cast<CallExpr*>(part.get())) {
1694 auto target = ce->object->evaluate(context);
1695 ArgumentsValue args = ce->args.evaluate(context);
1696 args.args.insert(position: args.args.begin(), x: result);
1697 result = target.call(context, args);
1698 } else {
1699 auto callable = part->evaluate(context);
1700 ArgumentsValue args;
1701 args.args.insert(position: args.args.begin(), x: result);
1702 result = callable.call(context, args);
1703 }
1704 }
1705 }
1706 return result;
1707 }
1708
1709 void prepend(std::shared_ptr<Expression> && e) {
1710 parts.insert(position: parts.begin(), x: std::move(e));
1711 }
1712};
1713
1714class Parser {
1715private:
1716 using CharIterator = std::string::const_iterator;
1717
1718 std::shared_ptr<std::string> template_str;
1719 CharIterator start, end, it;
1720 Options options;
1721
1722 Parser(const std::shared_ptr<std::string>& template_str, const Options & options) : template_str(template_str), options(options) {
1723 if (!template_str) throw std::runtime_error("Template string is null");
1724 start = it = this->template_str->begin();
1725 end = this->template_str->end();
1726 }
1727
1728 bool consumeSpaces(SpaceHandling space_handling = SpaceHandling::Strip) {
1729 if (space_handling == SpaceHandling::Strip) {
1730 while (it != end && std::isspace(*it)) ++it;
1731 }
1732 return true;
1733 }
1734
1735 std::unique_ptr<std::string> parseString() {
1736 auto doParse = [&](char quote) -> std::unique_ptr<std::string> {
1737 if (it == end || *it != quote) return nullptr;
1738 std::string result;
1739 bool escape = false;
1740 for (++it; it != end; ++it) {
1741 if (escape) {
1742 escape = false;
1743 switch (*it) {
1744 case 'n': result += '\n'; break;
1745 case 'r': result += '\r'; break;
1746 case 't': result += '\t'; break;
1747 case 'b': result += '\b'; break;
1748 case 'f': result += '\f'; break;
1749 case '\\': result += '\\'; break;
1750 default:
1751 if (*it == quote) {
1752 result += quote;
1753 } else {
1754 result += *it;
1755 }
1756 break;
1757 }
1758 } else if (*it == '\\') {
1759 escape = true;
1760 } else if (*it == quote) {
1761 ++it;
1762 return std::make_unique<std::string>(args: std::move(result));
1763 } else {
1764 result += *it;
1765 }
1766 }
1767 return nullptr;
1768 };
1769
1770 consumeSpaces();
1771 if (it == end) return nullptr;
1772 if (*it == '"') return doParse('"');
1773 if (*it == '\'') return doParse('\'');
1774 return nullptr;
1775 }
1776
1777 json parseNumber(CharIterator& it, const CharIterator& end) {
1778 auto before = it;
1779 consumeSpaces();
1780 auto start = it;
1781 bool hasDecimal = false;
1782 bool hasExponent = false;
1783
1784 if (it != end && (*it == '-' || *it == '+')) ++it;
1785
1786 while (it != end) {
1787 if (std::isdigit(*it)) {
1788 ++it;
1789 } else if (*it == '.') {
1790 if (hasDecimal) throw std::runtime_error("Multiple decimal points");
1791 hasDecimal = true;
1792 ++it;
1793 } else if (it != start && (*it == 'e' || *it == 'E')) {
1794 if (hasExponent) throw std::runtime_error("Multiple exponents");
1795 hasExponent = true;
1796 ++it;
1797 } else {
1798 break;
1799 }
1800 }
1801 if (start == it) {
1802 it = before;
1803 return json(); // No valid characters found
1804 }
1805
1806 std::string str(start, it);
1807 try {
1808 return json::parse(i&: str);
1809 } catch (json::parse_error& e) {
1810 throw std::runtime_error("Failed to parse number: '" + str + "' (" + std::string(e.what()) + ")");
1811 return json();
1812 }
1813 }
1814
1815 /** integer, float, bool, string */
1816 std::shared_ptr<Value> parseConstant() {
1817 auto start = it;
1818 consumeSpaces();
1819 if (it == end) return nullptr;
1820 if (*it == '"' || *it == '\'') {
1821 auto str = parseString();
1822 if (str) return std::make_shared<Value>(args&: *str);
1823 }
1824 static std::regex prim_tok(R"(true\b|True\b|false\b|False\b|None\b)");
1825 auto token = consumeToken(regex: prim_tok);
1826 if (!token.empty()) {
1827 if (token == "true" || token == "True") return std::make_shared<Value>(args: true);
1828 if (token == "false" || token == "False") return std::make_shared<Value>(args: false);
1829 if (token == "None") return std::make_shared<Value>(args: nullptr);
1830 throw std::runtime_error("Unknown constant token: " + token);
1831 }
1832
1833 auto number = parseNumber(it, end);
1834 if (!number.is_null()) return std::make_shared<Value>(args&: number);
1835
1836 it = start;
1837 return nullptr;
1838 }
1839
1840 class expression_parsing_error : public std::runtime_error {
1841 const CharIterator it;
1842 public:
1843 expression_parsing_error(const std::string & message, const CharIterator & it)
1844 : std::runtime_error(message), it(it) {}
1845 size_t get_pos(const CharIterator & begin) const {
1846 return std::distance(first: begin, last: it);
1847 }
1848 };
1849
1850 bool peekSymbols(const std::vector<std::string> & symbols) const {
1851 for (const auto & symbol : symbols) {
1852 if (std::distance(first: it, last: end) >= (int64_t) symbol.size() && std::string(it, it + symbol.size()) == symbol) {
1853 return true;
1854 }
1855 }
1856 return false;
1857 }
1858
1859 std::vector<std::string> consumeTokenGroups(const std::regex & regex, SpaceHandling space_handling = SpaceHandling::Strip) {
1860 auto start = it;
1861 consumeSpaces(space_handling);
1862 std::smatch match;
1863 if (std::regex_search(s: it, e: end, m&: match, re: regex) && match.position() == 0) {
1864 it += match[0].length();
1865 std::vector<std::string> ret;
1866 for (size_t i = 0, n = match.size(); i < n; ++i) {
1867 ret.push_back(x: match[i].str());
1868 }
1869 return ret;
1870 }
1871 it = start;
1872 return {};
1873 }
1874 std::string consumeToken(const std::regex & regex, SpaceHandling space_handling = SpaceHandling::Strip) {
1875 auto start = it;
1876 consumeSpaces(space_handling);
1877 std::smatch match;
1878 if (std::regex_search(s: it, e: end, m&: match, re: regex) && match.position() == 0) {
1879 it += match[0].length();
1880 return match[0].str();
1881 }
1882 it = start;
1883 return "";
1884 }
1885
1886 std::string consumeToken(const std::string & token, SpaceHandling space_handling = SpaceHandling::Strip) {
1887 auto start = it;
1888 consumeSpaces(space_handling);
1889 if (std::distance(first: it, last: end) >= (int64_t) token.size() && std::string(it, it + token.size()) == token) {
1890 it += token.size();
1891 return token;
1892 }
1893 it = start;
1894 return "";
1895 }
1896
1897 std::shared_ptr<Expression> parseExpression(bool allow_if_expr = true) {
1898 auto left = parseLogicalOr();
1899 if (it == end) return left;
1900
1901 if (!allow_if_expr) return left;
1902
1903 static std::regex if_tok(R"(if\b)");
1904 if (consumeToken(regex: if_tok).empty()) {
1905 return left;
1906 }
1907
1908 auto location = get_location();
1909 auto [condition, else_expr] = parseIfExpression();
1910 return std::make_shared<IfExpr>(args&: location, args: std::move(condition), args: std::move(left), args: std::move(else_expr));
1911 }
1912
1913 Location get_location() const {
1914 return {.source: template_str, .pos: (size_t) std::distance(first: start, last: it)};
1915 }
1916
1917 std::pair<std::shared_ptr<Expression>, std::shared_ptr<Expression>> parseIfExpression() {
1918 auto condition = parseLogicalOr();
1919 if (!condition) throw std::runtime_error("Expected condition expression");
1920
1921 static std::regex else_tok(R"(else\b)");
1922 std::shared_ptr<Expression> else_expr;
1923 if (!consumeToken(regex: else_tok).empty()) {
1924 else_expr = parseExpression();
1925 if (!else_expr) throw std::runtime_error("Expected 'else' expression");
1926 }
1927 return std::pair(std::move(condition), std::move(else_expr));
1928 }
1929
1930 std::shared_ptr<Expression> parseLogicalOr() {
1931 auto left = parseLogicalAnd();
1932 if (!left) throw std::runtime_error("Expected left side of 'logical or' expression");
1933
1934 static std::regex or_tok(R"(or\b)");
1935 auto location = get_location();
1936 while (!consumeToken(regex: or_tok).empty()) {
1937 auto right = parseLogicalAnd();
1938 if (!right) throw std::runtime_error("Expected right side of 'or' expression");
1939 left = std::make_shared<BinaryOpExpr>(args&: location, args: std::move(left), args: std::move(right), args: BinaryOpExpr::Op::Or);
1940 }
1941 return left;
1942 }
1943
1944 std::shared_ptr<Expression> parseLogicalNot() {
1945 static std::regex not_tok(R"(not\b)");
1946 auto location = get_location();
1947
1948 if (!consumeToken(regex: not_tok).empty()) {
1949 auto sub = parseLogicalNot();
1950 if (!sub) throw std::runtime_error("Expected expression after 'not' keyword");
1951 return std::make_shared<UnaryOpExpr>(args&: location, args: std::move(sub), args: UnaryOpExpr::Op::LogicalNot);
1952 }
1953 return parseLogicalCompare();
1954 }
1955
1956 std::shared_ptr<Expression> parseLogicalAnd() {
1957 auto left = parseLogicalNot();
1958 if (!left) throw std::runtime_error("Expected left side of 'logical and' expression");
1959
1960 static std::regex and_tok(R"(and\b)");
1961 auto location = get_location();
1962 while (!consumeToken(regex: and_tok).empty()) {
1963 auto right = parseLogicalNot();
1964 if (!right) throw std::runtime_error("Expected right side of 'and' expression");
1965 left = std::make_shared<BinaryOpExpr>(args&: location, args: std::move(left), args: std::move(right), args: BinaryOpExpr::Op::And);
1966 }
1967 return left;
1968 }
1969
1970 std::shared_ptr<Expression> parseLogicalCompare() {
1971 auto left = parseStringConcat();
1972 if (!left) throw std::runtime_error("Expected left side of 'logical compare' expression");
1973
1974 static std::regex compare_tok(R"(==|!=|<=?|>=?|in\b|is\b|not\s+in\b)");
1975 static std::regex not_tok(R"(not\b)");
1976 std::string op_str;
1977 while (!(op_str = consumeToken(regex: compare_tok)).empty()) {
1978 auto location = get_location();
1979 if (op_str == "is") {
1980 auto negated = !consumeToken(regex: not_tok).empty();
1981
1982 auto identifier = parseIdentifier();
1983 if (!identifier) throw std::runtime_error("Expected identifier after 'is' keyword");
1984
1985 return std::make_shared<BinaryOpExpr>(
1986 args&: left->location,
1987 args: std::move(left), args: std::move(identifier),
1988 args: negated ? BinaryOpExpr::Op::IsNot : BinaryOpExpr::Op::Is);
1989 }
1990 auto right = parseStringConcat();
1991 if (!right) throw std::runtime_error("Expected right side of 'logical compare' expression");
1992 BinaryOpExpr::Op op;
1993 if (op_str == "==") op = BinaryOpExpr::Op::Eq;
1994 else if (op_str == "!=") op = BinaryOpExpr::Op::Ne;
1995 else if (op_str == "<") op = BinaryOpExpr::Op::Lt;
1996 else if (op_str == ">") op = BinaryOpExpr::Op::Gt;
1997 else if (op_str == "<=") op = BinaryOpExpr::Op::Le;
1998 else if (op_str == ">=") op = BinaryOpExpr::Op::Ge;
1999 else if (op_str == "in") op = BinaryOpExpr::Op::In;
2000 else if (op_str.substr(pos: 0, n: 3) == "not") op = BinaryOpExpr::Op::NotIn;
2001 else throw std::runtime_error("Unknown comparison operator: " + op_str);
2002 left = std::make_shared<BinaryOpExpr>(args: get_location(), args: std::move(left), args: std::move(right), args&: op);
2003 }
2004 return left;
2005 }
2006
2007 Expression::Parameters parseParameters() {
2008 consumeSpaces();
2009 if (consumeToken(token: "(").empty()) throw std::runtime_error("Expected opening parenthesis in param list");
2010
2011 Expression::Parameters result;
2012
2013 while (it != end) {
2014 if (!consumeToken(token: ")").empty()) {
2015 return result;
2016 }
2017 auto expr = parseExpression();
2018 if (!expr) throw std::runtime_error("Expected expression in call args");
2019
2020 if (auto ident = dynamic_cast<VariableExpr*>(expr.get())) {
2021 if (!consumeToken(token: "=").empty()) {
2022 auto value = parseExpression();
2023 if (!value) throw std::runtime_error("Expected expression in for named arg");
2024 result.emplace_back(args: ident->get_name(), args: std::move(value));
2025 } else {
2026 result.emplace_back(args: ident->get_name(), args: nullptr);
2027 }
2028 } else {
2029 result.emplace_back(args: std::string(), args: std::move(expr));
2030 }
2031 if (consumeToken(token: ",").empty()) {
2032 if (consumeToken(token: ")").empty()) {
2033 throw std::runtime_error("Expected closing parenthesis in call args");
2034 }
2035 return result;
2036 }
2037 }
2038 throw std::runtime_error("Expected closing parenthesis in call args");
2039 }
2040
2041 ArgumentsExpression parseCallArgs() {
2042 consumeSpaces();
2043 if (consumeToken(token: "(").empty()) throw std::runtime_error("Expected opening parenthesis in call args");
2044
2045 ArgumentsExpression result;
2046
2047 while (it != end) {
2048 if (!consumeToken(token: ")").empty()) {
2049 return result;
2050 }
2051 auto expr = parseExpression();
2052 if (!expr) throw std::runtime_error("Expected expression in call args");
2053
2054 if (auto ident = dynamic_cast<VariableExpr*>(expr.get())) {
2055 if (!consumeToken(token: "=").empty()) {
2056 auto value = parseExpression();
2057 if (!value) throw std::runtime_error("Expected expression in for named arg");
2058 result.kwargs.emplace_back(args: ident->get_name(), args: std::move(value));
2059 } else {
2060 result.args.emplace_back(args: std::move(expr));
2061 }
2062 } else {
2063 result.args.emplace_back(args: std::move(expr));
2064 }
2065 if (consumeToken(token: ",").empty()) {
2066 if (consumeToken(token: ")").empty()) {
2067 throw std::runtime_error("Expected closing parenthesis in call args");
2068 }
2069 return result;
2070 }
2071 }
2072 throw std::runtime_error("Expected closing parenthesis in call args");
2073 }
2074
2075 std::shared_ptr<VariableExpr> parseIdentifier() {
2076 static std::regex ident_regex(R"((?!(?:not|is|and|or|del)\b)[a-zA-Z_]\w*)");
2077 auto location = get_location();
2078 auto ident = consumeToken(regex: ident_regex);
2079 if (ident.empty())
2080 return nullptr;
2081 return std::make_shared<VariableExpr>(args&: location, args&: ident);
2082 }
2083
2084 std::shared_ptr<Expression> parseStringConcat() {
2085 auto left = parseMathPow();
2086 if (!left) throw std::runtime_error("Expected left side of 'string concat' expression");
2087
2088 static std::regex concat_tok(R"(~(?!\}))");
2089 if (!consumeToken(regex: concat_tok).empty()) {
2090 auto right = parseLogicalAnd();
2091 if (!right) throw std::runtime_error("Expected right side of 'string concat' expression");
2092 left = std::make_shared<BinaryOpExpr>(args: get_location(), args: std::move(left), args: std::move(right), args: BinaryOpExpr::Op::StrConcat);
2093 }
2094 return left;
2095 }
2096
2097 std::shared_ptr<Expression> parseMathPow() {
2098 auto left = parseMathPlusMinus();
2099 if (!left) throw std::runtime_error("Expected left side of 'math pow' expression");
2100
2101 while (!consumeToken(token: "**").empty()) {
2102 auto right = parseMathPlusMinus();
2103 if (!right) throw std::runtime_error("Expected right side of 'math pow' expression");
2104 left = std::make_shared<BinaryOpExpr>(args: get_location(), args: std::move(left), args: std::move(right), args: BinaryOpExpr::Op::MulMul);
2105 }
2106 return left;
2107 }
2108
2109 std::shared_ptr<Expression> parseMathPlusMinus() {
2110 static std::regex plus_minus_tok(R"(\+|-(?![}%#]\}))");
2111
2112 auto left = parseMathMulDiv();
2113 if (!left) throw std::runtime_error("Expected left side of 'math plus/minus' expression");
2114 std::string op_str;
2115 while (!(op_str = consumeToken(regex: plus_minus_tok)).empty()) {
2116 auto right = parseMathMulDiv();
2117 if (!right) throw std::runtime_error("Expected right side of 'math plus/minus' expression");
2118 auto op = op_str == "+" ? BinaryOpExpr::Op::Add : BinaryOpExpr::Op::Sub;
2119 left = std::make_shared<BinaryOpExpr>(args: get_location(), args: std::move(left), args: std::move(right), args&: op);
2120 }
2121 return left;
2122 }
2123
2124 std::shared_ptr<Expression> parseMathMulDiv() {
2125 auto left = parseMathUnaryPlusMinus();
2126 if (!left) throw std::runtime_error("Expected left side of 'math mul/div' expression");
2127
2128 static std::regex mul_div_tok(R"(\*\*?|//?|%(?!\}))");
2129 std::string op_str;
2130 while (!(op_str = consumeToken(regex: mul_div_tok)).empty()) {
2131 auto right = parseMathUnaryPlusMinus();
2132 if (!right) throw std::runtime_error("Expected right side of 'math mul/div' expression");
2133 auto op = op_str == "*" ? BinaryOpExpr::Op::Mul
2134 : op_str == "**" ? BinaryOpExpr::Op::MulMul
2135 : op_str == "/" ? BinaryOpExpr::Op::Div
2136 : op_str == "//" ? BinaryOpExpr::Op::DivDiv
2137 : BinaryOpExpr::Op::Mod;
2138 left = std::make_shared<BinaryOpExpr>(args: get_location(), args: std::move(left), args: std::move(right), args&: op);
2139 }
2140
2141 if (!consumeToken(token: "|").empty()) {
2142 auto expr = parseMathMulDiv();
2143 if (auto filter = dynamic_cast<FilterExpr*>(expr.get())) {
2144 filter->prepend(e: std::move(left));
2145 return expr;
2146 } else {
2147 std::vector<std::shared_ptr<Expression>> parts;
2148 parts.emplace_back(args: std::move(left));
2149 parts.emplace_back(args: std::move(expr));
2150 return std::make_shared<FilterExpr>(args: get_location(), args: std::move(parts));
2151 }
2152 }
2153 return left;
2154 }
2155
2156 std::shared_ptr<Expression> call_func(const std::string & name, ArgumentsExpression && args) const {
2157 return std::make_shared<CallExpr>(args: get_location(), args: std::make_shared<VariableExpr>(args: get_location(), args: name), args: std::move(args));
2158 }
2159
2160 std::shared_ptr<Expression> parseMathUnaryPlusMinus() {
2161 static std::regex unary_plus_minus_tok(R"(\+|-(?![}%#]\}))");
2162 auto op_str = consumeToken(regex: unary_plus_minus_tok);
2163 auto expr = parseExpansion();
2164 if (!expr) throw std::runtime_error("Expected expr of 'unary plus/minus/expansion' expression");
2165
2166 if (!op_str.empty()) {
2167 auto op = op_str == "+" ? UnaryOpExpr::Op::Plus : UnaryOpExpr::Op::Minus;
2168 return std::make_shared<UnaryOpExpr>(args: get_location(), args: std::move(expr), args&: op);
2169 }
2170 return expr;
2171 }
2172
2173 std::shared_ptr<Expression> parseExpansion() {
2174 static std::regex expansion_tok(R"(\*\*?)");
2175 auto op_str = consumeToken(regex: expansion_tok);
2176 auto expr = parseValueExpression();
2177 if (op_str.empty()) return expr;
2178 if (!expr) throw std::runtime_error("Expected expr of 'expansion' expression");
2179 return std::make_shared<UnaryOpExpr>(args: get_location(), args: std::move(expr), args: op_str == "*" ? UnaryOpExpr::Op::Expansion : UnaryOpExpr::Op::ExpansionDict);
2180 }
2181
2182 std::shared_ptr<Expression> parseValueExpression() {
2183 auto parseValue = [&]() -> std::shared_ptr<Expression> {
2184 auto location = get_location();
2185 auto constant = parseConstant();
2186 if (constant) return std::make_shared<LiteralExpr>(args&: location, args&: *constant);
2187
2188 static std::regex null_regex(R"(null\b)");
2189 if (!consumeToken(regex: null_regex).empty()) return std::make_shared<LiteralExpr>(args&: location, args: Value());
2190
2191 auto identifier = parseIdentifier();
2192 if (identifier) return identifier;
2193
2194 auto braced = parseBracedExpressionOrArray();
2195 if (braced) return braced;
2196
2197 auto array = parseArray();
2198 if (array) return array;
2199
2200 auto dictionary = parseDictionary();
2201 if (dictionary) return dictionary;
2202
2203 throw std::runtime_error("Expected value expression");
2204 };
2205
2206 auto value = parseValue();
2207
2208 while (it != end && consumeSpaces() && peekSymbols(symbols: { "[", ".", "(" })) {
2209 if (!consumeToken(token: "[").empty()) {
2210 std::shared_ptr<Expression> index;
2211 auto slice_loc = get_location();
2212 std::shared_ptr<Expression> start, end, step;
2213 bool has_first_colon = false, has_second_colon = false;
2214
2215 if (!peekSymbols(symbols: { ":" })) {
2216 start = parseExpression();
2217 }
2218
2219 if (!consumeToken(token: ":").empty()) {
2220 has_first_colon = true;
2221 if (!peekSymbols(symbols: { ":", "]" })) {
2222 end = parseExpression();
2223 }
2224 if (!consumeToken(token: ":").empty()) {
2225 has_second_colon = true;
2226 if (!peekSymbols(symbols: { "]" })) {
2227 step = parseExpression();
2228 }
2229 }
2230 }
2231
2232 if ((has_first_colon || has_second_colon)) {
2233 index = std::make_shared<SliceExpr>(args&: slice_loc, args: std::move(start), args: std::move(end), args: std::move(step));
2234 } else {
2235 index = std::move(start);
2236 }
2237 if (!index) throw std::runtime_error("Empty index in subscript");
2238 if (consumeToken(token: "]").empty()) throw std::runtime_error("Expected closing bracket in subscript");
2239
2240 value = std::make_shared<SubscriptExpr>(args&: value->location, args: std::move(value), args: std::move(index));
2241 } else if (!consumeToken(token: ".").empty()) {
2242 auto identifier = parseIdentifier();
2243 if (!identifier) throw std::runtime_error("Expected identifier in subscript");
2244
2245 consumeSpaces();
2246 if (peekSymbols(symbols: { "(" })) {
2247 auto callParams = parseCallArgs();
2248 value = std::make_shared<MethodCallExpr>(args&: identifier->location, args: std::move(value), args: std::move(identifier), args: std::move(callParams));
2249 } else {
2250 auto key = std::make_shared<LiteralExpr>(args&: identifier->location, args: Value(identifier->get_name()));
2251 value = std::make_shared<SubscriptExpr>(args&: identifier->location, args: std::move(value), args: std::move(key));
2252 }
2253 } else if (peekSymbols(symbols: { "(" })) {
2254 auto callParams = parseCallArgs();
2255 value = std::make_shared<CallExpr>(args: get_location(), args: std::move(value), args: std::move(callParams));
2256 }
2257 consumeSpaces();
2258 }
2259
2260 return value;
2261 }
2262
2263 std::shared_ptr<Expression> parseBracedExpressionOrArray() {
2264 if (consumeToken(token: "(").empty()) return nullptr;
2265
2266 auto expr = parseExpression();
2267 if (!expr) throw std::runtime_error("Expected expression in braced expression");
2268
2269 if (!consumeToken(token: ")").empty()) {
2270 return expr; // Drop the parentheses
2271 }
2272
2273 std::vector<std::shared_ptr<Expression>> tuple;
2274 tuple.emplace_back(args: std::move(expr));
2275
2276 while (it != end) {
2277 if (consumeToken(token: ",").empty()) throw std::runtime_error("Expected comma in tuple");
2278 auto next = parseExpression();
2279 if (!next) throw std::runtime_error("Expected expression in tuple");
2280 tuple.push_back(x: std::move(next));
2281
2282 if (!consumeToken(token: ")").empty()) {
2283 return std::make_shared<ArrayExpr>(args: get_location(), args: std::move(tuple));
2284 }
2285 }
2286 throw std::runtime_error("Expected closing parenthesis");
2287 }
2288
2289 std::shared_ptr<Expression> parseArray() {
2290 if (consumeToken(token: "[").empty()) return nullptr;
2291
2292 std::vector<std::shared_ptr<Expression>> elements;
2293 if (!consumeToken(token: "]").empty()) {
2294 return std::make_shared<ArrayExpr>(args: get_location(), args: std::move(elements));
2295 }
2296 auto first_expr = parseExpression();
2297 if (!first_expr) throw std::runtime_error("Expected first expression in array");
2298 elements.push_back(x: std::move(first_expr));
2299
2300 while (it != end) {
2301 if (!consumeToken(token: ",").empty()) {
2302 auto expr = parseExpression();
2303 if (!expr) throw std::runtime_error("Expected expression in array");
2304 elements.push_back(x: std::move(expr));
2305 } else if (!consumeToken(token: "]").empty()) {
2306 return std::make_shared<ArrayExpr>(args: get_location(), args: std::move(elements));
2307 } else {
2308 throw std::runtime_error("Expected comma or closing bracket in array");
2309 }
2310 }
2311 throw std::runtime_error("Expected closing bracket");
2312 }
2313
2314 std::shared_ptr<Expression> parseDictionary() {
2315 if (consumeToken(token: "{").empty()) return nullptr;
2316
2317 std::vector<std::pair<std::shared_ptr<Expression>, std::shared_ptr<Expression>>> elements;
2318 if (!consumeToken(token: "}").empty()) {
2319 return std::make_shared<DictExpr>(args: get_location(), args: std::move(elements));
2320 }
2321
2322 auto parseKeyValuePair = [&]() {
2323 auto key = parseExpression();
2324 if (!key) throw std::runtime_error("Expected key in dictionary");
2325 if (consumeToken(token: ":").empty()) throw std::runtime_error("Expected colon betweek key & value in dictionary");
2326 auto value = parseExpression();
2327 if (!value) throw std::runtime_error("Expected value in dictionary");
2328 elements.emplace_back(args: std::pair(std::move(key), std::move(value)));
2329 };
2330
2331 parseKeyValuePair();
2332
2333 while (it != end) {
2334 if (!consumeToken(token: ",").empty()) {
2335 parseKeyValuePair();
2336 } else if (!consumeToken(token: "}").empty()) {
2337 return std::make_shared<DictExpr>(args: get_location(), args: std::move(elements));
2338 } else {
2339 throw std::runtime_error("Expected comma or closing brace in dictionary");
2340 }
2341 }
2342 throw std::runtime_error("Expected closing brace");
2343 }
2344
2345 SpaceHandling parsePreSpace(const std::string& s) const {
2346 if (s == "-")
2347 return SpaceHandling::Strip;
2348 return SpaceHandling::Keep;
2349 }
2350
2351 SpaceHandling parsePostSpace(const std::string& s) const {
2352 if (s == "-") return SpaceHandling::Strip;
2353 return SpaceHandling::Keep;
2354 }
2355
2356 using TemplateTokenVector = std::vector<std::unique_ptr<TemplateToken>>;
2357 using TemplateTokenIterator = TemplateTokenVector::const_iterator;
2358
2359 std::vector<std::string> parseVarNames() {
2360 static std::regex varnames_regex(R"(((?:\w+)(?:\s*,\s*(?:\w+))*)\s*)");
2361
2362 std::vector<std::string> group;
2363 if ((group = consumeTokenGroups(regex: varnames_regex)).empty()) throw std::runtime_error("Expected variable names");
2364 std::vector<std::string> varnames;
2365 std::istringstream iss(group[1]);
2366 std::string varname;
2367 while (std::getline(in&: iss, str&: varname, delim: ',')) {
2368 varnames.push_back(x: strip(s: varname));
2369 }
2370 return varnames;
2371 }
2372
2373 std::runtime_error unexpected(const TemplateToken & token) const {
2374 return std::runtime_error("Unexpected " + TemplateToken::typeToString(t: token.type)
2375 + error_location_suffix(source: *template_str, pos: token.location.pos));
2376 }
2377 std::runtime_error unterminated(const TemplateToken & token) const {
2378 return std::runtime_error("Unterminated " + TemplateToken::typeToString(t: token.type)
2379 + error_location_suffix(source: *template_str, pos: token.location.pos));
2380 }
2381
2382 TemplateTokenVector tokenize() {
2383 static std::regex comment_tok(R"(\{#([-~]?)([\s\S]*?)([-~]?)#\})");
2384 static std::regex expr_open_regex(R"(\{\{([-~])?)");
2385 static std::regex block_open_regex(R"(^\{%([-~])?\s*)");
2386 static std::regex block_keyword_tok(R"((if|else|elif|endif|for|endfor|generation|endgeneration|set|endset|block|endblock|macro|endmacro|filter|endfilter|break|continue|call|endcall)\b)");
2387 static std::regex non_text_open_regex(R"(\{\{|\{%|\{#)");
2388 static std::regex expr_close_regex(R"(\s*([-~])?\}\})");
2389 static std::regex block_close_regex(R"(\s*([-~])?%\})");
2390
2391 TemplateTokenVector tokens;
2392 std::vector<std::string> group;
2393 std::string text;
2394 std::smatch match;
2395
2396 try {
2397 while (it != end) {
2398 auto location = get_location();
2399
2400 if (!(group = consumeTokenGroups(regex: comment_tok, space_handling: SpaceHandling::Keep)).empty()) {
2401 auto pre_space = parsePreSpace(s: group[1]);
2402 auto content = group[2];
2403 auto post_space = parsePostSpace(s: group[3]);
2404 tokens.push_back(x: std::make_unique<CommentTemplateToken>(args&: location, args&: pre_space, args&: post_space, args&: content));
2405 } else if (!(group = consumeTokenGroups(regex: expr_open_regex, space_handling: SpaceHandling::Keep)).empty()) {
2406 auto pre_space = parsePreSpace(s: group[1]);
2407 auto expr = parseExpression();
2408
2409 if ((group = consumeTokenGroups(regex: expr_close_regex)).empty()) {
2410 throw std::runtime_error("Expected closing expression tag");
2411 }
2412
2413 auto post_space = parsePostSpace(s: group[1]);
2414 tokens.push_back(x: std::make_unique<ExpressionTemplateToken>(args&: location, args&: pre_space, args&: post_space, args: std::move(expr)));
2415 } else if (!(group = consumeTokenGroups(regex: block_open_regex, space_handling: SpaceHandling::Keep)).empty()) {
2416 auto pre_space = parsePreSpace(s: group[1]);
2417
2418 std::string keyword;
2419
2420 auto parseBlockClose = [&]() -> SpaceHandling {
2421 if ((group = consumeTokenGroups(regex: block_close_regex)).empty()) throw std::runtime_error("Expected closing block tag");
2422 return parsePostSpace(s: group[1]);
2423 };
2424
2425 if ((keyword = consumeToken(regex: block_keyword_tok)).empty()) throw std::runtime_error("Expected block keyword");
2426
2427 if (keyword == "if") {
2428 auto condition = parseExpression();
2429 if (!condition) throw std::runtime_error("Expected condition in if block");
2430
2431 auto post_space = parseBlockClose();
2432 tokens.push_back(x: std::make_unique<IfTemplateToken>(args&: location, args&: pre_space, args&: post_space, args: std::move(condition)));
2433 } else if (keyword == "elif") {
2434 auto condition = parseExpression();
2435 if (!condition) throw std::runtime_error("Expected condition in elif block");
2436
2437 auto post_space = parseBlockClose();
2438 tokens.push_back(x: std::make_unique<ElifTemplateToken>(args&: location, args&: pre_space, args&: post_space, args: std::move(condition)));
2439 } else if (keyword == "else") {
2440 auto post_space = parseBlockClose();
2441 tokens.push_back(x: std::make_unique<ElseTemplateToken>(args&: location, args&: pre_space, args&: post_space));
2442 } else if (keyword == "endif") {
2443 auto post_space = parseBlockClose();
2444 tokens.push_back(x: std::make_unique<EndIfTemplateToken>(args&: location, args&: pre_space, args&: post_space));
2445 } else if (keyword == "for") {
2446 static std::regex recursive_tok(R"(recursive\b)");
2447 static std::regex if_tok(R"(if\b)");
2448
2449 auto varnames = parseVarNames();
2450 static std::regex in_tok(R"(in\b)");
2451 if (consumeToken(regex: in_tok).empty()) throw std::runtime_error("Expected 'in' keyword in for block");
2452 auto iterable = parseExpression(/* allow_if_expr = */ allow_if_expr: false);
2453 if (!iterable) throw std::runtime_error("Expected iterable in for block");
2454
2455 std::shared_ptr<Expression> condition;
2456 if (!consumeToken(regex: if_tok).empty()) {
2457 condition = parseExpression();
2458 }
2459 auto recursive = !consumeToken(regex: recursive_tok).empty();
2460
2461 auto post_space = parseBlockClose();
2462 tokens.push_back(x: std::make_unique<ForTemplateToken>(args&: location, args&: pre_space, args&: post_space, args: std::move(varnames), args: std::move(iterable), args: std::move(condition), args&: recursive));
2463 } else if (keyword == "endfor") {
2464 auto post_space = parseBlockClose();
2465 tokens.push_back(x: std::make_unique<EndForTemplateToken>(args&: location, args&: pre_space, args&: post_space));
2466 } else if (keyword == "generation") {
2467 auto post_space = parseBlockClose();
2468 tokens.push_back(x: std::make_unique<GenerationTemplateToken>(args&: location, args&: pre_space, args&: post_space));
2469 } else if (keyword == "endgeneration") {
2470 auto post_space = parseBlockClose();
2471 tokens.push_back(x: std::make_unique<EndGenerationTemplateToken>(args&: location, args&: pre_space, args&: post_space));
2472 } else if (keyword == "set") {
2473 static std::regex namespaced_var_regex(R"((\w+)\s*\.\s*(\w+))");
2474
2475 std::string ns;
2476 std::vector<std::string> var_names;
2477 std::shared_ptr<Expression> value;
2478 if (!(group = consumeTokenGroups(regex: namespaced_var_regex)).empty()) {
2479 ns = group[1];
2480 var_names.push_back(x: group[2]);
2481
2482 if (consumeToken(token: "=").empty()) throw std::runtime_error("Expected equals sign in set block");
2483
2484 value = parseExpression();
2485 if (!value) throw std::runtime_error("Expected value in set block");
2486 } else {
2487 var_names = parseVarNames();
2488
2489 if (!consumeToken(token: "=").empty()) {
2490 value = parseExpression();
2491 if (!value) throw std::runtime_error("Expected value in set block");
2492 }
2493 }
2494 auto post_space = parseBlockClose();
2495 tokens.push_back(x: std::make_unique<SetTemplateToken>(args&: location, args&: pre_space, args&: post_space, args&: ns, args&: var_names, args: std::move(value)));
2496 } else if (keyword == "endset") {
2497 auto post_space = parseBlockClose();
2498 tokens.push_back(x: std::make_unique<EndSetTemplateToken>(args&: location, args&: pre_space, args&: post_space));
2499 } else if (keyword == "macro") {
2500 auto macroname = parseIdentifier();
2501 if (!macroname) throw std::runtime_error("Expected macro name in macro block");
2502 auto params = parseParameters();
2503
2504 auto post_space = parseBlockClose();
2505 tokens.push_back(x: std::make_unique<MacroTemplateToken>(args&: location, args&: pre_space, args&: post_space, args: std::move(macroname), args: std::move(params)));
2506 } else if (keyword == "endmacro") {
2507 auto post_space = parseBlockClose();
2508 tokens.push_back(x: std::make_unique<EndMacroTemplateToken>(args&: location, args&: pre_space, args&: post_space));
2509 } else if (keyword == "call") {
2510 auto expr = parseExpression();
2511 if (!expr) throw std::runtime_error("Expected expression in call block");
2512
2513 auto post_space = parseBlockClose();
2514 tokens.push_back(x: std::make_unique<CallTemplateToken>(args&: location, args&: pre_space, args&: post_space, args: std::move(expr)));
2515 } else if (keyword == "endcall") {
2516 auto post_space = parseBlockClose();
2517 tokens.push_back(x: std::make_unique<EndCallTemplateToken>(args&: location, args&: pre_space, args&: post_space));
2518 } else if (keyword == "filter") {
2519 auto filter = parseExpression();
2520 if (!filter) throw std::runtime_error("Expected expression in filter block");
2521
2522 auto post_space = parseBlockClose();
2523 tokens.push_back(x: std::make_unique<FilterTemplateToken>(args&: location, args&: pre_space, args&: post_space, args: std::move(filter)));
2524 } else if (keyword == "endfilter") {
2525 auto post_space = parseBlockClose();
2526 tokens.push_back(x: std::make_unique<EndFilterTemplateToken>(args&: location, args&: pre_space, args&: post_space));
2527 } else if (keyword == "break" || keyword == "continue") {
2528 auto post_space = parseBlockClose();
2529 tokens.push_back(x: std::make_unique<LoopControlTemplateToken>(args&: location, args&: pre_space, args&: post_space, args: keyword == "break" ? LoopControlType::Break : LoopControlType::Continue));
2530 } else {
2531 throw std::runtime_error("Unexpected block: " + keyword);
2532 }
2533 } else if (std::regex_search(s: it, e: end, m&: match, re: non_text_open_regex)) {
2534 if (!match.position()) {
2535 if (match[0] != "{#")
2536 throw std::runtime_error("Internal error: Expected a comment");
2537 throw std::runtime_error("Missing end of comment tag");
2538 }
2539 auto text_end = it + match.position();
2540 text = std::string(it, text_end);
2541 it = text_end;
2542 tokens.push_back(x: std::make_unique<TextTemplateToken>(args&: location, args: SpaceHandling::Keep, args: SpaceHandling::Keep, args&: text));
2543 } else {
2544 text = std::string(it, end);
2545 it = end;
2546 tokens.push_back(x: std::make_unique<TextTemplateToken>(args&: location, args: SpaceHandling::Keep, args: SpaceHandling::Keep, args&: text));
2547 }
2548 }
2549 return tokens;
2550 } catch (const std::exception & e) {
2551 throw std::runtime_error(e.what() + error_location_suffix(source: *template_str, pos: std::distance(first: start, last: it)));
2552 }
2553 }
2554
2555 std::shared_ptr<TemplateNode> parseTemplate(
2556 const TemplateTokenIterator & begin,
2557 TemplateTokenIterator & it,
2558 const TemplateTokenIterator & end,
2559 bool fully = false) const {
2560 std::vector<std::shared_ptr<TemplateNode>> children;
2561 while (it != end) {
2562 const auto start = it;
2563 const auto & token = *(it++);
2564 if (auto if_token = dynamic_cast<IfTemplateToken*>(token.get())) {
2565 std::vector<std::pair<std::shared_ptr<Expression>, std::shared_ptr<TemplateNode>>> cascade;
2566 cascade.emplace_back(args: std::move(if_token->condition), args: parseTemplate(begin, it, end));
2567
2568 while (it != end && (*it)->type == TemplateToken::Type::Elif) {
2569 auto elif_token = dynamic_cast<ElifTemplateToken*>((*(it++)).get());
2570 cascade.emplace_back(args: std::move(elif_token->condition), args: parseTemplate(begin, it, end));
2571 }
2572
2573 if (it != end && (*it)->type == TemplateToken::Type::Else) {
2574 cascade.emplace_back(args: nullptr, args: parseTemplate(begin, it&: ++it, end));
2575 }
2576 if (it == end || (*(it++))->type != TemplateToken::Type::EndIf) {
2577 throw unterminated(token: **start);
2578 }
2579 children.emplace_back(args: std::make_shared<IfNode>(args&: token->location, args: std::move(cascade)));
2580 } else if (auto for_token = dynamic_cast<ForTemplateToken*>(token.get())) {
2581 auto body = parseTemplate(begin, it, end);
2582 auto else_body = std::shared_ptr<TemplateNode>();
2583 if (it != end && (*it)->type == TemplateToken::Type::Else) {
2584 else_body = parseTemplate(begin, it&: ++it, end);
2585 }
2586 if (it == end || (*(it++))->type != TemplateToken::Type::EndFor) {
2587 throw unterminated(token: **start);
2588 }
2589 children.emplace_back(args: std::make_shared<ForNode>(args&: token->location, args: std::move(for_token->var_names), args: std::move(for_token->iterable), args: std::move(for_token->condition), args: std::move(body), args&: for_token->recursive, args: std::move(else_body)));
2590 } else if (dynamic_cast<GenerationTemplateToken*>(token.get())) {
2591 auto body = parseTemplate(begin, it, end);
2592 if (it == end || (*(it++))->type != TemplateToken::Type::EndGeneration) {
2593 throw unterminated(token: **start);
2594 }
2595 // Treat as a no-op, as our scope is templates for inference, not training (`{% generation %}` wraps generated tokens for masking).
2596 children.emplace_back(args: std::move(body));
2597 } else if (auto text_token = dynamic_cast<TextTemplateToken*>(token.get())) {
2598 SpaceHandling pre_space = (it - 1) != begin ? (*(it - 2))->post_space : SpaceHandling::Keep;
2599 SpaceHandling post_space = it != end ? (*it)->pre_space : SpaceHandling::Keep;
2600
2601 auto text = text_token->text;
2602 if (post_space == SpaceHandling::Strip) {
2603 static std::regex trailing_space_regex(R"(\s+$)");
2604 text = std::regex_replace(s: text, e: trailing_space_regex, fmt: "");
2605 } else if (options.lstrip_blocks && it != end) {
2606 auto i = text.size();
2607 while (i > 0 && (text[i - 1] == ' ' || text[i - 1] == '\t')) i--;
2608 if ((i == 0 && (it - 1) == begin) || (i > 0 && text[i - 1] == '\n')) {
2609 text.resize(n: i);
2610 }
2611 }
2612 if (pre_space == SpaceHandling::Strip) {
2613 static std::regex leading_space_regex(R"(^\s+)");
2614 text = std::regex_replace(s: text, e: leading_space_regex, fmt: "");
2615 } else if (options.trim_blocks && (it - 1) != begin && !dynamic_cast<ExpressionTemplateToken*>((*(it - 2)).get())) {
2616 if (!text.empty() && text[0] == '\n') {
2617 text.erase(pos: 0, n: 1);
2618 }
2619 }
2620 if (it == end && !options.keep_trailing_newline) {
2621 auto i = text.size();
2622 if (i > 0 && text[i - 1] == '\n') {
2623 i--;
2624 if (i > 0 && text[i - 1] == '\r') i--;
2625 text.resize(n: i);
2626 }
2627 }
2628 children.emplace_back(args: std::make_shared<TextNode>(args&: token->location, args&: text));
2629 } else if (auto expr_token = dynamic_cast<ExpressionTemplateToken*>(token.get())) {
2630 children.emplace_back(args: std::make_shared<ExpressionNode>(args&: token->location, args: std::move(expr_token->expr)));
2631 } else if (auto set_token = dynamic_cast<SetTemplateToken*>(token.get())) {
2632 if (set_token->value) {
2633 children.emplace_back(args: std::make_shared<SetNode>(args&: token->location, args&: set_token->ns, args&: set_token->var_names, args: std::move(set_token->value)));
2634 } else {
2635 auto value_template = parseTemplate(begin, it, end);
2636 if (it == end || (*(it++))->type != TemplateToken::Type::EndSet) {
2637 throw unterminated(token: **start);
2638 }
2639 if (!set_token->ns.empty()) throw std::runtime_error("Namespaced set not supported in set with template value");
2640 if (set_token->var_names.size() != 1) throw std::runtime_error("Structural assignment not supported in set with template value");
2641 auto & name = set_token->var_names[0];
2642 children.emplace_back(args: std::make_shared<SetTemplateNode>(args&: token->location, args&: name, args: std::move(value_template)));
2643 }
2644 } else if (auto macro_token = dynamic_cast<MacroTemplateToken*>(token.get())) {
2645 auto body = parseTemplate(begin, it, end);
2646 if (it == end || (*(it++))->type != TemplateToken::Type::EndMacro) {
2647 throw unterminated(token: **start);
2648 }
2649 children.emplace_back(args: std::make_shared<MacroNode>(args&: token->location, args: std::move(macro_token->name), args: std::move(macro_token->params), args: std::move(body)));
2650 } else if (auto call_token = dynamic_cast<CallTemplateToken*>(token.get())) {
2651 auto body = parseTemplate(begin, it, end);
2652 if (it == end || (*(it++))->type != TemplateToken::Type::EndCall) {
2653 throw unterminated(token: **start);
2654 }
2655 children.emplace_back(args: std::make_shared<CallNode>(args&: token->location, args: std::move(call_token->expr), args: std::move(body)));
2656 } else if (auto filter_token = dynamic_cast<FilterTemplateToken*>(token.get())) {
2657 auto body = parseTemplate(begin, it, end);
2658 if (it == end || (*(it++))->type != TemplateToken::Type::EndFilter) {
2659 throw unterminated(token: **start);
2660 }
2661 children.emplace_back(args: std::make_shared<FilterNode>(args&: token->location, args: std::move(filter_token->filter), args: std::move(body)));
2662 } else if (dynamic_cast<CommentTemplateToken*>(token.get())) {
2663 // Ignore comments
2664 } else if (auto ctrl_token = dynamic_cast<LoopControlTemplateToken*>(token.get())) {
2665 children.emplace_back(args: std::make_shared<LoopControlNode>(args&: token->location, args&: ctrl_token->control_type));
2666 } else if (dynamic_cast<EndForTemplateToken*>(token.get())
2667 || dynamic_cast<EndSetTemplateToken*>(token.get())
2668 || dynamic_cast<EndMacroTemplateToken*>(token.get())
2669 || dynamic_cast<EndCallTemplateToken*>(token.get())
2670 || dynamic_cast<EndFilterTemplateToken*>(token.get())
2671 || dynamic_cast<EndIfTemplateToken*>(token.get())
2672 || dynamic_cast<ElseTemplateToken*>(token.get())
2673 || dynamic_cast<EndGenerationTemplateToken*>(token.get())
2674 || dynamic_cast<ElifTemplateToken*>(token.get())) {
2675 it--; // unconsume the token
2676 break; // exit the loop
2677 } else {
2678 throw unexpected(token: **(it-1));
2679 }
2680 }
2681 if (fully && it != end) {
2682 throw unexpected(token: **it);
2683 }
2684 if (children.empty()) {
2685 return std::make_shared<TextNode>(args: Location { .source: template_str, .pos: 0 }, args: std::string());
2686 } else if (children.size() == 1) {
2687 return std::move(children[0]);
2688 } else {
2689 return std::make_shared<SequenceNode>(args: children[0]->location(), args: std::move(children));
2690 }
2691 }
2692
2693public:
2694
2695 static std::shared_ptr<TemplateNode> parse(const std::string& template_str, const Options & options) {
2696 Parser parser(std::make_shared<std::string>(args: normalize_newlines(s: template_str)), options);
2697 auto tokens = parser.tokenize();
2698 TemplateTokenIterator begin = tokens.begin();
2699 auto it = begin;
2700 TemplateTokenIterator end = tokens.end();
2701 return parser.parseTemplate(begin, it, end, /* fully= */ fully: true);
2702 }
2703};
2704
2705static Value simple_function(const std::string & fn_name, const std::vector<std::string> & params, const std::function<Value(const std::shared_ptr<Context> &, Value & args)> & fn) {
2706 std::map<std::string, size_t> named_positions;
2707 for (size_t i = 0, n = params.size(); i < n; i++) named_positions[params[i]] = i;
2708
2709 return Value::callable(callable: [=](const std::shared_ptr<Context> & context, ArgumentsValue & args) -> Value {
2710 auto args_obj = Value::object();
2711 std::vector<bool> provided_args(params.size());
2712 for (size_t i = 0, n = args.args.size(); i < n; i++) {
2713 auto & arg = args.args[i];
2714 if (i < params.size()) {
2715 args_obj.set(key: params[i], value: arg);
2716 provided_args[i] = true;
2717 } else {
2718 throw std::runtime_error("Too many positional params for " + fn_name);
2719 }
2720 }
2721 for (auto & [name, value] : args.kwargs) {
2722 auto named_pos_it = named_positions.find(x: name);
2723 if (named_pos_it == named_positions.end()) {
2724 throw std::runtime_error("Unknown argument " + name + " for function " + fn_name);
2725 }
2726 provided_args[named_pos_it->second] = true;
2727 args_obj.set(key: name, value);
2728 }
2729 return fn(context, args_obj);
2730 });
2731}
2732
2733inline std::shared_ptr<Context> Context::builtins() {
2734 auto globals = Value::object();
2735
2736 globals.set(key: "raise_exception", value: simple_function(fn_name: "raise_exception", params: { "message" }, fn: [](const std::shared_ptr<Context> &, Value & args) -> Value {
2737 throw std::runtime_error(args.at(index: "message").get<std::string>());
2738 }));
2739 globals.set(key: "tojson", value: simple_function(fn_name: "tojson", params: { "value", "indent", "ensure_ascii" }, fn: [](const std::shared_ptr<Context> &, Value & args) {
2740 return Value(args.at(index: "value").dump(indent: args.get<int64_t>(key: "indent", default_value: -1), /* to_json= */ to_json: true));
2741 }));
2742 globals.set(key: "items", value: simple_function(fn_name: "items", params: { "object" }, fn: [](const std::shared_ptr<Context> &, Value & args) {
2743 auto items = Value::array();
2744 if (args.contains(key: "object")) {
2745 auto & obj = args.at(index: "object");
2746 if (!obj.is_object()) {
2747 throw std::runtime_error("Can only get item pairs from a mapping");
2748 }
2749 for (auto & key : obj.keys()) {
2750 items.push_back(v: Value::array(values: {key, obj.at(index: key)}));
2751 }
2752 }
2753 return items;
2754 }));
2755 globals.set(key: "last", value: simple_function(fn_name: "last", params: { "items" }, fn: [](const std::shared_ptr<Context> &, Value & args) {
2756 auto items = args.at(index: "items");
2757 if (!items.is_array()) throw std::runtime_error("object is not a list");
2758 if (items.empty()) return Value();
2759 return items.at(index: items.size() - 1);
2760 }));
2761 globals.set(key: "trim", value: simple_function(fn_name: "trim", params: { "text" }, fn: [](const std::shared_ptr<Context> &, Value & args) {
2762 auto & text = args.at(index: "text");
2763 return text.is_null() ? text : Value(strip(s: text.get<std::string>()));
2764 }));
2765 auto char_transform_function = [](const std::string & name, const std::function<char(char)> & fn) {
2766 return simple_function(fn_name: name, params: { "text" }, fn: [=](const std::shared_ptr<Context> &, Value & args) {
2767 auto text = args.at(index: "text");
2768 if (text.is_null()) return text;
2769 std::string res;
2770 auto str = text.get<std::string>();
2771 std::transform(first: str.begin(), last: str.end(), result: std::back_inserter(x&: res), unary_op: fn);
2772 return Value(res);
2773 });
2774 };
2775 globals.set(key: "lower", value: char_transform_function("lower", ::tolower));
2776 globals.set(key: "upper", value: char_transform_function("upper", ::toupper));
2777 globals.set(key: "default", value: Value::callable(callable: [=](const std::shared_ptr<Context> &, ArgumentsValue & args) {
2778 args.expectArgs(method_name: "default", pos_count: {2, 3}, kw_count: {0, 1});
2779 auto & value = args.args[0];
2780 auto & default_value = args.args[1];
2781 bool boolean = false;
2782 if (args.args.size() == 3) {
2783 boolean = args.args[2].get<bool>();
2784 } else {
2785 Value bv = args.get_named(name: "boolean");
2786 if (!bv.is_null()) {
2787 boolean = bv.get<bool>();
2788 }
2789 }
2790 return boolean ? (value.to_bool() ? value : default_value) : value.is_null() ? default_value : value;
2791 }));
2792 auto escape = simple_function(fn_name: "escape", params: { "text" }, fn: [](const std::shared_ptr<Context> &, Value & args) {
2793 return Value(html_escape(s: args.at(index: "text").get<std::string>()));
2794 });
2795 globals.set(key: "e", value: escape);
2796 globals.set(key: "escape", value: escape);
2797 globals.set(key: "joiner", value: simple_function(fn_name: "joiner", params: { "sep" }, fn: [](const std::shared_ptr<Context> &, Value & args) {
2798 auto sep = args.get<std::string>(key: "sep", default_value: "");
2799 auto first = std::make_shared<bool>(args: true);
2800 return simple_function(fn_name: "", params: {}, fn: [sep, first](const std::shared_ptr<Context> &, const Value &) -> Value {
2801 if (*first) {
2802 *first = false;
2803 return "";
2804 }
2805 return sep;
2806 });
2807 return Value(html_escape(s: args.at(index: "text").get<std::string>()));
2808 }));
2809 globals.set(key: "count", value: simple_function(fn_name: "count", params: { "items" }, fn: [](const std::shared_ptr<Context> &, Value & args) {
2810 return Value((int64_t) args.at(index: "items").size());
2811 }));
2812 globals.set(key: "dictsort", value: simple_function(fn_name: "dictsort", params: { "value" }, fn: [](const std::shared_ptr<Context> &, Value & args) {
2813 if (args.size() != 1) throw std::runtime_error("dictsort expects exactly 1 argument (TODO: fix implementation)");
2814 auto & value = args.at(index: "value");
2815 auto keys = value.keys();
2816 std::sort(first: keys.begin(), last: keys.end());
2817 auto res = Value::array();
2818 for (auto & key : keys) {
2819 res.push_back(v: Value::array(values: {key, value.at(index: key)}));
2820 }
2821 return res;
2822 }));
2823 globals.set(key: "join", value: simple_function(fn_name: "join", params: { "items", "d" }, fn: [](const std::shared_ptr<Context> &, Value & args) {
2824 auto do_join = [](Value & items, const std::string & sep) {
2825 if (!items.is_array()) throw std::runtime_error("object is not iterable: " + items.dump());
2826 std::ostringstream oss;
2827 auto first = true;
2828 for (size_t i = 0, n = items.size(); i < n; ++i) {
2829 if (first) first = false;
2830 else oss << sep;
2831 oss << items.at(index: i).to_str();
2832 }
2833 return Value(oss.str());
2834 };
2835 auto sep = args.get<std::string>(key: "d", default_value: "");
2836 if (args.contains(key: "items")) {
2837 auto & items = args.at(index: "items");
2838 return do_join(items, sep);
2839 } else {
2840 return simple_function(fn_name: "", params: {"items"}, fn: [sep, do_join](const std::shared_ptr<Context> &, Value & args) {
2841 auto & items = args.at(index: "items");
2842 if (!items.to_bool() || !items.is_array()) throw std::runtime_error("join expects an array for items, got: " + items.dump());
2843 return do_join(items, sep);
2844 });
2845 }
2846 }));
2847 globals.set(key: "namespace", value: Value::callable(callable: [=](const std::shared_ptr<Context> &, ArgumentsValue & args) {
2848 auto ns = Value::object();
2849 args.expectArgs(method_name: "namespace", pos_count: {0, 0}, kw_count: {0, (std::numeric_limits<size_t>::max)()});
2850 for (auto & [name, value] : args.kwargs) {
2851 ns.set(key: name, value);
2852 }
2853 return ns;
2854 }));
2855 auto equalto = simple_function(fn_name: "equalto", params: { "expected", "actual" }, fn: [](const std::shared_ptr<Context> &, Value & args) -> Value {
2856 return args.at(index: "actual") == args.at(index: "expected");
2857 });
2858 globals.set(key: "equalto", value: equalto);
2859 globals.set(key: "==", value: equalto);
2860 globals.set(key: "length", value: simple_function(fn_name: "length", params: { "items" }, fn: [](const std::shared_ptr<Context> &, Value & args) -> Value {
2861 auto & items = args.at(index: "items");
2862 return (int64_t) items.size();
2863 }));
2864 globals.set(key: "safe", value: simple_function(fn_name: "safe", params: { "value" }, fn: [](const std::shared_ptr<Context> &, Value & args) -> Value {
2865 return args.at(index: "value").to_str();
2866 }));
2867 globals.set(key: "string", value: simple_function(fn_name: "string", params: { "value" }, fn: [](const std::shared_ptr<Context> &, Value & args) -> Value {
2868 return args.at(index: "value").to_str();
2869 }));
2870 globals.set(key: "int", value: simple_function(fn_name: "int", params: { "value" }, fn: [](const std::shared_ptr<Context> &, Value & args) -> Value {
2871 return args.at(index: "value").to_int();
2872 }));
2873 globals.set(key: "list", value: simple_function(fn_name: "list", params: { "items" }, fn: [](const std::shared_ptr<Context> &, Value & args) -> Value {
2874 auto & items = args.at(index: "items");
2875 if (!items.is_array()) throw std::runtime_error("object is not iterable");
2876 return items;
2877 }));
2878 globals.set(key: "in", value: simple_function(fn_name: "in", params: { "item", "items" }, fn: [](const std::shared_ptr<Context> &, Value & args) -> Value {
2879 return in(value: args.at(index: "item"), container: args.at(index: "items"));
2880 }));
2881 globals.set(key: "unique", value: simple_function(fn_name: "unique", params: { "items" }, fn: [](const std::shared_ptr<Context> &, Value & args) -> Value {
2882 auto & items = args.at(index: "items");
2883 if (!items.is_array()) throw std::runtime_error("object is not iterable");
2884 std::unordered_set<Value> seen;
2885 auto result = Value::array();
2886 for (size_t i = 0, n = items.size(); i < n; i++) {
2887 auto pair = seen.insert(x: items.at(index: i));
2888 if (pair.second) {
2889 result.push_back(v: items.at(index: i));
2890 }
2891 }
2892 return result;
2893 }));
2894 auto make_filter = [](const Value & filter, Value & extra_args) -> Value {
2895 return simple_function(fn_name: "", params: { "value" }, fn: [=](const std::shared_ptr<Context> & context, Value & args) {
2896 auto & value = args.at(index: "value");
2897 ArgumentsValue actual_args;
2898 actual_args.args.emplace_back(args&: value);
2899 for (size_t i = 0, n = extra_args.size(); i < n; i++) {
2900 actual_args.args.emplace_back(args: extra_args.at(index: i));
2901 }
2902 return filter.call(context, args&: actual_args);
2903 });
2904 };
2905 auto select_or_reject = [make_filter](bool is_select) {
2906 return Value::callable(callable: [=](const std::shared_ptr<Context> & context, ArgumentsValue & args) {
2907 args.expectArgs(method_name: is_select ? "select" : "reject", pos_count: {2, (std::numeric_limits<size_t>::max)()}, kw_count: {0, 0});
2908 auto & items = args.args[0];
2909 if (items.is_null()) {
2910 return Value::array();
2911 }
2912 if (!items.is_array()) {
2913 throw std::runtime_error("object is not iterable: " + items.dump());
2914 }
2915
2916 auto filter_fn = context->get(key: args.args[1]);
2917 if (filter_fn.is_null()) {
2918 throw std::runtime_error("Undefined filter: " + args.args[1].dump());
2919 }
2920
2921 auto filter_args = Value::array();
2922 for (size_t i = 2, n = args.args.size(); i < n; i++) {
2923 filter_args.push_back(v: args.args[i]);
2924 }
2925 auto filter = make_filter(filter_fn, filter_args);
2926
2927 auto res = Value::array();
2928 for (size_t i = 0, n = items.size(); i < n; i++) {
2929 auto & item = items.at(index: i);
2930 ArgumentsValue filter_args;
2931 filter_args.args.emplace_back(args&: item);
2932 auto pred_res = filter.call(context, args&: filter_args);
2933 if (pred_res.to_bool() == (is_select ? true : false)) {
2934 res.push_back(v: item);
2935 }
2936 }
2937 return res;
2938 });
2939 };
2940 globals.set(key: "select", value: select_or_reject(/* is_select= */ true));
2941 globals.set(key: "reject", value: select_or_reject(/* is_select= */ false));
2942 globals.set(key: "map", value: Value::callable(callable: [=](const std::shared_ptr<Context> & context, ArgumentsValue & args) {
2943 auto res = Value::array();
2944 if (args.args.size() == 1 &&
2945 ((args.has_named(name: "attribute") && args.kwargs.size() == 1) || (args.has_named(name: "default") && args.kwargs.size() == 2))) {
2946 auto & items = args.args[0];
2947 auto attr_name = args.get_named(name: "attribute");
2948 auto default_value = args.get_named(name: "default");
2949 for (size_t i = 0, n = items.size(); i < n; i++) {
2950 auto & item = items.at(index: i);
2951 auto attr = item.get(key: attr_name);
2952 res.push_back(v: attr.is_null() ? default_value : attr);
2953 }
2954 } else if (args.kwargs.empty() && args.args.size() >= 2) {
2955 auto fn = context->get(key: args.args[1]);
2956 if (fn.is_null()) throw std::runtime_error("Undefined filter: " + args.args[1].dump());
2957 ArgumentsValue filter_args { .args: {Value()}, .kwargs: {} };
2958 for (size_t i = 2, n = args.args.size(); i < n; i++) {
2959 filter_args.args.emplace_back(args&: args.args[i]);
2960 }
2961 for (size_t i = 0, n = args.args[0].size(); i < n; i++) {
2962 auto & item = args.args[0].at(index: i);
2963 filter_args.args[0] = item;
2964 res.push_back(v: fn.call(context, args&: filter_args));
2965 }
2966 } else {
2967 throw std::runtime_error("Invalid or unsupported arguments for map");
2968 }
2969 return res;
2970 }));
2971 globals.set(key: "indent", value: simple_function(fn_name: "indent", params: { "text", "indent", "first" }, fn: [](const std::shared_ptr<Context> &, Value & args) {
2972 auto text = args.at(index: "text").get<std::string>();
2973 auto first = args.get<bool>(key: "first", default_value: false);
2974 std::string out;
2975 std::string indent(args.get<int64_t>(key: "indent", default_value: 0), ' ');
2976 std::istringstream iss(text);
2977 std::string line;
2978 auto is_first = true;
2979 while (std::getline(in&: iss, str&: line, delim: '\n')) {
2980 auto needs_indent = !is_first || first;
2981 if (is_first) is_first = false;
2982 else out += "\n";
2983 if (needs_indent) out += indent;
2984 out += line;
2985 }
2986 if (!text.empty() && text.back() == '\n') out += "\n";
2987 return out;
2988 }));
2989 auto select_or_reject_attr = [](bool is_select) {
2990 return Value::callable(callable: [=](const std::shared_ptr<Context> & context, ArgumentsValue & args) {
2991 args.expectArgs(method_name: is_select ? "selectattr" : "rejectattr", pos_count: {2, (std::numeric_limits<size_t>::max)()}, kw_count: {0, 0});
2992 auto & items = args.args[0];
2993 if (items.is_null())
2994 return Value::array();
2995 if (!items.is_array()) throw std::runtime_error("object is not iterable: " + items.dump());
2996 auto attr_name = args.args[1].get<std::string>();
2997
2998 bool has_test = false;
2999 Value test_fn;
3000 ArgumentsValue test_args {.args: {Value()}, .kwargs: {}};
3001 if (args.args.size() >= 3) {
3002 has_test = true;
3003 test_fn = context->get(key: args.args[2]);
3004 if (test_fn.is_null()) throw std::runtime_error("Undefined test: " + args.args[2].dump());
3005 for (size_t i = 3, n = args.args.size(); i < n; i++) {
3006 test_args.args.emplace_back(args&: args.args[i]);
3007 }
3008 test_args.kwargs = args.kwargs;
3009 }
3010
3011 auto res = Value::array();
3012 for (size_t i = 0, n = items.size(); i < n; i++) {
3013 auto & item = items.at(index: i);
3014 auto attr = item.get(key: attr_name);
3015 if (has_test) {
3016 test_args.args[0] = attr;
3017 if (test_fn.call(context, args&: test_args).to_bool() == (is_select ? true : false)) {
3018 res.push_back(v: item);
3019 }
3020 } else {
3021 res.push_back(v: attr);
3022 }
3023 }
3024 return res;
3025 });
3026 };
3027 globals.set(key: "selectattr", value: select_or_reject_attr(/* is_select= */ true));
3028 globals.set(key: "rejectattr", value: select_or_reject_attr(/* is_select= */ false));
3029 globals.set(key: "range", value: Value::callable(callable: [=](const std::shared_ptr<Context> &, ArgumentsValue & args) {
3030 std::vector<int64_t> startEndStep(3);
3031 std::vector<bool> param_set(3);
3032 if (args.args.size() == 1) {
3033 startEndStep[1] = args.args[0].get<int64_t>();
3034 param_set[1] = true;
3035 } else {
3036 for (size_t i = 0; i < args.args.size(); i++) {
3037 auto & arg = args.args[i];
3038 auto v = arg.get<int64_t>();
3039 startEndStep[i] = v;
3040 param_set[i] = true;
3041 }
3042 }
3043 for (auto & [name, value] : args.kwargs) {
3044 size_t i;
3045 if (name == "start") {
3046 i = 0;
3047 } else if (name == "end") {
3048 i = 1;
3049 } else if (name == "step") {
3050 i = 2;
3051 } else {
3052 throw std::runtime_error("Unknown argument " + name + " for function range");
3053 }
3054
3055 if (param_set[i]) {
3056 throw std::runtime_error("Duplicate argument " + name + " for function range");
3057 }
3058 startEndStep[i] = value.get<int64_t>();
3059 param_set[i] = true;
3060 }
3061 if (!param_set[1]) {
3062 throw std::runtime_error("Missing required argument 'end' for function range");
3063 }
3064 int64_t start = param_set[0] ? startEndStep[0] : 0;
3065 int64_t end = startEndStep[1];
3066 int64_t step = param_set[2] ? startEndStep[2] : 1;
3067
3068 auto res = Value::array();
3069 if (step > 0) {
3070 for (int64_t i = start; i < end; i += step) {
3071 res.push_back(v: Value(i));
3072 }
3073 } else {
3074 for (int64_t i = start; i > end; i += step) {
3075 res.push_back(v: Value(i));
3076 }
3077 }
3078 return res;
3079 }));
3080
3081 return std::make_shared<Context>(args: std::move(globals));
3082}
3083
3084inline std::shared_ptr<Context> Context::make(Value && values, const std::shared_ptr<Context> & parent) {
3085 return std::make_shared<Context>(args: values.is_null() ? Value::object() : std::move(values), args: parent);
3086}
3087
3088} // namespace minja
3089