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