MQTTSuite
Loading...
Searching...
No Matches
inja.hpp
Go to the documentation of this file.
1/*
2 ___ _ Version 3.5.0
3 |_ _|_ __ (_) __ _ https://github.com/pantor/inja
4 | || '_ \ | |/ _` | Licensed under the MIT License <http://opensource.org/licenses/MIT>.
5 | || | | || | (_| |
6 |___|_| |_|/ |\__,_| Copyright (c) 2018-2025 Lars Berscheid
7 |__/
8Permission is hereby granted, free of charge, to any person obtaining a copy
9of this software and associated documentation files (the "Software"), to deal
10in the Software without restriction, including without limitation the rights
11to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12copies of the Software, and to permit persons to whom the Software is
13furnished to do so, subject to the following conditions:
14The above copyright notice and this permission notice shall be included in all
15copies or substantial portions of the Software.
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22SOFTWARE.
23*/
24
25#ifndef INCLUDE_INJA_INJA_HPP_
26#define INCLUDE_INJA_INJA_HPP_
27
28// #include "json.hpp"
29#ifndef INCLUDE_INJA_JSON_HPP_
30#define INCLUDE_INJA_JSON_HPP_
31
32#include <nlohmann/json.hpp>
33
34namespace inja {
35#ifndef INJA_DATA_TYPE
36 using json = nlohmann::json;
37#else
38 using json = INJA_DATA_TYPE;
39#endif
40} // namespace inja
41
42#endif // INCLUDE_INJA_JSON_HPP_
43
44// #include "throw.hpp"
45#ifndef INCLUDE_INJA_THROW_HPP_
46#define INCLUDE_INJA_THROW_HPP_
47
48#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(INJA_NOEXCEPTION)
49#ifndef INJA_THROW
50#define INJA_THROW(exception) throw exception
51#endif
52#else
53#include <cstdlib>
54#ifndef INJA_THROW
55#define INJA_THROW(exception)
56 std::abort();
57 std::ignore = exception
58#endif
59#ifndef INJA_NOEXCEPTION
60#define INJA_NOEXCEPTION
61#endif
62#endif
63
64#endif // INCLUDE_INJA_THROW_HPP_
65
66// #include "environment.hpp"
67#ifndef INCLUDE_INJA_ENVIRONMENT_HPP_
68#define INCLUDE_INJA_ENVIRONMENT_HPP_
69
70#include <fstream>
71#include <iostream>
72#include <sstream>
73#include <string>
74#include <string_view>
75
76// #include "json.hpp"
77
78// #include "config.hpp"
79#ifndef INCLUDE_INJA_CONFIG_HPP_
80#define INCLUDE_INJA_CONFIG_HPP_
81
82#include <filesystem>
83#include <functional>
84#include <string>
85
86// #include "template.hpp"
87#ifndef INCLUDE_INJA_TEMPLATE_HPP_
88#define INCLUDE_INJA_TEMPLATE_HPP_
89
90#include <map>
91#include <memory>
92#include <string>
93
94// #include "node.hpp"
95#ifndef INCLUDE_INJA_NODE_HPP_
96#define INCLUDE_INJA_NODE_HPP_
97
98#include <cstddef>
99#include <memory>
100#include <string>
101#include <string_view>
102#include <tuple>
103#include <vector>
104
105// #include "function_storage.hpp"
106#ifndef INCLUDE_INJA_FUNCTION_STORAGE_HPP_
107#define INCLUDE_INJA_FUNCTION_STORAGE_HPP_
108
109#include <functional>
110#include <map>
111#include <string>
112#include <string_view>
113#include <utility>
114#include <vector>
115
116// #include "json.hpp"
117
118namespace inja {
119
120 using Arguments = std::vector<const json*>;
121 using CallbackFunction = std::function<json(Arguments& args)>;
122 using VoidCallbackFunction = std::function<void(Arguments& args)>;
123
124 /*!
125 * \brief Class for builtin functions and user-defined callbacks.
126 */
128 public:
180
182 explicit FunctionData(const Operation& op, const CallbackFunction& cb = CallbackFunction{})
183 : operation(op)
184 , callback(cb) {
185 }
188 };
189
190 private:
191 const int VARIADIC{-1};
192
193 std::map<std::pair<std::string, int>, FunctionData> function_storage = {
194 {std::make_pair("at", 2), FunctionData{Operation::At}},
195 {std::make_pair("capitalize", 1), FunctionData{Operation::Capitalize}},
196 {std::make_pair("default", 2), FunctionData{Operation::Default}},
197 {std::make_pair("divisibleBy", 2), FunctionData{Operation::DivisibleBy}},
198 {std::make_pair("even", 1), FunctionData{Operation::Even}},
199 {std::make_pair("exists", 1), FunctionData{Operation::Exists}},
200 {std::make_pair("existsIn", 2), FunctionData{Operation::ExistsInObject}},
201 {std::make_pair("first", 1), FunctionData{Operation::First}},
202 {std::make_pair("float", 1), FunctionData{Operation::Float}},
203 {std::make_pair("int", 1), FunctionData{Operation::Int}},
204 {std::make_pair("isArray", 1), FunctionData{Operation::IsArray}},
205 {std::make_pair("isBoolean", 1), FunctionData{Operation::IsBoolean}},
206 {std::make_pair("isFloat", 1), FunctionData{Operation::IsFloat}},
207 {std::make_pair("isInteger", 1), FunctionData{Operation::IsInteger}},
208 {std::make_pair("isNumber", 1), FunctionData{Operation::IsNumber}},
209 {std::make_pair("isObject", 1), FunctionData{Operation::IsObject}},
210 {std::make_pair("isString", 1), FunctionData{Operation::IsString}},
211 {std::make_pair("last", 1), FunctionData{Operation::Last}},
212 {std::make_pair("length", 1), FunctionData{Operation::Length}},
213 {std::make_pair("lower", 1), FunctionData{Operation::Lower}},
214 {std::make_pair("max", 1), FunctionData{Operation::Max}},
215 {std::make_pair("min", 1), FunctionData{Operation::Min}},
216 {std::make_pair("odd", 1), FunctionData{Operation::Odd}},
217 {std::make_pair("range", 1), FunctionData{Operation::Range}},
218 {std::make_pair("replace", 3), FunctionData{Operation::Replace}},
219 {std::make_pair("round", 2), FunctionData{Operation::Round}},
220 {std::make_pair("sort", 1), FunctionData{Operation::Sort}},
221 {std::make_pair("upper", 1), FunctionData{Operation::Upper}},
222 {std::make_pair("super", 0), FunctionData{Operation::Super}},
223 {std::make_pair("super", 1), FunctionData{Operation::Super}},
224 {std::make_pair("join", 2), FunctionData{Operation::Join}},
225 };
226
227 public:
228 void add_builtin(std::string_view name, int num_args, Operation op) {
229 function_storage.emplace(std::make_pair(static_cast<std::string>(name), num_args), FunctionData{op});
230 }
231
232 void add_callback(std::string_view name, int num_args, const CallbackFunction& callback) {
233 function_storage.emplace(std::make_pair(static_cast<std::string>(name), num_args), FunctionData{Operation::Callback, callback});
234 }
235
236 FunctionData find_function(std::string_view name, int num_args) const {
237 auto it = function_storage.find(std::make_pair(static_cast<std::string>(name), num_args));
238 if (it != function_storage.end()) {
239 return it->second;
240
241 // Find variadic function
242 } else if (num_args > 0) {
243 it = function_storage.find(std::make_pair(static_cast<std::string>(name), VARIADIC));
244 if (it != function_storage.end()) {
245 return it->second;
246 }
247 }
248
250 }
251 };
252
253} // namespace inja
254
255#endif // INCLUDE_INJA_FUNCTION_STORAGE_HPP_
256
257// #include "utils.hpp"
258#ifndef INCLUDE_INJA_UTILS_HPP_
259#define INCLUDE_INJA_UTILS_HPP_
260
261#include <algorithm>
262#include <cstddef>
263#include <string>
264#include <string_view>
265#include <utility>
266
267// #include "exceptions.hpp"
268#ifndef INCLUDE_INJA_EXCEPTIONS_HPP_
269#define INCLUDE_INJA_EXCEPTIONS_HPP_
270
271#include <cstddef>
272#include <stdexcept>
273#include <string>
274
275namespace inja {
276
278 size_t line;
279 size_t column;
280 };
281
282 struct InjaError : public std::runtime_error {
283 const std::string type;
284 const std::string message;
285
287
288 explicit InjaError(const std::string& type, const std::string& message)
289 : std::runtime_error("[inja.exception." + type + "] " + message)
290 , type(type)
291 , message(message)
292 , location({0, 0}) {
293 }
294
295 explicit InjaError(const std::string& type, const std::string& message, SourceLocation location)
296 : std::runtime_error("[inja.exception." + type + "] (at " + std::to_string(location.line) + ":" +
297 std::to_string(location.column) + ") " + message)
298 , type(type)
299 , message(message)
300 , location(location) {
301 }
302 };
303
304 struct ParserError : public InjaError {
305 explicit ParserError(const std::string& message, SourceLocation location)
306 : InjaError("parser_error", message, location) {
307 }
308 };
309
310 struct RenderError : public InjaError {
311 explicit RenderError(const std::string& message, SourceLocation location)
312 : InjaError("render_error", message, location) {
313 }
314 };
315
316 struct FileError : public InjaError {
317 explicit FileError(const std::string& message)
318 : InjaError("file_error", message) {
319 }
320 explicit FileError(const std::string& message, SourceLocation location)
321 : InjaError("file_error", message, location) {
322 }
323 };
324
325 struct DataError : public InjaError {
326 explicit DataError(const std::string& message, SourceLocation location)
327 : InjaError("data_error", message, location) {
328 }
329 };
330
331} // namespace inja
332
333#endif // INCLUDE_INJA_EXCEPTIONS_HPP_
334
335namespace inja {
336
337 namespace string_view {
338 inline std::string_view slice(std::string_view view, size_t start, size_t end) {
339 start = std::min(start, view.size());
340 end = std::min(std::max(start, end), view.size());
341 return view.substr(start, end - start);
342 }
343
344 inline std::pair<std::string_view, std::string_view> split(std::string_view view, char Separator) {
345 const size_t idx = view.find(Separator);
346 if (idx == std::string_view::npos) {
347 return std::make_pair(view, std::string_view());
348 }
349 return std::make_pair(slice(view, 0, idx), slice(view, idx + 1, std::string_view::npos));
350 }
351
352 inline bool starts_with(std::string_view view, std::string_view prefix) {
353 return (view.size() >= prefix.size() && view.compare(0, prefix.size(), prefix) == 0);
354 }
355 } // namespace string_view
356
357 inline SourceLocation get_source_location(std::string_view content, size_t pos) {
358 // Get line and offset position (starts at 1:1)
359 auto sliced = string_view::slice(content, 0, pos);
360 const std::size_t last_newline = sliced.rfind('\n');
361
362 if (last_newline == std::string_view::npos) {
363 return {1, sliced.length() + 1};
364 }
365
366 // Count newlines
367 size_t count_lines = 0;
368 size_t search_start = 0;
369 while (search_start <= sliced.size()) {
370 search_start = sliced.find('\n', search_start) + 1;
371 if (search_start == 0) {
372 break;
373 }
374 count_lines += 1;
375 }
376
377 return {count_lines + 1, sliced.length() - last_newline};
378 }
379
380 inline void replace_substring(std::string& s, const std::string& f, const std::string& t) {
381 if (f.empty()) {
382 return;
383 }
384 for (auto pos = s.find(f); // find first occurrence of f
385 pos != std::string::npos; // make sure f was found
386 s.replace(pos, f.size(), t), // replace with t, and
387 pos = s.find(f, pos + t.size())) // find next occurrence of f
388 {
389 }
390 }
391
392} // namespace inja
393
394#endif // INCLUDE_INJA_UTILS_HPP_
395
396// #include "json.hpp"
397
398namespace inja {
399
400 class NodeVisitor;
401 class BlockNode;
402 class TextNode;
403 class ExpressionNode;
404 class LiteralNode;
405 class DataNode;
406 class FunctionNode;
407 class ExpressionListNode;
408 class StatementNode;
409 class ForStatementNode;
412 class IfStatementNode;
415 class BlockStatementNode;
416 class SetStatementNode;
417
419 public:
420 virtual ~NodeVisitor() = default;
421
422 virtual void visit(const BlockNode& node) = 0;
423 virtual void visit(const TextNode& node) = 0;
424 virtual void visit(const ExpressionNode& node) = 0;
425 virtual void visit(const LiteralNode& node) = 0;
426 virtual void visit(const DataNode& node) = 0;
427 virtual void visit(const FunctionNode& node) = 0;
428 virtual void visit(const ExpressionListNode& node) = 0;
429 virtual void visit(const StatementNode& node) = 0;
430 virtual void visit(const ForStatementNode& node) = 0;
431 virtual void visit(const ForArrayStatementNode& node) = 0;
432 virtual void visit(const ForObjectStatementNode& node) = 0;
433 virtual void visit(const IfStatementNode& node) = 0;
434 virtual void visit(const IncludeStatementNode& node) = 0;
435 virtual void visit(const ExtendsStatementNode& node) = 0;
436 virtual void visit(const BlockStatementNode& node) = 0;
437 virtual void visit(const SetStatementNode& node) = 0;
438 };
439
440 /*!
441 * \brief Base node class for the abstract syntax tree (AST).
442 */
443 class AstNode {
444 public:
445 virtual void accept(NodeVisitor& v) const = 0;
446
447 size_t pos;
448
449 explicit AstNode(size_t pos)
450 : pos(pos) {
451 }
452 virtual ~AstNode() {
453 }
454 };
455
456 class BlockNode : public AstNode {
457 public:
458 std::vector<std::shared_ptr<AstNode>> nodes;
459
460 explicit BlockNode()
461 : AstNode(0) {
462 }
463
464 void accept(NodeVisitor& v) const override {
465 v.visit(*this);
466 }
467 };
468
469 class TextNode : public AstNode {
470 public:
471 const size_t length;
472
473 explicit TextNode(size_t pos, size_t length)
474 : AstNode(pos)
475 , length(length) {
476 }
477
478 void accept(NodeVisitor& v) const override {
479 v.visit(*this);
480 }
481 };
482
483 class ExpressionNode : public AstNode {
484 public:
485 explicit ExpressionNode(size_t pos)
486 : AstNode(pos) {
487 }
488
489 void accept(NodeVisitor& v) const override {
490 v.visit(*this);
491 }
492 };
493
495 public:
496 const json value;
497
498 explicit LiteralNode(std::string_view data_text, size_t pos)
500 , value(json::parse(data_text)) {
501 }
502
503 void accept(NodeVisitor& v) const override {
504 v.visit(*this);
505 }
506 };
507
508 class DataNode : public ExpressionNode {
509 public:
510 const std::string name;
511 const json::json_pointer ptr;
512
513 static std::string convert_dot_to_ptr(std::string_view ptr_name) {
514 std::string result;
515 do {
516 std::string_view part;
517 std::tie(part, ptr_name) = string_view::split(ptr_name, '.');
518 result.push_back('/');
519 result.append(part.begin(), part.end());
520 } while (!ptr_name.empty());
521 return result;
522 }
523
524 explicit DataNode(std::string_view ptr_name, size_t pos)
526 , name(ptr_name)
527 , ptr(json::json_pointer(convert_dot_to_ptr(ptr_name))) {
528 }
529
530 void accept(NodeVisitor& v) const override {
531 v.visit(*this);
532 }
533 };
534
537
538 public:
539 enum class Associativity {
542 };
543
544 unsigned int precedence;
546
548
549 std::string name;
550 int number_args; // Can also be negative -> -1 for unknown number
551 std::vector<std::shared_ptr<ExpressionNode>> arguments;
553
554 explicit FunctionNode(std::string_view name, size_t pos)
556 , precedence(8)
559 , name(name)
560 , number_args(0) {
561 }
562 explicit FunctionNode(Op operation, size_t pos)
564 , operation(operation)
565 , number_args(1) {
566 switch (operation) {
567 case Op::Not: {
568 number_args = 1;
569 precedence = 4;
571 } break;
572 case Op::And: {
573 number_args = 2;
574 precedence = 1;
576 } break;
577 case Op::Or: {
578 number_args = 2;
579 precedence = 1;
581 } break;
582 case Op::In: {
583 number_args = 2;
584 precedence = 2;
586 } break;
587 case Op::Equal: {
588 number_args = 2;
589 precedence = 2;
591 } break;
592 case Op::NotEqual: {
593 number_args = 2;
594 precedence = 2;
596 } break;
597 case Op::Greater: {
598 number_args = 2;
599 precedence = 2;
601 } break;
602 case Op::GreaterEqual: {
603 number_args = 2;
604 precedence = 2;
606 } break;
607 case Op::Less: {
608 number_args = 2;
609 precedence = 2;
611 } break;
612 case Op::LessEqual: {
613 number_args = 2;
614 precedence = 2;
616 } break;
617 case Op::Add: {
618 number_args = 2;
619 precedence = 3;
621 } break;
622 case Op::Subtract: {
623 number_args = 2;
624 precedence = 3;
626 } break;
627 case Op::Multiplication: {
628 number_args = 2;
629 precedence = 4;
631 } break;
632 case Op::Division: {
633 number_args = 2;
634 precedence = 4;
636 } break;
637 case Op::Power: {
638 number_args = 2;
639 precedence = 5;
641 } break;
642 case Op::Modulo: {
643 number_args = 2;
644 precedence = 4;
646 } break;
647 case Op::AtId: {
648 number_args = 2;
649 precedence = 8;
651 } break;
652 default: {
653 precedence = 1;
655 }
656 }
657 }
658
659 void accept(NodeVisitor& v) const override {
660 v.visit(*this);
661 }
662 };
663
665 public:
666 std::shared_ptr<ExpressionNode> root;
667
669 : AstNode(0) {
670 }
671 explicit ExpressionListNode(size_t pos)
672 : AstNode(pos) {
673 }
674
675 void accept(NodeVisitor& v) const override {
676 v.visit(*this);
677 }
678 };
679
680 class StatementNode : public AstNode {
681 public:
682 explicit StatementNode(size_t pos)
683 : AstNode(pos) {
684 }
685
686 virtual void accept(NodeVisitor& v) const = 0;
687 };
688
690 public:
694
695 explicit ForStatementNode(BlockNode* const parent, size_t pos)
696 : StatementNode(pos)
697 , parent(parent) {
698 }
699
700 virtual void accept(NodeVisitor& v) const = 0;
701 };
702
704 public:
705 const std::string value;
706
707 explicit ForArrayStatementNode(const std::string& value, BlockNode* const parent, size_t pos)
708 : ForStatementNode(parent, pos)
709 , value(value) {
710 }
711
712 void accept(NodeVisitor& v) const override {
713 v.visit(*this);
714 }
715 };
716
718 public:
719 const std::string key;
720 const std::string value;
721
722 explicit ForObjectStatementNode(const std::string& key, const std::string& value, BlockNode* const parent, size_t pos)
723 : ForStatementNode(parent, pos)
724 , key(key)
725 , value(value) {
726 }
727
728 void accept(NodeVisitor& v) const override {
729 v.visit(*this);
730 }
731 };
732
734 public:
739
740 const bool is_nested;
742
743 explicit IfStatementNode(BlockNode* const parent, size_t pos)
744 : StatementNode(pos)
745 , parent(parent)
746 , is_nested(false) {
747 }
748 explicit IfStatementNode(bool is_nested, BlockNode* const parent, size_t pos)
749 : StatementNode(pos)
750 , parent(parent)
751 , is_nested(is_nested) {
752 }
753
754 void accept(NodeVisitor& v) const override {
755 v.visit(*this);
756 }
757 };
758
760 public:
761 const std::string file;
762
763 explicit IncludeStatementNode(const std::string& file, size_t pos)
764 : StatementNode(pos)
765 , file(file) {
766 }
767
768 void accept(NodeVisitor& v) const override {
769 v.visit(*this);
770 }
771 };
772
774 public:
775 const std::string file;
776
777 explicit ExtendsStatementNode(const std::string& file, size_t pos)
778 : StatementNode(pos)
779 , file(file) {
780 }
781
782 void accept(NodeVisitor& v) const override {
783 v.visit(*this);
784 }
785 };
786
788 public:
789 const std::string name;
792
793 explicit BlockStatementNode(BlockNode* const parent, const std::string& name, size_t pos)
794 : StatementNode(pos)
795 , name(name)
796 , parent(parent) {
797 }
798
799 void accept(NodeVisitor& v) const override {
800 v.visit(*this);
801 }
802 };
803
805 public:
806 const std::string key;
808
809 explicit SetStatementNode(const std::string& key, size_t pos)
810 : StatementNode(pos)
811 , key(key) {
812 }
813
814 void accept(NodeVisitor& v) const override {
815 v.visit(*this);
816 }
817 };
818
819} // namespace inja
820
821#endif // INCLUDE_INJA_NODE_HPP_
822
823// #include "statistics.hpp"
824#ifndef INCLUDE_INJA_STATISTICS_HPP_
825#define INCLUDE_INJA_STATISTICS_HPP_
826
827// #include "node.hpp"
828
829namespace inja {
830
831 /*!
832 * \brief A class for counting statistics on a Template.
833 */
835 void visit(const BlockNode& node) override {
836 for (const auto& n : node.nodes) {
837 n->accept(*this);
838 }
839 }
840
841 void visit(const TextNode&) override {
842 }
843 void visit(const ExpressionNode&) override {
844 }
845 void visit(const LiteralNode&) override {
846 }
847
848 void visit(const DataNode&) override {
849 variable_counter += 1;
850 }
851
852 void visit(const FunctionNode& node) override {
853 for (const auto& n : node.arguments) {
854 n->accept(*this);
855 }
856 }
857
858 void visit(const ExpressionListNode& node) override {
859 node.root->accept(*this);
860 }
861
862 void visit(const StatementNode&) override {
863 }
864 void visit(const ForStatementNode&) override {
865 }
866
867 void visit(const ForArrayStatementNode& node) override {
868 node.condition.accept(*this);
869 node.body.accept(*this);
870 }
871
872 void visit(const ForObjectStatementNode& node) override {
873 node.condition.accept(*this);
874 node.body.accept(*this);
875 }
876
877 void visit(const IfStatementNode& node) override {
878 node.condition.accept(*this);
881 }
882
883 void visit(const IncludeStatementNode&) override {
884 }
885
886 void visit(const ExtendsStatementNode&) override {
887 }
888
889 void visit(const BlockStatementNode& node) override {
890 node.block.accept(*this);
891 }
892
893 void visit(const SetStatementNode&) override {
894 }
895
896 public:
898
899 explicit StatisticsVisitor() {
900 }
901 };
902
903} // namespace inja
904
905#endif // INCLUDE_INJA_STATISTICS_HPP_
906
907namespace inja {
908
909 /*!
910 * \brief The main inja Template.
911 */
912 struct Template {
914 std::string content;
915 std::map<std::string, std::shared_ptr<BlockStatementNode>> block_storage;
916
917 explicit Template() {
918 }
919 explicit Template(std::string content)
920 : content(std::move(content)) {
921 }
922
923 /// Return number of variables (total number, not distinct ones) in the template
924 size_t count_variables() const {
925 auto statistic_visitor = StatisticsVisitor();
926 root.accept(statistic_visitor);
927 return statistic_visitor.variable_counter;
928 }
929 };
930
931 using TemplateStorage = std::map<std::string, Template>;
932
933} // namespace inja
934
935#endif // INCLUDE_INJA_TEMPLATE_HPP_
936
937namespace inja {
938
939 /*!
940 * \brief Class for lexer configuration.
941 */
942 struct LexerConfig {
943 std::string statement_open{"{%"};
944 std::string statement_open_no_lstrip{"{%+"};
945 std::string statement_open_force_lstrip{"{%-"};
946 std::string statement_close{"%}"};
948 std::string line_statement{"##"};
949 std::string expression_open{"{{"};
951 std::string expression_close{"}}"};
953 std::string comment_open{"{#"};
954 std::string comment_open_force_lstrip{"{#-"};
955 std::string comment_close{"#}"};
956 std::string comment_close_force_rstrip{"-#}"};
957 std::string open_chars{"#{"};
958
959 bool trim_blocks{false};
960 bool lstrip_blocks{false};
961
963 open_chars = "";
964 if (open_chars.find(line_statement[0]) == std::string::npos) {
966 }
967 if (open_chars.find(statement_open[0]) == std::string::npos) {
969 }
970 if (open_chars.find(statement_open_no_lstrip[0]) == std::string::npos) {
972 }
973 if (open_chars.find(statement_open_force_lstrip[0]) == std::string::npos) {
975 }
976 if (open_chars.find(expression_open[0]) == std::string::npos) {
978 }
979 if (open_chars.find(expression_open_force_lstrip[0]) == std::string::npos) {
981 }
982 if (open_chars.find(comment_open[0]) == std::string::npos) {
984 }
985 if (open_chars.find(comment_open_force_lstrip[0]) == std::string::npos) {
987 }
988 }
989 };
990
991 /*!
992 * \brief Class for parser configuration.
993 */
996
997 std::function<Template(const std::filesystem::path&, const std::string&)> include_callback;
998 };
999
1000 /*!
1001 * \brief Class for render configuration.
1002 */
1005 bool html_autoescape{false};
1006 };
1007
1008} // namespace inja
1009
1010#endif // INCLUDE_INJA_CONFIG_HPP_
1011
1012// #include "function_storage.hpp"
1013
1014// #include "parser.hpp"
1015#ifndef INCLUDE_INJA_PARSER_HPP_
1016#define INCLUDE_INJA_PARSER_HPP_
1017
1018#include <cstddef>
1019#include <filesystem>
1020#include <fstream>
1021#include <iterator>
1022#include <memory>
1023#include <stack>
1024#include <string>
1025#include <string_view>
1026#include <utility>
1027#include <vector>
1028
1029// #include "config.hpp"
1030
1031// #include "exceptions.hpp"
1032
1033// #include "function_storage.hpp"
1034
1035// #include "lexer.hpp"
1036#ifndef INCLUDE_INJA_LEXER_HPP_
1037#define INCLUDE_INJA_LEXER_HPP_
1038
1039#include <cctype>
1040#include <cstddef>
1041#include <string_view>
1042
1043// #include "config.hpp"
1044
1045// #include "exceptions.hpp"
1046
1047// #include "token.hpp"
1048#ifndef INCLUDE_INJA_TOKEN_HPP_
1049#define INCLUDE_INJA_TOKEN_HPP_
1050
1051#include <string>
1052#include <string_view>
1053
1054namespace inja {
1055
1056 /*!
1057 * \brief Helper-class for the inja Lexer.
1058 */
1059 struct Token {
1098
1100 std::string_view text;
1101
1102 explicit constexpr Token() = default;
1103 explicit constexpr Token(Kind kind, std::string_view text)
1104 : kind(kind)
1105 , text(text) {
1106 }
1107
1108 std::string describe() const {
1109 switch (kind) {
1110 case Kind::Text:
1111 return "<text>";
1113 return "<eol>";
1114 case Kind::Eof:
1115 return "<eof>";
1116 default:
1117 return static_cast<std::string>(text);
1118 }
1119 }
1120 };
1121
1122} // namespace inja
1123
1124#endif // INCLUDE_INJA_TOKEN_HPP_
1125
1126// #include "utils.hpp"
1127
1128namespace inja {
1129
1130 /*!
1131 * \brief Class for lexing an inja Template.
1132 */
1133 class Lexer {
1149
1150 enum class MinusState {
1153 };
1154
1156
1159 std::string_view m_in;
1161 size_t pos;
1162
1163 Token
1164 scan_body(std::string_view close, Token::Kind closeKind, std::string_view close_trim = std::string_view(), bool trim = false) {
1165 again:
1166 // skip whitespace (except for \n as it might be a close)
1167 if (tok_start >= m_in.size()) {
1169 }
1170 const char ch = m_in[tok_start];
1171 if (ch == ' ' || ch == '\t' || ch == '\r') {
1172 tok_start += 1;
1173 goto again;
1174 }
1175
1176 // check for close
1177 if (!close_trim.empty() && inja::string_view::starts_with(m_in.substr(tok_start), close_trim)) {
1179 pos = tok_start + close_trim.size();
1180 const Token tok = make_token(closeKind);
1182 return tok;
1183 }
1184
1187 pos = tok_start + close.size();
1188 const Token tok = make_token(closeKind);
1189 if (trim) {
1191 }
1192 return tok;
1193 }
1194
1195 // skip \n
1196 if (ch == '\n') {
1197 tok_start += 1;
1198 goto again;
1199 }
1200
1201 pos = tok_start + 1;
1202 if (std::isalpha(ch)) {
1204 return scan_id();
1205 }
1206
1207 const MinusState current_minus_state = minus_state;
1210 }
1211
1212 switch (ch) {
1213 case '+':
1215 case '-':
1216 if (current_minus_state == MinusState::Operator) {
1218 }
1219 return scan_number();
1220 case '*':
1222 case '/':
1224 case '^':
1226 case '%':
1228 case '.':
1230 case ',':
1232 case ':':
1234 case '|':
1236 case '(':
1238 case ')':
1241 case '[':
1243 case ']':
1246 case '{':
1248 case '}':
1251 case '>':
1252 if (pos < m_in.size() && m_in[pos] == '=') {
1253 pos += 1;
1255 }
1257 case '<':
1258 if (pos < m_in.size() && m_in[pos] == '=') {
1259 pos += 1;
1261 }
1263 case '=':
1264 if (pos < m_in.size() && m_in[pos] == '=') {
1265 pos += 1;
1267 }
1269 case '!':
1270 if (pos < m_in.size() && m_in[pos] == '=') {
1271 pos += 1;
1273 }
1275 case '\"':
1276 return scan_string();
1277 case '0':
1278 case '1':
1279 case '2':
1280 case '3':
1281 case '4':
1282 case '5':
1283 case '6':
1284 case '7':
1285 case '8':
1286 case '9':
1288 return scan_number();
1289 case '_':
1290 case '@':
1291 case '$':
1293 return scan_id();
1294 default:
1296 }
1297 }
1298
1300 for (;;) {
1301 if (pos >= m_in.size()) {
1302 break;
1303 }
1304 const char ch = m_in[pos];
1305 if (!std::isalnum(ch) && ch != '.' && ch != '/' && ch != '_' && ch != '-') {
1306 break;
1307 }
1308 pos += 1;
1309 }
1311 }
1312
1314 for (;;) {
1315 if (pos >= m_in.size()) {
1316 break;
1317 }
1318 const char ch = m_in[pos];
1319 // be very permissive in lexer (we'll catch errors when conversion happens)
1320 if (!(std::isdigit(ch) || ch == '.' || ch == 'e' || ch == 'E' ||
1321 (ch == '+' && (pos == 0 || m_in[pos - 1] == 'e' || m_in[pos - 1] == 'E')) ||
1322 (ch == '-' && (pos == 0 || m_in[pos - 1] == 'e' || m_in[pos - 1] == 'E')))) {
1323 break;
1324 }
1325 pos += 1;
1326 }
1328 }
1329
1331 bool escape{false};
1332 for (;;) {
1333 if (pos >= m_in.size()) {
1334 break;
1335 }
1336 const char ch = m_in[pos++];
1337 if (ch == '\\') {
1338 escape = !escape;
1339 } else if (!escape && ch == m_in[tok_start]) {
1340 break;
1341 } else {
1342 escape = false;
1343 }
1344 }
1346 }
1347
1351
1353 if (pos < m_in.size()) {
1354 while (pos < m_in.size() && (m_in[pos] == ' ' || m_in[pos] == '\t' || m_in[pos] == '\n' || m_in[pos] == '\r')) {
1355 pos += 1;
1356 }
1357 }
1358 }
1359
1361 if (pos < m_in.size()) {
1362 while (pos < m_in.size() && (m_in[pos] == ' ' || m_in[pos] == '\t')) {
1363 pos += 1;
1364 }
1365 }
1366
1367 if (pos < m_in.size()) {
1368 const char ch = m_in[pos];
1369 if (ch == '\n') {
1370 pos += 1;
1371 } else if (ch == '\r') {
1372 pos += 1;
1373 if (pos < m_in.size() && m_in[pos] == '\n') {
1374 pos += 1;
1375 }
1376 }
1377 }
1378 }
1379
1380 static std::string_view clear_final_line_if_whitespace(std::string_view text) {
1381 std::string_view result = text;
1382 while (!result.empty()) {
1383 const char ch = result.back();
1384 if (ch == ' ' || ch == '\t') {
1385 result.remove_suffix(1);
1386 } else if (ch == '\n' || ch == '\r') {
1387 break;
1388 } else {
1389 return text;
1390 }
1391 }
1392 return result;
1393 }
1394
1395 public:
1396 explicit Lexer(const LexerConfig& config)
1397 : config(config)
1400 , tok_start(0)
1401 , pos(0) {
1402 }
1403
1407
1408 void start(std::string_view input) {
1409 m_in = input;
1410 tok_start = 0;
1411 pos = 0;
1414
1415 // Consume byte order mark (BOM) for UTF-8
1416 if (inja::string_view::starts_with(m_in, "\xEF\xBB\xBF")) {
1417 m_in = m_in.substr(3);
1418 }
1419 }
1420
1422 tok_start = pos;
1423
1424 again:
1425 if (tok_start >= m_in.size()) {
1427 }
1428
1429 switch (state) {
1430 default:
1431 case State::Text: {
1432 // fast-scan to first open character
1433 const size_t open_start = m_in.substr(pos).find_first_of(config.open_chars);
1434 if (open_start == std::string_view::npos) {
1435 // didn't find open, return remaining text as text token
1436 pos = m_in.size();
1438 }
1439 pos += open_start;
1440
1441 // try to match one of the opening sequences, and get the close
1442 const std::string_view open_str = m_in.substr(pos);
1443 bool must_lstrip = false;
1447 must_lstrip = true;
1448 } else {
1450 }
1456 must_lstrip = true;
1457 } else {
1459 must_lstrip = config.lstrip_blocks;
1460 }
1464 must_lstrip = true;
1465 } else {
1467 must_lstrip = config.lstrip_blocks;
1468 }
1469 } else if ((pos == 0 || m_in[pos - 1] == '\n') && inja::string_view::starts_with(open_str, config.line_statement)) {
1471 } else {
1472 pos += 1; // wasn't actually an opening sequence
1473 goto again;
1474 }
1475
1476 std::string_view text = string_view::slice(m_in, tok_start, pos);
1477 if (must_lstrip) {
1479 }
1480
1481 if (text.empty()) {
1482 goto again; // don't generate empty token
1483 }
1484 return Token(Token::Kind::Text, text);
1485 }
1488 pos += config.expression_open.size();
1490 }
1495 }
1496 case State::LineStart: {
1498 pos += config.line_statement.size();
1500 }
1501 case State::StatementStart: {
1503 pos += config.statement_open.size();
1505 }
1510 }
1515 }
1516 case State::CommentStart: {
1518 pos += config.comment_open.size();
1520 }
1525 }
1528 case State::LineBody:
1531 return scan_body(
1533 case State::CommentBody: {
1534 // fast-scan to comment close
1535 const size_t end = m_in.substr(pos).find(config.comment_close);
1536 if (end == std::string_view::npos) {
1537 pos = m_in.size();
1539 }
1540
1541 // Check for trim pattern
1542 const bool must_rstrip = inja::string_view::starts_with(m_in.substr(pos + end - 1), config.comment_close_force_rstrip);
1543
1544 // return the entire comment in the close token
1546 pos += end + config.comment_close.size();
1548
1549 if (must_rstrip || config.trim_blocks) {
1551 }
1552 return tok;
1553 }
1554 }
1555 }
1556
1557 const LexerConfig& get_config() const {
1558 return config;
1559 }
1560 };
1561
1562} // namespace inja
1563
1564#endif // INCLUDE_INJA_LEXER_HPP_
1565
1566// #include "node.hpp"
1567
1568// #include "template.hpp"
1569
1570// #include "throw.hpp"
1571
1572// #include "token.hpp"
1573
1574namespace inja {
1575
1576 /*!
1577 * \brief Class for parsing an inja Template.
1578 */
1579 class Parser {
1580 using Arguments = std::vector<std::shared_ptr<ExpressionNode>>;
1581 using OperatorStack = std::stack<std::shared_ptr<FunctionNode>>;
1582
1584
1588
1590 bool have_peek_tok{false};
1591
1592 std::string_view literal_start;
1593
1596
1600
1601 void throw_parser_error(const std::string& message) const {
1603 }
1604
1606 if (have_peek_tok) {
1607 tok = peek_tok;
1608 have_peek_tok = false;
1609 } else {
1610 tok = lexer.scan();
1611 }
1612 }
1613
1615 if (!have_peek_tok) {
1617 have_peek_tok = true;
1618 }
1619 }
1620
1621 void add_literal(Arguments& arguments, const char* content_ptr) {
1622 const char* begin = literal_start.data();
1623 const char* end = tok.text.data() + tok.text.size();
1624
1625 const std::string_view data_text(begin, static_cast<std::size_t>(end - begin));
1626 arguments.emplace_back(std::make_shared<LiteralNode>(data_text, data_text.data() - content_ptr));
1627 }
1628
1629 void add_operator(Arguments& arguments, OperatorStack& operator_stack) {
1630 auto function = operator_stack.top();
1631 operator_stack.pop();
1632
1633 if (static_cast<int>(arguments.size()) < function->number_args) {
1634 throw_parser_error("too few arguments");
1635 }
1636
1637 for (int i = 0; i < function->number_args; ++i) {
1638 function->arguments.insert(function->arguments.begin(), arguments.back());
1639 arguments.pop_back();
1640 }
1641 arguments.emplace_back(function);
1642 }
1643
1644 void add_to_template_storage(const std::filesystem::path& path, std::string& template_name) {
1645 if (template_storage.find(template_name) != template_storage.end()) {
1646 return;
1647 }
1648
1649 const std::string original_name = template_name;
1650
1652 // Build the relative path
1653 template_name = (path / original_name).string();
1654 if (template_name.compare(0, 2, "./") == 0) {
1655 template_name.erase(0, 2);
1656 }
1657
1658 if (template_storage.find(template_name) == template_storage.end()) {
1659 // Load file
1660 std::ifstream file;
1661 file.open(template_name);
1662 if (!file.fail()) {
1663 const std::string text((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
1664
1665 auto include_template = Template(text);
1666 template_storage.emplace(template_name, include_template);
1667 parse_into_template(template_storage[template_name], template_name);
1668 return;
1669 } else if (!config.include_callback) {
1670 INJA_THROW(FileError("failed accessing file at '" + template_name + "'"));
1671 }
1672 }
1673 }
1674
1675 // Try include callback
1677 auto include_template = config.include_callback(path, original_name);
1678 template_storage.emplace(template_name, include_template);
1679 }
1680 }
1681
1682 std::string parse_filename() const {
1684 throw_parser_error("expected string, got '" + tok.describe() + "'");
1685 }
1686
1687 if (tok.text.length() < 2) {
1688 throw_parser_error("expected filename, got '" + static_cast<std::string>(tok.text) + "'");
1689 }
1690
1691 // Remove first and last character ""
1692 return std::string{tok.text.substr(1, tok.text.length() - 2)};
1693 }
1694
1695 bool parse_expression(Template& tmpl, Token::Kind closing) {
1697 return tok.kind == closing;
1698 }
1699
1700 std::shared_ptr<ExpressionNode> parse_expression(Template& tmpl) {
1701 size_t current_bracket_level{0};
1702 size_t current_brace_level{0};
1703 Arguments arguments;
1704 OperatorStack operator_stack;
1705
1706 while (tok.kind != Token::Kind::Eof) {
1707 // Literals
1708 switch (tok.kind) {
1709 case Token::Kind::String: {
1710 if (current_brace_level == 0 && current_bracket_level == 0) {
1712 add_literal(arguments, tmpl.content.c_str());
1713 }
1714 } break;
1715 case Token::Kind::Number: {
1716 if (current_brace_level == 0 && current_bracket_level == 0) {
1718 add_literal(arguments, tmpl.content.c_str());
1719 }
1720 } break;
1722 if (current_brace_level == 0 && current_bracket_level == 0) {
1724 }
1725 current_bracket_level += 1;
1726 } break;
1728 if (current_brace_level == 0 && current_bracket_level == 0) {
1730 }
1731 current_brace_level += 1;
1732 } break;
1734 if (current_bracket_level == 0) {
1735 throw_parser_error("unexpected ']'");
1736 }
1737
1738 current_bracket_level -= 1;
1739 if (current_brace_level == 0 && current_bracket_level == 0) {
1740 add_literal(arguments, tmpl.content.c_str());
1741 }
1742 } break;
1744 if (current_brace_level == 0) {
1745 throw_parser_error("unexpected '}'");
1746 }
1747
1748 current_brace_level -= 1;
1749 if (current_brace_level == 0 && current_bracket_level == 0) {
1750 add_literal(arguments, tmpl.content.c_str());
1751 }
1752 } break;
1753 case Token::Kind::Id: {
1755
1756 // Data Literal
1757 if (tok.text == static_cast<decltype(tok.text)>("true") || tok.text == static_cast<decltype(tok.text)>("false") ||
1758 tok.text == static_cast<decltype(tok.text)>("null")) {
1759 if (current_brace_level == 0 && current_bracket_level == 0) {
1761 add_literal(arguments, tmpl.content.c_str());
1762 }
1763
1764 // Operator
1765 } else if (tok.text == "and" || tok.text == "or" || tok.text == "in" || tok.text == "not") {
1766 goto parse_operator;
1767
1768 // Functions
1769 } else if (peek_tok.kind == Token::Kind::LeftParen) {
1770 auto func = std::make_shared<FunctionNode>(tok.text, tok.text.data() - tmpl.content.c_str());
1772 do {
1774 auto expr = parse_expression(tmpl);
1775 if (!expr) {
1776 break;
1777 }
1778 func->number_args += 1;
1779 func->arguments.emplace_back(expr);
1780 } while (tok.kind == Token::Kind::Comma);
1782 throw_parser_error("expected right parenthesis, got '" + tok.describe() + "'");
1783 }
1784
1785 auto function_data = function_storage.find_function(func->name, func->number_args);
1786 if (function_data.operation == FunctionStorage::Operation::None) {
1787 throw_parser_error("unknown function " + func->name);
1788 }
1789 func->operation = function_data.operation;
1791 func->callback = function_data.callback;
1792 }
1793 arguments.emplace_back(func);
1794
1795 // Variables
1796 } else {
1797 arguments.emplace_back(
1798 std::make_shared<DataNode>(static_cast<std::string>(tok.text), tok.text.data() - tmpl.content.c_str()));
1799 }
1800
1801 // Operators
1802 } break;
1803 case Token::Kind::Equal:
1809 case Token::Kind::Plus:
1810 case Token::Kind::Minus:
1811 case Token::Kind::Times:
1812 case Token::Kind::Slash:
1813 case Token::Kind::Power:
1815 case Token::Kind::Dot: {
1816 parse_operator:
1817 FunctionStorage::Operation operation;
1818 switch (tok.kind) {
1819 case Token::Kind::Id: {
1820 if (tok.text == "and") {
1822 } else if (tok.text == "or") {
1824 } else if (tok.text == "in") {
1826 } else if (tok.text == "not") {
1828 } else {
1829 throw_parser_error("unknown operator in parser.");
1830 }
1831 } break;
1832 case Token::Kind::Equal: {
1834 } break;
1835 case Token::Kind::NotEqual: {
1837 } break;
1840 } break;
1843 } break;
1844 case Token::Kind::LessThan: {
1846 } break;
1849 } break;
1850 case Token::Kind::Plus: {
1852 } break;
1853 case Token::Kind::Minus: {
1855 } break;
1856 case Token::Kind::Times: {
1858 } break;
1859 case Token::Kind::Slash: {
1861 } break;
1862 case Token::Kind::Power: {
1864 } break;
1865 case Token::Kind::Percent: {
1867 } break;
1868 case Token::Kind::Dot: {
1870 } break;
1871 default: {
1872 throw_parser_error("unknown operator in parser.");
1873 }
1874 }
1875 auto function_node = std::make_shared<FunctionNode>(operation, tok.text.data() - tmpl.content.c_str());
1876
1877 while (!operator_stack.empty() && ((operator_stack.top()->precedence > function_node->precedence) ||
1878 (operator_stack.top()->precedence == function_node->precedence &&
1880 add_operator(arguments, operator_stack);
1881 }
1882
1883 operator_stack.emplace(function_node);
1884 } break;
1885 case Token::Kind::Comma: {
1886 if (current_brace_level == 0 && current_bracket_level == 0) {
1887 goto break_loop;
1888 }
1889 } break;
1890 case Token::Kind::Colon: {
1891 if (current_brace_level == 0 && current_bracket_level == 0) {
1892 throw_parser_error("unexpected ':'");
1893 }
1894 } break;
1897 auto expr = parse_expression(tmpl);
1899 throw_parser_error("expected right parenthesis, got '" + tok.describe() + "'");
1900 }
1901 if (!expr) {
1902 throw_parser_error("empty expression in parentheses");
1903 }
1904 arguments.emplace_back(expr);
1905 } break;
1906
1907 // parse function call pipe syntax
1908 case Token::Kind::Pipe: {
1909 // get function name
1911 if (tok.kind != Token::Kind::Id) {
1912 throw_parser_error("expected function name, got '" + tok.describe() + "'");
1913 }
1914 auto func = std::make_shared<FunctionNode>(tok.text, tok.text.data() - tmpl.content.c_str());
1915 // add first parameter as last value from arguments
1916 func->number_args += 1;
1917 func->arguments.emplace_back(arguments.back());
1918 arguments.pop_back();
1922 // parse additional parameters
1923 do {
1925 auto expr = parse_expression(tmpl);
1926 if (!expr) {
1927 break;
1928 }
1929 func->number_args += 1;
1930 func->arguments.emplace_back(expr);
1931 } while (tok.kind == Token::Kind::Comma);
1933 throw_parser_error("expected right parenthesis, got '" + tok.describe() + "'");
1934 }
1935 }
1936 // search store for defined function with such name and number of args
1937 auto function_data = function_storage.find_function(func->name, func->number_args);
1938 if (function_data.operation == FunctionStorage::Operation::None) {
1939 throw_parser_error("unknown function " + func->name);
1940 }
1941 func->operation = function_data.operation;
1943 func->callback = function_data.callback;
1944 }
1945 arguments.emplace_back(func);
1946 } break;
1947 default:
1948 goto break_loop;
1949 }
1950
1952 }
1953
1954 break_loop:
1955 while (!operator_stack.empty()) {
1956 add_operator(arguments, operator_stack);
1957 }
1958
1959 std::shared_ptr<ExpressionNode> expr;
1960 if (arguments.size() == 1) {
1961 expr = arguments[0];
1962 arguments = {};
1963 } else if (arguments.size() > 1) {
1964 throw_parser_error("malformed expression");
1965 }
1966 return expr;
1967 }
1968
1969 bool parse_statement(Template& tmpl, Token::Kind closing, const std::filesystem::path& path) {
1970 if (tok.kind != Token::Kind::Id) {
1971 return false;
1972 }
1973
1974 if (tok.text == static_cast<decltype(tok.text)>("if")) {
1976
1977 auto if_statement_node = std::make_shared<IfStatementNode>(current_block, tok.text.data() - tmpl.content.c_str());
1978 current_block->nodes.emplace_back(if_statement_node);
1979 if_statement_stack.emplace(if_statement_node.get());
1980 current_block = &if_statement_node->true_statement;
1981 current_expression_list = &if_statement_node->condition;
1982
1983 if (!parse_expression(tmpl, closing)) {
1984 return false;
1985 }
1986 } else if (tok.text == static_cast<decltype(tok.text)>("else")) {
1987 if (if_statement_stack.empty()) {
1988 throw_parser_error("else without matching if");
1989 }
1990 auto& if_statement_data = if_statement_stack.top();
1992
1993 if_statement_data->has_false_statement = true;
1994 current_block = &if_statement_data->false_statement;
1995
1996 // Chained else if
1997 if (tok.kind == Token::Kind::Id && tok.text == static_cast<decltype(tok.text)>("if")) {
1999
2000 auto if_statement_node = std::make_shared<IfStatementNode>(true, current_block, tok.text.data() - tmpl.content.c_str());
2001 current_block->nodes.emplace_back(if_statement_node);
2002 if_statement_stack.emplace(if_statement_node.get());
2003 current_block = &if_statement_node->true_statement;
2004 current_expression_list = &if_statement_node->condition;
2005
2006 if (!parse_expression(tmpl, closing)) {
2007 return false;
2008 }
2009 }
2010 } else if (tok.text == static_cast<decltype(tok.text)>("endif")) {
2011 if (if_statement_stack.empty()) {
2012 throw_parser_error("endif without matching if");
2013 }
2014
2015 // Nested if statements
2016 while (if_statement_stack.top()->is_nested) {
2017 if_statement_stack.pop();
2018 }
2019
2020 auto& if_statement_data = if_statement_stack.top();
2022
2023 current_block = if_statement_data->parent;
2024 if_statement_stack.pop();
2025 } else if (tok.text == static_cast<decltype(tok.text)>("block")) {
2027
2028 if (tok.kind != Token::Kind::Id) {
2029 throw_parser_error("expected block name, got '" + tok.describe() + "'");
2030 }
2031
2032 const std::string block_name = static_cast<std::string>(tok.text);
2033
2034 auto block_statement_node =
2035 std::make_shared<BlockStatementNode>(current_block, block_name, tok.text.data() - tmpl.content.c_str());
2036 current_block->nodes.emplace_back(block_statement_node);
2037 block_statement_stack.emplace(block_statement_node.get());
2038 current_block = &block_statement_node->block;
2039 auto success = tmpl.block_storage.emplace(block_name, block_statement_node);
2040 if (!success.second) {
2041 throw_parser_error("block with the name '" + block_name + "' does already exist");
2042 }
2043
2045 } else if (tok.text == static_cast<decltype(tok.text)>("endblock")) {
2046 if (block_statement_stack.empty()) {
2047 throw_parser_error("endblock without matching block");
2048 }
2049
2050 auto& block_statement_data = block_statement_stack.top();
2052
2053 current_block = block_statement_data->parent;
2055 } else if (tok.text == static_cast<decltype(tok.text)>("for")) {
2057
2058 // options: for a in arr; for a, b in obj
2059 if (tok.kind != Token::Kind::Id) {
2060 throw_parser_error("expected id, got '" + tok.describe() + "'");
2061 }
2062
2063 Token value_token = tok;
2065
2066 // Object type
2067 std::shared_ptr<ForStatementNode> for_statement_node;
2070 if (tok.kind != Token::Kind::Id) {
2071 throw_parser_error("expected id, got '" + tok.describe() + "'");
2072 }
2073
2074 const Token key_token = value_token;
2075 value_token = tok;
2077
2078 for_statement_node = std::make_shared<ForObjectStatementNode>(static_cast<std::string>(key_token.text),
2079 static_cast<std::string>(value_token.text),
2081 tok.text.data() - tmpl.content.c_str());
2082
2083 // Array type
2084 } else {
2085 for_statement_node = std::make_shared<ForArrayStatementNode>(
2086 static_cast<std::string>(value_token.text), current_block, tok.text.data() - tmpl.content.c_str());
2087 }
2088
2089 current_block->nodes.emplace_back(for_statement_node);
2090 for_statement_stack.emplace(for_statement_node.get());
2091 current_block = &for_statement_node->body;
2092 current_expression_list = &for_statement_node->condition;
2093
2094 if (tok.kind != Token::Kind::Id || tok.text != static_cast<decltype(tok.text)>("in")) {
2095 throw_parser_error("expected 'in', got '" + tok.describe() + "'");
2096 }
2098
2099 if (!parse_expression(tmpl, closing)) {
2100 return false;
2101 }
2102 } else if (tok.text == static_cast<decltype(tok.text)>("endfor")) {
2103 if (for_statement_stack.empty()) {
2104 throw_parser_error("endfor without matching for");
2105 }
2106
2107 auto& for_statement_data = for_statement_stack.top();
2109
2110 current_block = for_statement_data->parent;
2111 for_statement_stack.pop();
2112 } else if (tok.text == static_cast<decltype(tok.text)>("include")) {
2114
2115 std::string template_name = parse_filename();
2116 add_to_template_storage(path, template_name);
2117
2118 current_block->nodes.emplace_back(
2119 std::make_shared<IncludeStatementNode>(template_name, tok.text.data() - tmpl.content.c_str()));
2120
2122 } else if (tok.text == static_cast<decltype(tok.text)>("extends")) {
2124
2125 std::string template_name = parse_filename();
2126 add_to_template_storage(path, template_name);
2127
2128 current_block->nodes.emplace_back(
2129 std::make_shared<ExtendsStatementNode>(template_name, tok.text.data() - tmpl.content.c_str()));
2130
2132 } else if (tok.text == static_cast<decltype(tok.text)>("set")) {
2134
2135 if (tok.kind != Token::Kind::Id) {
2136 throw_parser_error("expected variable name, got '" + tok.describe() + "'");
2137 }
2138
2139 const std::string key = static_cast<std::string>(tok.text);
2141
2142 auto set_statement_node = std::make_shared<SetStatementNode>(key, tok.text.data() - tmpl.content.c_str());
2143 current_block->nodes.emplace_back(set_statement_node);
2144 current_expression_list = &set_statement_node->expression;
2145
2146 if (tok.text != static_cast<decltype(tok.text)>("=")) {
2147 throw_parser_error("expected '=', got '" + tok.describe() + "'");
2148 }
2150
2151 if (!parse_expression(tmpl, closing)) {
2152 return false;
2153 }
2154 } else {
2155 return false;
2156 }
2157 return true;
2158 }
2159
2160 void parse_into(Template& tmpl, const std::filesystem::path& path) {
2162 current_block = &tmpl.root;
2163
2164 for (;;) {
2166 switch (tok.kind) {
2167 case Token::Kind::Eof: {
2168 if (!if_statement_stack.empty()) {
2169 throw_parser_error("unmatched if");
2170 }
2171 if (!for_statement_stack.empty()) {
2172 throw_parser_error("unmatched for");
2173 }
2174 }
2175 current_block = nullptr;
2176 return;
2177 case Token::Kind::Text: {
2178 current_block->nodes.emplace_back(
2179 std::make_shared<TextNode>(tok.text.data() - tmpl.content.c_str(), tok.text.size()));
2180 } break;
2184 throw_parser_error("expected statement, got '" + tok.describe() + "'");
2185 }
2187 throw_parser_error("expected statement close, got '" + tok.describe() + "'");
2188 }
2189 } break;
2193 throw_parser_error("expected statement, got '" + tok.describe() + "'");
2194 }
2196 throw_parser_error("expected line statement close, got '" + tok.describe() + "'");
2197 }
2198 } break;
2201
2202 auto expression_list_node = std::make_shared<ExpressionListNode>(tok.text.data() - tmpl.content.c_str());
2203 current_block->nodes.emplace_back(expression_list_node);
2204 current_expression_list = expression_list_node.get();
2205
2207 throw_parser_error("expected expression close, got '" + tok.describe() + "'");
2208 }
2209 } break;
2213 throw_parser_error("expected comment close, got '" + tok.describe() + "'");
2214 }
2215 } break;
2216 default: {
2217 throw_parser_error("unexpected token '" + tok.describe() + "'");
2218 } break;
2219 }
2220 }
2221 }
2222
2223 public:
2224 explicit Parser(const ParserConfig& parser_config,
2225 const LexerConfig& lexer_config,
2226 TemplateStorage& template_storage,
2227 const FunctionStorage& function_storage)
2228 : config(parser_config)
2229 , lexer(lexer_config)
2230 , template_storage(template_storage)
2231 , function_storage(function_storage) {
2232 }
2233
2234 Template parse(std::string_view input, const std::filesystem::path& path) {
2235 auto result = Template(std::string(input));
2236 parse_into(result, path);
2237 return result;
2238 }
2239
2240 void parse_into_template(Template& tmpl, const std::filesystem::path& filename) {
2242 sub_parser.parse_into(tmpl, filename.parent_path());
2243 }
2244
2245 static std::string load_file(const std::filesystem::path& filename) {
2246 std::ifstream file;
2247 file.open(filename);
2248 if (file.fail()) {
2249 INJA_THROW(FileError("failed accessing file at '" + filename.string() + "'"));
2250 }
2251 std::string text((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
2252 return text;
2253 }
2254 };
2255
2256} // namespace inja
2257
2258#endif // INCLUDE_INJA_PARSER_HPP_
2259
2260// #include "renderer.hpp"
2261#ifndef INCLUDE_INJA_RENDERER_HPP_
2262#define INCLUDE_INJA_RENDERER_HPP_
2263
2264#include <algorithm>
2265#include <array>
2266#include <cctype>
2267#include <cmath>
2268#include <cstddef>
2269#include <filesystem>
2270#include <memory>
2271#include <numeric>
2272#include <ostream>
2273#include <sstream>
2274#include <stack>
2275#include <string>
2276#include <utility>
2277#include <vector>
2278
2279// #include "config.hpp"
2280
2281// #include "exceptions.hpp"
2282
2283// #include "function_storage.hpp"
2284
2285// #include "node.hpp"
2286
2287// #include "template.hpp"
2288
2289// #include "throw.hpp"
2290
2291// #include "utils.hpp"
2292
2293namespace inja {
2294
2295 /*!
2296 @brief Escapes HTML
2297 */
2298 inline std::string htmlescape(const std::string& data) {
2299 std::string buffer;
2300 buffer.reserve(data.size() + data.size() / 10);
2301 for (size_t pos = 0; pos != data.size(); ++pos) {
2302 switch (data[pos]) {
2303 case '&':
2304 buffer.append("&amp;");
2305 break;
2306 case '\"':
2307 buffer.append("&quot;");
2308 break;
2309 case '\'':
2310 buffer.append("&apos;");
2311 break;
2312 case '<':
2313 buffer.append("&lt;");
2314 break;
2315 case '>':
2316 buffer.append("&gt;");
2317 break;
2318 default:
2319 buffer.append(&data[pos], 1);
2320 break;
2321 }
2322 }
2323 return buffer;
2324 }
2325
2326 /*!
2327 * \brief Class for rendering a Template with data.
2328 */
2329 class Renderer : public NodeVisitor {
2331
2335
2337 size_t current_level{0};
2338 std::vector<const Template*> template_stack;
2340
2342 std::ostream* output_stream;
2343
2346
2347 std::vector<std::shared_ptr<json>> data_tmp_stack;
2348 std::stack<const json*> data_eval_stack;
2349 std::stack<const DataNode*> not_found_stack;
2350
2351 bool break_rendering{false};
2352
2353 static bool truthy(const json* data) {
2354 if (data->is_boolean()) {
2355 return data->get<bool>();
2356 } else if (data->is_number()) {
2357 return (*data != 0);
2358 } else if (data->is_null()) {
2359 return false;
2360 }
2361 return !data->empty();
2362 }
2363
2364 void print_data(const std::shared_ptr<json>& value) {
2365 if (value->is_string()) {
2367 *output_stream << htmlescape(value->get_ref<const json::string_t&>());
2368 } else {
2369 *output_stream << value->get_ref<const json::string_t&>();
2370 }
2371 } else if (value->is_number_unsigned()) {
2372 *output_stream << value->get<const json::number_unsigned_t>();
2373 } else if (value->is_number_integer()) {
2374 *output_stream << value->get<const json::number_integer_t>();
2375 } else if (value->is_null()) {
2376 } else {
2377 *output_stream << value->dump();
2378 }
2379 }
2380
2381 const std::shared_ptr<json> eval_expression_list(const ExpressionListNode& expression_list) {
2382 if (!expression_list.root) {
2383 throw_renderer_error("empty expression", expression_list);
2384 }
2385
2386 expression_list.root->accept(*this);
2387
2388 if (data_eval_stack.empty()) {
2389 throw_renderer_error("empty expression", expression_list);
2390 } else if (data_eval_stack.size() != 1) {
2391 throw_renderer_error("malformed expression", expression_list);
2392 }
2393
2394 const auto result = data_eval_stack.top();
2395 data_eval_stack.pop();
2396
2397 if (result == nullptr) {
2398 if (not_found_stack.empty()) {
2399 throw_renderer_error("expression could not be evaluated", expression_list);
2400 }
2401
2402 const auto node = not_found_stack.top();
2403 not_found_stack.pop();
2404
2405 throw_renderer_error("variable '" + static_cast<std::string>(node->name) + "' not found", *node);
2406 }
2407 return std::make_shared<json>(*result);
2408 }
2409
2410 void throw_renderer_error(const std::string& message, const AstNode& node) {
2412 INJA_THROW(RenderError(message, loc));
2413 }
2414
2415 void make_result(const json&& result) {
2416 auto result_ptr = std::make_shared<json>(result);
2417 data_tmp_stack.push_back(result_ptr);
2418 data_eval_stack.push(result_ptr.get());
2419 }
2420
2421 template <size_t N, size_t N_start = 0, bool throw_not_found = true>
2422 std::array<const json*, N> get_arguments(const FunctionNode& node) {
2423 if (node.arguments.size() < N_start + N) {
2424 throw_renderer_error("function needs " + std::to_string(N_start + N) + " variables, but has only found " +
2425 std::to_string(node.arguments.size()),
2426 node);
2427 }
2428
2429 for (size_t i = N_start; i < N_start + N; i += 1) {
2430 node.arguments[i]->accept(*this);
2431 }
2432
2433 if (data_eval_stack.size() < N) {
2434 throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " +
2435 std::to_string(data_eval_stack.size()),
2436 node);
2437 }
2438
2439 std::array<const json*, N> result;
2440 for (size_t i = 0; i < N; i += 1) {
2441 result[N - i - 1] = data_eval_stack.top();
2442 data_eval_stack.pop();
2443
2444 if (!result[N - i - 1]) {
2445 const auto data_node = not_found_stack.top();
2446 not_found_stack.pop();
2447
2448 if (throw_not_found) {
2449 throw_renderer_error("variable '" + static_cast<std::string>(data_node->name) + "' not found", *data_node);
2450 }
2451 }
2452 }
2453 return result;
2454 }
2455
2456 template <bool throw_not_found = true>
2458 const size_t N = node.arguments.size();
2459 for (const auto& a : node.arguments) {
2460 a->accept(*this);
2461 }
2462
2463 if (data_eval_stack.size() < N) {
2464 throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " +
2465 std::to_string(data_eval_stack.size()),
2466 node);
2467 }
2468
2469 Arguments result{N};
2470 for (size_t i = 0; i < N; i += 1) {
2471 result[N - i - 1] = data_eval_stack.top();
2472 data_eval_stack.pop();
2473
2474 if (!result[N - i - 1]) {
2475 const auto data_node = not_found_stack.top();
2476 not_found_stack.pop();
2477
2478 if (throw_not_found) {
2479 throw_renderer_error("variable '" + static_cast<std::string>(data_node->name) + "' not found", *data_node);
2480 }
2481 }
2482 }
2483 return result;
2484 }
2485
2486 void visit(const BlockNode& node) override {
2487 for (const auto& n : node.nodes) {
2488 n->accept(*this);
2489
2490 if (break_rendering) {
2491 break;
2492 }
2493 }
2494 }
2495
2496 void visit(const TextNode& node) override {
2497 output_stream->write(current_template->content.c_str() + node.pos, static_cast<std::streamsize>(node.length));
2498 }
2499
2500 void visit(const ExpressionNode&) override {
2501 }
2502
2503 void visit(const LiteralNode& node) override {
2504 data_eval_stack.push(&node.value);
2505 }
2506
2507 void visit(const DataNode& node) override {
2508 if (additional_data.contains(node.ptr)) {
2509 data_eval_stack.push(&(additional_data[node.ptr]));
2510 } else if (data_input->contains(node.ptr)) {
2511 data_eval_stack.push(&(*data_input)[node.ptr]);
2512 } else {
2513 // Try to evaluate as a no-argument callback
2514 const auto function_data = function_storage.find_function(node.name, 0);
2516 Arguments empty_args{};
2517 const auto value = std::make_shared<json>(function_data.callback(empty_args));
2518 data_tmp_stack.push_back(value);
2519 data_eval_stack.push(value.get());
2520 } else {
2521 data_eval_stack.push(nullptr);
2522 not_found_stack.emplace(&node);
2523 }
2524 }
2525 }
2526
2527 void visit(const FunctionNode& node) override {
2528 switch (node.operation) {
2529 case Op::Not: {
2530 const auto args = get_arguments<1>(node);
2531 make_result(!truthy(args[0]));
2532 } break;
2533 case Op::And: {
2535 } break;
2536 case Op::Or: {
2538 } break;
2539 case Op::In: {
2540 const auto args = get_arguments<2>(node);
2541 make_result(std::find(args[1]->begin(), args[1]->end(), *args[0]) != args[1]->end());
2542 } break;
2543 case Op::Equal: {
2544 const auto args = get_arguments<2>(node);
2545 make_result(*args[0] == *args[1]);
2546 } break;
2547 case Op::NotEqual: {
2548 const auto args = get_arguments<2>(node);
2549 make_result(*args[0] != *args[1]);
2550 } break;
2551 case Op::Greater: {
2552 const auto args = get_arguments<2>(node);
2553 make_result(*args[0] > *args[1]);
2554 } break;
2555 case Op::GreaterEqual: {
2556 const auto args = get_arguments<2>(node);
2557 make_result(*args[0] >= *args[1]);
2558 } break;
2559 case Op::Less: {
2560 const auto args = get_arguments<2>(node);
2561 make_result(*args[0] < *args[1]);
2562 } break;
2563 case Op::LessEqual: {
2564 const auto args = get_arguments<2>(node);
2565 make_result(*args[0] <= *args[1]);
2566 } break;
2567 case Op::Add: {
2568 const auto args = get_arguments<2>(node);
2569 if (args[0]->is_string() && args[1]->is_string()) {
2570 make_result(args[0]->get_ref<const json::string_t&>() + args[1]->get_ref<const json::string_t&>());
2571 } else if (args[0]->is_number_integer() && args[1]->is_number_integer()) {
2572 make_result(args[0]->get<const json::number_integer_t>() + args[1]->get<const json::number_integer_t>());
2573 } else {
2574 make_result(args[0]->get<const json::number_float_t>() + args[1]->get<const json::number_float_t>());
2575 }
2576 } break;
2577 case Op::Subtract: {
2578 const auto args = get_arguments<2>(node);
2579 if (args[0]->is_number_integer() && args[1]->is_number_integer()) {
2580 make_result(args[0]->get<const json::number_integer_t>() - args[1]->get<const json::number_integer_t>());
2581 } else {
2582 make_result(args[0]->get<const json::number_float_t>() - args[1]->get<const json::number_float_t>());
2583 }
2584 } break;
2585 case Op::Multiplication: {
2586 const auto args = get_arguments<2>(node);
2587 if (args[0]->is_number_integer() && args[1]->is_number_integer()) {
2588 make_result(args[0]->get<const json::number_integer_t>() * args[1]->get<const json::number_integer_t>());
2589 } else {
2590 make_result(args[0]->get<const json::number_float_t>() * args[1]->get<const json::number_float_t>());
2591 }
2592 } break;
2593 case Op::Division: {
2594 const auto args = get_arguments<2>(node);
2595 if (args[1]->get<const json::number_float_t>() == 0) {
2596 throw_renderer_error("division by zero", node);
2597 }
2598 make_result(args[0]->get<const json::number_float_t>() / args[1]->get<const json::number_float_t>());
2599 } break;
2600 case Op::Power: {
2601 const auto args = get_arguments<2>(node);
2602 if (args[0]->is_number_integer() && args[1]->get<const json::number_integer_t>() >= 0) {
2603 const auto result = static_cast<json::number_integer_t>(
2604 std::pow(args[0]->get<const json::number_integer_t>(), args[1]->get<const json::number_integer_t>()));
2605 make_result(result);
2606 } else {
2607 const auto result =
2608 std::pow(args[0]->get<const json::number_float_t>(), args[1]->get<const json::number_integer_t>());
2609 make_result(result);
2610 }
2611 } break;
2612 case Op::Modulo: {
2613 const auto args = get_arguments<2>(node);
2614 make_result(args[0]->get<const json::number_integer_t>() % args[1]->get<const json::number_integer_t>());
2615 } break;
2616 case Op::AtId: {
2617 const auto container = get_arguments<1, 0, false>(node)[0];
2618 node.arguments[1]->accept(*this);
2619 if (not_found_stack.empty()) {
2620 throw_renderer_error("could not find element with given name", node);
2621 }
2622 const auto id_node = not_found_stack.top();
2623 not_found_stack.pop();
2624 data_eval_stack.pop();
2625 data_eval_stack.push(&container->at(id_node->name));
2626 } break;
2627 case Op::At: {
2628 const auto args = get_arguments<2>(node);
2629 if (args[0]->is_object()) {
2630 data_eval_stack.push(&args[0]->at(args[1]->get<std::string>()));
2631 } else {
2632 data_eval_stack.push(&args[0]->at(args[1]->get<std::size_t>()));
2633 }
2634 } break;
2635 case Op::Capitalize: {
2636 auto result = get_arguments<1>(node)[0]->get<json::string_t>();
2637 result[0] = static_cast<char>(::toupper(result[0]));
2638 std::transform(result.begin() + 1, result.end(), result.begin() + 1, [](char c) {
2639 return static_cast<char>(::tolower(c));
2640 });
2641 make_result(std::move(result));
2642 } break;
2643 case Op::Default: {
2644 const auto test_arg = get_arguments<1, 0, false>(node)[0];
2645 data_eval_stack.push((test_arg != nullptr) ? test_arg : get_arguments<1, 1>(node)[0]);
2646 } break;
2647 case Op::DivisibleBy: {
2648 const auto args = get_arguments<2>(node);
2649 const auto divisor = args[1]->get<const json::number_integer_t>();
2650 make_result((divisor != 0) && (args[0]->get<const json::number_integer_t>() % divisor == 0));
2651 } break;
2652 case Op::Even: {
2653 make_result(get_arguments<1>(node)[0]->get<const json::number_integer_t>() % 2 == 0);
2654 } break;
2655 case Op::Exists: {
2656 auto&& name = get_arguments<1>(node)[0]->get_ref<const json::string_t&>();
2657 make_result(data_input->contains(json::json_pointer(DataNode::convert_dot_to_ptr(name))));
2658 } break;
2659 case Op::ExistsInObject: {
2660 const auto args = get_arguments<2>(node);
2661 auto&& name = args[1]->get_ref<const json::string_t&>();
2662 make_result(args[0]->find(name) != args[0]->end());
2663 } break;
2664 case Op::First: {
2665 const auto result = &get_arguments<1>(node)[0]->front();
2666 data_eval_stack.push(result);
2667 } break;
2668 case Op::Float: {
2669 make_result(std::stod(get_arguments<1>(node)[0]->get_ref<const json::string_t&>()));
2670 } break;
2671 case Op::Int: {
2672 make_result(std::stoi(get_arguments<1>(node)[0]->get_ref<const json::string_t&>()));
2673 } break;
2674 case Op::Last: {
2675 const auto result = &get_arguments<1>(node)[0]->back();
2676 data_eval_stack.push(result);
2677 } break;
2678 case Op::Length: {
2679 const auto val = get_arguments<1>(node)[0];
2680 if (val->is_string()) {
2681 make_result(val->get_ref<const json::string_t&>().length());
2682 } else {
2683 make_result(val->size());
2684 }
2685 } break;
2686 case Op::Lower: {
2687 auto result = get_arguments<1>(node)[0]->get<json::string_t>();
2688 std::transform(result.begin(), result.end(), result.begin(), [](char c) {
2689 return static_cast<char>(::tolower(c));
2690 });
2691 make_result(std::move(result));
2692 } break;
2693 case Op::Max: {
2694 const auto args = get_arguments<1>(node);
2695 const auto result = std::max_element(args[0]->begin(), args[0]->end());
2696 data_eval_stack.push(&(*result));
2697 } break;
2698 case Op::Min: {
2699 const auto args = get_arguments<1>(node);
2700 const auto result = std::min_element(args[0]->begin(), args[0]->end());
2701 data_eval_stack.push(&(*result));
2702 } break;
2703 case Op::Odd: {
2704 make_result(get_arguments<1>(node)[0]->get<const json::number_integer_t>() % 2 != 0);
2705 } break;
2706 case Op::Range: {
2707 std::vector<int> result(get_arguments<1>(node)[0]->get<const std::size_t>());
2708 std::iota(result.begin(), result.end(), 0);
2709 make_result(std::move(result));
2710 } break;
2711 case Op::Replace: {
2712 const auto args = get_arguments<3>(node);
2713 auto result = args[0]->get<std::string>();
2714 replace_substring(result, args[1]->get<std::string>(), args[2]->get<std::string>());
2715 make_result(std::move(result));
2716 } break;
2717 case Op::Round: {
2718 const auto args = get_arguments<2>(node);
2719 const auto precision = args[1]->get<const json::number_integer_t>();
2720 const double result =
2721 std::round(args[0]->get<const json::number_float_t>() * std::pow(10.0, precision)) / std::pow(10.0, precision);
2722 if (precision == 0) {
2723 make_result(static_cast<int>(result));
2724 } else {
2725 make_result(result);
2726 }
2727 } break;
2728 case Op::Sort: {
2729 auto result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->get<std::vector<json>>());
2730 std::sort(result_ptr->begin(), result_ptr->end());
2731 data_tmp_stack.push_back(result_ptr);
2732 data_eval_stack.push(result_ptr.get());
2733 } break;
2734 case Op::Upper: {
2735 auto result = get_arguments<1>(node)[0]->get<json::string_t>();
2736 std::transform(result.begin(), result.end(), result.begin(), [](char c) {
2737 return static_cast<char>(::toupper(c));
2738 });
2739 make_result(std::move(result));
2740 } break;
2741 case Op::IsBoolean: {
2742 make_result(get_arguments<1>(node)[0]->is_boolean());
2743 } break;
2744 case Op::IsNumber: {
2745 make_result(get_arguments<1>(node)[0]->is_number());
2746 } break;
2747 case Op::IsInteger: {
2748 make_result(get_arguments<1>(node)[0]->is_number_integer());
2749 } break;
2750 case Op::IsFloat: {
2751 make_result(get_arguments<1>(node)[0]->is_number_float());
2752 } break;
2753 case Op::IsObject: {
2754 make_result(get_arguments<1>(node)[0]->is_object());
2755 } break;
2756 case Op::IsArray: {
2757 make_result(get_arguments<1>(node)[0]->is_array());
2758 } break;
2759 case Op::IsString: {
2760 make_result(get_arguments<1>(node)[0]->is_string());
2761 } break;
2762 case Op::Callback: {
2763 auto args = get_argument_vector(node);
2764 make_result(node.callback(args));
2765 } break;
2766 case Op::Super: {
2767 const auto args = get_argument_vector(node);
2768 const size_t old_level = current_level;
2769 const size_t level_diff = (args.size() == 1) ? args[0]->get<std::size_t>() : 1;
2770 const size_t level = current_level + level_diff;
2771
2772 if (block_statement_stack.empty()) {
2773 throw_renderer_error("super() call is not within a block", node);
2774 }
2775
2776 if (level < 1 || level > template_stack.size() - 1) {
2777 throw_renderer_error("level of super() call does not match parent templates (between 1 and " +
2778 std::to_string(template_stack.size() - 1) + ")",
2779 node);
2780 }
2781
2782 const auto current_block_statement = block_statement_stack.back();
2783 const Template* new_template = template_stack.at(level);
2784 const Template* old_template = current_template;
2785 const auto block_it = new_template->block_storage.find(current_block_statement->name);
2786 if (block_it != new_template->block_storage.end()) {
2787 current_template = new_template;
2788 current_level = level;
2789 block_it->second->block.accept(*this);
2790 current_level = old_level;
2791 current_template = old_template;
2792 } else {
2793 throw_renderer_error("could not find block with name '" + current_block_statement->name + "'", node);
2794 }
2795 make_result(nullptr);
2796 } break;
2797 case Op::Join: {
2798 const auto args = get_arguments<2>(node);
2799 const auto separator = args[1]->get<json::string_t>();
2800 std::ostringstream os;
2801 std::string sep;
2802 for (const auto& value : *args[0]) {
2803 os << sep;
2804 if (value.is_string()) {
2805 os << value.get<std::string>(); // otherwise the value is surrounded with ""
2806 } else {
2807 os << value.dump();
2808 }
2809 sep = separator;
2810 }
2811 make_result(os.str());
2812 } break;
2813 case Op::None:
2814 break;
2815 }
2816 }
2817
2818 void visit(const ExpressionListNode& node) override {
2820 }
2821
2822 void visit(const StatementNode&) override {
2823 }
2824
2825 void visit(const ForStatementNode&) override {
2826 }
2827
2828 void visit(const ForArrayStatementNode& node) override {
2829 const auto result = eval_expression_list(node.condition);
2830 if (!result->is_array()) {
2831 throw_renderer_error("object must be an array", node);
2832 }
2833
2834 if (!current_loop_data->empty()) {
2835 auto tmp = *current_loop_data; // Because of clang-3
2836 (*current_loop_data)["parent"] = std::move(tmp);
2837 }
2838
2839 size_t index = 0;
2840 (*current_loop_data)["is_first"] = true;
2841 (*current_loop_data)["is_last"] = (result->size() <= 1);
2842 for (auto it = result->begin(); it != result->end(); ++it) {
2843 additional_data[static_cast<std::string>(node.value)] = *it;
2844
2845 (*current_loop_data)["index"] = index;
2846 (*current_loop_data)["index1"] = index + 1;
2847 if (index == 1) {
2848 (*current_loop_data)["is_first"] = false;
2849 }
2850 if (index == result->size() - 1) {
2851 (*current_loop_data)["is_last"] = true;
2852 }
2853
2854 node.body.accept(*this);
2855 ++index;
2856 }
2857
2858 additional_data[static_cast<std::string>(node.value)].clear();
2859 if (!(*current_loop_data)["parent"].empty()) {
2860 const auto tmp = (*current_loop_data)["parent"];
2861 *current_loop_data = tmp;
2862 } else {
2864 }
2865 }
2866
2867 void visit(const ForObjectStatementNode& node) override {
2868 const auto result = eval_expression_list(node.condition);
2869 if (!result->is_object()) {
2870 throw_renderer_error("object must be an object", node);
2871 }
2872
2873 if (!current_loop_data->empty()) {
2874 (*current_loop_data)["parent"] = std::move(*current_loop_data);
2875 }
2876
2877 size_t index = 0;
2878 (*current_loop_data)["is_first"] = true;
2879 (*current_loop_data)["is_last"] = (result->size() <= 1);
2880 for (auto it = result->begin(); it != result->end(); ++it) {
2881 additional_data[static_cast<std::string>(node.key)] = it.key();
2882 additional_data[static_cast<std::string>(node.value)] = it.value();
2883
2884 (*current_loop_data)["index"] = index;
2885 (*current_loop_data)["index1"] = index + 1;
2886 if (index == 1) {
2887 (*current_loop_data)["is_first"] = false;
2888 }
2889 if (index == result->size() - 1) {
2890 (*current_loop_data)["is_last"] = true;
2891 }
2892
2893 node.body.accept(*this);
2894 ++index;
2895 }
2896
2897 additional_data[static_cast<std::string>(node.key)].clear();
2898 additional_data[static_cast<std::string>(node.value)].clear();
2899 if (!(*current_loop_data)["parent"].empty()) {
2900 *current_loop_data = std::move((*current_loop_data)["parent"]);
2901 } else {
2903 }
2904 }
2905
2906 void visit(const IfStatementNode& node) override {
2907 const auto result = eval_expression_list(node.condition);
2908 if (truthy(result.get())) {
2909 node.true_statement.accept(*this);
2910 } else if (node.has_false_statement) {
2912 }
2913 }
2914
2915 void visit(const IncludeStatementNode& node) override {
2917 const auto included_template_it = template_storage.find(node.file);
2918 if (included_template_it != template_storage.end()) {
2919 sub_renderer.render_to(*output_stream, included_template_it->second, *data_input, &additional_data);
2921 throw_renderer_error("include '" + node.file + "' not found", node);
2922 }
2923 }
2924
2925 void visit(const ExtendsStatementNode& node) override {
2926 const auto included_template_it = template_storage.find(node.file);
2927 if (included_template_it != template_storage.end()) {
2928 const Template* parent_template = &included_template_it->second;
2930 break_rendering = true;
2932 throw_renderer_error("extends '" + node.file + "' not found", node);
2933 }
2934 }
2935
2936 void visit(const BlockStatementNode& node) override {
2937 const size_t old_level = current_level;
2938 current_level = 0;
2940 const auto block_it = current_template->block_storage.find(node.name);
2941 if (block_it != current_template->block_storage.end()) {
2942 block_statement_stack.emplace_back(&node);
2943 block_it->second->block.accept(*this);
2944 block_statement_stack.pop_back();
2945 }
2946 current_level = old_level;
2948 }
2949
2950 void visit(const SetStatementNode& node) override {
2951 std::string ptr = node.key;
2952 replace_substring(ptr, ".", "/");
2953 ptr = "/" + ptr;
2954 additional_data[json::json_pointer(ptr)] = *eval_expression_list(node.expression);
2955 }
2956
2957 public:
2958 explicit Renderer(const RenderConfig& config, const TemplateStorage& template_storage, const FunctionStorage& function_storage)
2959 : config(config)
2960 , template_storage(template_storage)
2961 , function_storage(function_storage) {
2962 }
2963
2964 void render_to(std::ostream& os, const Template& tmpl, const json& data, json* loop_data = nullptr) {
2965 output_stream = &os;
2966 current_template = &tmpl;
2967 data_input = &data;
2968 if (loop_data != nullptr) {
2969 additional_data = *loop_data;
2971 }
2972
2973 template_stack.emplace_back(current_template);
2975
2976 data_tmp_stack.clear();
2977 }
2978 };
2979
2980} // namespace inja
2981
2982#endif // INCLUDE_INJA_RENDERER_HPP_
2983
2984// #include "template.hpp"
2985
2986// #include "throw.hpp"
2987
2988namespace inja {
2989
2990 /*!
2991 * \brief Class for changing the configuration.
2992 */
2996
2997 protected:
3001
3002 std::filesystem::path input_path;
3003 std::filesystem::path output_path;
3004
3005 public:
3007 : Environment("") {
3008 }
3009 explicit Environment(const std::filesystem::path& global_path)
3010 : input_path(global_path)
3011 , output_path(global_path) {
3012 }
3013 Environment(const std::filesystem::path& input_path, const std::filesystem::path& output_path)
3014 : input_path(input_path)
3015 , output_path(output_path) {
3016 }
3017
3018 /// Sets the opener and closer for template statements
3019 void set_statement(const std::string& open, const std::string& close) {
3026 }
3027
3028 /// Sets the opener for template line statements
3029 void set_line_statement(const std::string& open) {
3032 }
3033
3034 /// Sets the opener and closer for template expressions
3035 void set_expression(const std::string& open, const std::string& close) {
3041 }
3042
3043 /// Sets the opener and closer for template comments
3044 void set_comment(const std::string& open, const std::string& close) {
3050 }
3051
3052 /// Sets whether to remove the first newline after a block
3053 void set_trim_blocks(bool trim_blocks) {
3054 lexer_config.trim_blocks = trim_blocks;
3055 }
3056
3057 /// Sets whether to strip the spaces and tabs from the start of a line to a block
3058 void set_lstrip_blocks(bool lstrip_blocks) {
3059 lexer_config.lstrip_blocks = lstrip_blocks;
3060 }
3061
3062 /// Sets the element notation syntax
3063 void set_search_included_templates_in_files(bool search_in_files) {
3065 }
3066
3067 /// Sets whether a missing include will throw an error
3068 void set_throw_at_missing_includes(bool will_throw) {
3070 }
3071
3072 /// Sets whether we'll automatically perform HTML escape
3073 void set_html_autoescape(bool will_escape) {
3074 render_config.html_autoescape = will_escape;
3075 }
3076
3077 Template parse(std::string_view input) {
3079 return parser.parse(input, input_path);
3080 }
3081
3082 Template parse_template(const std::filesystem::path& filename) {
3084 auto result = Template(Parser::load_file(input_path / filename));
3085 parser.parse_into_template(result, (input_path / filename).string());
3086 return result;
3087 }
3088
3089 Template parse_file(const std::filesystem::path& filename) {
3090 return parse_template(filename);
3091 }
3092
3093 std::string render(std::string_view input, const json& data) {
3094 return render(parse(input), data);
3095 }
3096
3097 std::string render(const Template& tmpl, const json& data) {
3098 std::stringstream os;
3099 render_to(os, tmpl, data);
3100 return os.str();
3101 }
3102
3103 std::string render_file(const std::filesystem::path& filename, const json& data) {
3104 return render(parse_template(filename), data);
3105 }
3106
3107 std::string render_file_with_json_file(const std::filesystem::path& filename, const std::string& filename_data) {
3108 const json data = load_json(filename_data);
3109 return render_file(filename, data);
3110 }
3111
3112 void write(const std::filesystem::path& filename, const json& data, const std::string& filename_out) {
3113 std::ofstream file(output_path / filename_out);
3114 file << render_file(filename, data);
3115 file.close();
3116 }
3117
3118 void write(const Template& temp, const json& data, const std::string& filename_out) {
3119 std::ofstream file(output_path / filename_out);
3120 file << render(temp, data);
3121 file.close();
3122 }
3123
3124 void
3125 write_with_json_file(const std::filesystem::path& filename, const std::string& filename_data, const std::string& filename_out) {
3126 const json data = load_json(filename_data);
3127 write(filename, data, filename_out);
3128 }
3129
3130 void write_with_json_file(const Template& temp, const std::string& filename_data, const std::string& filename_out) {
3131 const json data = load_json(filename_data);
3132 write(temp, data, filename_out);
3133 }
3134
3135 std::ostream& render_to(std::ostream& os, const Template& tmpl, const json& data) {
3137 return os;
3138 }
3139
3140 std::ostream& render_to(std::ostream& os, const std::string_view input, const json& data) {
3141 return render_to(os, parse(input), data);
3142 }
3143
3144 std::string load_file(const std::string& filename) {
3146 return Parser::load_file(input_path / filename);
3147 }
3148
3149 json load_json(const std::string& filename) {
3150 std::ifstream file;
3151 file.open(input_path / filename);
3152 if (file.fail()) {
3153 INJA_THROW(FileError("failed accessing file at '" + (input_path / filename).string() + "'"));
3154 }
3155
3156 return json::parse(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
3157 }
3158
3159 /*!
3160 @brief Adds a variadic callback
3161 */
3162 void add_callback(const std::string& name, const CallbackFunction& callback) {
3163 add_callback(name, -1, callback);
3164 }
3165
3166 /*!
3167 @brief Adds a variadic void callback
3168 */
3169 void add_void_callback(const std::string& name, const VoidCallbackFunction& callback) {
3170 add_void_callback(name, -1, callback);
3171 }
3172
3173 /*!
3174 @brief Adds a callback with given number or arguments
3175 */
3176 void add_callback(const std::string& name, int num_args, const CallbackFunction& callback) {
3177 function_storage.add_callback(name, num_args, callback);
3178 }
3179
3180 /*!
3181 @brief Adds a void callback with given number or arguments
3182 */
3183 void add_void_callback(const std::string& name, int num_args, const VoidCallbackFunction& callback) {
3184 function_storage.add_callback(name, num_args, [callback](Arguments& args) {
3185 callback(args);
3186 return json();
3187 });
3188 }
3189
3190 /** Includes a template with a given name into the environment.
3191 * Then, a template can be rendered in another template using the
3192 * include "<name>" syntax.
3193 */
3194 void include_template(const std::string& name, const Template& tmpl) {
3195 template_storage[name] = tmpl;
3196 }
3197
3198 /*!
3199 @brief Sets a function that is called when an included file is not found
3200 */
3201 void set_include_callback(const std::function<Template(const std::filesystem::path&, const std::string&)>& callback) {
3203 }
3204 };
3205
3206 /*!
3207 @brief render with default settings to a string
3208 */
3209 inline std::string render(std::string_view input, const json& data) {
3210 return Environment().render(input, data);
3211 }
3212
3213 /*!
3214 @brief render with default settings to the given output stream
3215 */
3216 inline void render_to(std::ostream& os, std::string_view input, const json& data) {
3217 Environment env;
3218 env.render_to(os, env.parse(input), data);
3219 }
3220
3221} // namespace inja
3222
3223#endif // INCLUDE_INJA_ENVIRONMENT_HPP_
3224
3225// #include "exceptions.hpp"
3226
3227// #include "parser.hpp"
3228
3229// #include "renderer.hpp"
3230
3231// #include "template.hpp"
3232
3233#endif // INCLUDE_INJA_INJA_HPP_
std::vector< mqtt::lib::Function > functions
std::vector< mqtt::lib::VoidFunction > voidFunctions
Base node class for the abstract syntax tree (AST).
Definition inja.hpp:443
AstNode(size_t pos)
Definition inja.hpp:449
size_t pos
Definition inja.hpp:447
virtual void accept(NodeVisitor &v) const =0
virtual ~AstNode()
Definition inja.hpp:452
std::vector< std::shared_ptr< AstNode > > nodes
Definition inja.hpp:458
void accept(NodeVisitor &v) const override
Definition inja.hpp:464
void accept(NodeVisitor &v) const override
Definition inja.hpp:799
const std::string name
Definition inja.hpp:789
BlockStatementNode(BlockNode *const parent, const std::string &name, size_t pos)
Definition inja.hpp:793
BlockNode *const parent
Definition inja.hpp:791
static std::string convert_dot_to_ptr(std::string_view ptr_name)
Definition inja.hpp:513
void accept(NodeVisitor &v) const override
Definition inja.hpp:530
DataNode(std::string_view ptr_name, size_t pos)
Definition inja.hpp:524
const json::json_pointer ptr
Definition inja.hpp:511
const std::string name
Definition inja.hpp:510
Class for changing the configuration.
Definition inja.hpp:2993
LexerConfig lexer_config
Definition inja.hpp:2998
Environment(const std::filesystem::path &input_path, const std::filesystem::path &output_path)
Definition inja.hpp:3013
std::ostream & render_to(std::ostream &os, const Template &tmpl, const json &data)
Definition inja.hpp:3135
TemplateStorage template_storage
Definition inja.hpp:2995
FunctionStorage function_storage
Definition inja.hpp:2994
void set_throw_at_missing_includes(bool will_throw)
Sets whether a missing include will throw an error.
Definition inja.hpp:3068
void add_void_callback(const std::string &name, int num_args, const VoidCallbackFunction &callback)
Adds a void callback with given number or arguments.
Definition inja.hpp:3183
void set_statement(const std::string &open, const std::string &close)
Sets the opener and closer for template statements.
Definition inja.hpp:3019
std::string render_file(const std::filesystem::path &filename, const json &data)
Definition inja.hpp:3103
std::string render(const Template &tmpl, const json &data)
Definition inja.hpp:3097
Environment(const std::filesystem::path &global_path)
Definition inja.hpp:3009
void set_search_included_templates_in_files(bool search_in_files)
Sets the element notation syntax.
Definition inja.hpp:3063
json load_json(const std::string &filename)
Definition inja.hpp:3149
void set_trim_blocks(bool trim_blocks)
Sets whether to remove the first newline after a block.
Definition inja.hpp:3053
std::string load_file(const std::string &filename)
Definition inja.hpp:3144
RenderConfig render_config
Definition inja.hpp:3000
void set_lstrip_blocks(bool lstrip_blocks)
Sets whether to strip the spaces and tabs from the start of a line to a block.
Definition inja.hpp:3058
void set_comment(const std::string &open, const std::string &close)
Sets the opener and closer for template comments.
Definition inja.hpp:3044
void set_line_statement(const std::string &open)
Sets the opener for template line statements.
Definition inja.hpp:3029
void set_html_autoescape(bool will_escape)
Sets whether we'll automatically perform HTML escape.
Definition inja.hpp:3073
std::filesystem::path output_path
Definition inja.hpp:3003
std::string render_file_with_json_file(const std::filesystem::path &filename, const std::string &filename_data)
Definition inja.hpp:3107
void write(const Template &temp, const json &data, const std::string &filename_out)
Definition inja.hpp:3118
Template parse(std::string_view input)
Definition inja.hpp:3077
Template parse_template(const std::filesystem::path &filename)
Definition inja.hpp:3082
void set_include_callback(const std::function< Template(const std::filesystem::path &, const std::string &)> &callback)
Sets a function that is called when an included file is not found.
Definition inja.hpp:3201
void write_with_json_file(const Template &temp, const std::string &filename_data, const std::string &filename_out)
Definition inja.hpp:3130
void set_expression(const std::string &open, const std::string &close)
Sets the opener and closer for template expressions.
Definition inja.hpp:3035
std::string render(std::string_view input, const json &data)
Definition inja.hpp:3093
void add_callback(const std::string &name, const CallbackFunction &callback)
Adds a variadic callback.
Definition inja.hpp:3162
void include_template(const std::string &name, const Template &tmpl)
Definition inja.hpp:3194
std::filesystem::path input_path
Definition inja.hpp:3002
void add_void_callback(const std::string &name, const VoidCallbackFunction &callback)
Adds a variadic void callback.
Definition inja.hpp:3169
void add_callback(const std::string &name, int num_args, const CallbackFunction &callback)
Adds a callback with given number or arguments.
Definition inja.hpp:3176
std::ostream & render_to(std::ostream &os, const std::string_view input, const json &data)
Definition inja.hpp:3140
void write_with_json_file(const std::filesystem::path &filename, const std::string &filename_data, const std::string &filename_out)
Definition inja.hpp:3125
ParserConfig parser_config
Definition inja.hpp:2999
void write(const std::filesystem::path &filename, const json &data, const std::string &filename_out)
Definition inja.hpp:3112
Template parse_file(const std::filesystem::path &filename)
Definition inja.hpp:3089
void accept(NodeVisitor &v) const override
Definition inja.hpp:675
ExpressionListNode(size_t pos)
Definition inja.hpp:671
std::shared_ptr< ExpressionNode > root
Definition inja.hpp:666
void accept(NodeVisitor &v) const override
Definition inja.hpp:489
ExpressionNode(size_t pos)
Definition inja.hpp:485
ExtendsStatementNode(const std::string &file, size_t pos)
Definition inja.hpp:777
const std::string file
Definition inja.hpp:775
void accept(NodeVisitor &v) const override
Definition inja.hpp:782
ForArrayStatementNode(const std::string &value, BlockNode *const parent, size_t pos)
Definition inja.hpp:707
void accept(NodeVisitor &v) const override
Definition inja.hpp:712
const std::string value
Definition inja.hpp:705
ForObjectStatementNode(const std::string &key, const std::string &value, BlockNode *const parent, size_t pos)
Definition inja.hpp:722
const std::string key
Definition inja.hpp:719
void accept(NodeVisitor &v) const override
Definition inja.hpp:728
const std::string value
Definition inja.hpp:720
ExpressionListNode condition
Definition inja.hpp:691
virtual void accept(NodeVisitor &v) const =0
ForStatementNode(BlockNode *const parent, size_t pos)
Definition inja.hpp:695
BlockNode *const parent
Definition inja.hpp:693
std::vector< std::shared_ptr< ExpressionNode > > arguments
Definition inja.hpp:551
Associativity associativity
Definition inja.hpp:545
void accept(NodeVisitor &v) const override
Definition inja.hpp:659
CallbackFunction callback
Definition inja.hpp:552
FunctionNode(Op operation, size_t pos)
Definition inja.hpp:562
std::string name
Definition inja.hpp:549
FunctionStorage::Operation Op
Definition inja.hpp:536
unsigned int precedence
Definition inja.hpp:544
FunctionNode(std::string_view name, size_t pos)
Definition inja.hpp:554
Class for builtin functions and user-defined callbacks.
Definition inja.hpp:127
void add_builtin(std::string_view name, int num_args, Operation op)
Definition inja.hpp:228
std::map< std::pair< std::string, int >, FunctionData > function_storage
Definition inja.hpp:193
const int VARIADIC
Definition inja.hpp:191
void add_callback(std::string_view name, int num_args, const CallbackFunction &callback)
Definition inja.hpp:232
FunctionData find_function(std::string_view name, int num_args) const
Definition inja.hpp:236
void accept(NodeVisitor &v) const override
Definition inja.hpp:754
BlockNode *const parent
Definition inja.hpp:738
const bool is_nested
Definition inja.hpp:740
BlockNode false_statement
Definition inja.hpp:737
BlockNode true_statement
Definition inja.hpp:736
IfStatementNode(BlockNode *const parent, size_t pos)
Definition inja.hpp:743
ExpressionListNode condition
Definition inja.hpp:735
IfStatementNode(bool is_nested, BlockNode *const parent, size_t pos)
Definition inja.hpp:748
const std::string file
Definition inja.hpp:761
void accept(NodeVisitor &v) const override
Definition inja.hpp:768
IncludeStatementNode(const std::string &file, size_t pos)
Definition inja.hpp:763
Class for lexing an inja Template.
Definition inja.hpp:1133
const LexerConfig & config
Definition inja.hpp:1155
SourceLocation current_position() const
Definition inja.hpp:1404
static std::string_view clear_final_line_if_whitespace(std::string_view text)
Definition inja.hpp:1380
const LexerConfig & get_config() const
Definition inja.hpp:1557
Token scan_string()
Definition inja.hpp:1330
void skip_whitespaces_and_newlines()
Definition inja.hpp:1352
Token make_token(Token::Kind kind) const
Definition inja.hpp:1348
Token scan()
Definition inja.hpp:1421
Token scan_body(std::string_view close, Token::Kind closeKind, std::string_view close_trim=std::string_view(), bool trim=false)
Definition inja.hpp:1164
std::string_view m_in
Definition inja.hpp:1159
Token scan_number()
Definition inja.hpp:1313
size_t pos
Definition inja.hpp:1161
State state
Definition inja.hpp:1157
Token scan_id()
Definition inja.hpp:1299
Lexer(const LexerConfig &config)
Definition inja.hpp:1396
size_t tok_start
Definition inja.hpp:1160
void skip_whitespaces_and_first_newline()
Definition inja.hpp:1360
void start(std::string_view input)
Definition inja.hpp:1408
MinusState minus_state
Definition inja.hpp:1158
void accept(NodeVisitor &v) const override
Definition inja.hpp:503
LiteralNode(std::string_view data_text, size_t pos)
Definition inja.hpp:498
const json value
Definition inja.hpp:496
virtual void visit(const DataNode &node)=0
virtual void visit(const SetStatementNode &node)=0
virtual void visit(const ExpressionListNode &node)=0
virtual ~NodeVisitor()=default
virtual void visit(const StatementNode &node)=0
virtual void visit(const ExtendsStatementNode &node)=0
virtual void visit(const IncludeStatementNode &node)=0
virtual void visit(const TextNode &node)=0
virtual void visit(const ForStatementNode &node)=0
virtual void visit(const LiteralNode &node)=0
virtual void visit(const ExpressionNode &node)=0
virtual void visit(const ForObjectStatementNode &node)=0
virtual void visit(const BlockStatementNode &node)=0
virtual void visit(const IfStatementNode &node)=0
virtual void visit(const FunctionNode &node)=0
virtual void visit(const ForArrayStatementNode &node)=0
virtual void visit(const BlockNode &node)=0
Class for parsing an inja Template.
Definition inja.hpp:1579
void get_peek_token()
Definition inja.hpp:1614
void throw_parser_error(const std::string &message) const
Definition inja.hpp:1601
std::string parse_filename() const
Definition inja.hpp:1682
std::stack< BlockStatementNode * > block_statement_stack
Definition inja.hpp:1599
void parse_into(Template &tmpl, const std::filesystem::path &path)
Definition inja.hpp:2160
void add_operator(Arguments &arguments, OperatorStack &operator_stack)
Definition inja.hpp:1629
std::stack< ForStatementNode * > for_statement_stack
Definition inja.hpp:1598
ExpressionListNode * current_expression_list
Definition inja.hpp:1595
std::vector< std::shared_ptr< ExpressionNode > > Arguments
Definition inja.hpp:1580
TemplateStorage & template_storage
Definition inja.hpp:1586
Token peek_tok
Definition inja.hpp:1589
Token tok
Definition inja.hpp:1589
std::stack< std::shared_ptr< FunctionNode > > OperatorStack
Definition inja.hpp:1581
std::shared_ptr< ExpressionNode > parse_expression(Template &tmpl)
Definition inja.hpp:1700
Parser(const ParserConfig &parser_config, const LexerConfig &lexer_config, TemplateStorage &template_storage, const FunctionStorage &function_storage)
Definition inja.hpp:2224
const FunctionStorage & function_storage
Definition inja.hpp:1587
void add_to_template_storage(const std::filesystem::path &path, std::string &template_name)
Definition inja.hpp:1644
const ParserConfig & config
Definition inja.hpp:1583
static std::string load_file(const std::filesystem::path &filename)
Definition inja.hpp:2245
void get_next_token()
Definition inja.hpp:1605
std::stack< IfStatementNode * > if_statement_stack
Definition inja.hpp:1597
BlockNode * current_block
Definition inja.hpp:1594
void add_literal(Arguments &arguments, const char *content_ptr)
Definition inja.hpp:1621
Template parse(std::string_view input, const std::filesystem::path &path)
Definition inja.hpp:2234
std::string_view literal_start
Definition inja.hpp:1592
bool parse_expression(Template &tmpl, Token::Kind closing)
Definition inja.hpp:1695
Lexer lexer
Definition inja.hpp:1585
bool have_peek_tok
Definition inja.hpp:1590
void parse_into_template(Template &tmpl, const std::filesystem::path &filename)
Definition inja.hpp:2240
bool parse_statement(Template &tmpl, Token::Kind closing, const std::filesystem::path &path)
Definition inja.hpp:1969
Class for rendering a Template with data.
Definition inja.hpp:2329
const json * data_input
Definition inja.hpp:2341
std::array< const json *, N > get_arguments(const FunctionNode &node)
Definition inja.hpp:2422
void visit(const FunctionNode &node) override
Definition inja.hpp:2527
void visit(const ExpressionListNode &node) override
Definition inja.hpp:2818
const std::shared_ptr< json > eval_expression_list(const ExpressionListNode &expression_list)
Definition inja.hpp:2381
std::vector< std::shared_ptr< json > > data_tmp_stack
Definition inja.hpp:2347
void visit(const DataNode &node) override
Definition inja.hpp:2507
void visit(const StatementNode &) override
Definition inja.hpp:2822
size_t current_level
Definition inja.hpp:2337
std::ostream * output_stream
Definition inja.hpp:2342
void throw_renderer_error(const std::string &message, const AstNode &node)
Definition inja.hpp:2410
const RenderConfig config
Definition inja.hpp:2332
std::stack< const json * > data_eval_stack
Definition inja.hpp:2348
json additional_data
Definition inja.hpp:2344
std::stack< const DataNode * > not_found_stack
Definition inja.hpp:2349
void visit(const TextNode &node) override
Definition inja.hpp:2496
std::vector< const BlockStatementNode * > block_statement_stack
Definition inja.hpp:2339
Arguments get_argument_vector(const FunctionNode &node)
Definition inja.hpp:2457
static bool truthy(const json *data)
Definition inja.hpp:2353
const Template * current_template
Definition inja.hpp:2336
void visit(const ExpressionNode &) override
Definition inja.hpp:2500
void print_data(const std::shared_ptr< json > &value)
Definition inja.hpp:2364
void visit(const SetStatementNode &node) override
Definition inja.hpp:2950
bool break_rendering
Definition inja.hpp:2351
void visit(const IncludeStatementNode &node) override
Definition inja.hpp:2915
void visit(const ForStatementNode &) override
Definition inja.hpp:2825
FunctionStorage::Operation Op
Definition inja.hpp:2330
std::vector< const Template * > template_stack
Definition inja.hpp:2338
void visit(const ForArrayStatementNode &node) override
Definition inja.hpp:2828
const FunctionStorage & function_storage
Definition inja.hpp:2334
const TemplateStorage & template_storage
Definition inja.hpp:2333
void visit(const IfStatementNode &node) override
Definition inja.hpp:2906
void make_result(const json &&result)
Definition inja.hpp:2415
void render_to(std::ostream &os, const Template &tmpl, const json &data, json *loop_data=nullptr)
Definition inja.hpp:2964
Renderer(const RenderConfig &config, const TemplateStorage &template_storage, const FunctionStorage &function_storage)
Definition inja.hpp:2958
void visit(const BlockNode &node) override
Definition inja.hpp:2486
void visit(const LiteralNode &node) override
Definition inja.hpp:2503
json * current_loop_data
Definition inja.hpp:2345
void visit(const ExtendsStatementNode &node) override
Definition inja.hpp:2925
void visit(const ForObjectStatementNode &node) override
Definition inja.hpp:2867
void visit(const BlockStatementNode &node) override
Definition inja.hpp:2936
ExpressionListNode expression
Definition inja.hpp:807
const std::string key
Definition inja.hpp:806
SetStatementNode(const std::string &key, size_t pos)
Definition inja.hpp:809
void accept(NodeVisitor &v) const override
Definition inja.hpp:814
StatementNode(size_t pos)
Definition inja.hpp:682
virtual void accept(NodeVisitor &v) const =0
A class for counting statistics on a Template.
Definition inja.hpp:834
void visit(const LiteralNode &) override
Definition inja.hpp:845
void visit(const IfStatementNode &node) override
Definition inja.hpp:877
void visit(const DataNode &) override
Definition inja.hpp:848
void visit(const IncludeStatementNode &) override
Definition inja.hpp:883
void visit(const ForArrayStatementNode &node) override
Definition inja.hpp:867
void visit(const StatementNode &) override
Definition inja.hpp:862
void visit(const BlockNode &node) override
Definition inja.hpp:835
void visit(const FunctionNode &node) override
Definition inja.hpp:852
void visit(const ExpressionListNode &node) override
Definition inja.hpp:858
void visit(const ForStatementNode &) override
Definition inja.hpp:864
void visit(const SetStatementNode &) override
Definition inja.hpp:893
void visit(const ExtendsStatementNode &) override
Definition inja.hpp:886
void visit(const TextNode &) override
Definition inja.hpp:841
void visit(const ExpressionNode &) override
Definition inja.hpp:843
void visit(const ForObjectStatementNode &node) override
Definition inja.hpp:872
void visit(const BlockStatementNode &node) override
Definition inja.hpp:889
const size_t length
Definition inja.hpp:471
TextNode(size_t pos, size_t length)
Definition inja.hpp:473
void accept(NodeVisitor &v) const override
Definition inja.hpp:478
static void store(const inja::Arguments &args)
Definition Storage.cpp:63
static double recall_as_float(const inja::Arguments &args)
Definition Storage.cpp:85
static const std::string & recall(const inja::Arguments &args)
Definition Storage.cpp:67
static bool exists(const inja::Arguments &args)
Definition Storage.cpp:109
static bool is_empty(const inja::Arguments &args)
Definition Storage.cpp:99
Storage & operator=(const Storage &)=delete
static int recall_as_int(const inja::Arguments &args)
Definition Storage.cpp:71
std::map< std::string, std::string > storage
Definition Storage.h:104
#define INJA_THROW(exception)
Definition inja.hpp:55
std::pair< std::string_view, std::string_view > split(std::string_view view, char Separator)
Definition inja.hpp:344
bool starts_with(std::string_view view, std::string_view prefix)
Definition inja.hpp:352
std::string_view slice(std::string_view view, size_t start, size_t end)
Definition inja.hpp:338
std::vector< const nlohmann::json * > Arguments
void replace_substring(std::string &s, const std::string &f, const std::string &t)
Definition inja.hpp:380
std::function< void(Arguments &args)> VoidCallbackFunction
Definition inja.hpp:122
std::string render(std::string_view input, const json &data)
render with default settings to a string
Definition inja.hpp:3209
std::map< std::string, Template > TemplateStorage
Definition inja.hpp:931
void render_to(std::ostream &os, std::string_view input, const json &data)
render with default settings to the given output stream
Definition inja.hpp:3216
std::function< json(Arguments &args)> CallbackFunction
Definition inja.hpp:121
SourceLocation get_source_location(std::string_view content, size_t pos)
Definition inja.hpp:357
std::string htmlescape(const std::string &data)
Escapes HTML.
Definition inja.hpp:2298
nlohmann::json json
DataError(const std::string &message, SourceLocation location)
Definition inja.hpp:326
FileError(const std::string &message)
Definition inja.hpp:317
FileError(const std::string &message, SourceLocation location)
Definition inja.hpp:320
FunctionData(const Operation &op, const CallbackFunction &cb=CallbackFunction{})
Definition inja.hpp:182
const CallbackFunction callback
Definition inja.hpp:187
InjaError(const std::string &type, const std::string &message)
Definition inja.hpp:288
InjaError(const std::string &type, const std::string &message, SourceLocation location)
Definition inja.hpp:295
const std::string type
Definition inja.hpp:283
const SourceLocation location
Definition inja.hpp:286
const std::string message
Definition inja.hpp:284
Class for lexer configuration.
Definition inja.hpp:942
std::string expression_open_force_lstrip
Definition inja.hpp:950
std::string expression_close
Definition inja.hpp:951
std::string expression_close_force_rstrip
Definition inja.hpp:952
std::string comment_open_force_lstrip
Definition inja.hpp:954
std::string comment_close
Definition inja.hpp:955
std::string statement_close
Definition inja.hpp:946
std::string expression_open
Definition inja.hpp:949
std::string comment_close_force_rstrip
Definition inja.hpp:956
std::string statement_open_force_lstrip
Definition inja.hpp:945
std::string statement_close_force_rstrip
Definition inja.hpp:947
std::string open_chars
Definition inja.hpp:957
std::string statement_open_no_lstrip
Definition inja.hpp:944
void update_open_chars()
Definition inja.hpp:962
std::string comment_open
Definition inja.hpp:953
std::string statement_open
Definition inja.hpp:943
std::string line_statement
Definition inja.hpp:948
Class for parser configuration.
Definition inja.hpp:994
bool search_included_templates_in_files
Definition inja.hpp:995
std::function< Template(const std::filesystem::path &, const std::string &)> include_callback
Definition inja.hpp:997
ParserError(const std::string &message, SourceLocation location)
Definition inja.hpp:305
Class for render configuration.
Definition inja.hpp:1003
bool throw_at_missing_includes
Definition inja.hpp:1004
RenderError(const std::string &message, SourceLocation location)
Definition inja.hpp:311
The main inja Template.
Definition inja.hpp:912
size_t count_variables() const
Return number of variables (total number, not distinct ones) in the template.
Definition inja.hpp:924
std::map< std::string, std::shared_ptr< BlockStatementNode > > block_storage
Definition inja.hpp:915
BlockNode root
Definition inja.hpp:913
std::string content
Definition inja.hpp:914
Template(std::string content)
Definition inja.hpp:919
Helper-class for the inja Lexer.
Definition inja.hpp:1059
std::string_view text
Definition inja.hpp:1100
constexpr Token()=default
Kind kind
Definition inja.hpp:1099
std::string describe() const
Definition inja.hpp:1108
constexpr Token(Kind kind, std::string_view text)
Definition inja.hpp:1103