diff --git a/3party/inja/inja.hpp b/3party/inja/inja.hpp index f65f22b..f9643fc 100644 --- a/3party/inja/inja.hpp +++ b/3party/inja/inja.hpp @@ -24,29 +24,27 @@ SOFTWARE. #ifndef INCLUDE_INJA_INJA_HPP_ #define INCLUDE_INJA_INJA_HPP_ + #include namespace inja { - #ifndef INJA_DATA_TYPE using json = nlohmann::json; #else using json = INJA_DATA_TYPE; #endif -}// namespace inja +} // namespace inja -#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) \ - || defined(_CPPUNWIND)) \ - && !defined(INJA_NOEXCEPTION) +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(INJA_NOEXCEPTION) #ifndef INJA_THROW #define INJA_THROW(exception) throw exception #endif #else #include #ifndef INJA_THROW -#define INJA_THROW(exception) \ - std::abort(); \ - std::ignore = exception +#define INJA_THROW(exception) \ + std::abort(); \ + std::ignore = exception #endif #ifndef INJA_NOEXCEPTION #define INJA_NOEXCEPTION @@ -62,7 +60,7 @@ using json = INJA_DATA_TYPE; #include #include #include -// #include +#include "string_view.hpp" // #include "config.hpp" #ifndef INCLUDE_INJA_CONFIG_HPP_ @@ -85,163 +83,147 @@ using json = INJA_DATA_TYPE; #define INCLUDE_INJA_NODE_HPP_ #include -// #include +#include "string_view.hpp" #include // #include "function_storage.hpp" #ifndef INCLUDE_INJA_FUNCTION_STORAGE_HPP_ #define INCLUDE_INJA_FUNCTION_STORAGE_HPP_ -// #include +#include "string_view.hpp" #include namespace inja { -using Arguments = std::vector; -using CallbackFunction = std::function; -using VoidCallbackFunction = std::function; +using Arguments = std::vector; +using CallbackFunction = std::function; +using VoidCallbackFunction = std::function; /*! * \brief Class for builtin functions and user-defined callbacks. */ class FunctionStorage { public: - enum class Operation { - Not, - And, - Or, - In, - Equal, - NotEqual, - Greater, - GreaterEqual, - Less, - LessEqual, - Add, - Subtract, - Multiplication, - Division, - Power, - Modulo, - AtId, - At, - Default, - DivisibleBy, - Even, - Exists, - ExistsInObject, - First, - Float, - Int, - IsArray, - IsBoolean, - IsFloat, - IsInteger, - IsNumber, - IsObject, - IsString, - Last, - Length, - Lower, - Max, - Min, - Odd, - Range, - Round, - Sort, - Upper, - Super, - Join, - Callback, - None, - }; + enum class Operation { + Not, + And, + Or, + In, + Equal, + NotEqual, + Greater, + GreaterEqual, + Less, + LessEqual, + Add, + Subtract, + Multiplication, + Division, + Power, + Modulo, + AtId, + At, + Default, + DivisibleBy, + Even, + Exists, + ExistsInObject, + First, + Float, + Int, + IsArray, + IsBoolean, + IsFloat, + IsInteger, + IsNumber, + IsObject, + IsString, + Last, + Length, + Lower, + Max, + Min, + Odd, + Range, + Round, + Sort, + Upper, + Super, + Join, + Callback, + None, + }; - struct FunctionData { - explicit FunctionData(const Operation &op, - const CallbackFunction &cb = CallbackFunction{}) - : operation(op), - callback(cb) - {} - - const Operation operation; - const CallbackFunction callback; - }; + struct FunctionData { + explicit FunctionData(const Operation& op, const CallbackFunction& cb = CallbackFunction {}): operation(op), callback(cb) {} + const Operation operation; + const CallbackFunction callback; + }; private: - const int VARIADIC{-1}; + const int VARIADIC {-1}; - std::map, FunctionData> function_storage = { - {std::make_pair("at", 2), FunctionData{Operation::At}}, - {std::make_pair("default", 2), FunctionData{Operation::Default}}, - {std::make_pair("divisibleBy", 2), - FunctionData{Operation::DivisibleBy}}, - {std::make_pair("even", 1), FunctionData{Operation::Even}}, - {std::make_pair("exists", 1), FunctionData{Operation::Exists}}, - {std::make_pair("existsIn", 2), - FunctionData{Operation::ExistsInObject}}, - {std::make_pair("first", 1), FunctionData{Operation::First}}, - {std::make_pair("float", 1), FunctionData{Operation::Float}}, - {std::make_pair("int", 1), FunctionData{Operation::Int}}, - {std::make_pair("isArray", 1), FunctionData{Operation::IsArray}}, - {std::make_pair("isBoolean", 1), FunctionData{Operation::IsBoolean}}, - {std::make_pair("isFloat", 1), FunctionData{Operation::IsFloat}}, - {std::make_pair("isInteger", 1), FunctionData{Operation::IsInteger}}, - {std::make_pair("isNumber", 1), FunctionData{Operation::IsNumber}}, - {std::make_pair("isObject", 1), FunctionData{Operation::IsObject}}, - {std::make_pair("isString", 1), FunctionData{Operation::IsString}}, - {std::make_pair("last", 1), FunctionData{Operation::Last}}, - {std::make_pair("length", 1), FunctionData{Operation::Length}}, - {std::make_pair("lower", 1), FunctionData{Operation::Lower}}, - {std::make_pair("max", 1), FunctionData{Operation::Max}}, - {std::make_pair("min", 1), FunctionData{Operation::Min}}, - {std::make_pair("odd", 1), FunctionData{Operation::Odd}}, - {std::make_pair("range", 1), FunctionData{Operation::Range}}, - {std::make_pair("round", 2), FunctionData{Operation::Round}}, - {std::make_pair("sort", 1), FunctionData{Operation::Sort}}, - {std::make_pair("upper", 1), FunctionData{Operation::Upper}}, - {std::make_pair("super", 0), FunctionData{Operation::Super}}, - {std::make_pair("super", 1), FunctionData{Operation::Super}}, - {std::make_pair("join", 2), FunctionData{Operation::Join}}, - }; + std::map, FunctionData> function_storage = { + {std::make_pair("at", 2), FunctionData {Operation::At}}, + {std::make_pair("default", 2), FunctionData {Operation::Default}}, + {std::make_pair("divisibleBy", 2), FunctionData {Operation::DivisibleBy}}, + {std::make_pair("even", 1), FunctionData {Operation::Even}}, + {std::make_pair("exists", 1), FunctionData {Operation::Exists}}, + {std::make_pair("existsIn", 2), FunctionData {Operation::ExistsInObject}}, + {std::make_pair("first", 1), FunctionData {Operation::First}}, + {std::make_pair("float", 1), FunctionData {Operation::Float}}, + {std::make_pair("int", 1), FunctionData {Operation::Int}}, + {std::make_pair("isArray", 1), FunctionData {Operation::IsArray}}, + {std::make_pair("isBoolean", 1), FunctionData {Operation::IsBoolean}}, + {std::make_pair("isFloat", 1), FunctionData {Operation::IsFloat}}, + {std::make_pair("isInteger", 1), FunctionData {Operation::IsInteger}}, + {std::make_pair("isNumber", 1), FunctionData {Operation::IsNumber}}, + {std::make_pair("isObject", 1), FunctionData {Operation::IsObject}}, + {std::make_pair("isString", 1), FunctionData {Operation::IsString}}, + {std::make_pair("last", 1), FunctionData {Operation::Last}}, + {std::make_pair("length", 1), FunctionData {Operation::Length}}, + {std::make_pair("lower", 1), FunctionData {Operation::Lower}}, + {std::make_pair("max", 1), FunctionData {Operation::Max}}, + {std::make_pair("min", 1), FunctionData {Operation::Min}}, + {std::make_pair("odd", 1), FunctionData {Operation::Odd}}, + {std::make_pair("range", 1), FunctionData {Operation::Range}}, + {std::make_pair("round", 2), FunctionData {Operation::Round}}, + {std::make_pair("sort", 1), FunctionData {Operation::Sort}}, + {std::make_pair("upper", 1), FunctionData {Operation::Upper}}, + {std::make_pair("super", 0), FunctionData {Operation::Super}}, + {std::make_pair("super", 1), FunctionData {Operation::Super}}, + {std::make_pair("join", 2), FunctionData {Operation::Join}}, + }; public: - void add_builtin(std::string name, int num_args, Operation op) - { - function_storage.emplace( - std::make_pair(static_cast(name), num_args), - FunctionData{op}); + void add_builtin(nonstd::string_view name, int num_args, Operation op) { + function_storage.emplace(std::make_pair(static_cast(name), num_args), FunctionData {op}); + } + + void add_callback(nonstd::string_view name, int num_args, const CallbackFunction& callback) { + function_storage.emplace(std::make_pair(static_cast(name), num_args), FunctionData {Operation::Callback, callback}); + } + + FunctionData find_function(nonstd::string_view name, int num_args) const { + auto it = function_storage.find(std::make_pair(static_cast(name), num_args)); + if (it != function_storage.end()) { + return it->second; + + // Find variadic function + } else if (num_args > 0) { + it = function_storage.find(std::make_pair(static_cast(name), VARIADIC)); + if (it != function_storage.end()) { + return it->second; + } } - void add_callback(std::string name, - int num_args, - const CallbackFunction &callback) - { - function_storage.emplace( - std::make_pair(static_cast(name), num_args), - FunctionData{Operation::Callback, callback}); - } - - FunctionData find_function(std::string name, int num_args) const - { - auto it = function_storage.find( - std::make_pair(static_cast(name), num_args)); - if (it != function_storage.end()) { - return it->second; - - // Find variadic function - } else if (num_args > 0) { - it = function_storage.find( - std::make_pair(static_cast(name), VARIADIC)); - if (it != function_storage.end()) { return it->second; } - } - - return FunctionData{Operation::None}; - } + return FunctionData {Operation::None}; + } }; -}// namespace inja +} // namespace inja -#endif// INCLUDE_INJA_FUNCTION_STORAGE_HPP_ +#endif // INCLUDE_INJA_FUNCTION_STORAGE_HPP_ // #include "utils.hpp" #ifndef INCLUDE_INJA_UTILS_HPP_ @@ -250,7 +232,7 @@ public: #include #include #include -#include +#include "string_view.hpp" #include // #include "exceptions.hpp" @@ -263,132 +245,106 @@ public: namespace inja { struct SourceLocation { - size_t line; - size_t column; + size_t line; + size_t column; }; struct InjaError : public std::runtime_error { - const std::string type; - const std::string message; + const std::string type; + const std::string message; - const SourceLocation location; + const SourceLocation location; - explicit InjaError(const std::string &type, const std::string &message) - : std::runtime_error("[inja.exception." + type + "] " + message), - type(type), - message(message), - location({0, 0}) - {} + explicit InjaError(const std::string& type, const std::string& message) + : std::runtime_error("[inja.exception." + type + "] " + message), type(type), message(message), location({0, 0}) {} - explicit InjaError(const std::string &type, - const std::string &message, - SourceLocation location) - : std::runtime_error( - "[inja.exception." + type + "] (at " + std::to_string(location.line) - + ":" + std::to_string(location.column) + ") " + message), - type(type), - message(message), - location(location) - {} + explicit InjaError(const std::string& type, const std::string& message, SourceLocation location) + : std::runtime_error("[inja.exception." + type + "] (at " + std::to_string(location.line) + ":" + std::to_string(location.column) + ") " + message), + type(type), message(message), location(location) {} }; struct ParserError : public InjaError { - explicit ParserError(const std::string &message, SourceLocation location) - : InjaError("parser_error", message, location) - {} + explicit ParserError(const std::string& message, SourceLocation location): InjaError("parser_error", message, location) {} }; struct RenderError : public InjaError { - explicit RenderError(const std::string &message, SourceLocation location) - : InjaError("render_error", message, location) - {} + explicit RenderError(const std::string& message, SourceLocation location): InjaError("render_error", message, location) {} }; struct FileError : public InjaError { - explicit FileError(const std::string &message) - : InjaError("file_error", message) - {} - - explicit FileError(const std::string &message, SourceLocation location) - : InjaError("file_error", message, location) - {} + explicit FileError(const std::string& message): InjaError("file_error", message) {} + explicit FileError(const std::string& message, SourceLocation location): InjaError("file_error", message, location) {} }; struct DataError : public InjaError { - explicit DataError(const std::string &message, SourceLocation location) - : InjaError("data_error", message, location) - {} + explicit DataError(const std::string& message, SourceLocation location): InjaError("data_error", message, location) {} }; -}// namespace inja +} // namespace inja + +#endif // INCLUDE_INJA_EXCEPTIONS_HPP_ -#endif// INCLUDE_INJA_EXCEPTIONS_HPP_ namespace inja { -namespace string { -inline std::string -slice(std::string view, size_t start, size_t end) -{ - start = std::min(start, view.size()); - end = std::min(std::max(start, end), view.size()); - return view.substr(start, end - start); +namespace string_view { +inline nonstd::string_view slice(nonstd::string_view view, size_t start, size_t end) { + start = std::min(start, view.size()); + end = std::min(std::max(start, end), view.size()); + return view.substr(start, end - start); } -inline std::pair -split(std::string view, char Separator) -{ - size_t idx = view.find(Separator); - if (idx == std::string::npos) { - return std::make_pair(view, std::string()); +inline std::pair split(nonstd::string_view view, char Separator) { + size_t idx = view.find(Separator); + if (idx == nonstd::string_view::npos) { + return std::make_pair(view, nonstd::string_view()); + } + return std::make_pair(slice(view, 0, idx), slice(view, idx + 1, nonstd::string_view::npos)); +} + +inline bool starts_with(nonstd::string_view view, nonstd::string_view prefix) { + return (view.size() >= prefix.size() && view.compare(0, prefix.size(), prefix) == 0); +} +} // namespace string_view + +inline SourceLocation get_source_location(nonstd::string_view content, size_t pos) { + // Get line and offset position (starts at 1:1) + auto sliced = string_view::slice(content, 0, pos); + std::size_t last_newline = sliced.rfind("\n"); + + if (last_newline == nonstd::string_view::npos) { + return {1, sliced.length() + 1}; + } + + // Count newlines + size_t count_lines = 0; + size_t search_start = 0; + while (search_start <= sliced.size()) { + search_start = sliced.find("\n", search_start) + 1; + if (search_start == 0) { + break; } - return std::make_pair(slice(view, 0, idx), - slice(view, idx + 1, std::string::npos)); + count_lines += 1; + } + + return {count_lines + 1, sliced.length() - last_newline}; } -inline bool -starts_with(std::string view, std::string prefix) -{ - return (view.size() >= prefix.size() - && view.compare(0, prefix.size(), prefix) == 0); -} -}// namespace string - -inline SourceLocation -get_source_location(std::string content, size_t pos) -{ - // Get line and offset position (starts at 1:1) - auto sliced = string::slice(content, 0, pos); - std::size_t last_newline = sliced.rfind("\n"); - - if (last_newline == std::string::npos) { return {1, sliced.length() + 1}; } - - // Count newlines - size_t count_lines = 0; - size_t search_start = 0; - while (search_start <= sliced.size()) { - search_start = sliced.find("\n", search_start) + 1; - if (search_start == 0) { break; } - count_lines += 1; - } - - return {count_lines + 1, sliced.length() - last_newline}; +inline void replace_substring(std::string& s, const std::string& f, const std::string& t) { + if (f.empty()) { + return; + } + for (auto pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} } -inline void -replace_substring(std::string &s, const std::string &f, const std::string &t) -{ - if (f.empty()) { return; } - for (auto pos = s.find(f); // find first occurrence of f - pos != std::string::npos; // make sure f was found - s.replace(pos, f.size(), t), // replace with t, and - pos = s.find(f, pos + t.size()))// find next occurrence of f - {} -} +} // namespace inja -}// namespace inja +#endif // INCLUDE_INJA_UTILS_HPP_ -#endif// INCLUDE_INJA_UTILS_HPP_ namespace inja { @@ -412,24 +368,24 @@ class SetStatementNode; class NodeVisitor { public: - virtual ~NodeVisitor() = default; + virtual ~NodeVisitor() = default; - virtual void visit(const BlockNode &node) = 0; - virtual void visit(const TextNode &node) = 0; - virtual void visit(const ExpressionNode &node) = 0; - virtual void visit(const LiteralNode &node) = 0; - virtual void visit(const DataNode &node) = 0; - virtual void visit(const FunctionNode &node) = 0; - virtual void visit(const ExpressionListNode &node) = 0; - virtual void visit(const StatementNode &node) = 0; - virtual void visit(const ForStatementNode &node) = 0; - virtual void visit(const ForArrayStatementNode &node) = 0; - virtual void visit(const ForObjectStatementNode &node) = 0; - virtual void visit(const IfStatementNode &node) = 0; - virtual void visit(const IncludeStatementNode &node) = 0; - virtual void visit(const ExtendsStatementNode &node) = 0; - virtual void visit(const BlockStatementNode &node) = 0; - virtual void visit(const SetStatementNode &node) = 0; + virtual void visit(const BlockNode& node) = 0; + virtual void visit(const TextNode& node) = 0; + virtual void visit(const ExpressionNode& node) = 0; + virtual void visit(const LiteralNode& node) = 0; + virtual void visit(const DataNode& node) = 0; + virtual void visit(const FunctionNode& node) = 0; + virtual void visit(const ExpressionListNode& node) = 0; + virtual void visit(const StatementNode& node) = 0; + virtual void visit(const ForStatementNode& node) = 0; + virtual void visit(const ForArrayStatementNode& node) = 0; + virtual void visit(const ForObjectStatementNode& node) = 0; + virtual void visit(const IfStatementNode& node) = 0; + virtual void visit(const IncludeStatementNode& node) = 0; + virtual void visit(const ExtendsStatementNode& node) = 0; + virtual void visit(const BlockStatementNode& node) = 0; + virtual void visit(const SetStatementNode& node) = 0; }; /*! @@ -437,355 +393,321 @@ public: */ class AstNode { public: - virtual void accept(NodeVisitor &v) const = 0; + virtual void accept(NodeVisitor& v) const = 0; - size_t pos; + size_t pos; - AstNode(size_t pos) : pos(pos) {} - - virtual ~AstNode() {} + AstNode(size_t pos): pos(pos) {} + virtual ~AstNode() {} }; class BlockNode : public AstNode { public: - std::vector> nodes; + std::vector> nodes; - explicit BlockNode() : AstNode(0) {} + explicit BlockNode(): AstNode(0) {} - void accept(NodeVisitor &v) const { v.visit(*this); } + void accept(NodeVisitor& v) const { + v.visit(*this); + } }; class TextNode : public AstNode { public: - const size_t length; + const size_t length; - explicit TextNode(size_t pos, size_t length) : AstNode(pos), length(length) - {} + explicit TextNode(size_t pos, size_t length): AstNode(pos), length(length) {} - void accept(NodeVisitor &v) const { v.visit(*this); } + void accept(NodeVisitor& v) const { + v.visit(*this); + } }; class ExpressionNode : public AstNode { public: - explicit ExpressionNode(size_t pos) : AstNode(pos) {} + explicit ExpressionNode(size_t pos): AstNode(pos) {} - void accept(NodeVisitor &v) const { v.visit(*this); } + void accept(NodeVisitor& v) const { + v.visit(*this); + } }; class LiteralNode : public ExpressionNode { public: - const json value; + const json value; - explicit LiteralNode(std::string data_text, size_t pos) - : ExpressionNode(pos), - value(json::parse(data_text)) - {} + explicit LiteralNode(nonstd::string_view data_text, size_t pos): ExpressionNode(pos), value(json::parse(data_text)) {} - void accept(NodeVisitor &v) const { v.visit(*this); } + void accept(NodeVisitor& v) const { + v.visit(*this); + } }; class DataNode : public ExpressionNode { public: - const std::string name; - const json::json_pointer ptr; + const std::string name; + const json::json_pointer ptr; - static std::string convert_dot_to_ptr(std::string ptr_name) - { - std::string result; - do { - std::string part; - std::tie(part, ptr_name) = string::split(ptr_name, '.'); - result.push_back('/'); - result.append(part.begin(), part.end()); - } while (!ptr_name.empty()); - return result; - } + static std::string convert_dot_to_ptr(nonstd::string_view ptr_name) { + std::string result; + do { + nonstd::string_view part; + std::tie(part, ptr_name) = string_view::split(ptr_name, '.'); + result.push_back('/'); + result.append(part.begin(), part.end()); + } while (!ptr_name.empty()); + return result; + } - explicit DataNode(std::string ptr_name, size_t pos) - : ExpressionNode(pos), - name(ptr_name), - ptr(json::json_pointer(convert_dot_to_ptr(ptr_name))) - {} + explicit DataNode(nonstd::string_view ptr_name, size_t pos): ExpressionNode(pos), name(ptr_name), ptr(json::json_pointer(convert_dot_to_ptr(ptr_name))) {} - void accept(NodeVisitor &v) const { v.visit(*this); } + void accept(NodeVisitor& v) const { + v.visit(*this); + } }; class FunctionNode : public ExpressionNode { - using Op = FunctionStorage::Operation; + using Op = FunctionStorage::Operation; public: - enum class Associativity { - Left, - Right, - }; + enum class Associativity { + Left, + Right, + }; - unsigned int precedence; - Associativity associativity; + unsigned int precedence; + Associativity associativity; - Op operation; + Op operation; - std::string name; - int number_args;// Can also be negative -> -1 for unknown number - std::vector> arguments; - CallbackFunction callback; + std::string name; + int number_args; // Can also be negative -> -1 for unknown number + std::vector> arguments; + CallbackFunction callback; - explicit FunctionNode(std::string name, size_t pos) - : ExpressionNode(pos), - precedence(8), - associativity(Associativity::Left), - operation(Op::Callback), - name(name), - number_args(0) - {} - - explicit FunctionNode(Op operation, size_t pos) - : ExpressionNode(pos), - operation(operation), - number_args(1) - { - switch (operation) { - case Op::Not: { - number_args = 1; - precedence = 4; - associativity = Associativity::Left; - } break; - case Op::And: { - number_args = 2; - precedence = 1; - associativity = Associativity::Left; - } break; - case Op::Or: { - number_args = 2; - precedence = 1; - associativity = Associativity::Left; - } break; - case Op::In: { - number_args = 2; - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::Equal: { - number_args = 2; - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::NotEqual: { - number_args = 2; - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::Greater: { - number_args = 2; - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::GreaterEqual: { - number_args = 2; - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::Less: { - number_args = 2; - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::LessEqual: { - number_args = 2; - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::Add: { - number_args = 2; - precedence = 3; - associativity = Associativity::Left; - } break; - case Op::Subtract: { - number_args = 2; - precedence = 3; - associativity = Associativity::Left; - } break; - case Op::Multiplication: { - number_args = 2; - precedence = 4; - associativity = Associativity::Left; - } break; - case Op::Division: { - number_args = 2; - precedence = 4; - associativity = Associativity::Left; - } break; - case Op::Power: { - number_args = 2; - precedence = 5; - associativity = Associativity::Right; - } break; - case Op::Modulo: { - number_args = 2; - precedence = 4; - associativity = Associativity::Left; - } break; - case Op::AtId: { - number_args = 2; - precedence = 8; - associativity = Associativity::Left; - } break; - default: { - precedence = 1; - associativity = Associativity::Left; - } - } + explicit FunctionNode(nonstd::string_view name, size_t pos) + : ExpressionNode(pos), precedence(8), associativity(Associativity::Left), operation(Op::Callback), name(name), number_args(0) {} + explicit FunctionNode(Op operation, size_t pos): ExpressionNode(pos), operation(operation), number_args(1) { + switch (operation) { + case Op::Not: { + number_args = 1; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::And: { + number_args = 2; + precedence = 1; + associativity = Associativity::Left; + } break; + case Op::Or: { + number_args = 2; + precedence = 1; + associativity = Associativity::Left; + } break; + case Op::In: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Equal: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::NotEqual: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Greater: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::GreaterEqual: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Less: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::LessEqual: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Add: { + number_args = 2; + precedence = 3; + associativity = Associativity::Left; + } break; + case Op::Subtract: { + number_args = 2; + precedence = 3; + associativity = Associativity::Left; + } break; + case Op::Multiplication: { + number_args = 2; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::Division: { + number_args = 2; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::Power: { + number_args = 2; + precedence = 5; + associativity = Associativity::Right; + } break; + case Op::Modulo: { + number_args = 2; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::AtId: { + number_args = 2; + precedence = 8; + associativity = Associativity::Left; + } break; + default: { + precedence = 1; + associativity = Associativity::Left; } + } + } - void accept(NodeVisitor &v) const { v.visit(*this); } + void accept(NodeVisitor& v) const { + v.visit(*this); + } }; class ExpressionListNode : public AstNode { public: - std::shared_ptr root; + std::shared_ptr root; - explicit ExpressionListNode() : AstNode(0) {} + explicit ExpressionListNode(): AstNode(0) {} + explicit ExpressionListNode(size_t pos): AstNode(pos) {} - explicit ExpressionListNode(size_t pos) : AstNode(pos) {} - - void accept(NodeVisitor &v) const { v.visit(*this); } + void accept(NodeVisitor& v) const { + v.visit(*this); + } }; class StatementNode : public AstNode { public: - StatementNode(size_t pos) : AstNode(pos) {} + StatementNode(size_t pos): AstNode(pos) {} - virtual void accept(NodeVisitor &v) const = 0; + virtual void accept(NodeVisitor& v) const = 0; }; class ForStatementNode : public StatementNode { public: - ExpressionListNode condition; - BlockNode body; - BlockNode *const parent; + ExpressionListNode condition; + BlockNode body; + BlockNode* const parent; - ForStatementNode(BlockNode *const parent, size_t pos) - : StatementNode(pos), - parent(parent) - {} + ForStatementNode(BlockNode* const parent, size_t pos): StatementNode(pos), parent(parent) {} - virtual void accept(NodeVisitor &v) const = 0; + virtual void accept(NodeVisitor& v) const = 0; }; class ForArrayStatementNode : public ForStatementNode { public: - const std::string value; + const std::string value; - explicit ForArrayStatementNode(const std::string &value, - BlockNode *const parent, - size_t pos) - : ForStatementNode(parent, pos), - value(value) - {} + explicit ForArrayStatementNode(const std::string& value, BlockNode* const parent, size_t pos): ForStatementNode(parent, pos), value(value) {} - void accept(NodeVisitor &v) const { v.visit(*this); } + void accept(NodeVisitor& v) const { + v.visit(*this); + } }; class ForObjectStatementNode : public ForStatementNode { public: - const std::string key; - const std::string value; + const std::string key; + const std::string value; - explicit ForObjectStatementNode(const std::string &key, - const std::string &value, - BlockNode *const parent, - size_t pos) - : ForStatementNode(parent, pos), - key(key), - value(value) - {} + explicit ForObjectStatementNode(const std::string& key, const std::string& value, BlockNode* const parent, size_t pos) + : ForStatementNode(parent, pos), key(key), value(value) {} - void accept(NodeVisitor &v) const { v.visit(*this); } + void accept(NodeVisitor& v) const { + v.visit(*this); + } }; class IfStatementNode : public StatementNode { public: - ExpressionListNode condition; - BlockNode true_statement; - BlockNode false_statement; - BlockNode *const parent; + ExpressionListNode condition; + BlockNode true_statement; + BlockNode false_statement; + BlockNode* const parent; - const bool is_nested; - bool has_false_statement{false}; + const bool is_nested; + bool has_false_statement {false}; - explicit IfStatementNode(BlockNode *const parent, size_t pos) - : StatementNode(pos), - parent(parent), - is_nested(false) - {} + explicit IfStatementNode(BlockNode* const parent, size_t pos): StatementNode(pos), parent(parent), is_nested(false) {} + explicit IfStatementNode(bool is_nested, BlockNode* const parent, size_t pos): StatementNode(pos), parent(parent), is_nested(is_nested) {} - explicit IfStatementNode(bool is_nested, - BlockNode *const parent, - size_t pos) - : StatementNode(pos), - parent(parent), - is_nested(is_nested) - {} - - void accept(NodeVisitor &v) const { v.visit(*this); } + void accept(NodeVisitor& v) const { + v.visit(*this); + } }; class IncludeStatementNode : public StatementNode { public: - const std::string file; + const std::string file; - explicit IncludeStatementNode(const std::string &file, size_t pos) - : StatementNode(pos), - file(file) - {} + explicit IncludeStatementNode(const std::string& file, size_t pos): StatementNode(pos), file(file) {} - void accept(NodeVisitor &v) const { v.visit(*this); } + void accept(NodeVisitor& v) const { + v.visit(*this); + } }; class ExtendsStatementNode : public StatementNode { public: - const std::string file; + const std::string file; - explicit ExtendsStatementNode(const std::string &file, size_t pos) - : StatementNode(pos), - file(file) - {} + explicit ExtendsStatementNode(const std::string& file, size_t pos): StatementNode(pos), file(file) {} - void accept(NodeVisitor &v) const { v.visit(*this); }; + void accept(NodeVisitor& v) const { + v.visit(*this); + }; }; class BlockStatementNode : public StatementNode { public: - const std::string name; - BlockNode block; - BlockNode *const parent; + const std::string name; + BlockNode block; + BlockNode* const parent; - explicit BlockStatementNode(BlockNode *const parent, - const std::string &name, - size_t pos) - : StatementNode(pos), - name(name), - parent(parent) - {} + explicit BlockStatementNode(BlockNode* const parent, const std::string& name, size_t pos): StatementNode(pos), name(name), parent(parent) {} - void accept(NodeVisitor &v) const { v.visit(*this); }; + void accept(NodeVisitor& v) const { + v.visit(*this); + }; }; class SetStatementNode : public StatementNode { public: - const std::string key; - ExpressionListNode expression; + const std::string key; + ExpressionListNode expression; - explicit SetStatementNode(const std::string &key, size_t pos) - : StatementNode(pos), - key(key) - {} + explicit SetStatementNode(const std::string& key, size_t pos): StatementNode(pos), key(key) {} - void accept(NodeVisitor &v) const { v.visit(*this); } + void accept(NodeVisitor& v) const { + v.visit(*this); + } }; -}// namespace inja +} // namespace inja -#endif// INCLUDE_INJA_NODE_HPP_ +#endif // INCLUDE_INJA_NODE_HPP_ // #include "statistics.hpp" #ifndef INCLUDE_INJA_STATISTICS_HPP_ @@ -793,72 +715,76 @@ public: // #include "node.hpp" + namespace inja { /*! * \brief A class for counting statistics on a Template. */ class StatisticsVisitor : public NodeVisitor { - void visit(const BlockNode &node) - { - for (auto &n : node.nodes) { n->accept(*this); } + void visit(const BlockNode& node) { + for (auto& n : node.nodes) { + n->accept(*this); } + } - void visit(const TextNode &) {} + void visit(const TextNode&) {} + void visit(const ExpressionNode&) {} + void visit(const LiteralNode&) {} - void visit(const ExpressionNode &) {} + void visit(const DataNode&) { + variable_counter += 1; + } - void visit(const LiteralNode &) {} - - void visit(const DataNode &) { variable_counter += 1; } - - void visit(const FunctionNode &node) - { - for (auto &n : node.arguments) { n->accept(*this); } + void visit(const FunctionNode& node) { + for (auto& n : node.arguments) { + n->accept(*this); } + } - void visit(const ExpressionListNode &node) { node.root->accept(*this); } + void visit(const ExpressionListNode& node) { + node.root->accept(*this); + } - void visit(const StatementNode &) {} + void visit(const StatementNode&) {} + void visit(const ForStatementNode&) {} - void visit(const ForStatementNode &) {} + void visit(const ForArrayStatementNode& node) { + node.condition.accept(*this); + node.body.accept(*this); + } - void visit(const ForArrayStatementNode &node) - { - node.condition.accept(*this); - node.body.accept(*this); - } + void visit(const ForObjectStatementNode& node) { + node.condition.accept(*this); + node.body.accept(*this); + } - void visit(const ForObjectStatementNode &node) - { - node.condition.accept(*this); - node.body.accept(*this); - } + void visit(const IfStatementNode& node) { + node.condition.accept(*this); + node.true_statement.accept(*this); + node.false_statement.accept(*this); + } - void visit(const IfStatementNode &node) - { - node.condition.accept(*this); - node.true_statement.accept(*this); - node.false_statement.accept(*this); - } + void visit(const IncludeStatementNode&) {} - void visit(const IncludeStatementNode &) {} + void visit(const ExtendsStatementNode&) {} - void visit(const ExtendsStatementNode &) {} + void visit(const BlockStatementNode& node) { + node.block.accept(*this); + } - void visit(const BlockStatementNode &node) { node.block.accept(*this); } - - void visit(const SetStatementNode &) {} + void visit(const SetStatementNode&) {} public: - unsigned int variable_counter; + unsigned int variable_counter; - explicit StatisticsVisitor() : variable_counter(0) {} + explicit StatisticsVisitor(): variable_counter(0) {} }; -}// namespace inja +} // namespace inja + +#endif // INCLUDE_INJA_STATISTICS_HPP_ -#endif// INCLUDE_INJA_STATISTICS_HPP_ namespace inja { @@ -866,28 +792,27 @@ namespace inja { * \brief The main inja Template. */ struct Template { - BlockNode root; - std::string content; - std::map> block_storage; + BlockNode root; + std::string content; + std::map> block_storage; - explicit Template() {} + explicit Template() {} + explicit Template(const std::string& content): content(content) {} - explicit Template(const std::string &content) : content(content) {} - - /// Return number of variables (total number, not distinct ones) in the template - int count_variables() - { - auto statistic_visitor = StatisticsVisitor(); - root.accept(statistic_visitor); - return statistic_visitor.variable_counter; - } + /// Return number of variables (total number, not distinct ones) in the template + int count_variables() { + auto statistic_visitor = StatisticsVisitor(); + root.accept(statistic_visitor); + return statistic_visitor.variable_counter; + } }; using TemplateStorage = std::map; -}// namespace inja +} // namespace inja + +#endif // INCLUDE_INJA_TEMPLATE_HPP_ -#endif// INCLUDE_INJA_TEMPLATE_HPP_ namespace inja { @@ -895,78 +820,73 @@ namespace inja { * \brief Class for lexer configuration. */ struct LexerConfig { - std::string statement_open{"{%"}; - std::string statement_open_no_lstrip{"{%+"}; - std::string statement_open_force_lstrip{"{%-"}; - std::string statement_close{"%}"}; - std::string statement_close_force_rstrip{"-%}"}; - std::string line_statement{"##"}; - std::string expression_open{"{{"}; - std::string expression_open_force_lstrip{"{{-"}; - std::string expression_close{"}}"}; - std::string expression_close_force_rstrip{"-}}"}; - std::string comment_open{"{#"}; - std::string comment_open_force_lstrip{"{#-"}; - std::string comment_close{"#}"}; - std::string comment_close_force_rstrip{"-#}"}; - std::string open_chars{"#{"}; + std::string statement_open {"{%"}; + std::string statement_open_no_lstrip {"{%+"}; + std::string statement_open_force_lstrip {"{%-"}; + std::string statement_close {"%}"}; + std::string statement_close_force_rstrip {"-%}"}; + std::string line_statement {"##"}; + std::string expression_open {"{{"}; + std::string expression_open_force_lstrip {"{{-"}; + std::string expression_close {"}}"}; + std::string expression_close_force_rstrip {"-}}"}; + std::string comment_open {"{#"}; + std::string comment_open_force_lstrip {"{#-"}; + std::string comment_close {"#}"}; + std::string comment_close_force_rstrip {"-#}"}; + std::string open_chars {"#{"}; - bool trim_blocks{false}; - bool lstrip_blocks{false}; + bool trim_blocks {false}; + bool lstrip_blocks {false}; - void update_open_chars() - { - open_chars = ""; - if (open_chars.find(line_statement[0]) == std::string::npos) { - open_chars += line_statement[0]; - } - if (open_chars.find(statement_open[0]) == std::string::npos) { - open_chars += statement_open[0]; - } - if (open_chars.find(statement_open_no_lstrip[0]) == std::string::npos) { - open_chars += statement_open_no_lstrip[0]; - } - if (open_chars.find(statement_open_force_lstrip[0]) - == std::string::npos) { - open_chars += statement_open_force_lstrip[0]; - } - if (open_chars.find(expression_open[0]) == std::string::npos) { - open_chars += expression_open[0]; - } - if (open_chars.find(expression_open_force_lstrip[0]) - == std::string::npos) { - open_chars += expression_open_force_lstrip[0]; - } - if (open_chars.find(comment_open[0]) == std::string::npos) { - open_chars += comment_open[0]; - } - if (open_chars.find(comment_open_force_lstrip[0]) - == std::string::npos) { - open_chars += comment_open_force_lstrip[0]; - } + void update_open_chars() { + open_chars = ""; + if (open_chars.find(line_statement[0]) == std::string::npos) { + open_chars += line_statement[0]; } + if (open_chars.find(statement_open[0]) == std::string::npos) { + open_chars += statement_open[0]; + } + if (open_chars.find(statement_open_no_lstrip[0]) == std::string::npos) { + open_chars += statement_open_no_lstrip[0]; + } + if (open_chars.find(statement_open_force_lstrip[0]) == std::string::npos) { + open_chars += statement_open_force_lstrip[0]; + } + if (open_chars.find(expression_open[0]) == std::string::npos) { + open_chars += expression_open[0]; + } + if (open_chars.find(expression_open_force_lstrip[0]) == std::string::npos) { + open_chars += expression_open_force_lstrip[0]; + } + if (open_chars.find(comment_open[0]) == std::string::npos) { + open_chars += comment_open[0]; + } + if (open_chars.find(comment_open_force_lstrip[0]) == std::string::npos) { + open_chars += comment_open_force_lstrip[0]; + } + } }; /*! * \brief Class for parser configuration. */ struct ParserConfig { - bool search_included_templates_in_files{true}; + bool search_included_templates_in_files {true}; - std::function - include_callback; + std::function include_callback; }; /*! * \brief Class for render configuration. */ struct RenderConfig { - bool throw_at_missing_includes{true}; + bool throw_at_missing_includes {true}; }; -}// namespace inja +} // namespace inja -#endif// INCLUDE_INJA_CONFIG_HPP_ +#endif // INCLUDE_INJA_CONFIG_HPP_ // #include "function_storage.hpp" @@ -1000,7 +920,7 @@ struct RenderConfig { #define INCLUDE_INJA_TOKEN_HPP_ #include -#include +#include "string_view.hpp" namespace inja { @@ -1008,523 +928,496 @@ namespace inja { * \brief Helper-class for the inja Lexer. */ struct Token { - enum class Kind { - Text, - ExpressionOpen, // {{ - ExpressionClose, // }} - LineStatementOpen, // ## - LineStatementClose,// \n - StatementOpen, // {% - StatementClose, // %} - CommentOpen, // {# - CommentClose, // #} - Id, // this, this.foo - Number, // 1, 2, -1, 5.2, -5.3 - String, // "this" - Plus, // + - Minus, // - - Times, // * - Slash, // / - Percent, // % - Power, // ^ - Comma, // , - Dot, // . - Colon, // : - LeftParen, // ( - RightParen, // ) - LeftBracket, // [ - RightBracket, // ] - LeftBrace, // { - RightBrace, // } - Equal, // == - NotEqual, // != - GreaterThan, // > - GreaterEqual, // >= - LessThan, // < - LessEqual, // <= - Unknown, - Eof, - }; + enum class Kind { + Text, + ExpressionOpen, // {{ + ExpressionClose, // }} + LineStatementOpen, // ## + LineStatementClose, // \n + StatementOpen, // {% + StatementClose, // %} + CommentOpen, // {# + CommentClose, // #} + Id, // this, this.foo + Number, // 1, 2, -1, 5.2, -5.3 + String, // "this" + Plus, // + + Minus, // - + Times, // * + Slash, // / + Percent, // % + Power, // ^ + Comma, // , + Dot, // . + Colon, // : + LeftParen, // ( + RightParen, // ) + LeftBracket, // [ + RightBracket, // ] + LeftBrace, // { + RightBrace, // } + Equal, // == + NotEqual, // != + GreaterThan, // > + GreaterEqual, // >= + LessThan, // < + LessEqual, // <= + Unknown, + Eof, + }; - Kind kind{Kind::Unknown}; - std::string text; + Kind kind {Kind::Unknown}; + nonstd::string_view text; - explicit Token() = default; + explicit constexpr Token() = default; + explicit constexpr Token(Kind kind, nonstd::string_view text): kind(kind), text(text) {} - explicit Token(Kind kind, std::string text) : kind(kind), text(text) {} - - std::string describe() const - { - switch (kind) { - case Kind::Text: - return ""; - case Kind::LineStatementClose: - return ""; - case Kind::Eof: - return ""; - default: - return static_cast(text); - } + std::string describe() const { + switch (kind) { + case Kind::Text: + return ""; + case Kind::LineStatementClose: + return ""; + case Kind::Eof: + return ""; + default: + return static_cast(text); } + } }; -}// namespace inja +} // namespace inja -#endif// INCLUDE_INJA_TOKEN_HPP_ +#endif // INCLUDE_INJA_TOKEN_HPP_ // #include "utils.hpp" + namespace inja { /*! * \brief Class for lexing an inja Template. */ class Lexer { - enum class State { - Text, - ExpressionStart, - ExpressionStartForceLstrip, - ExpressionBody, - LineStart, - LineBody, - StatementStart, - StatementStartNoLstrip, - StatementStartForceLstrip, - StatementBody, - CommentStart, - CommentStartForceLstrip, - CommentBody, - }; + enum class State { + Text, + ExpressionStart, + ExpressionStartForceLstrip, + ExpressionBody, + LineStart, + LineBody, + StatementStart, + StatementStartNoLstrip, + StatementStartForceLstrip, + StatementBody, + CommentStart, + CommentStartForceLstrip, + CommentBody, + }; - enum class MinusState { - Operator, - Number, - }; + enum class MinusState { + Operator, + Number, + }; - const LexerConfig &config; + const LexerConfig& config; - State state; - MinusState minus_state; - std::string m_in; - size_t tok_start; - size_t pos; + State state; + MinusState minus_state; + nonstd::string_view m_in; + size_t tok_start; + size_t pos; - Token scan_body(std::string close, - Token::Kind closeKind, - std::string close_trim = std::string(), - bool trim = false) - { - again: - // skip whitespace (except for \n as it might be a close) - if (tok_start >= m_in.size()) { return make_token(Token::Kind::Eof); } - const char ch = m_in[tok_start]; - if (ch == ' ' || ch == '\t' || ch == '\r') { - tok_start += 1; - goto again; - } - - // check for close - if (!close_trim.empty() - && inja::string::starts_with(m_in.substr(tok_start), close_trim)) { - state = State::Text; - pos = tok_start + close_trim.size(); - const Token tok = make_token(closeKind); - skip_whitespaces_and_newlines(); - return tok; - } - - if (inja::string::starts_with(m_in.substr(tok_start), close)) { - state = State::Text; - pos = tok_start + close.size(); - const Token tok = make_token(closeKind); - if (trim) { skip_whitespaces_and_first_newline(); } - return tok; - } - - // skip \n - if (ch == '\n') { - tok_start += 1; - goto again; - } - - pos = tok_start + 1; - if (std::isalpha(ch)) { - minus_state = MinusState::Operator; - return scan_id(); - } - - const MinusState current_minus_state = minus_state; - if (minus_state == MinusState::Operator) { - minus_state = MinusState::Number; - } - - switch (ch) { - case '+': - return make_token(Token::Kind::Plus); - case '-': - if (current_minus_state == MinusState::Operator) { - return make_token(Token::Kind::Minus); - } - return scan_number(); - case '*': - return make_token(Token::Kind::Times); - case '/': - return make_token(Token::Kind::Slash); - case '^': - return make_token(Token::Kind::Power); - case '%': - return make_token(Token::Kind::Percent); - case '.': - return make_token(Token::Kind::Dot); - case ',': - return make_token(Token::Kind::Comma); - case ':': - return make_token(Token::Kind::Colon); - case '(': - return make_token(Token::Kind::LeftParen); - case ')': - minus_state = MinusState::Operator; - return make_token(Token::Kind::RightParen); - case '[': - return make_token(Token::Kind::LeftBracket); - case ']': - minus_state = MinusState::Operator; - return make_token(Token::Kind::RightBracket); - case '{': - return make_token(Token::Kind::LeftBrace); - case '}': - minus_state = MinusState::Operator; - return make_token(Token::Kind::RightBrace); - case '>': - if (pos < m_in.size() && m_in[pos] == '=') { - pos += 1; - return make_token(Token::Kind::GreaterEqual); - } - return make_token(Token::Kind::GreaterThan); - case '<': - if (pos < m_in.size() && m_in[pos] == '=') { - pos += 1; - return make_token(Token::Kind::LessEqual); - } - return make_token(Token::Kind::LessThan); - case '=': - if (pos < m_in.size() && m_in[pos] == '=') { - pos += 1; - return make_token(Token::Kind::Equal); - } - return make_token(Token::Kind::Unknown); - case '!': - if (pos < m_in.size() && m_in[pos] == '=') { - pos += 1; - return make_token(Token::Kind::NotEqual); - } - return make_token(Token::Kind::Unknown); - case '\"': - return scan_string(); - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - minus_state = MinusState::Operator; - return scan_number(); - case '_': - case '@': - case '$': - minus_state = MinusState::Operator; - return scan_id(); - default: - return make_token(Token::Kind::Unknown); - } + Token scan_body(nonstd::string_view close, Token::Kind closeKind, nonstd::string_view close_trim = nonstd::string_view(), bool trim = false) { + again: + // skip whitespace (except for \n as it might be a close) + if (tok_start >= m_in.size()) { + return make_token(Token::Kind::Eof); + } + const char ch = m_in[tok_start]; + if (ch == ' ' || ch == '\t' || ch == '\r') { + tok_start += 1; + goto again; } - Token scan_id() - { - for (;;) { - if (pos >= m_in.size()) { break; } - const char ch = m_in[pos]; - if (!std::isalnum(ch) && ch != '.' && ch != '/' && ch != '_' - && ch != '-') { - break; - } - pos += 1; - } - return make_token(Token::Kind::Id); + // check for close + if (!close_trim.empty() && inja::string_view::starts_with(m_in.substr(tok_start), close_trim)) { + state = State::Text; + pos = tok_start + close_trim.size(); + const Token tok = make_token(closeKind); + skip_whitespaces_and_newlines(); + return tok; } - Token scan_number() - { - for (;;) { - if (pos >= m_in.size()) { break; } - const char ch = m_in[pos]; - // be very permissive in lexer (we'll catch errors when conversion happens) - if (!(std::isdigit(ch) || ch == '.' || ch == 'e' || ch == 'E' - || (ch == '+' - && (pos == 0 || m_in[pos - 1] == 'e' - || m_in[pos - 1] == 'E')) - || (ch == '-' - && (pos == 0 || m_in[pos - 1] == 'e' - || m_in[pos - 1] == 'E')))) { - break; - } - pos += 1; - } - return make_token(Token::Kind::Number); + if (inja::string_view::starts_with(m_in.substr(tok_start), close)) { + state = State::Text; + pos = tok_start + close.size(); + const Token tok = make_token(closeKind); + if (trim) { + skip_whitespaces_and_first_newline(); + } + return tok; } - Token scan_string() - { - bool escape{false}; - for (;;) { - if (pos >= m_in.size()) { break; } - const char ch = m_in[pos++]; - if (ch == '\\') { - escape = true; - } else if (!escape && ch == m_in[tok_start]) { - break; - } else { - escape = false; - } - } - return make_token(Token::Kind::String); + // skip \n + if (ch == '\n') { + tok_start += 1; + goto again; } - Token make_token(Token::Kind kind) const - { - return Token(kind, string::slice(m_in, tok_start, pos)); + pos = tok_start + 1; + if (std::isalpha(ch)) { + minus_state = MinusState::Operator; + return scan_id(); } - void skip_whitespaces_and_newlines() - { - if (pos < m_in.size()) { - while (pos < m_in.size() - && (m_in[pos] == ' ' || m_in[pos] == '\t' - || m_in[pos] == '\n' || m_in[pos] == '\r')) { - pos += 1; - } - } + const MinusState current_minus_state = minus_state; + if (minus_state == MinusState::Operator) { + minus_state = MinusState::Number; } - void skip_whitespaces_and_first_newline() - { - if (pos < m_in.size()) { - while (pos < m_in.size() - && (m_in[pos] == ' ' || m_in[pos] == '\t')) { - pos += 1; - } - } + switch (ch) { + case '+': + return make_token(Token::Kind::Plus); + case '-': + if (current_minus_state == MinusState::Operator) { + return make_token(Token::Kind::Minus); + } + return scan_number(); + case '*': + return make_token(Token::Kind::Times); + case '/': + return make_token(Token::Kind::Slash); + case '^': + return make_token(Token::Kind::Power); + case '%': + return make_token(Token::Kind::Percent); + case '.': + return make_token(Token::Kind::Dot); + case ',': + return make_token(Token::Kind::Comma); + case ':': + return make_token(Token::Kind::Colon); + case '(': + return make_token(Token::Kind::LeftParen); + case ')': + minus_state = MinusState::Operator; + return make_token(Token::Kind::RightParen); + case '[': + return make_token(Token::Kind::LeftBracket); + case ']': + minus_state = MinusState::Operator; + return make_token(Token::Kind::RightBracket); + case '{': + return make_token(Token::Kind::LeftBrace); + case '}': + minus_state = MinusState::Operator; + return make_token(Token::Kind::RightBrace); + case '>': + if (pos < m_in.size() && m_in[pos] == '=') { + pos += 1; + return make_token(Token::Kind::GreaterEqual); + } + return make_token(Token::Kind::GreaterThan); + case '<': + if (pos < m_in.size() && m_in[pos] == '=') { + pos += 1; + return make_token(Token::Kind::LessEqual); + } + return make_token(Token::Kind::LessThan); + case '=': + if (pos < m_in.size() && m_in[pos] == '=') { + pos += 1; + return make_token(Token::Kind::Equal); + } + return make_token(Token::Kind::Unknown); + case '!': + if (pos < m_in.size() && m_in[pos] == '=') { + pos += 1; + return make_token(Token::Kind::NotEqual); + } + return make_token(Token::Kind::Unknown); + case '\"': + return scan_string(); + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + minus_state = MinusState::Operator; + return scan_number(); + case '_': + case '@': + case '$': + minus_state = MinusState::Operator; + return scan_id(); + default: + return make_token(Token::Kind::Unknown); + } + } - if (pos < m_in.size()) { - const char ch = m_in[pos]; - if (ch == '\n') { - pos += 1; - } else if (ch == '\r') { - pos += 1; - if (pos < m_in.size() && m_in[pos] == '\n') { pos += 1; } - } - } + Token scan_id() { + for (;;) { + if (pos >= m_in.size()) { + break; + } + const char ch = m_in[pos]; + if (!std::isalnum(ch) && ch != '.' && ch != '/' && ch != '_' && ch != '-') { + break; + } + pos += 1; + } + return make_token(Token::Kind::Id); + } + + Token scan_number() { + for (;;) { + if (pos >= m_in.size()) { + break; + } + const char ch = m_in[pos]; + // be very permissive in lexer (we'll catch errors when conversion happens) + if (!(std::isdigit(ch) || ch == '.' || ch == 'e' || ch == 'E' || (ch == '+' && (pos == 0 || m_in[pos-1] == 'e' || m_in[pos-1] == 'E')) || (ch == '-' && (pos == 0 || m_in[pos-1] == 'e' || m_in[pos-1] == 'E')))) { + break; + } + pos += 1; + } + return make_token(Token::Kind::Number); + } + + Token scan_string() { + bool escape {false}; + for (;;) { + if (pos >= m_in.size()) { + break; + } + const char ch = m_in[pos++]; + if (ch == '\\') { + escape = true; + } else if (!escape && ch == m_in[tok_start]) { + break; + } else { + escape = false; + } + } + return make_token(Token::Kind::String); + } + + Token make_token(Token::Kind kind) const { + return Token(kind, string_view::slice(m_in, tok_start, pos)); + } + + void skip_whitespaces_and_newlines() { + if (pos < m_in.size()) { + while (pos < m_in.size() && (m_in[pos] == ' ' || m_in[pos] == '\t' || m_in[pos] == '\n' || m_in[pos] == '\r')) { + pos += 1; + } + } + } + + void skip_whitespaces_and_first_newline() { + if (pos < m_in.size()) { + while (pos < m_in.size() && (m_in[pos] == ' ' || m_in[pos] == '\t')) { + pos += 1; + } } - static std::string clear_final_line_if_whitespace(std::string text) - { - std::string result = text; - while (!result.empty()) { - const char ch = result.back(); - if (ch == ' ' || ch == '\t') { - // result.remove_suffix(1); - result.pop_back(); - } else if (ch == '\n' || ch == '\r') { - break; - } else { - return text; - } + if (pos < m_in.size()) { + const char ch = m_in[pos]; + if (ch == '\n') { + pos += 1; + } else if (ch == '\r') { + pos += 1; + if (pos < m_in.size() && m_in[pos] == '\n') { + pos += 1; } - return result; + } } + } + + static nonstd::string_view clear_final_line_if_whitespace(nonstd::string_view text) { + nonstd::string_view result = text; + while (!result.empty()) { + const char ch = result.back(); + if (ch == ' ' || ch == '\t') { + result.remove_suffix(1); + } else if (ch == '\n' || ch == '\r') { + break; + } else { + return text; + } + } + return result; + } public: - explicit Lexer(const LexerConfig &config) - : config(config), - state(State::Text), - minus_state(MinusState::Number) - {} + explicit Lexer(const LexerConfig& config): config(config), state(State::Text), minus_state(MinusState::Number) {} - SourceLocation current_position() const - { - return get_source_location(m_in, tok_start); + SourceLocation current_position() const { + return get_source_location(m_in, tok_start); + } + + void start(nonstd::string_view input) { + m_in = input; + tok_start = 0; + pos = 0; + state = State::Text; + minus_state = MinusState::Number; + + // Consume byte order mark (BOM) for UTF-8 + if (inja::string_view::starts_with(m_in, "\xEF\xBB\xBF")) { + m_in = m_in.substr(3); + } + } + + Token scan() { + tok_start = pos; + + again: + if (tok_start >= m_in.size()) { + return make_token(Token::Kind::Eof); } - void start(std::string input) - { - m_in = input; - tok_start = 0; - pos = 0; - state = State::Text; - minus_state = MinusState::Number; + switch (state) { + default: + case State::Text: { + // fast-scan to first open character + const size_t open_start = m_in.substr(pos).find_first_of(config.open_chars); + if (open_start == nonstd::string_view::npos) { + // didn't find open, return remaining text as text token + pos = m_in.size(); + return make_token(Token::Kind::Text); + } + pos += open_start; - // Consume byte order mark (BOM) for UTF-8 - if (inja::string::starts_with(m_in, "\xEF\xBB\xBF")) { - m_in = m_in.substr(3); + // try to match one of the opening sequences, and get the close + nonstd::string_view open_str = m_in.substr(pos); + bool must_lstrip = false; + if (inja::string_view::starts_with(open_str, config.expression_open)) { + if (inja::string_view::starts_with(open_str, config.expression_open_force_lstrip)) { + state = State::ExpressionStartForceLstrip; + must_lstrip = true; + } else { + state = State::ExpressionStart; } + } else if (inja::string_view::starts_with(open_str, config.statement_open)) { + if (inja::string_view::starts_with(open_str, config.statement_open_no_lstrip)) { + state = State::StatementStartNoLstrip; + } else if (inja::string_view::starts_with(open_str, config.statement_open_force_lstrip)) { + state = State::StatementStartForceLstrip; + must_lstrip = true; + } else { + state = State::StatementStart; + must_lstrip = config.lstrip_blocks; + } + } else if (inja::string_view::starts_with(open_str, config.comment_open)) { + if (inja::string_view::starts_with(open_str, config.comment_open_force_lstrip)) { + state = State::CommentStartForceLstrip; + must_lstrip = true; + } else { + state = State::CommentStart; + must_lstrip = config.lstrip_blocks; + } + } else if ((pos == 0 || m_in[pos - 1] == '\n') && inja::string_view::starts_with(open_str, config.line_statement)) { + state = State::LineStart; + } else { + pos += 1; // wasn't actually an opening sequence + goto again; + } + + nonstd::string_view text = string_view::slice(m_in, tok_start, pos); + if (must_lstrip) { + text = clear_final_line_if_whitespace(text); + } + + if (text.empty()) { + goto again; // don't generate empty token + } + return Token(Token::Kind::Text, text); } - - Token scan() - { - tok_start = pos; - - again: - if (tok_start >= m_in.size()) { return make_token(Token::Kind::Eof); } - - switch (state) { - default: - case State::Text: { - // fast-scan to first open character - const size_t open_start = - m_in.substr(pos).find_first_of(config.open_chars); - if (open_start == std::string::npos) { - // didn't find open, return remaining text as text token - pos = m_in.size(); - return make_token(Token::Kind::Text); - } - pos += open_start; - - // try to match one of the opening sequences, and get the close - std::string open_str = m_in.substr(pos); - bool must_lstrip = false; - if (inja::string::starts_with(open_str, config.expression_open)) { - if (inja::string::starts_with( - open_str, config.expression_open_force_lstrip)) { - state = State::ExpressionStartForceLstrip; - must_lstrip = true; - } else { - state = State::ExpressionStart; - } - } else if (inja::string::starts_with(open_str, - config.statement_open)) { - if (inja::string::starts_with( - open_str, config.statement_open_no_lstrip)) { - state = State::StatementStartNoLstrip; - } else if (inja::string::starts_with( - open_str, config.statement_open_force_lstrip)) { - state = State::StatementStartForceLstrip; - must_lstrip = true; - } else { - state = State::StatementStart; - must_lstrip = config.lstrip_blocks; - } - } else if (inja::string::starts_with(open_str, - config.comment_open)) { - if (inja::string::starts_with( - open_str, config.comment_open_force_lstrip)) { - state = State::CommentStartForceLstrip; - must_lstrip = true; - } else { - state = State::CommentStart; - must_lstrip = config.lstrip_blocks; - } - } else if ((pos == 0 || m_in[pos - 1] == '\n') - && inja::string::starts_with(open_str, - config.line_statement)) { - state = State::LineStart; - } else { - pos += 1;// wasn't actually an opening sequence - goto again; - } - - std::string text = string::slice(m_in, tok_start, pos); - if (must_lstrip) { text = clear_final_line_if_whitespace(text); } - - if (text.empty()) { - goto again;// don't generate empty token - } - return Token(Token::Kind::Text, text); - } - case State::ExpressionStart: { - state = State::ExpressionBody; - pos += config.expression_open.size(); - return make_token(Token::Kind::ExpressionOpen); - } - case State::ExpressionStartForceLstrip: { - state = State::ExpressionBody; - pos += config.expression_open_force_lstrip.size(); - return make_token(Token::Kind::ExpressionOpen); - } - case State::LineStart: { - state = State::LineBody; - pos += config.line_statement.size(); - return make_token(Token::Kind::LineStatementOpen); - } - case State::StatementStart: { - state = State::StatementBody; - pos += config.statement_open.size(); - return make_token(Token::Kind::StatementOpen); - } - case State::StatementStartNoLstrip: { - state = State::StatementBody; - pos += config.statement_open_no_lstrip.size(); - return make_token(Token::Kind::StatementOpen); - } - case State::StatementStartForceLstrip: { - state = State::StatementBody; - pos += config.statement_open_force_lstrip.size(); - return make_token(Token::Kind::StatementOpen); - } - case State::CommentStart: { - state = State::CommentBody; - pos += config.comment_open.size(); - return make_token(Token::Kind::CommentOpen); - } - case State::CommentStartForceLstrip: { - state = State::CommentBody; - pos += config.comment_open_force_lstrip.size(); - return make_token(Token::Kind::CommentOpen); - } - case State::ExpressionBody: - return scan_body(config.expression_close, - Token::Kind::ExpressionClose, - config.expression_close_force_rstrip); - case State::LineBody: - return scan_body("\n", Token::Kind::LineStatementClose); - case State::StatementBody: - return scan_body( - config.statement_close, Token::Kind::StatementClose, - config.statement_close_force_rstrip, config.trim_blocks); - case State::CommentBody: { - // fast-scan to comment close - const size_t end = m_in.substr(pos).find(config.comment_close); - if (end == std::string::npos) { - pos = m_in.size(); - return make_token(Token::Kind::Eof); - } - - // Check for trim pattern - const bool must_rstrip = inja::string::starts_with( - m_in.substr(pos + end - 1), config.comment_close_force_rstrip); - - // return the entire comment in the close token - state = State::Text; - pos += end + config.comment_close.size(); - Token tok = make_token(Token::Kind::CommentClose); - - if (must_rstrip || config.trim_blocks) { - skip_whitespaces_and_first_newline(); - } - return tok; - } - } + case State::ExpressionStart: { + state = State::ExpressionBody; + pos += config.expression_open.size(); + return make_token(Token::Kind::ExpressionOpen); } + case State::ExpressionStartForceLstrip: { + state = State::ExpressionBody; + pos += config.expression_open_force_lstrip.size(); + return make_token(Token::Kind::ExpressionOpen); + } + case State::LineStart: { + state = State::LineBody; + pos += config.line_statement.size(); + return make_token(Token::Kind::LineStatementOpen); + } + case State::StatementStart: { + state = State::StatementBody; + pos += config.statement_open.size(); + return make_token(Token::Kind::StatementOpen); + } + case State::StatementStartNoLstrip: { + state = State::StatementBody; + pos += config.statement_open_no_lstrip.size(); + return make_token(Token::Kind::StatementOpen); + } + case State::StatementStartForceLstrip: { + state = State::StatementBody; + pos += config.statement_open_force_lstrip.size(); + return make_token(Token::Kind::StatementOpen); + } + case State::CommentStart: { + state = State::CommentBody; + pos += config.comment_open.size(); + return make_token(Token::Kind::CommentOpen); + } + case State::CommentStartForceLstrip: { + state = State::CommentBody; + pos += config.comment_open_force_lstrip.size(); + return make_token(Token::Kind::CommentOpen); + } + case State::ExpressionBody: + return scan_body(config.expression_close, Token::Kind::ExpressionClose, config.expression_close_force_rstrip); + case State::LineBody: + return scan_body("\n", Token::Kind::LineStatementClose); + case State::StatementBody: + return scan_body(config.statement_close, Token::Kind::StatementClose, config.statement_close_force_rstrip, config.trim_blocks); + case State::CommentBody: { + // fast-scan to comment close + const size_t end = m_in.substr(pos).find(config.comment_close); + if (end == nonstd::string_view::npos) { + pos = m_in.size(); + return make_token(Token::Kind::Eof); + } - const LexerConfig &get_config() const { return config; } + // Check for trim pattern + const bool must_rstrip = inja::string_view::starts_with(m_in.substr(pos + end - 1), config.comment_close_force_rstrip); + + // return the entire comment in the close token + state = State::Text; + pos += end + config.comment_close.size(); + Token tok = make_token(Token::Kind::CommentClose); + + if (must_rstrip || config.trim_blocks) { + skip_whitespaces_and_first_newline(); + } + return tok; + } + } + } + + const LexerConfig& get_config() const { + return config; + } }; -}// namespace inja +} // namespace inja -#endif// INCLUDE_INJA_LEXER_HPP_ +#endif // INCLUDE_INJA_LEXER_HPP_ // #include "node.hpp" @@ -1534,709 +1427,639 @@ public: // #include "utils.hpp" + namespace inja { /*! * \brief Class for parsing an inja Template. */ class Parser { - using Arguments = std::vector>; - using OperatorStack = std::stack>; + using Arguments = std::vector>; + using OperatorStack = std::stack>; - const ParserConfig &config; + const ParserConfig& config; - Lexer lexer; - TemplateStorage &template_storage; - const FunctionStorage &function_storage; + Lexer lexer; + TemplateStorage& template_storage; + const FunctionStorage& function_storage; - Token tok, peek_tok; - bool have_peek_tok{false}; + Token tok, peek_tok; + bool have_peek_tok {false}; - std::string literal_start; + nonstd::string_view literal_start; - BlockNode *current_block{nullptr}; - ExpressionListNode *current_expression_list{nullptr}; + BlockNode* current_block {nullptr}; + ExpressionListNode* current_expression_list {nullptr}; - std::stack if_statement_stack; - std::stack for_statement_stack; - std::stack block_statement_stack; + std::stack if_statement_stack; + std::stack for_statement_stack; + std::stack block_statement_stack; - inline void throw_parser_error(const std::string &message) const - { - INJA_THROW(ParserError(message, lexer.current_position())); + inline void throw_parser_error(const std::string& message) const { + INJA_THROW(ParserError(message, lexer.current_position())); + } + + inline void get_next_token() { + if (have_peek_tok) { + tok = peek_tok; + have_peek_tok = false; + } else { + tok = lexer.scan(); + } + } + + inline void get_peek_token() { + if (!have_peek_tok) { + peek_tok = lexer.scan(); + have_peek_tok = true; + } + } + + inline void add_literal(Arguments &arguments, const char* content_ptr) { + nonstd::string_view data_text(literal_start.data(), tok.text.data() - literal_start.data() + tok.text.size()); + arguments.emplace_back(std::make_shared(data_text, data_text.data() - content_ptr)); + } + + inline void add_operator(Arguments &arguments, OperatorStack &operator_stack) { + auto function = operator_stack.top(); + operator_stack.pop(); + + if (static_cast(arguments.size()) < function->number_args) { + throw_parser_error("too few arguments"); } - inline void get_next_token() - { - if (have_peek_tok) { - tok = peek_tok; - have_peek_tok = false; + for (int i = 0; i < function->number_args; ++i) { + function->arguments.insert(function->arguments.begin(), arguments.back()); + arguments.pop_back(); + } + arguments.emplace_back(function); + } + + void add_to_template_storage(nonstd::string_view path, std::string& template_name) { + if (template_storage.find(template_name) != template_storage.end()) { + return; + } + + std::string original_path = static_cast(path); + std::string original_name = template_name; + + if (config.search_included_templates_in_files) { + // Build the relative path + template_name = original_path + original_name; + if (template_name.compare(0, 2, "./") == 0) { + template_name.erase(0, 2); + } + + if (template_storage.find(template_name) == template_storage.end()) { + // Load file + std::ifstream file; + file.open(template_name); + if (!file.fail()) { + std::string text((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + + auto include_template = Template(text); + template_storage.emplace(template_name, include_template); + parse_into_template(template_storage[template_name], template_name); + return; + } else if (!config.include_callback) { + INJA_THROW(FileError("failed accessing file at '" + template_name + "'")); + } + } + } + + // Try include callback + if (config.include_callback) { + auto include_template = config.include_callback(original_path, original_name); + template_storage.emplace(template_name, include_template); + } + } + + std::string parse_filename() const { + if (tok.kind != Token::Kind::String) { + throw_parser_error("expected string, got '" + tok.describe() + "'"); + } + + if (tok.text.length() < 2) { + throw_parser_error("expected filename, got '" + static_cast(tok.text) + "'"); + } + + // Remove first and last character "" + return std::string {tok.text.substr(1, tok.text.length() - 2)}; + } + + bool parse_expression(Template& tmpl, Token::Kind closing) { + current_expression_list->root = parse_expression(tmpl); + return tok.kind == closing; + } + + std::shared_ptr parse_expression(Template& tmpl) { + size_t current_bracket_level {0}; + size_t current_brace_level {0}; + Arguments arguments; + OperatorStack operator_stack; + + while (tok.kind != Token::Kind::Eof) { + // Literals + switch (tok.kind) { + case Token::Kind::String: { + if (current_brace_level == 0 && current_bracket_level == 0) { + literal_start = tok.text; + add_literal(arguments, tmpl.content.c_str()); + } + } break; + case Token::Kind::Number: { + if (current_brace_level == 0 && current_bracket_level == 0) { + literal_start = tok.text; + add_literal(arguments, tmpl.content.c_str()); + } + } break; + case Token::Kind::LeftBracket: { + if (current_brace_level == 0 && current_bracket_level == 0) { + literal_start = tok.text; + } + current_bracket_level += 1; + } break; + case Token::Kind::LeftBrace: { + if (current_brace_level == 0 && current_bracket_level == 0) { + literal_start = tok.text; + } + current_brace_level += 1; + } break; + case Token::Kind::RightBracket: { + if (current_bracket_level == 0) { + throw_parser_error("unexpected ']'"); + } + + current_bracket_level -= 1; + if (current_brace_level == 0 && current_bracket_level == 0) { + add_literal(arguments, tmpl.content.c_str()); + } + } break; + case Token::Kind::RightBrace: { + if (current_brace_level == 0) { + throw_parser_error("unexpected '}'"); + } + + current_brace_level -= 1; + if (current_brace_level == 0 && current_bracket_level == 0) { + add_literal(arguments, tmpl.content.c_str()); + } + } break; + case Token::Kind::Id: { + get_peek_token(); + + // Data Literal + if (tok.text == static_cast("true") || tok.text == static_cast("false") || + tok.text == static_cast("null")) { + if (current_brace_level == 0 && current_bracket_level == 0) { + literal_start = tok.text; + add_literal(arguments, tmpl.content.c_str()); + } + + // Operator + } else if (tok.text == "and" || tok.text == "or" || tok.text == "in" || tok.text == "not") { + goto parse_operator; + + // Functions + } else if (peek_tok.kind == Token::Kind::LeftParen) { + auto func = std::make_shared(tok.text, tok.text.data() - tmpl.content.c_str()); + get_next_token(); + do { + get_next_token(); + auto expr = parse_expression(tmpl); + if (!expr) { + break; + } + func->number_args += 1; + func->arguments.emplace_back(expr); + } while (tok.kind == Token::Kind::Comma); + if (tok.kind != Token::Kind::RightParen) { + throw_parser_error("expected right parenthesis, got '" + tok.describe() + "'"); + } + + auto function_data = function_storage.find_function(func->name, func->number_args); + if (function_data.operation == FunctionStorage::Operation::None) { + throw_parser_error("unknown function " + func->name); + } + func->operation = function_data.operation; + if (function_data.operation == FunctionStorage::Operation::Callback) { + func->callback = function_data.callback; + } + arguments.emplace_back(func); + + // Variables } else { - tok = lexer.scan(); + arguments.emplace_back(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str())); } + + // Operators + } break; + case Token::Kind::Equal: + case Token::Kind::NotEqual: + case Token::Kind::GreaterThan: + case Token::Kind::GreaterEqual: + case Token::Kind::LessThan: + case Token::Kind::LessEqual: + case Token::Kind::Plus: + case Token::Kind::Minus: + case Token::Kind::Times: + case Token::Kind::Slash: + case Token::Kind::Power: + case Token::Kind::Percent: + case Token::Kind::Dot: { + + parse_operator: + FunctionStorage::Operation operation; + switch (tok.kind) { + case Token::Kind::Id: { + if (tok.text == "and") { + operation = FunctionStorage::Operation::And; + } else if (tok.text == "or") { + operation = FunctionStorage::Operation::Or; + } else if (tok.text == "in") { + operation = FunctionStorage::Operation::In; + } else if (tok.text == "not") { + operation = FunctionStorage::Operation::Not; + } else { + throw_parser_error("unknown operator in parser."); + } + } break; + case Token::Kind::Equal: { + operation = FunctionStorage::Operation::Equal; + } break; + case Token::Kind::NotEqual: { + operation = FunctionStorage::Operation::NotEqual; + } break; + case Token::Kind::GreaterThan: { + operation = FunctionStorage::Operation::Greater; + } break; + case Token::Kind::GreaterEqual: { + operation = FunctionStorage::Operation::GreaterEqual; + } break; + case Token::Kind::LessThan: { + operation = FunctionStorage::Operation::Less; + } break; + case Token::Kind::LessEqual: { + operation = FunctionStorage::Operation::LessEqual; + } break; + case Token::Kind::Plus: { + operation = FunctionStorage::Operation::Add; + } break; + case Token::Kind::Minus: { + operation = FunctionStorage::Operation::Subtract; + } break; + case Token::Kind::Times: { + operation = FunctionStorage::Operation::Multiplication; + } break; + case Token::Kind::Slash: { + operation = FunctionStorage::Operation::Division; + } break; + case Token::Kind::Power: { + operation = FunctionStorage::Operation::Power; + } break; + case Token::Kind::Percent: { + operation = FunctionStorage::Operation::Modulo; + } break; + case Token::Kind::Dot: { + operation = FunctionStorage::Operation::AtId; + } break; + default: { + throw_parser_error("unknown operator in parser."); + } + } + auto function_node = std::make_shared(operation, tok.text.data() - tmpl.content.c_str()); + + while (!operator_stack.empty() && + ((operator_stack.top()->precedence > function_node->precedence) || + (operator_stack.top()->precedence == function_node->precedence && function_node->associativity == FunctionNode::Associativity::Left))) { + add_operator(arguments, operator_stack); + } + + operator_stack.emplace(function_node); + } break; + case Token::Kind::Comma: { + if (current_brace_level == 0 && current_bracket_level == 0) { + goto break_loop; + } + } break; + case Token::Kind::Colon: { + if (current_brace_level == 0 && current_bracket_level == 0) { + throw_parser_error("unexpected ':'"); + } + } break; + case Token::Kind::LeftParen: { + get_next_token(); + auto expr = parse_expression(tmpl); + if (tok.kind != Token::Kind::RightParen) { + throw_parser_error("expected right parenthesis, got '" + tok.describe() + "'"); + } + if (!expr) { + throw_parser_error("empty expression in parentheses"); + } + arguments.emplace_back(expr); + } break; + default: + goto break_loop; + } + + get_next_token(); } - inline void get_peek_token() - { - if (!have_peek_tok) { - peek_tok = lexer.scan(); - have_peek_tok = true; - } + break_loop: + while (!operator_stack.empty()) { + add_operator(arguments, operator_stack); } - inline void add_literal(Arguments &arguments, const char *content_ptr) - { - std::string data_text(literal_start.data(), - tok.text.data() - literal_start.data() - + tok.text.size()); - arguments.emplace_back(std::make_shared( - data_text, data_text.data() - content_ptr)); + std::shared_ptr expr; + if (arguments.size() == 1) { + expr = arguments[0]; + arguments = {}; + } else if (arguments.size() > 1) { + throw_parser_error("malformed expression"); + } + return expr; + } + + bool parse_statement(Template& tmpl, Token::Kind closing, nonstd::string_view path) { + if (tok.kind != Token::Kind::Id) { + return false; } - inline void add_operator(Arguments &arguments, - OperatorStack &operator_stack) - { - auto function = operator_stack.top(); - operator_stack.pop(); + if (tok.text == static_cast("if")) { + get_next_token(); - if (static_cast(arguments.size()) < function->number_args) { - throw_parser_error("too few arguments"); + auto if_statement_node = std::make_shared(current_block, tok.text.data() - tmpl.content.c_str()); + current_block->nodes.emplace_back(if_statement_node); + if_statement_stack.emplace(if_statement_node.get()); + current_block = &if_statement_node->true_statement; + current_expression_list = &if_statement_node->condition; + + if (!parse_expression(tmpl, closing)) { + return false; + } + } else if (tok.text == static_cast("else")) { + if (if_statement_stack.empty()) { + throw_parser_error("else without matching if"); + } + auto& if_statement_data = if_statement_stack.top(); + get_next_token(); + + if_statement_data->has_false_statement = true; + current_block = &if_statement_data->false_statement; + + // Chained else if + if (tok.kind == Token::Kind::Id && tok.text == static_cast("if")) { + get_next_token(); + + auto if_statement_node = std::make_shared(true, current_block, tok.text.data() - tmpl.content.c_str()); + current_block->nodes.emplace_back(if_statement_node); + if_statement_stack.emplace(if_statement_node.get()); + current_block = &if_statement_node->true_statement; + current_expression_list = &if_statement_node->condition; + + if (!parse_expression(tmpl, closing)) { + return false; + } + } + } else if (tok.text == static_cast("endif")) { + if (if_statement_stack.empty()) { + throw_parser_error("endif without matching if"); + } + + // Nested if statements + while (if_statement_stack.top()->is_nested) { + if_statement_stack.pop(); + } + + auto& if_statement_data = if_statement_stack.top(); + get_next_token(); + + current_block = if_statement_data->parent; + if_statement_stack.pop(); + } else if (tok.text == static_cast("block")) { + get_next_token(); + + if (tok.kind != Token::Kind::Id) { + throw_parser_error("expected block name, got '" + tok.describe() + "'"); + } + + const std::string block_name = static_cast(tok.text); + + auto block_statement_node = std::make_shared(current_block, block_name, tok.text.data() - tmpl.content.c_str()); + current_block->nodes.emplace_back(block_statement_node); + block_statement_stack.emplace(block_statement_node.get()); + current_block = &block_statement_node->block; + auto success = tmpl.block_storage.emplace(block_name, block_statement_node); + if (!success.second) { + throw_parser_error("block with the name '" + block_name + "' does already exist"); + } + + get_next_token(); + } else if (tok.text == static_cast("endblock")) { + if (block_statement_stack.empty()) { + throw_parser_error("endblock without matching block"); + } + + auto& block_statement_data = block_statement_stack.top(); + get_next_token(); + + current_block = block_statement_data->parent; + block_statement_stack.pop(); + } else if (tok.text == static_cast("for")) { + get_next_token(); + + // options: for a in arr; for a, b in obj + if (tok.kind != Token::Kind::Id) { + throw_parser_error("expected id, got '" + tok.describe() + "'"); + } + + Token value_token = tok; + get_next_token(); + + // Object type + std::shared_ptr for_statement_node; + if (tok.kind == Token::Kind::Comma) { + get_next_token(); + if (tok.kind != Token::Kind::Id) { + throw_parser_error("expected id, got '" + tok.describe() + "'"); } - for (int i = 0; i < function->number_args; ++i) { - function->arguments.insert(function->arguments.begin(), - arguments.back()); - arguments.pop_back(); - } - arguments.emplace_back(function); + Token key_token = std::move(value_token); + value_token = tok; + get_next_token(); + + for_statement_node = std::make_shared(static_cast(key_token.text), static_cast(value_token.text), + current_block, tok.text.data() - tmpl.content.c_str()); + + // Array type + } else { + for_statement_node = + std::make_shared(static_cast(value_token.text), current_block, tok.text.data() - tmpl.content.c_str()); + } + + current_block->nodes.emplace_back(for_statement_node); + for_statement_stack.emplace(for_statement_node.get()); + current_block = &for_statement_node->body; + current_expression_list = &for_statement_node->condition; + + if (tok.kind != Token::Kind::Id || tok.text != static_cast("in")) { + throw_parser_error("expected 'in', got '" + tok.describe() + "'"); + } + get_next_token(); + + if (!parse_expression(tmpl, closing)) { + return false; + } + } else if (tok.text == static_cast("endfor")) { + if (for_statement_stack.empty()) { + throw_parser_error("endfor without matching for"); + } + + auto& for_statement_data = for_statement_stack.top(); + get_next_token(); + + current_block = for_statement_data->parent; + for_statement_stack.pop(); + } else if (tok.text == static_cast("include")) { + get_next_token(); + + std::string template_name = parse_filename(); + add_to_template_storage(path, template_name); + + current_block->nodes.emplace_back(std::make_shared(template_name, tok.text.data() - tmpl.content.c_str())); + + get_next_token(); + } else if (tok.text == static_cast("extends")) { + get_next_token(); + + std::string template_name = parse_filename(); + add_to_template_storage(path, template_name); + + current_block->nodes.emplace_back(std::make_shared(template_name, tok.text.data() - tmpl.content.c_str())); + + get_next_token(); + } else if (tok.text == static_cast("set")) { + get_next_token(); + + if (tok.kind != Token::Kind::Id) { + throw_parser_error("expected variable name, got '" + tok.describe() + "'"); + } + + std::string key = static_cast(tok.text); + get_next_token(); + + auto set_statement_node = std::make_shared(key, tok.text.data() - tmpl.content.c_str()); + current_block->nodes.emplace_back(set_statement_node); + current_expression_list = &set_statement_node->expression; + + if (tok.text != static_cast("=")) { + throw_parser_error("expected '=', got '" + tok.describe() + "'"); + } + get_next_token(); + + if (!parse_expression(tmpl, closing)) { + return false; + } + } else { + return false; } + return true; + } - void add_to_template_storage(std::string path, std::string &template_name) - { - if (template_storage.find(template_name) != template_storage.end()) { - return; + void parse_into(Template& tmpl, nonstd::string_view path) { + lexer.start(tmpl.content); + current_block = &tmpl.root; + + for (;;) { + get_next_token(); + switch (tok.kind) { + case Token::Kind::Eof: { + if (!if_statement_stack.empty()) { + throw_parser_error("unmatched if"); } - - std::string original_path = static_cast(path); - std::string original_name = template_name; - - if (config.search_included_templates_in_files) { - // Build the relative path - template_name = original_path + original_name; - if (template_name.compare(0, 2, "./") == 0) { - template_name.erase(0, 2); - } - - if (template_storage.find(template_name) - == template_storage.end()) { - // Load file - std::ifstream file; - file.open(template_name); - if (!file.fail()) { - std::string text((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); - - auto include_template = Template(text); - template_storage.emplace(template_name, include_template); - parse_into_template(template_storage[template_name], - template_name); - return; - } else if (!config.include_callback) { - INJA_THROW(FileError("failed accessing file at '" - + template_name + "'")); - } - } + if (!for_statement_stack.empty()) { + throw_parser_error("unmatched for"); } - - // Try include callback - if (config.include_callback) { - auto include_template = - config.include_callback(original_path, original_name); - template_storage.emplace(template_name, include_template); - } - } - - std::string parse_filename() const - { - if (tok.kind != Token::Kind::String) { - throw_parser_error("expected string, got '" + tok.describe() + "'"); - } - - if (tok.text.length() < 2) { - throw_parser_error("expected filename, got '" - + static_cast(tok.text) + "'"); - } - - // Remove first and last character "" - return std::string{tok.text.substr(1, tok.text.length() - 2)}; - } - - bool parse_expression(Template &tmpl, Token::Kind closing) - { - current_expression_list->root = parse_expression(tmpl); - return tok.kind == closing; - } - - std::shared_ptr parse_expression(Template &tmpl) - { - size_t current_bracket_level{0}; - size_t current_brace_level{0}; - Arguments arguments; - OperatorStack operator_stack; - - while (tok.kind != Token::Kind::Eof) { - // Literals - switch (tok.kind) { - case Token::Kind::String: { - if (current_brace_level == 0 && current_bracket_level == 0) { - literal_start = tok.text; - add_literal(arguments, tmpl.content.c_str()); - } - } break; - case Token::Kind::Number: { - if (current_brace_level == 0 && current_bracket_level == 0) { - literal_start = tok.text; - add_literal(arguments, tmpl.content.c_str()); - } - } break; - case Token::Kind::LeftBracket: { - if (current_brace_level == 0 && current_bracket_level == 0) { - literal_start = tok.text; - } - current_bracket_level += 1; - } break; - case Token::Kind::LeftBrace: { - if (current_brace_level == 0 && current_bracket_level == 0) { - literal_start = tok.text; - } - current_brace_level += 1; - } break; - case Token::Kind::RightBracket: { - if (current_bracket_level == 0) { - throw_parser_error("unexpected ']'"); - } - - current_bracket_level -= 1; - if (current_brace_level == 0 && current_bracket_level == 0) { - add_literal(arguments, tmpl.content.c_str()); - } - } break; - case Token::Kind::RightBrace: { - if (current_brace_level == 0) { - throw_parser_error("unexpected '}'"); - } - - current_brace_level -= 1; - if (current_brace_level == 0 && current_bracket_level == 0) { - add_literal(arguments, tmpl.content.c_str()); - } - } break; - case Token::Kind::Id: { - get_peek_token(); - - // Data Literal - if (tok.text == static_cast("true") - || tok.text == static_cast("false") - || tok.text == static_cast("null")) { - if (current_brace_level == 0 - && current_bracket_level == 0) { - literal_start = tok.text; - add_literal(arguments, tmpl.content.c_str()); - } - - // Operator - } else if (tok.text == "and" || tok.text == "or" - || tok.text == "in" || tok.text == "not") { - goto parse_operator; - - // Functions - } else if (peek_tok.kind == Token::Kind::LeftParen) { - auto func = std::make_shared( - tok.text, tok.text.data() - tmpl.content.c_str()); - get_next_token(); - do { - get_next_token(); - auto expr = parse_expression(tmpl); - if (!expr) { break; } - func->number_args += 1; - func->arguments.emplace_back(expr); - } while (tok.kind == Token::Kind::Comma); - if (tok.kind != Token::Kind::RightParen) { - throw_parser_error("expected right parenthesis, got '" - + tok.describe() + "'"); - } - - auto function_data = function_storage.find_function( - func->name, func->number_args); - if (function_data.operation - == FunctionStorage::Operation::None) { - throw_parser_error("unknown function " + func->name); - } - func->operation = function_data.operation; - if (function_data.operation - == FunctionStorage::Operation::Callback) { - func->callback = function_data.callback; - } - arguments.emplace_back(func); - - // Variables - } else { - arguments.emplace_back(std::make_shared( - static_cast(tok.text), - tok.text.data() - tmpl.content.c_str())); - } - - // Operators - } break; - case Token::Kind::Equal: - case Token::Kind::NotEqual: - case Token::Kind::GreaterThan: - case Token::Kind::GreaterEqual: - case Token::Kind::LessThan: - case Token::Kind::LessEqual: - case Token::Kind::Plus: - case Token::Kind::Minus: - case Token::Kind::Times: - case Token::Kind::Slash: - case Token::Kind::Power: - case Token::Kind::Percent: - case Token::Kind::Dot: { - - parse_operator: - FunctionStorage::Operation operation; - switch (tok.kind) { - case Token::Kind::Id: { - if (tok.text == "and") { - operation = FunctionStorage::Operation::And; - } else if (tok.text == "or") { - operation = FunctionStorage::Operation::Or; - } else if (tok.text == "in") { - operation = FunctionStorage::Operation::In; - } else if (tok.text == "not") { - operation = FunctionStorage::Operation::Not; - } else { - throw_parser_error("unknown operator in parser."); - } - } break; - case Token::Kind::Equal: { - operation = FunctionStorage::Operation::Equal; - } break; - case Token::Kind::NotEqual: { - operation = FunctionStorage::Operation::NotEqual; - } break; - case Token::Kind::GreaterThan: { - operation = FunctionStorage::Operation::Greater; - } break; - case Token::Kind::GreaterEqual: { - operation = FunctionStorage::Operation::GreaterEqual; - } break; - case Token::Kind::LessThan: { - operation = FunctionStorage::Operation::Less; - } break; - case Token::Kind::LessEqual: { - operation = FunctionStorage::Operation::LessEqual; - } break; - case Token::Kind::Plus: { - operation = FunctionStorage::Operation::Add; - } break; - case Token::Kind::Minus: { - operation = FunctionStorage::Operation::Subtract; - } break; - case Token::Kind::Times: { - operation = FunctionStorage::Operation::Multiplication; - } break; - case Token::Kind::Slash: { - operation = FunctionStorage::Operation::Division; - } break; - case Token::Kind::Power: { - operation = FunctionStorage::Operation::Power; - } break; - case Token::Kind::Percent: { - operation = FunctionStorage::Operation::Modulo; - } break; - case Token::Kind::Dot: { - operation = FunctionStorage::Operation::AtId; - } break; - default: { - throw_parser_error("unknown operator in parser."); - } - } - auto function_node = std::make_shared( - operation, tok.text.data() - tmpl.content.c_str()); - - while (!operator_stack.empty() - && ((operator_stack.top()->precedence - > function_node->precedence) - || (operator_stack.top()->precedence - == function_node->precedence - && function_node->associativity - == FunctionNode::Associativity::Left))) { - add_operator(arguments, operator_stack); - } - - operator_stack.emplace(function_node); - } break; - case Token::Kind::Comma: { - if (current_brace_level == 0 && current_bracket_level == 0) { - goto break_loop; - } - } break; - case Token::Kind::Colon: { - if (current_brace_level == 0 && current_bracket_level == 0) { - throw_parser_error("unexpected ':'"); - } - } break; - case Token::Kind::LeftParen: { - get_next_token(); - auto expr = parse_expression(tmpl); - if (tok.kind != Token::Kind::RightParen) { - throw_parser_error("expected right parenthesis, got '" - + tok.describe() + "'"); - } - if (!expr) { - throw_parser_error("empty expression in parentheses"); - } - arguments.emplace_back(expr); - } break; - default: - goto break_loop; - } - - get_next_token(); - } - - break_loop: - while (!operator_stack.empty()) { - add_operator(arguments, operator_stack); - } - - std::shared_ptr expr; - if (arguments.size() == 1) { - expr = arguments[0]; - arguments = {}; - } else if (arguments.size() > 1) { - throw_parser_error("malformed expression"); - } - return expr; - } - - bool parse_statement(Template &tmpl, Token::Kind closing, std::string path) - { - if (tok.kind != Token::Kind::Id) { return false; } - - if (tok.text == static_cast("if")) { - get_next_token(); - - auto if_statement_node = std::make_shared( - current_block, tok.text.data() - tmpl.content.c_str()); - current_block->nodes.emplace_back(if_statement_node); - if_statement_stack.emplace(if_statement_node.get()); - current_block = &if_statement_node->true_statement; - current_expression_list = &if_statement_node->condition; - - if (!parse_expression(tmpl, closing)) { return false; } - } else if (tok.text == static_cast("else")) { - if (if_statement_stack.empty()) { - throw_parser_error("else without matching if"); - } - auto &if_statement_data = if_statement_stack.top(); - get_next_token(); - - if_statement_data->has_false_statement = true; - current_block = &if_statement_data->false_statement; - - // Chained else if - if (tok.kind == Token::Kind::Id - && tok.text == static_cast("if")) { - get_next_token(); - - auto if_statement_node = std::make_shared( - true, current_block, - tok.text.data() - tmpl.content.c_str()); - current_block->nodes.emplace_back(if_statement_node); - if_statement_stack.emplace(if_statement_node.get()); - current_block = &if_statement_node->true_statement; - current_expression_list = &if_statement_node->condition; - - if (!parse_expression(tmpl, closing)) { return false; } - } - } else if (tok.text == static_cast("endif")) { - if (if_statement_stack.empty()) { - throw_parser_error("endif without matching if"); - } - - // Nested if statements - while (if_statement_stack.top()->is_nested) { - if_statement_stack.pop(); - } - - auto &if_statement_data = if_statement_stack.top(); - get_next_token(); - - current_block = if_statement_data->parent; - if_statement_stack.pop(); - } else if (tok.text == static_cast("block")) { - get_next_token(); - - if (tok.kind != Token::Kind::Id) { - throw_parser_error("expected block name, got '" + tok.describe() - + "'"); - } - - const std::string block_name = static_cast(tok.text); - - auto block_statement_node = std::make_shared( - current_block, block_name, - tok.text.data() - tmpl.content.c_str()); - current_block->nodes.emplace_back(block_statement_node); - block_statement_stack.emplace(block_statement_node.get()); - current_block = &block_statement_node->block; - auto success = - tmpl.block_storage.emplace(block_name, block_statement_node); - if (!success.second) { - throw_parser_error("block with the name '" + block_name - + "' does already exist"); - } - - get_next_token(); - } else if (tok.text == static_cast("endblock")) { - if (block_statement_stack.empty()) { - throw_parser_error("endblock without matching block"); - } - - auto &block_statement_data = block_statement_stack.top(); - get_next_token(); - - current_block = block_statement_data->parent; - block_statement_stack.pop(); - } else if (tok.text == static_cast("for")) { - get_next_token(); - - // options: for a in arr; for a, b in obj - if (tok.kind != Token::Kind::Id) { - throw_parser_error("expected id, got '" + tok.describe() + "'"); - } - - Token value_token = tok; - get_next_token(); - - // Object type - std::shared_ptr for_statement_node; - if (tok.kind == Token::Kind::Comma) { - get_next_token(); - if (tok.kind != Token::Kind::Id) { - throw_parser_error("expected id, got '" + tok.describe() - + "'"); - } - - Token key_token = std::move(value_token); - value_token = tok; - get_next_token(); - - for_statement_node = std::make_shared( - static_cast(key_token.text), - static_cast(value_token.text), current_block, - tok.text.data() - tmpl.content.c_str()); - - // Array type - } else { - for_statement_node = std::make_shared( - static_cast(value_token.text), current_block, - tok.text.data() - tmpl.content.c_str()); - } - - current_block->nodes.emplace_back(for_statement_node); - for_statement_stack.emplace(for_statement_node.get()); - current_block = &for_statement_node->body; - current_expression_list = &for_statement_node->condition; - - if (tok.kind != Token::Kind::Id - || tok.text != static_cast("in")) { - throw_parser_error("expected 'in', got '" + tok.describe() - + "'"); - } - get_next_token(); - - if (!parse_expression(tmpl, closing)) { return false; } - } else if (tok.text == static_cast("endfor")) { - if (for_statement_stack.empty()) { - throw_parser_error("endfor without matching for"); - } - - auto &for_statement_data = for_statement_stack.top(); - get_next_token(); - - current_block = for_statement_data->parent; - for_statement_stack.pop(); - } else if (tok.text == static_cast("include")) { - get_next_token(); - - std::string template_name = parse_filename(); - add_to_template_storage(path, template_name); - - current_block->nodes.emplace_back( - std::make_shared( - template_name, tok.text.data() - tmpl.content.c_str())); - - get_next_token(); - } else if (tok.text == static_cast("extends")) { - get_next_token(); - - std::string template_name = parse_filename(); - add_to_template_storage(path, template_name); - - current_block->nodes.emplace_back( - std::make_shared( - template_name, tok.text.data() - tmpl.content.c_str())); - - get_next_token(); - } else if (tok.text == static_cast("set")) { - get_next_token(); - - if (tok.kind != Token::Kind::Id) { - throw_parser_error("expected variable name, got '" - + tok.describe() + "'"); - } - - std::string key = static_cast(tok.text); - get_next_token(); - - auto set_statement_node = std::make_shared( - key, tok.text.data() - tmpl.content.c_str()); - current_block->nodes.emplace_back(set_statement_node); - current_expression_list = &set_statement_node->expression; - - if (tok.text != static_cast("=")) { - throw_parser_error("expected '=', got '" + tok.describe() - + "'"); - } - get_next_token(); - - if (!parse_expression(tmpl, closing)) { return false; } - } else { - return false; - } - return true; - } - - void parse_into(Template &tmpl, std::string path) - { - lexer.start(tmpl.content); - current_block = &tmpl.root; - - for (;;) { - get_next_token(); - switch (tok.kind) { - case Token::Kind::Eof: { - if (!if_statement_stack.empty()) { - throw_parser_error("unmatched if"); - } - if (!for_statement_stack.empty()) { - throw_parser_error("unmatched for"); - } - } - return; - case Token::Kind::Text: { - current_block->nodes.emplace_back(std::make_shared( - tok.text.data() - tmpl.content.c_str(), tok.text.size())); - } break; - case Token::Kind::StatementOpen: { - get_next_token(); - if (!parse_statement(tmpl, Token::Kind::StatementClose, path)) { - throw_parser_error("expected statement, got '" - + tok.describe() + "'"); - } - if (tok.kind != Token::Kind::StatementClose) { - throw_parser_error("expected statement close, got '" - + tok.describe() + "'"); - } - } break; - case Token::Kind::LineStatementOpen: { - get_next_token(); - if (!parse_statement(tmpl, Token::Kind::LineStatementClose, - path)) { - throw_parser_error("expected statement, got '" - + tok.describe() + "'"); - } - if (tok.kind != Token::Kind::LineStatementClose - && tok.kind != Token::Kind::Eof) { - throw_parser_error("expected line statement close, got '" - + tok.describe() + "'"); - } - } break; - case Token::Kind::ExpressionOpen: { - get_next_token(); - - auto expression_list_node = - std::make_shared( - tok.text.data() - tmpl.content.c_str()); - current_block->nodes.emplace_back(expression_list_node); - current_expression_list = expression_list_node.get(); - - if (!parse_expression(tmpl, Token::Kind::ExpressionClose)) { - throw_parser_error("expected expression close, got '" - + tok.describe() + "'"); - } - } break; - case Token::Kind::CommentOpen: { - get_next_token(); - if (tok.kind != Token::Kind::CommentClose) { - throw_parser_error("expected comment close, got '" - + tok.describe() + "'"); - } - } break; - default: { - throw_parser_error("unexpected token '" + tok.describe() + "'"); - } break; - } + } + return; + case Token::Kind::Text: { + current_block->nodes.emplace_back(std::make_shared(tok.text.data() - tmpl.content.c_str(), tok.text.size())); + } break; + case Token::Kind::StatementOpen: { + get_next_token(); + if (!parse_statement(tmpl, Token::Kind::StatementClose, path)) { + throw_parser_error("expected statement, got '" + tok.describe() + "'"); } + if (tok.kind != Token::Kind::StatementClose) { + throw_parser_error("expected statement close, got '" + tok.describe() + "'"); + } + } break; + case Token::Kind::LineStatementOpen: { + get_next_token(); + if (!parse_statement(tmpl, Token::Kind::LineStatementClose, path)) { + throw_parser_error("expected statement, got '" + tok.describe() + "'"); + } + if (tok.kind != Token::Kind::LineStatementClose && tok.kind != Token::Kind::Eof) { + throw_parser_error("expected line statement close, got '" + tok.describe() + "'"); + } + } break; + case Token::Kind::ExpressionOpen: { + get_next_token(); + + auto expression_list_node = std::make_shared(tok.text.data() - tmpl.content.c_str()); + current_block->nodes.emplace_back(expression_list_node); + current_expression_list = expression_list_node.get(); + + if (!parse_expression(tmpl, Token::Kind::ExpressionClose)) { + throw_parser_error("expected expression close, got '" + tok.describe() + "'"); + } + } break; + case Token::Kind::CommentOpen: { + get_next_token(); + if (tok.kind != Token::Kind::CommentClose) { + throw_parser_error("expected comment close, got '" + tok.describe() + "'"); + } + } break; + default: { + throw_parser_error("unexpected token '" + tok.describe() + "'"); + } break; + } } + } public: - explicit Parser(const ParserConfig &parser_config, - const LexerConfig &lexer_config, - TemplateStorage &template_storage, - const FunctionStorage &function_storage) - : config(parser_config), - lexer(lexer_config), - template_storage(template_storage), - function_storage(function_storage) - {} + explicit Parser(const ParserConfig& parser_config, const LexerConfig& lexer_config, TemplateStorage& template_storage, + const FunctionStorage& function_storage) + : config(parser_config), lexer(lexer_config), template_storage(template_storage), function_storage(function_storage) {} - Template parse(std::string input, std::string path) - { - auto result = Template(static_cast(input)); - parse_into(result, path); - return result; - } - - void parse_into_template(Template &tmpl, std::string filename) - { - std::string path = filename.substr(0, filename.find_last_of("/\\") + 1); - - // StringRef path = sys::path::parent_path(filename); - auto sub_parser = Parser(config, lexer.get_config(), template_storage, - function_storage); - sub_parser.parse_into(tmpl, path); - } - - std::string load_file(const std::string &filename) - { - std::ifstream file; - file.open(filename); - if (file.fail()) { - INJA_THROW( - FileError("failed accessing file at '" + filename + "'")); - } - std::string text((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); - return text; + Template parse(nonstd::string_view input, nonstd::string_view path) { + auto result = Template(static_cast(input)); + parse_into(result, path); + return result; + } + + void parse_into_template(Template& tmpl, nonstd::string_view filename) { + nonstd::string_view path = filename.substr(0, filename.find_last_of("/\\") + 1); + + // StringRef path = sys::path::parent_path(filename); + auto sub_parser = Parser(config, lexer.get_config(), template_storage, function_storage); + sub_parser.parse_into(tmpl, path); + } + + std::string load_file(const std::string& filename) { + std::ifstream file; + file.open(filename); + if (file.fail()) { + INJA_THROW(FileError("failed accessing file at '" + filename + "'")); } + std::string text((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + return text; + } }; -}// namespace inja +} // namespace inja -#endif// INCLUDE_INJA_PARSER_HPP_ +#endif // INCLUDE_INJA_PARSER_HPP_ // #include "renderer.hpp" #ifndef INCLUDE_INJA_RENDERER_HPP_ @@ -2258,1015 +2081,849 @@ public: // #include "utils.hpp" + namespace inja { /*! * \brief Class for rendering a Template with data. */ class Renderer : public NodeVisitor { - using Op = FunctionStorage::Operation; + using Op = FunctionStorage::Operation; - const RenderConfig config; - const TemplateStorage &template_storage; - const FunctionStorage &function_storage; + const RenderConfig config; + const TemplateStorage& template_storage; + const FunctionStorage& function_storage; - const Template *current_template; - size_t current_level{0}; - std::vector template_stack; - std::vector block_statement_stack; + const Template* current_template; + size_t current_level {0}; + std::vector template_stack; + std::vector block_statement_stack; - const json *data_input; - std::ostream *output_stream; + const json* data_input; + std::ostream* output_stream; - json additional_data; - json *current_loop_data = &additional_data["loop"]; + json additional_data; + json* current_loop_data = &additional_data["loop"]; - std::vector> data_tmp_stack; - std::stack data_eval_stack; - std::stack not_found_stack; + std::vector> data_tmp_stack; + std::stack data_eval_stack; + std::stack not_found_stack; - bool break_rendering{false}; + bool break_rendering {false}; - static bool truthy(const json *data) - { - if (data->is_boolean()) { - return data->get(); - } else if (data->is_number()) { - return (*data != 0); - } else if (data->is_null()) { - return false; - } - return !data->empty(); + static bool truthy(const json* data) { + if (data->is_boolean()) { + return data->get(); + } else if (data->is_number()) { + return (*data != 0); + } else if (data->is_null()) { + return false; + } + return !data->empty(); + } + + void print_data(const std::shared_ptr value) { + if (value->is_string()) { + *output_stream << value->get_ref(); + } else if (value->is_number_integer()) { + *output_stream << value->get(); + } else if (value->is_null()) { + } else { + *output_stream << value->dump(); + } + } + + const std::shared_ptr eval_expression_list(const ExpressionListNode& expression_list) { + if (!expression_list.root) { + throw_renderer_error("empty expression", expression_list); } - void print_data(const std::shared_ptr value) - { - if (value->is_string()) { - *output_stream << value->get_ref(); - } else if (value->is_number_integer()) { - *output_stream << value->get(); - } else if (value->is_null()) { - } else { - *output_stream << value->dump(); - } + expression_list.root->accept(*this); + + if (data_eval_stack.empty()) { + throw_renderer_error("empty expression", expression_list); + } else if (data_eval_stack.size() != 1) { + throw_renderer_error("malformed expression", expression_list); } - const std::shared_ptr - eval_expression_list(const ExpressionListNode &expression_list) - { - if (!expression_list.root) { - throw_renderer_error("empty expression", expression_list); - } + const auto result = data_eval_stack.top(); + data_eval_stack.pop(); - expression_list.root->accept(*this); + if (!result) { + if (not_found_stack.empty()) { + throw_renderer_error("expression could not be evaluated", expression_list); + } - if (data_eval_stack.empty()) { - throw_renderer_error("empty expression", expression_list); - } else if (data_eval_stack.size() != 1) { - throw_renderer_error("malformed expression", expression_list); - } + auto node = not_found_stack.top(); + not_found_stack.pop(); - const auto result = data_eval_stack.top(); - data_eval_stack.pop(); + throw_renderer_error("variable '" + static_cast(node->name) + "' not found", *node); + } + return std::make_shared(*result); + } - if (!result) { - if (not_found_stack.empty()) { - throw_renderer_error("expression could not be evaluated", - expression_list); - } + void throw_renderer_error(const std::string& message, const AstNode& node) { + SourceLocation loc = get_source_location(current_template->content, node.pos); + INJA_THROW(RenderError(message, loc)); + } - auto node = not_found_stack.top(); - not_found_stack.pop(); + void make_result(const json&& result) { + auto result_ptr = std::make_shared(result); + data_tmp_stack.push_back(result_ptr); + data_eval_stack.push(result_ptr.get()); + } - throw_renderer_error("variable '" - + static_cast(node->name) - + "' not found", - *node); - } - return std::make_shared(*result); + template std::array get_arguments(const FunctionNode& node) { + if (node.arguments.size() < N_start + N) { + throw_renderer_error("function needs " + std::to_string(N_start + N) + " variables, but has only found " + std::to_string(node.arguments.size()), node); } - void throw_renderer_error(const std::string &message, const AstNode &node) - { - SourceLocation loc = - get_source_location(current_template->content, node.pos); - INJA_THROW(RenderError(message, loc)); + for (size_t i = N_start; i < N_start + N; i += 1) { + node.arguments[i]->accept(*this); } - void make_result(const json &&result) - { - auto result_ptr = std::make_shared(result); - data_tmp_stack.push_back(result_ptr); - data_eval_stack.push(result_ptr.get()); + if (data_eval_stack.size() < N) { + throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(data_eval_stack.size()), node); } - template - std::array get_arguments(const FunctionNode &node) - { - if (node.arguments.size() < N_start + N) { - throw_renderer_error("function needs " + std::to_string(N_start + N) - + " variables, but has only found " - + std::to_string(node.arguments.size()), - node); + std::array result; + for (size_t i = 0; i < N; i += 1) { + result[N - i - 1] = data_eval_stack.top(); + data_eval_stack.pop(); + + if (!result[N - i - 1]) { + const auto data_node = not_found_stack.top(); + not_found_stack.pop(); + + if (throw_not_found) { + throw_renderer_error("variable '" + static_cast(data_node->name) + "' not found", *data_node); } + } + } + return result; + } - for (size_t i = N_start; i < N_start + N; i += 1) { - node.arguments[i]->accept(*this); - } - - if (data_eval_stack.size() < N) { - throw_renderer_error("function needs " + std::to_string(N) - + " variables, but has only found " - + std::to_string(data_eval_stack.size()), - node); - } - - std::array result; - for (size_t i = 0; i < N; i += 1) { - result[N - i - 1] = data_eval_stack.top(); - data_eval_stack.pop(); - - if (!result[N - i - 1]) { - const auto data_node = not_found_stack.top(); - not_found_stack.pop(); - - if (throw_not_found) { - throw_renderer_error( - "variable '" + static_cast(data_node->name) - + "' not found", - *data_node); - } - } - } - return result; + template Arguments get_argument_vector(const FunctionNode& node) { + const size_t N = node.arguments.size(); + for (auto a : node.arguments) { + a->accept(*this); } - template - Arguments get_argument_vector(const FunctionNode &node) - { - const size_t N = node.arguments.size(); - for (auto a : node.arguments) { a->accept(*this); } - - if (data_eval_stack.size() < N) { - throw_renderer_error("function needs " + std::to_string(N) - + " variables, but has only found " - + std::to_string(data_eval_stack.size()), - node); - } - - Arguments result{N}; - for (size_t i = 0; i < N; i += 1) { - result[N - i - 1] = data_eval_stack.top(); - data_eval_stack.pop(); - - if (!result[N - i - 1]) { - const auto data_node = not_found_stack.top(); - not_found_stack.pop(); - - if (throw_not_found) { - throw_renderer_error( - "variable '" + static_cast(data_node->name) - + "' not found", - *data_node); - } - } - } - return result; + if (data_eval_stack.size() < N) { + throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(data_eval_stack.size()), node); } - void visit(const BlockNode &node) - { - for (auto &n : node.nodes) { - n->accept(*this); + Arguments result {N}; + for (size_t i = 0; i < N; i += 1) { + result[N - i - 1] = data_eval_stack.top(); + data_eval_stack.pop(); - if (break_rendering) { break; } + if (!result[N - i - 1]) { + const auto data_node = not_found_stack.top(); + not_found_stack.pop(); + + if (throw_not_found) { + throw_renderer_error("variable '" + static_cast(data_node->name) + "' not found", *data_node); } + } } + return result; + } - void visit(const TextNode &node) - { - output_stream->write(current_template->content.c_str() + node.pos, - node.length); + void visit(const BlockNode& node) { + for (auto& n : node.nodes) { + n->accept(*this); + + if (break_rendering) { + break; + } } + } - void visit(const ExpressionNode &) {} + void visit(const TextNode& node) { + output_stream->write(current_template->content.c_str() + node.pos, node.length); + } - void visit(const LiteralNode &node) { data_eval_stack.push(&node.value); } + void visit(const ExpressionNode&) {} - void visit(const DataNode &node) - { - if (additional_data.contains(node.ptr)) { - data_eval_stack.push(&(additional_data[node.ptr])); - } else if (data_input->contains(node.ptr)) { - data_eval_stack.push(&(*data_input)[node.ptr]); - } else { - // Try to evaluate as a no-argument callback - const auto function_data = - function_storage.find_function(node.name, 0); - if (function_data.operation - == FunctionStorage::Operation::Callback) { - Arguments empty_args{}; - const auto value = - std::make_shared(function_data.callback(empty_args)); - data_tmp_stack.push_back(value); - data_eval_stack.push(value.get()); - } else { - data_eval_stack.push(nullptr); - not_found_stack.emplace(&node); - } - } + void visit(const LiteralNode& node) { + data_eval_stack.push(&node.value); + } + + void visit(const DataNode& node) { + if (additional_data.contains(node.ptr)) { + data_eval_stack.push(&(additional_data[node.ptr])); + } else if (data_input->contains(node.ptr)) { + data_eval_stack.push(&(*data_input)[node.ptr]); + } else { + // Try to evaluate as a no-argument callback + const auto function_data = function_storage.find_function(node.name, 0); + if (function_data.operation == FunctionStorage::Operation::Callback) { + Arguments empty_args {}; + const auto value = std::make_shared(function_data.callback(empty_args)); + data_tmp_stack.push_back(value); + data_eval_stack.push(value.get()); + } else { + data_eval_stack.push(nullptr); + not_found_stack.emplace(&node); + } } + } - void visit(const FunctionNode &node) - { - switch (node.operation) { - case Op::Not: { - const auto args = get_arguments<1>(node); - make_result(!truthy(args[0])); - } break; - case Op::And: { - make_result(truthy(get_arguments<1, 0>(node)[0]) - && truthy(get_arguments<1, 1>(node)[0])); - } break; - case Op::Or: { - make_result(truthy(get_arguments<1, 0>(node)[0]) - || truthy(get_arguments<1, 1>(node)[0])); - } break; - case Op::In: { - const auto args = get_arguments<2>(node); - make_result(std::find(args[1]->begin(), args[1]->end(), *args[0]) - != args[1]->end()); - } break; - case Op::Equal: { - const auto args = get_arguments<2>(node); - make_result(*args[0] == *args[1]); - } break; - case Op::NotEqual: { - const auto args = get_arguments<2>(node); - make_result(*args[0] != *args[1]); - } break; - case Op::Greater: { - const auto args = get_arguments<2>(node); - make_result(*args[0] > *args[1]); - } break; - case Op::GreaterEqual: { - const auto args = get_arguments<2>(node); - make_result(*args[0] >= *args[1]); - } break; - case Op::Less: { - const auto args = get_arguments<2>(node); - make_result(*args[0] < *args[1]); - } break; - case Op::LessEqual: { - const auto args = get_arguments<2>(node); - make_result(*args[0] <= *args[1]); - } break; - case Op::Add: { - const auto args = get_arguments<2>(node); - if (args[0]->is_string() && args[1]->is_string()) { - make_result(args[0]->get_ref() - + args[1]->get_ref()); - } else if (args[0]->is_number_integer() - && args[1]->is_number_integer()) { - make_result(args[0]->get() - + args[1]->get()); - } else { - make_result(args[0]->get() - + args[1]->get()); - } - } break; - case Op::Subtract: { - const auto args = get_arguments<2>(node); - if (args[0]->is_number_integer() && args[1]->is_number_integer()) { - make_result(args[0]->get() - - args[1]->get()); - } else { - make_result(args[0]->get() - - args[1]->get()); - } - } break; - case Op::Multiplication: { - const auto args = get_arguments<2>(node); - if (args[0]->is_number_integer() && args[1]->is_number_integer()) { - make_result(args[0]->get() - * args[1]->get()); - } else { - make_result(args[0]->get() - * args[1]->get()); - } - } break; - case Op::Division: { - const auto args = get_arguments<2>(node); - if (args[1]->get() == 0) { - throw_renderer_error("division by zero", node); - } - make_result(args[0]->get() - / args[1]->get()); - } break; - case Op::Power: { - const auto args = get_arguments<2>(node); - if (args[0]->is_number_integer() - && args[1]->get() >= 0) { - const auto result = static_cast( - std::pow(args[0]->get(), - args[1]->get())); - make_result(result); - } else { - const auto result = - std::pow(args[0]->get(), - args[1]->get()); - make_result(result); - } - } break; - case Op::Modulo: { - const auto args = get_arguments<2>(node); - make_result(args[0]->get() - % args[1]->get()); - } break; - case Op::AtId: { - const auto container = get_arguments<1, 0, false>(node)[0]; - node.arguments[1]->accept(*this); - if (not_found_stack.empty()) { - throw_renderer_error("could not find element with given name", - node); - } - const auto id_node = not_found_stack.top(); - not_found_stack.pop(); - data_eval_stack.pop(); - data_eval_stack.push(&container->at(id_node->name)); - } break; - case Op::At: { - const auto args = get_arguments<2>(node); - if (args[0]->is_object()) { - data_eval_stack.push(&args[0]->at(args[1]->get())); - } else { - data_eval_stack.push(&args[0]->at(args[1]->get())); - } - } break; - case Op::Default: { - const auto test_arg = get_arguments<1, 0, false>(node)[0]; - data_eval_stack.push(test_arg ? test_arg - : get_arguments<1, 1>(node)[0]); - } break; - case Op::DivisibleBy: { - const auto args = get_arguments<2>(node); - const auto divisor = args[1]->get(); - make_result( - (divisor != 0) - && (args[0]->get() % divisor - == 0)); - } break; - case Op::Even: { - make_result( - get_arguments<1>(node)[0]->get() - % 2 - == 0); - } break; - case Op::Exists: { - auto &&name = - get_arguments<1>(node)[0]->get_ref(); - make_result(data_input->contains( - json::json_pointer(DataNode::convert_dot_to_ptr(name)))); - } break; - case Op::ExistsInObject: { - const auto args = get_arguments<2>(node); - auto &&name = args[1]->get_ref(); - make_result(args[0]->find(name) != args[0]->end()); - } break; - case Op::First: { - const auto result = &get_arguments<1>(node)[0]->front(); - data_eval_stack.push(result); - } break; - case Op::Float: { - make_result(std::stod( - get_arguments<1>(node)[0]->get_ref())); - } break; - case Op::Int: { - make_result(std::stoi( - get_arguments<1>(node)[0]->get_ref())); - } break; - case Op::Last: { - const auto result = &get_arguments<1>(node)[0]->back(); - data_eval_stack.push(result); - } break; - case Op::Length: { - const auto val = get_arguments<1>(node)[0]; - if (val->is_string()) { - make_result(val->get_ref().length()); - } else { - make_result(val->size()); - } - } break; - case Op::Lower: { - auto result = get_arguments<1>(node)[0]->get(); - std::transform( - result.begin(), result.end(), result.begin(), - [](char c) { return static_cast(::tolower(c)); }); - make_result(std::move(result)); - } break; - case Op::Max: { - const auto args = get_arguments<1>(node); - const auto result = - std::max_element(args[0]->begin(), args[0]->end()); - data_eval_stack.push(&(*result)); - } break; - case Op::Min: { - const auto args = get_arguments<1>(node); - const auto result = - std::min_element(args[0]->begin(), args[0]->end()); - data_eval_stack.push(&(*result)); - } break; - case Op::Odd: { - make_result( - get_arguments<1>(node)[0]->get() - % 2 - != 0); - } break; - case Op::Range: { - std::vector result( - get_arguments<1>(node)[0]->get()); - std::iota(result.begin(), result.end(), 0); - make_result(std::move(result)); - } break; - case Op::Round: { - const auto args = get_arguments<2>(node); - const int precision = args[1]->get(); - const double result = - std::round(args[0]->get() - * std::pow(10.0, precision)) - / std::pow(10.0, precision); - if (precision == 0) { - make_result(int(result)); - } else { - make_result(result); - } - } break; - case Op::Sort: { - auto result_ptr = std::make_shared( - get_arguments<1>(node)[0]->get>()); - std::sort(result_ptr->begin(), result_ptr->end()); - data_tmp_stack.push_back(result_ptr); - data_eval_stack.push(result_ptr.get()); - } break; - case Op::Upper: { - auto result = get_arguments<1>(node)[0]->get(); - std::transform( - result.begin(), result.end(), result.begin(), - [](char c) { return static_cast(::toupper(c)); }); - make_result(std::move(result)); - } break; - case Op::IsBoolean: { - make_result(get_arguments<1>(node)[0]->is_boolean()); - } break; - case Op::IsNumber: { - make_result(get_arguments<1>(node)[0]->is_number()); - } break; - case Op::IsInteger: { - make_result(get_arguments<1>(node)[0]->is_number_integer()); - } break; - case Op::IsFloat: { - make_result(get_arguments<1>(node)[0]->is_number_float()); - } break; - case Op::IsObject: { - make_result(get_arguments<1>(node)[0]->is_object()); - } break; - case Op::IsArray: { - make_result(get_arguments<1>(node)[0]->is_array()); - } break; - case Op::IsString: { - make_result(get_arguments<1>(node)[0]->is_string()); - } break; - case Op::Callback: { - auto args = get_argument_vector(node); - make_result(node.callback(args)); - } break; - case Op::Super: { - const auto args = get_argument_vector(node); - const size_t old_level = current_level; - const size_t level_diff = - (args.size() == 1) ? args[0]->get() : 1; - const size_t level = current_level + level_diff; + void visit(const FunctionNode& node) { + switch (node.operation) { + case Op::Not: { + const auto args = get_arguments<1>(node); + make_result(!truthy(args[0])); + } break; + case Op::And: { + make_result(truthy(get_arguments<1, 0>(node)[0]) && truthy(get_arguments<1, 1>(node)[0])); + } break; + case Op::Or: { + make_result(truthy(get_arguments<1, 0>(node)[0]) || truthy(get_arguments<1, 1>(node)[0])); + } break; + case Op::In: { + const auto args = get_arguments<2>(node); + make_result(std::find(args[1]->begin(), args[1]->end(), *args[0]) != args[1]->end()); + } break; + case Op::Equal: { + const auto args = get_arguments<2>(node); + make_result(*args[0] == *args[1]); + } break; + case Op::NotEqual: { + const auto args = get_arguments<2>(node); + make_result(*args[0] != *args[1]); + } break; + case Op::Greater: { + const auto args = get_arguments<2>(node); + make_result(*args[0] > *args[1]); + } break; + case Op::GreaterEqual: { + const auto args = get_arguments<2>(node); + make_result(*args[0] >= *args[1]); + } break; + case Op::Less: { + const auto args = get_arguments<2>(node); + make_result(*args[0] < *args[1]); + } break; + case Op::LessEqual: { + const auto args = get_arguments<2>(node); + make_result(*args[0] <= *args[1]); + } break; + case Op::Add: { + const auto args = get_arguments<2>(node); + if (args[0]->is_string() && args[1]->is_string()) { + make_result(args[0]->get_ref() + args[1]->get_ref()); + } else if (args[0]->is_number_integer() && args[1]->is_number_integer()) { + make_result(args[0]->get() + args[1]->get()); + } else { + make_result(args[0]->get() + args[1]->get()); + } + } break; + case Op::Subtract: { + const auto args = get_arguments<2>(node); + if (args[0]->is_number_integer() && args[1]->is_number_integer()) { + make_result(args[0]->get() - args[1]->get()); + } else { + make_result(args[0]->get() - args[1]->get()); + } + } break; + case Op::Multiplication: { + const auto args = get_arguments<2>(node); + if (args[0]->is_number_integer() && args[1]->is_number_integer()) { + make_result(args[0]->get() * args[1]->get()); + } else { + make_result(args[0]->get() * args[1]->get()); + } + } break; + case Op::Division: { + const auto args = get_arguments<2>(node); + if (args[1]->get() == 0) { + throw_renderer_error("division by zero", node); + } + make_result(args[0]->get() / args[1]->get()); + } break; + case Op::Power: { + const auto args = get_arguments<2>(node); + if (args[0]->is_number_integer() && args[1]->get() >= 0) { + const auto result = static_cast(std::pow(args[0]->get(), args[1]->get())); + make_result(result); + } else { + const auto result = std::pow(args[0]->get(), args[1]->get()); + make_result(result); + } + } break; + case Op::Modulo: { + const auto args = get_arguments<2>(node); + make_result(args[0]->get() % args[1]->get()); + } break; + case Op::AtId: { + const auto container = get_arguments<1, 0, false>(node)[0]; + node.arguments[1]->accept(*this); + if (not_found_stack.empty()) { + throw_renderer_error("could not find element with given name", node); + } + const auto id_node = not_found_stack.top(); + not_found_stack.pop(); + data_eval_stack.pop(); + data_eval_stack.push(&container->at(id_node->name)); + } break; + case Op::At: { + const auto args = get_arguments<2>(node); + if (args[0]->is_object()) { + data_eval_stack.push(&args[0]->at(args[1]->get())); + } else { + data_eval_stack.push(&args[0]->at(args[1]->get())); + } + } break; + case Op::Default: { + const auto test_arg = get_arguments<1, 0, false>(node)[0]; + data_eval_stack.push(test_arg ? test_arg : get_arguments<1, 1>(node)[0]); + } break; + case Op::DivisibleBy: { + const auto args = get_arguments<2>(node); + const auto divisor = args[1]->get(); + make_result((divisor != 0) && (args[0]->get() % divisor == 0)); + } break; + case Op::Even: { + make_result(get_arguments<1>(node)[0]->get() % 2 == 0); + } break; + case Op::Exists: { + auto&& name = get_arguments<1>(node)[0]->get_ref(); + make_result(data_input->contains(json::json_pointer(DataNode::convert_dot_to_ptr(name)))); + } break; + case Op::ExistsInObject: { + const auto args = get_arguments<2>(node); + auto&& name = args[1]->get_ref(); + make_result(args[0]->find(name) != args[0]->end()); + } break; + case Op::First: { + const auto result = &get_arguments<1>(node)[0]->front(); + data_eval_stack.push(result); + } break; + case Op::Float: { + make_result(std::stod(get_arguments<1>(node)[0]->get_ref())); + } break; + case Op::Int: { + make_result(std::stoi(get_arguments<1>(node)[0]->get_ref())); + } break; + case Op::Last: { + const auto result = &get_arguments<1>(node)[0]->back(); + data_eval_stack.push(result); + } break; + case Op::Length: { + const auto val = get_arguments<1>(node)[0]; + if (val->is_string()) { + make_result(val->get_ref().length()); + } else { + make_result(val->size()); + } + } break; + case Op::Lower: { + auto result = get_arguments<1>(node)[0]->get(); + std::transform(result.begin(), result.end(), result.begin(), [](char c) { return static_cast(::tolower(c)); }); + make_result(std::move(result)); + } break; + case Op::Max: { + const auto args = get_arguments<1>(node); + const auto result = std::max_element(args[0]->begin(), args[0]->end()); + data_eval_stack.push(&(*result)); + } break; + case Op::Min: { + const auto args = get_arguments<1>(node); + const auto result = std::min_element(args[0]->begin(), args[0]->end()); + data_eval_stack.push(&(*result)); + } break; + case Op::Odd: { + make_result(get_arguments<1>(node)[0]->get() % 2 != 0); + } break; + case Op::Range: { + std::vector result(get_arguments<1>(node)[0]->get()); + std::iota(result.begin(), result.end(), 0); + make_result(std::move(result)); + } break; + case Op::Round: { + const auto args = get_arguments<2>(node); + const int precision = args[1]->get(); + const double result = std::round(args[0]->get() * std::pow(10.0, precision)) / std::pow(10.0, precision); + if (precision == 0) { + make_result(int(result)); + } else { + make_result(result); + } + } break; + case Op::Sort: { + auto result_ptr = std::make_shared(get_arguments<1>(node)[0]->get>()); + std::sort(result_ptr->begin(), result_ptr->end()); + data_tmp_stack.push_back(result_ptr); + data_eval_stack.push(result_ptr.get()); + } break; + case Op::Upper: { + auto result = get_arguments<1>(node)[0]->get(); + std::transform(result.begin(), result.end(), result.begin(), [](char c) { return static_cast(::toupper(c)); }); + make_result(std::move(result)); + } break; + case Op::IsBoolean: { + make_result(get_arguments<1>(node)[0]->is_boolean()); + } break; + case Op::IsNumber: { + make_result(get_arguments<1>(node)[0]->is_number()); + } break; + case Op::IsInteger: { + make_result(get_arguments<1>(node)[0]->is_number_integer()); + } break; + case Op::IsFloat: { + make_result(get_arguments<1>(node)[0]->is_number_float()); + } break; + case Op::IsObject: { + make_result(get_arguments<1>(node)[0]->is_object()); + } break; + case Op::IsArray: { + make_result(get_arguments<1>(node)[0]->is_array()); + } break; + case Op::IsString: { + make_result(get_arguments<1>(node)[0]->is_string()); + } break; + case Op::Callback: { + auto args = get_argument_vector(node); + make_result(node.callback(args)); + } break; + case Op::Super: { + const auto args = get_argument_vector(node); + const size_t old_level = current_level; + const size_t level_diff = (args.size() == 1) ? args[0]->get() : 1; + const size_t level = current_level + level_diff; - if (block_statement_stack.empty()) { - throw_renderer_error("super() call is not within a block", - node); - } + if (block_statement_stack.empty()) { + throw_renderer_error("super() call is not within a block", node); + } - if (level < 1 || level > template_stack.size() - 1) { - throw_renderer_error( - "level of super() call does not match parent templates " - "(between 1 and " - + std::to_string(template_stack.size() - 1) + ")", - node); - } + if (level < 1 || level > template_stack.size() - 1) { + throw_renderer_error("level of super() call does not match parent templates (between 1 and " + std::to_string(template_stack.size() - 1) + ")", node); + } - const auto current_block_statement = block_statement_stack.back(); - const Template *new_template = template_stack.at(level); - const Template *old_template = current_template; - const auto block_it = - new_template->block_storage.find(current_block_statement->name); - if (block_it != new_template->block_storage.end()) { - current_template = new_template; - current_level = level; - block_it->second->block.accept(*this); - current_level = old_level; - current_template = old_template; - } else { - throw_renderer_error("could not find block with name '" - + current_block_statement->name + "'", - node); - } - make_result(nullptr); - } break; - case Op::Join: { - const auto args = get_arguments<2>(node); - const auto separator = args[1]->get(); - std::ostringstream os; - std::string sep; - for (const auto &value : *args[0]) { - os << sep; - if (value.is_string()) { - os << value.get< - std:: - string>();// otherwise the value is surrounded with "" - } else { - os << value.dump(); - } - sep = separator; - } - make_result(os.str()); - } break; - case Op::None: - break; - } - } - - void visit(const ExpressionListNode &node) - { - print_data(eval_expression_list(node)); - } - - void visit(const StatementNode &) {} - - void visit(const ForStatementNode &) {} - - void visit(const ForArrayStatementNode &node) - { - const auto result = eval_expression_list(node.condition); - if (!result->is_array()) { - throw_renderer_error("object must be an array", node); - } - - if (!current_loop_data->empty()) { - auto tmp = *current_loop_data;// Because of clang-3 - (*current_loop_data)["parent"] = std::move(tmp); - } - - size_t index = 0; - (*current_loop_data)["is_first"] = true; - (*current_loop_data)["is_last"] = (result->size() <= 1); - for (auto it = result->begin(); it != result->end(); ++it) { - additional_data[static_cast(node.value)] = *it; - - (*current_loop_data)["index"] = index; - (*current_loop_data)["index1"] = index + 1; - if (index == 1) { (*current_loop_data)["is_first"] = false; } - if (index == result->size() - 1) { - (*current_loop_data)["is_last"] = true; - } - - node.body.accept(*this); - ++index; - } - - additional_data[static_cast(node.value)].clear(); - if (!(*current_loop_data)["parent"].empty()) { - const auto tmp = (*current_loop_data)["parent"]; - *current_loop_data = std::move(tmp); - } else { - current_loop_data = &additional_data["loop"]; - } - } - - void visit(const ForObjectStatementNode &node) - { - const auto result = eval_expression_list(node.condition); - if (!result->is_object()) { - throw_renderer_error("object must be an object", node); - } - - if (!current_loop_data->empty()) { - (*current_loop_data)["parent"] = std::move(*current_loop_data); - } - - size_t index = 0; - (*current_loop_data)["is_first"] = true; - (*current_loop_data)["is_last"] = (result->size() <= 1); - for (auto it = result->begin(); it != result->end(); ++it) { - additional_data[static_cast(node.key)] = it.key(); - additional_data[static_cast(node.value)] = it.value(); - - (*current_loop_data)["index"] = index; - (*current_loop_data)["index1"] = index + 1; - if (index == 1) { (*current_loop_data)["is_first"] = false; } - if (index == result->size() - 1) { - (*current_loop_data)["is_last"] = true; - } - - node.body.accept(*this); - ++index; - } - - additional_data[static_cast(node.key)].clear(); - additional_data[static_cast(node.value)].clear(); - if (!(*current_loop_data)["parent"].empty()) { - *current_loop_data = std::move((*current_loop_data)["parent"]); - } else { - current_loop_data = &additional_data["loop"]; - } - } - - void visit(const IfStatementNode &node) - { - const auto result = eval_expression_list(node.condition); - if (truthy(result.get())) { - node.true_statement.accept(*this); - } else if (node.has_false_statement) { - node.false_statement.accept(*this); - } - } - - void visit(const IncludeStatementNode &node) - { - auto sub_renderer = - Renderer(config, template_storage, function_storage); - const auto included_template_it = template_storage.find(node.file); - if (included_template_it != template_storage.end()) { - sub_renderer.render_to(*output_stream, included_template_it->second, - *data_input, &additional_data); - } else if (config.throw_at_missing_includes) { - throw_renderer_error("include '" + node.file + "' not found", node); - } - } - - void visit(const ExtendsStatementNode &node) - { - const auto included_template_it = template_storage.find(node.file); - if (included_template_it != template_storage.end()) { - const Template *parent_template = &included_template_it->second; - render_to(*output_stream, *parent_template, *data_input, - &additional_data); - break_rendering = true; - } else if (config.throw_at_missing_includes) { - throw_renderer_error("extends '" + node.file + "' not found", node); - } - } - - void visit(const BlockStatementNode &node) - { - const size_t old_level = current_level; - current_level = 0; - current_template = template_stack.front(); - const auto block_it = current_template->block_storage.find(node.name); - if (block_it != current_template->block_storage.end()) { - block_statement_stack.emplace_back(&node); - block_it->second->block.accept(*this); - block_statement_stack.pop_back(); - } + const auto current_block_statement = block_statement_stack.back(); + const Template* new_template = template_stack.at(level); + const Template* old_template = current_template; + const auto block_it = new_template->block_storage.find(current_block_statement->name); + if (block_it != new_template->block_storage.end()) { + current_template = new_template; + current_level = level; + block_it->second->block.accept(*this); current_level = old_level; - current_template = template_stack.back(); + current_template = old_template; + } else { + throw_renderer_error("could not find block with name '" + current_block_statement->name + "'", node); + } + make_result(nullptr); + } break; + case Op::Join: { + const auto args = get_arguments<2>(node); + const auto separator = args[1]->get(); + std::ostringstream os; + std::string sep; + for (const auto& value : *args[0]) { + os << sep; + if (value.is_string()) { + os << value.get(); // otherwise the value is surrounded with "" + } else { + os << value.dump(); + } + sep = separator; + } + make_result(os.str()); + } break; + case Op::None: + break; + } + } + + void visit(const ExpressionListNode& node) { + print_data(eval_expression_list(node)); + } + + void visit(const StatementNode&) {} + + void visit(const ForStatementNode&) {} + + void visit(const ForArrayStatementNode& node) { + const auto result = eval_expression_list(node.condition); + if (!result->is_array()) { + throw_renderer_error("object must be an array", node); } - void visit(const SetStatementNode &node) - { - std::string ptr = node.key; - replace_substring(ptr, ".", "/"); - ptr = "/" + ptr; - additional_data[json::json_pointer(ptr)] = - *eval_expression_list(node.expression); + if (!current_loop_data->empty()) { + auto tmp = *current_loop_data; // Because of clang-3 + (*current_loop_data)["parent"] = std::move(tmp); } + size_t index = 0; + (*current_loop_data)["is_first"] = true; + (*current_loop_data)["is_last"] = (result->size() <= 1); + for (auto it = result->begin(); it != result->end(); ++it) { + additional_data[static_cast(node.value)] = *it; + + (*current_loop_data)["index"] = index; + (*current_loop_data)["index1"] = index + 1; + if (index == 1) { + (*current_loop_data)["is_first"] = false; + } + if (index == result->size() - 1) { + (*current_loop_data)["is_last"] = true; + } + + node.body.accept(*this); + ++index; + } + + additional_data[static_cast(node.value)].clear(); + if (!(*current_loop_data)["parent"].empty()) { + const auto tmp = (*current_loop_data)["parent"]; + *current_loop_data = std::move(tmp); + } else { + current_loop_data = &additional_data["loop"]; + } + } + + void visit(const ForObjectStatementNode& node) { + const auto result = eval_expression_list(node.condition); + if (!result->is_object()) { + throw_renderer_error("object must be an object", node); + } + + if (!current_loop_data->empty()) { + (*current_loop_data)["parent"] = std::move(*current_loop_data); + } + + size_t index = 0; + (*current_loop_data)["is_first"] = true; + (*current_loop_data)["is_last"] = (result->size() <= 1); + for (auto it = result->begin(); it != result->end(); ++it) { + additional_data[static_cast(node.key)] = it.key(); + additional_data[static_cast(node.value)] = it.value(); + + (*current_loop_data)["index"] = index; + (*current_loop_data)["index1"] = index + 1; + if (index == 1) { + (*current_loop_data)["is_first"] = false; + } + if (index == result->size() - 1) { + (*current_loop_data)["is_last"] = true; + } + + node.body.accept(*this); + ++index; + } + + additional_data[static_cast(node.key)].clear(); + additional_data[static_cast(node.value)].clear(); + if (!(*current_loop_data)["parent"].empty()) { + *current_loop_data = std::move((*current_loop_data)["parent"]); + } else { + current_loop_data = &additional_data["loop"]; + } + } + + void visit(const IfStatementNode& node) { + const auto result = eval_expression_list(node.condition); + if (truthy(result.get())) { + node.true_statement.accept(*this); + } else if (node.has_false_statement) { + node.false_statement.accept(*this); + } + } + + void visit(const IncludeStatementNode& node) { + auto sub_renderer = Renderer(config, template_storage, function_storage); + const auto included_template_it = template_storage.find(node.file); + if (included_template_it != template_storage.end()) { + sub_renderer.render_to(*output_stream, included_template_it->second, *data_input, &additional_data); + } else if (config.throw_at_missing_includes) { + throw_renderer_error("include '" + node.file + "' not found", node); + } + } + + void visit(const ExtendsStatementNode& node) { + const auto included_template_it = template_storage.find(node.file); + if (included_template_it != template_storage.end()) { + const Template* parent_template = &included_template_it->second; + render_to(*output_stream, *parent_template, *data_input, &additional_data); + break_rendering = true; + } else if (config.throw_at_missing_includes) { + throw_renderer_error("extends '" + node.file + "' not found", node); + } + } + + void visit(const BlockStatementNode& node) { + const size_t old_level = current_level; + current_level = 0; + current_template = template_stack.front(); + const auto block_it = current_template->block_storage.find(node.name); + if (block_it != current_template->block_storage.end()) { + block_statement_stack.emplace_back(&node); + block_it->second->block.accept(*this); + block_statement_stack.pop_back(); + } + current_level = old_level; + current_template = template_stack.back(); + } + + void visit(const SetStatementNode& node) { + std::string ptr = node.key; + replace_substring(ptr, ".", "/"); + ptr = "/" + ptr; + additional_data[json::json_pointer(ptr)] = *eval_expression_list(node.expression); + } + public: - Renderer(const RenderConfig &config, - const TemplateStorage &template_storage, - const FunctionStorage &function_storage) - : config(config), - template_storage(template_storage), - function_storage(function_storage) - {} + Renderer(const RenderConfig& config, const TemplateStorage& template_storage, const FunctionStorage& function_storage) + : config(config), template_storage(template_storage), function_storage(function_storage) {} - void render_to(std::ostream &os, - const Template &tmpl, - const json &data, - json *loop_data = nullptr) - { - output_stream = &os; - current_template = &tmpl; - data_input = &data; - if (loop_data) { - additional_data = *loop_data; - current_loop_data = &additional_data["loop"]; - } - - template_stack.emplace_back(current_template); - current_template->root.accept(*this); - - data_tmp_stack.clear(); + void render_to(std::ostream& os, const Template& tmpl, const json& data, json* loop_data = nullptr) { + output_stream = &os; + current_template = &tmpl; + data_input = &data; + if (loop_data) { + additional_data = *loop_data; + current_loop_data = &additional_data["loop"]; } + + template_stack.emplace_back(current_template); + current_template->root.accept(*this); + + data_tmp_stack.clear(); + } }; -}// namespace inja +} // namespace inja -#endif// INCLUDE_INJA_RENDERER_HPP_ +#endif // INCLUDE_INJA_RENDERER_HPP_ // #include "template.hpp" // #include "utils.hpp" + namespace inja { /*! * \brief Class for changing the configuration. */ class Environment { - LexerConfig lexer_config; - ParserConfig parser_config; - RenderConfig render_config; + LexerConfig lexer_config; + ParserConfig parser_config; + RenderConfig render_config; - FunctionStorage function_storage; - TemplateStorage template_storage; + FunctionStorage function_storage; + TemplateStorage template_storage; protected: - std::string input_path; - std::string output_path; + std::string input_path; + std::string output_path; public: - Environment() : Environment("") {} + Environment(): Environment("") {} - explicit Environment(const std::string &global_path) - : input_path(global_path), - output_path(global_path) - {} + explicit Environment(const std::string& global_path): input_path(global_path), output_path(global_path) {} - Environment(const std::string &input_path, const std::string &output_path) - : input_path(input_path), - output_path(output_path) - {} + Environment(const std::string& input_path, const std::string& output_path): input_path(input_path), output_path(output_path) {} - /// Sets the opener and closer for template statements - void set_statement(const std::string &open, const std::string &close) - { - lexer_config.statement_open = open; - lexer_config.statement_open_no_lstrip = open + "+"; - lexer_config.statement_open_force_lstrip = open + "-"; - lexer_config.statement_close = close; - lexer_config.statement_close_force_rstrip = "-" + close; - lexer_config.update_open_chars(); + /// Sets the opener and closer for template statements + void set_statement(const std::string& open, const std::string& close) { + lexer_config.statement_open = open; + lexer_config.statement_open_no_lstrip = open + "+"; + lexer_config.statement_open_force_lstrip = open + "-"; + lexer_config.statement_close = close; + lexer_config.statement_close_force_rstrip = "-" + close; + lexer_config.update_open_chars(); + } + + /// Sets the opener for template line statements + void set_line_statement(const std::string& open) { + lexer_config.line_statement = open; + lexer_config.update_open_chars(); + } + + /// Sets the opener and closer for template expressions + void set_expression(const std::string& open, const std::string& close) { + lexer_config.expression_open = open; + lexer_config.expression_open_force_lstrip = open + "-"; + lexer_config.expression_close = close; + lexer_config.expression_close_force_rstrip = "-" + close; + lexer_config.update_open_chars(); + } + + /// Sets the opener and closer for template comments + void set_comment(const std::string& open, const std::string& close) { + lexer_config.comment_open = open; + lexer_config.comment_open_force_lstrip = open + "-"; + lexer_config.comment_close = close; + lexer_config.comment_close_force_rstrip = "-" + close; + lexer_config.update_open_chars(); + } + + /// Sets whether to remove the first newline after a block + void set_trim_blocks(bool trim_blocks) { + lexer_config.trim_blocks = trim_blocks; + } + + /// Sets whether to strip the spaces and tabs from the start of a line to a block + void set_lstrip_blocks(bool lstrip_blocks) { + lexer_config.lstrip_blocks = lstrip_blocks; + } + + /// Sets the element notation syntax + void set_search_included_templates_in_files(bool search_in_files) { + parser_config.search_included_templates_in_files = search_in_files; + } + + /// Sets whether a missing include will throw an error + void set_throw_at_missing_includes(bool will_throw) { + render_config.throw_at_missing_includes = will_throw; + } + + Template parse(nonstd::string_view input) { + Parser parser(parser_config, lexer_config, template_storage, function_storage); + return parser.parse(input, input_path); + } + + Template parse_template(const std::string& filename) { + Parser parser(parser_config, lexer_config, template_storage, function_storage); + auto result = Template(parser.load_file(input_path + static_cast(filename))); + parser.parse_into_template(result, input_path + static_cast(filename)); + return result; + } + + Template parse_file(const std::string& filename) { + return parse_template(filename); + } + + std::string render(nonstd::string_view input, const json& data) { + return render(parse(input), data); + } + + std::string render(const Template& tmpl, const json& data) { + std::stringstream os; + render_to(os, tmpl, data); + return os.str(); + } + + std::string render_file(const std::string& filename, const json& data) { + return render(parse_template(filename), data); + } + + std::string render_file_with_json_file(const std::string& filename, const std::string& filename_data) { + const json data = load_json(filename_data); + return render_file(filename, data); + } + + void write(const std::string& filename, const json& data, const std::string& filename_out) { + std::ofstream file(output_path + filename_out); + file << render_file(filename, data); + file.close(); + } + + void write(const Template& temp, const json& data, const std::string& filename_out) { + std::ofstream file(output_path + filename_out); + file << render(temp, data); + file.close(); + } + + void write_with_json_file(const std::string& filename, const std::string& filename_data, const std::string& filename_out) { + const json data = load_json(filename_data); + write(filename, data, filename_out); + } + + void write_with_json_file(const Template& temp, const std::string& filename_data, const std::string& filename_out) { + const json data = load_json(filename_data); + write(temp, data, filename_out); + } + + std::ostream& render_to(std::ostream& os, const Template& tmpl, const json& data) { + Renderer(render_config, template_storage, function_storage).render_to(os, tmpl, data); + return os; + } + + std::string load_file(const std::string& filename) { + Parser parser(parser_config, lexer_config, template_storage, function_storage); + return parser.load_file(input_path + filename); + } + + json load_json(const std::string& filename) { + std::ifstream file; + file.open(input_path + filename); + if (file.fail()) { + INJA_THROW(FileError("failed accessing file at '" + input_path + filename + "'")); } - /// Sets the opener for template line statements - void set_line_statement(const std::string &open) - { - lexer_config.line_statement = open; - lexer_config.update_open_chars(); - } + return json::parse(std::istreambuf_iterator(file), std::istreambuf_iterator()); + } - /// Sets the opener and closer for template expressions - void set_expression(const std::string &open, const std::string &close) - { - lexer_config.expression_open = open; - lexer_config.expression_open_force_lstrip = open + "-"; - lexer_config.expression_close = close; - lexer_config.expression_close_force_rstrip = "-" + close; - lexer_config.update_open_chars(); - } - - /// Sets the opener and closer for template comments - void set_comment(const std::string &open, const std::string &close) - { - lexer_config.comment_open = open; - lexer_config.comment_open_force_lstrip = open + "-"; - lexer_config.comment_close = close; - lexer_config.comment_close_force_rstrip = "-" + close; - lexer_config.update_open_chars(); - } - - /// Sets whether to remove the first newline after a block - void set_trim_blocks(bool trim_blocks) - { - lexer_config.trim_blocks = trim_blocks; - } - - /// Sets whether to strip the spaces and tabs from the start of a line to a block - void set_lstrip_blocks(bool lstrip_blocks) - { - lexer_config.lstrip_blocks = lstrip_blocks; - } - - /// Sets the element notation syntax - void set_search_included_templates_in_files(bool search_in_files) - { - parser_config.search_included_templates_in_files = search_in_files; - } - - /// Sets whether a missing include will throw an error - void set_throw_at_missing_includes(bool will_throw) - { - render_config.throw_at_missing_includes = will_throw; - } - - Template parse(std::string input) - { - Parser parser(parser_config, lexer_config, template_storage, - function_storage); - return parser.parse(input, input_path); - } - - Template parse_template(const std::string &filename) - { - Parser parser(parser_config, lexer_config, template_storage, - function_storage); - auto result = Template( - parser.load_file(input_path + static_cast(filename))); - parser.parse_into_template( - result, input_path + static_cast(filename)); - return result; - } - - Template parse_file(const std::string &filename) - { - return parse_template(filename); - } - - std::string render(std::string input, const json &data) - { - return render(parse(input), data); - } - - std::string render(const Template &tmpl, const json &data) - { - std::stringstream os; - render_to(os, tmpl, data); - return os.str(); - } - - std::string render_file(const std::string &filename, const json &data) - { - return render(parse_template(filename), data); - } - - std::string render_file_with_json_file(const std::string &filename, - const std::string &filename_data) - { - const json data = load_json(filename_data); - return render_file(filename, data); - } - - void write(const std::string &filename, - const json &data, - const std::string &filename_out) - { - std::ofstream file(output_path + filename_out); - file << render_file(filename, data); - file.close(); - } - - void write(const Template &temp, - const json &data, - const std::string &filename_out) - { - std::ofstream file(output_path + filename_out); - file << render(temp, data); - file.close(); - } - - void write_with_json_file(const std::string &filename, - const std::string &filename_data, - const std::string &filename_out) - { - const json data = load_json(filename_data); - write(filename, data, filename_out); - } - - void write_with_json_file(const Template &temp, - const std::string &filename_data, - const std::string &filename_out) - { - const json data = load_json(filename_data); - write(temp, data, filename_out); - } - - std::ostream & - render_to(std::ostream &os, const Template &tmpl, const json &data) - { - Renderer(render_config, template_storage, function_storage) - .render_to(os, tmpl, data); - return os; - } - - std::string load_file(const std::string &filename) - { - Parser parser(parser_config, lexer_config, template_storage, - function_storage); - return parser.load_file(input_path + filename); - } - - json load_json(const std::string &filename) - { - std::ifstream file; - file.open(input_path + filename); - if (file.fail()) { - INJA_THROW(FileError("failed accessing file at '" + input_path - + filename + "'")); - } - - return json::parse(std::istreambuf_iterator(file), - std::istreambuf_iterator()); - } - - /*! + /*! @brief Adds a variadic callback */ - void add_callback(const std::string &name, const CallbackFunction &callback) - { - add_callback(name, -1, callback); - } + void add_callback(const std::string& name, const CallbackFunction& callback) { + add_callback(name, -1, callback); + } - /*! + /*! @brief Adds a variadic void callback */ - void add_void_callback(const std::string &name, - const VoidCallbackFunction &callback) - { - add_void_callback(name, -1, callback); - } + void add_void_callback(const std::string& name, const VoidCallbackFunction& callback) { + add_void_callback(name, -1, callback); + } - /*! + /*! @brief Adds a callback with given number or arguments */ - void add_callback(const std::string &name, - int num_args, - const CallbackFunction &callback) - { - function_storage.add_callback(name, num_args, callback); - } + void add_callback(const std::string& name, int num_args, const CallbackFunction& callback) { + function_storage.add_callback(name, num_args, callback); + } - /*! + /*! @brief Adds a void callback with given number or arguments */ - void add_void_callback(const std::string &name, - int num_args, - const VoidCallbackFunction &callback) - { - function_storage.add_callback(name, num_args, - [callback](Arguments &args) { - callback(args); - return json(); - }); - } + void add_void_callback(const std::string& name, int num_args, const VoidCallbackFunction& callback) { + function_storage.add_callback(name, num_args, [callback](Arguments& args) { + callback(args); + return json(); + }); + } - /** Includes a template with a given name into the environment. + /** Includes a template with a given name into the environment. * Then, a template can be rendered in another template using the * include "" syntax. */ - void include_template(const std::string &name, const Template &tmpl) - { - template_storage[name] = tmpl; - } + void include_template(const std::string& name, const Template& tmpl) { + template_storage[name] = tmpl; + } - /*! + /*! @brief Sets a function that is called when an included file is not found */ - void set_include_callback( - const std::function - &callback) - { - parser_config.include_callback = callback; - } + void set_include_callback(const std::function& callback) { + parser_config.include_callback = callback; + } }; /*! @brief render with default settings to a string */ -inline std::string -render(std::string input, const json &data) -{ - return Environment().render(input, data); +inline std::string render(nonstd::string_view input, const json& data) { + return Environment().render(input, data); } /*! @brief render with default settings to the given output stream */ -inline void -render_to(std::ostream &os, std::string input, const json &data) -{ - Environment env; - env.render_to(os, env.parse(input), data); +inline void render_to(std::ostream& os, nonstd::string_view input, const json& data) { + Environment env; + env.render_to(os, env.parse(input), data); } -}// namespace inja +} // namespace inja -#endif// INCLUDE_INJA_ENVIRONMENT_HPP_ +#endif // INCLUDE_INJA_ENVIRONMENT_HPP_ // #include "exceptions.hpp" @@ -3276,4 +2933,5 @@ render_to(std::ostream &os, std::string input, const json &data) // #include "template.hpp" -#endif// INCLUDE_INJA_INJA_HPP_ + +#endif // INCLUDE_INJA_INJA_HPP_ diff --git a/3party/inja/string_view.hpp b/3party/inja/string_view.hpp new file mode 100644 index 0000000..78607e7 --- /dev/null +++ b/3party/inja/string_view.hpp @@ -0,0 +1,1684 @@ +// Copyright 2017-2020 by Martin Moene +// +// string-view lite, a C++17-like string_view for C++98 and later. +// For more information see https://github.com/martinmoene/string-view-lite +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#ifndef NONSTD_SV_LITE_H_INCLUDED +#define NONSTD_SV_LITE_H_INCLUDED + +#define string_view_lite_MAJOR 1 +#define string_view_lite_MINOR 7 +#define string_view_lite_PATCH 0 + +#define string_view_lite_VERSION nssv_STRINGIFY(string_view_lite_MAJOR) "." nssv_STRINGIFY(string_view_lite_MINOR) "." nssv_STRINGIFY(string_view_lite_PATCH) + +#define nssv_STRINGIFY( x ) nssv_STRINGIFY_( x ) +#define nssv_STRINGIFY_( x ) #x + +// string-view lite configuration: + +#define nssv_STRING_VIEW_DEFAULT 0 +#define nssv_STRING_VIEW_NONSTD 1 +#define nssv_STRING_VIEW_STD 2 + +// tweak header support: + +#ifdef __has_include +# if __has_include() +# include +# endif +#define nssv_HAVE_TWEAK_HEADER 1 +#else +#define nssv_HAVE_TWEAK_HEADER 0 +//# pragma message("string_view.hpp: Note: Tweak header not supported.") +#endif + +// string_view selection and configuration: + +#if !defined( nssv_CONFIG_SELECT_STRING_VIEW ) +# define nssv_CONFIG_SELECT_STRING_VIEW ( nssv_HAVE_STD_STRING_VIEW ? nssv_STRING_VIEW_STD : nssv_STRING_VIEW_NONSTD ) +#endif + +#ifndef nssv_CONFIG_STD_SV_OPERATOR +# define nssv_CONFIG_STD_SV_OPERATOR 0 +#endif + +#ifndef nssv_CONFIG_USR_SV_OPERATOR +# define nssv_CONFIG_USR_SV_OPERATOR 1 +#endif + +#ifdef nssv_CONFIG_CONVERSION_STD_STRING +# define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS nssv_CONFIG_CONVERSION_STD_STRING +# define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS nssv_CONFIG_CONVERSION_STD_STRING +#endif + +#ifndef nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS +# define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS 1 +#endif + +#ifndef nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS +# define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS 1 +#endif + +#ifndef nssv_CONFIG_NO_STREAM_INSERTION +# define nssv_CONFIG_NO_STREAM_INSERTION 0 +#endif + +// Control presence of exception handling (try and auto discover): + +#ifndef nssv_CONFIG_NO_EXCEPTIONS +# if defined(_MSC_VER) +# include // for _HAS_EXCEPTIONS +# endif +# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS) +# define nssv_CONFIG_NO_EXCEPTIONS 0 +# else +# define nssv_CONFIG_NO_EXCEPTIONS 1 +# endif +#endif + +// C++ language version detection (C++23 is speculative): +// Note: VC14.0/1900 (VS2015) lacks too much from C++14. + +#ifndef nssv_CPLUSPLUS +# if defined(_MSVC_LANG ) && !defined(__clang__) +# define nssv_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) +# else +# define nssv_CPLUSPLUS __cplusplus +# endif +#endif + +#define nssv_CPP98_OR_GREATER ( nssv_CPLUSPLUS >= 199711L ) +#define nssv_CPP11_OR_GREATER ( nssv_CPLUSPLUS >= 201103L ) +#define nssv_CPP11_OR_GREATER_ ( nssv_CPLUSPLUS >= 201103L ) +#define nssv_CPP14_OR_GREATER ( nssv_CPLUSPLUS >= 201402L ) +#define nssv_CPP17_OR_GREATER ( nssv_CPLUSPLUS >= 201703L ) +#define nssv_CPP20_OR_GREATER ( nssv_CPLUSPLUS >= 202002L ) +#define nssv_CPP23_OR_GREATER ( nssv_CPLUSPLUS >= 202300L ) + +// use C++17 std::string_view if available and requested: + +#if nssv_CPP17_OR_GREATER && defined(__has_include ) +# if __has_include( ) +# define nssv_HAVE_STD_STRING_VIEW 1 +# else +# define nssv_HAVE_STD_STRING_VIEW 0 +# endif +#else +# define nssv_HAVE_STD_STRING_VIEW 0 +#endif + +#define nssv_USES_STD_STRING_VIEW ( (nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_STD) || ((nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_DEFAULT) && nssv_HAVE_STD_STRING_VIEW) ) + +#define nssv_HAVE_STARTS_WITH ( nssv_CPP20_OR_GREATER || !nssv_USES_STD_STRING_VIEW ) +#define nssv_HAVE_ENDS_WITH nssv_HAVE_STARTS_WITH + +// +// Use C++17 std::string_view: +// + +#if nssv_USES_STD_STRING_VIEW + +#include + +// Extensions for std::string: + +#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS + +namespace nonstd { + +template< class CharT, class Traits, class Allocator = std::allocator > +std::basic_string +to_string( std::basic_string_view v, Allocator const & a = Allocator() ) +{ + return std::basic_string( v.begin(), v.end(), a ); +} + +template< class CharT, class Traits, class Allocator > +std::basic_string_view +to_string_view( std::basic_string const & s ) +{ + return std::basic_string_view( s.data(), s.size() ); +} + +// Literal operators sv and _sv: + +#if nssv_CONFIG_STD_SV_OPERATOR + +using namespace std::literals::string_view_literals; + +#endif + +#if nssv_CONFIG_USR_SV_OPERATOR + +inline namespace literals { +inline namespace string_view_literals { + + +constexpr std::string_view operator "" _sv( const char* str, size_t len ) noexcept // (1) +{ + return std::string_view{ str, len }; +} + +constexpr std::u16string_view operator "" _sv( const char16_t* str, size_t len ) noexcept // (2) +{ + return std::u16string_view{ str, len }; +} + +constexpr std::u32string_view operator "" _sv( const char32_t* str, size_t len ) noexcept // (3) +{ + return std::u32string_view{ str, len }; +} + +constexpr std::wstring_view operator "" _sv( const wchar_t* str, size_t len ) noexcept // (4) +{ + return std::wstring_view{ str, len }; +} + +}} // namespace literals::string_view_literals + +#endif // nssv_CONFIG_USR_SV_OPERATOR + +} // namespace nonstd + +#endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS + +namespace nonstd { + +using std::string_view; +using std::wstring_view; +using std::u16string_view; +using std::u32string_view; +using std::basic_string_view; + +// literal "sv" and "_sv", see above + +using std::operator==; +using std::operator!=; +using std::operator<; +using std::operator<=; +using std::operator>; +using std::operator>=; + +using std::operator<<; + +} // namespace nonstd + +#else // nssv_HAVE_STD_STRING_VIEW + +// +// Before C++17: use string_view lite: +// + +// Compiler versions: +// +// MSVC++ 6.0 _MSC_VER == 1200 nssv_COMPILER_MSVC_VERSION == 60 (Visual Studio 6.0) +// MSVC++ 7.0 _MSC_VER == 1300 nssv_COMPILER_MSVC_VERSION == 70 (Visual Studio .NET 2002) +// MSVC++ 7.1 _MSC_VER == 1310 nssv_COMPILER_MSVC_VERSION == 71 (Visual Studio .NET 2003) +// MSVC++ 8.0 _MSC_VER == 1400 nssv_COMPILER_MSVC_VERSION == 80 (Visual Studio 2005) +// MSVC++ 9.0 _MSC_VER == 1500 nssv_COMPILER_MSVC_VERSION == 90 (Visual Studio 2008) +// MSVC++ 10.0 _MSC_VER == 1600 nssv_COMPILER_MSVC_VERSION == 100 (Visual Studio 2010) +// MSVC++ 11.0 _MSC_VER == 1700 nssv_COMPILER_MSVC_VERSION == 110 (Visual Studio 2012) +// MSVC++ 12.0 _MSC_VER == 1800 nssv_COMPILER_MSVC_VERSION == 120 (Visual Studio 2013) +// MSVC++ 14.0 _MSC_VER == 1900 nssv_COMPILER_MSVC_VERSION == 140 (Visual Studio 2015) +// MSVC++ 14.1 _MSC_VER >= 1910 nssv_COMPILER_MSVC_VERSION == 141 (Visual Studio 2017) +// MSVC++ 14.2 _MSC_VER >= 1920 nssv_COMPILER_MSVC_VERSION == 142 (Visual Studio 2019) + +#if defined(_MSC_VER ) && !defined(__clang__) +# define nssv_COMPILER_MSVC_VER (_MSC_VER ) +# define nssv_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) ) +#else +# define nssv_COMPILER_MSVC_VER 0 +# define nssv_COMPILER_MSVC_VERSION 0 +#endif + +#define nssv_COMPILER_VERSION( major, minor, patch ) ( 10 * ( 10 * (major) + (minor) ) + (patch) ) + +#if defined( __apple_build_version__ ) +# define nssv_COMPILER_APPLECLANG_VERSION nssv_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) +# define nssv_COMPILER_CLANG_VERSION 0 +#elif defined( __clang__ ) +# define nssv_COMPILER_APPLECLANG_VERSION 0 +# define nssv_COMPILER_CLANG_VERSION nssv_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) +#else +# define nssv_COMPILER_APPLECLANG_VERSION 0 +# define nssv_COMPILER_CLANG_VERSION 0 +#endif + +#if defined(__GNUC__) && !defined(__clang__) +# define nssv_COMPILER_GNUC_VERSION nssv_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#else +# define nssv_COMPILER_GNUC_VERSION 0 +#endif + +// half-open range [lo..hi): +#define nssv_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) ) + +// Presence of language and library features: + +#ifdef _HAS_CPP0X +# define nssv_HAS_CPP0X _HAS_CPP0X +#else +# define nssv_HAS_CPP0X 0 +#endif + +// Unless defined otherwise below, consider VC14 as C++11 for variant-lite: + +#if nssv_COMPILER_MSVC_VER >= 1900 +# undef nssv_CPP11_OR_GREATER +# define nssv_CPP11_OR_GREATER 1 +#endif + +#define nssv_CPP11_90 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1500) +#define nssv_CPP11_100 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1600) +#define nssv_CPP11_110 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1700) +#define nssv_CPP11_120 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1800) +#define nssv_CPP11_140 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1900) +#define nssv_CPP11_141 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1910) + +#define nssv_CPP14_000 (nssv_CPP14_OR_GREATER) +#define nssv_CPP17_000 (nssv_CPP17_OR_GREATER) + +// Presence of C++11 language features: + +#define nssv_HAVE_CONSTEXPR_11 nssv_CPP11_140 +#define nssv_HAVE_EXPLICIT_CONVERSION nssv_CPP11_140 +#define nssv_HAVE_INLINE_NAMESPACE nssv_CPP11_140 +#define nssv_HAVE_IS_DEFAULT nssv_CPP11_140 +#define nssv_HAVE_IS_DELETE nssv_CPP11_140 +#define nssv_HAVE_NOEXCEPT nssv_CPP11_140 +#define nssv_HAVE_NULLPTR nssv_CPP11_100 +#define nssv_HAVE_REF_QUALIFIER nssv_CPP11_140 +#define nssv_HAVE_UNICODE_LITERALS nssv_CPP11_140 +#define nssv_HAVE_USER_DEFINED_LITERALS nssv_CPP11_140 +#define nssv_HAVE_WCHAR16_T nssv_CPP11_100 +#define nssv_HAVE_WCHAR32_T nssv_CPP11_100 + +#if ! ( ( nssv_CPP11_OR_GREATER && nssv_COMPILER_CLANG_VERSION ) || nssv_BETWEEN( nssv_COMPILER_CLANG_VERSION, 300, 400 ) ) +# define nssv_HAVE_STD_DEFINED_LITERALS nssv_CPP11_140 +#else +# define nssv_HAVE_STD_DEFINED_LITERALS 0 +#endif + +// Presence of C++14 language features: + +#define nssv_HAVE_CONSTEXPR_14 nssv_CPP14_000 + +// Presence of C++17 language features: + +#define nssv_HAVE_NODISCARD nssv_CPP17_000 + +// Presence of C++ library features: + +#define nssv_HAVE_STD_HASH nssv_CPP11_120 + +// Presence of compiler intrinsics: + +// Providing char-type specializations for compare() and length() that +// use compiler intrinsics can improve compile- and run-time performance. +// +// The challenge is in using the right combinations of builtin availability +// and its constexpr-ness. +// +// | compiler | __builtin_memcmp (constexpr) | memcmp (constexpr) | +// |----------|------------------------------|---------------------| +// | clang | 4.0 (>= 4.0 ) | any (? ) | +// | clang-a | 9.0 (>= 9.0 ) | any (? ) | +// | gcc | any (constexpr) | any (? ) | +// | msvc | >= 14.2 C++17 (>= 14.2 ) | any (? ) | + +#define nssv_HAVE_BUILTIN_VER ( (nssv_CPP17_000 && nssv_COMPILER_MSVC_VERSION >= 142) || nssv_COMPILER_GNUC_VERSION > 0 || nssv_COMPILER_CLANG_VERSION >= 400 || nssv_COMPILER_APPLECLANG_VERSION >= 900 ) +#define nssv_HAVE_BUILTIN_CE ( nssv_HAVE_BUILTIN_VER ) + +#define nssv_HAVE_BUILTIN_MEMCMP ( (nssv_HAVE_CONSTEXPR_14 && nssv_HAVE_BUILTIN_CE) || !nssv_HAVE_CONSTEXPR_14 ) +#define nssv_HAVE_BUILTIN_STRLEN ( (nssv_HAVE_CONSTEXPR_11 && nssv_HAVE_BUILTIN_CE) || !nssv_HAVE_CONSTEXPR_11 ) + +#ifdef __has_builtin +# define nssv_HAVE_BUILTIN( x ) __has_builtin( x ) +#else +# define nssv_HAVE_BUILTIN( x ) 0 +#endif + +#if nssv_HAVE_BUILTIN(__builtin_memcmp) || nssv_HAVE_BUILTIN_VER +# define nssv_BUILTIN_MEMCMP __builtin_memcmp +#else +# define nssv_BUILTIN_MEMCMP memcmp +#endif + +#if nssv_HAVE_BUILTIN(__builtin_strlen) || nssv_HAVE_BUILTIN_VER +# define nssv_BUILTIN_STRLEN __builtin_strlen +#else +# define nssv_BUILTIN_STRLEN strlen +#endif + +// C++ feature usage: + +#if nssv_HAVE_CONSTEXPR_11 +# define nssv_constexpr constexpr +#else +# define nssv_constexpr /*constexpr*/ +#endif + +#if nssv_HAVE_CONSTEXPR_14 +# define nssv_constexpr14 constexpr +#else +# define nssv_constexpr14 /*constexpr*/ +#endif + +#if nssv_HAVE_EXPLICIT_CONVERSION +# define nssv_explicit explicit +#else +# define nssv_explicit /*explicit*/ +#endif + +#if nssv_HAVE_INLINE_NAMESPACE +# define nssv_inline_ns inline +#else +# define nssv_inline_ns /*inline*/ +#endif + +#if nssv_HAVE_NOEXCEPT +# define nssv_noexcept noexcept +#else +# define nssv_noexcept /*noexcept*/ +#endif + +//#if nssv_HAVE_REF_QUALIFIER +//# define nssv_ref_qual & +//# define nssv_refref_qual && +//#else +//# define nssv_ref_qual /*&*/ +//# define nssv_refref_qual /*&&*/ +//#endif + +#if nssv_HAVE_NULLPTR +# define nssv_nullptr nullptr +#else +# define nssv_nullptr NULL +#endif + +#if nssv_HAVE_NODISCARD +# define nssv_nodiscard [[nodiscard]] +#else +# define nssv_nodiscard /*[[nodiscard]]*/ +#endif + +// Additional includes: + +#include +#include +#include +#include +#include // std::char_traits<> + +#if ! nssv_CONFIG_NO_STREAM_INSERTION +# include +#endif + +#if ! nssv_CONFIG_NO_EXCEPTIONS +# include +#endif + +#if nssv_CPP11_OR_GREATER +# include +#endif + +// Clang, GNUC, MSVC warning suppression macros: + +#if defined(__clang__) +# pragma clang diagnostic ignored "-Wreserved-user-defined-literal" +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wuser-defined-literals" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wliteral-suffix" +#endif // __clang__ + +#if nssv_COMPILER_MSVC_VERSION >= 140 +# define nssv_SUPPRESS_MSGSL_WARNING(expr) [[gsl::suppress(expr)]] +# define nssv_SUPPRESS_MSVC_WARNING(code, descr) __pragma(warning(suppress: code) ) +# define nssv_DISABLE_MSVC_WARNINGS(codes) __pragma(warning(push)) __pragma(warning(disable: codes)) +#else +# define nssv_SUPPRESS_MSGSL_WARNING(expr) +# define nssv_SUPPRESS_MSVC_WARNING(code, descr) +# define nssv_DISABLE_MSVC_WARNINGS(codes) +#endif + +#if defined(__clang__) +# define nssv_RESTORE_WARNINGS() _Pragma("clang diagnostic pop") +#elif defined(__GNUC__) +# define nssv_RESTORE_WARNINGS() _Pragma("GCC diagnostic pop") +#elif nssv_COMPILER_MSVC_VERSION >= 140 +# define nssv_RESTORE_WARNINGS() __pragma(warning(pop )) +#else +# define nssv_RESTORE_WARNINGS() +#endif + +// Suppress the following MSVC (GSL) warnings: +// - C4455, non-gsl : 'operator ""sv': literal suffix identifiers that do not +// start with an underscore are reserved +// - C26472, gsl::t.1 : don't use a static_cast for arithmetic conversions; +// use brace initialization, gsl::narrow_cast or gsl::narow +// - C26481: gsl::b.1 : don't use pointer arithmetic. Use span instead + +nssv_DISABLE_MSVC_WARNINGS( 4455 26481 26472 ) +//nssv_DISABLE_CLANG_WARNINGS( "-Wuser-defined-literals" ) +//nssv_DISABLE_GNUC_WARNINGS( -Wliteral-suffix ) + +namespace nonstd { namespace sv_lite { + +// +// basic_string_view declaration: +// + +template +< + class CharT, + class Traits = std::char_traits +> +class basic_string_view; + +namespace detail { + +// support constexpr comparison in C++14; +// for C++17 and later, use provided traits: + +template< typename CharT > +inline nssv_constexpr14 int compare( CharT const * s1, CharT const * s2, std::size_t count ) +{ + while ( count-- != 0 ) + { + if ( *s1 < *s2 ) return -1; + if ( *s1 > *s2 ) return +1; + ++s1; ++s2; + } + return 0; +} + +#if nssv_HAVE_BUILTIN_MEMCMP + +// specialization of compare() for char, see also generic compare() above: + +inline nssv_constexpr14 int compare( char const * s1, char const * s2, std::size_t count ) +{ + return nssv_BUILTIN_MEMCMP( s1, s2, count ); +} + +#endif + +#if nssv_HAVE_BUILTIN_STRLEN + +// specialization of length() for char, see also generic length() further below: + +inline nssv_constexpr std::size_t length( char const * s ) +{ + return nssv_BUILTIN_STRLEN( s ); +} + +#endif + +#if defined(__OPTIMIZE__) + +// gcc, clang provide __OPTIMIZE__ +// Expect tail call optimization to make length() non-recursive: + +template< typename CharT > +inline nssv_constexpr std::size_t length( CharT * s, std::size_t result = 0 ) +{ + return *s == '\0' ? result : length( s + 1, result + 1 ); +} + +#else // OPTIMIZE + +// non-recursive: + +template< typename CharT > +inline nssv_constexpr14 std::size_t length( CharT * s ) +{ + std::size_t result = 0; + while ( *s++ != '\0' ) + { + ++result; + } + return result; +} + +#endif // OPTIMIZE + +#if nssv_CPP11_OR_GREATER && ! nssv_CPP17_OR_GREATER +#if defined(__OPTIMIZE__) + +// gcc, clang provide __OPTIMIZE__ +// Expect tail call optimization to make search() non-recursive: + +template< class CharT, class Traits = std::char_traits > +constexpr const CharT* search( basic_string_view haystack, basic_string_view needle ) +{ + return haystack.starts_with( needle ) ? haystack.begin() : + haystack.empty() ? haystack.end() : search( haystack.substr(1), needle ); +} + +#else // OPTIMIZE + +// non-recursive: + +template< class CharT, class Traits = std::char_traits > +constexpr const CharT* search( basic_string_view haystack, basic_string_view needle ) +{ + return std::search( haystack.begin(), haystack.end(), needle.begin(), needle.end() ); +} + +#endif // OPTIMIZE +#endif // nssv_CPP11_OR_GREATER && ! nssv_CPP17_OR_GREATER + +} // namespace detail + +// +// basic_string_view: +// + +template +< + class CharT, + class Traits /* = std::char_traits */ +> +class basic_string_view +{ +public: + // Member types: + + typedef Traits traits_type; + typedef CharT value_type; + + typedef CharT * pointer; + typedef CharT const * const_pointer; + typedef CharT & reference; + typedef CharT const & const_reference; + + typedef const_pointer iterator; + typedef const_pointer const_iterator; + typedef std::reverse_iterator< const_iterator > reverse_iterator; + typedef std::reverse_iterator< const_iterator > const_reverse_iterator; + + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + // 24.4.2.1 Construction and assignment: + + nssv_constexpr basic_string_view() nssv_noexcept + : data_( nssv_nullptr ) + , size_( 0 ) + {} + +#if nssv_CPP11_OR_GREATER + nssv_constexpr basic_string_view( basic_string_view const & other ) nssv_noexcept = default; +#else + nssv_constexpr basic_string_view( basic_string_view const & other ) nssv_noexcept + : data_( other.data_) + , size_( other.size_) + {} +#endif + + nssv_constexpr basic_string_view( CharT const * s, size_type count ) nssv_noexcept // non-standard noexcept + : data_( s ) + , size_( count ) + {} + + nssv_constexpr basic_string_view( CharT const * s) nssv_noexcept // non-standard noexcept + : data_( s ) +#if nssv_CPP17_OR_GREATER + , size_( Traits::length(s) ) +#elif nssv_CPP11_OR_GREATER + , size_( detail::length(s) ) +#else + , size_( Traits::length(s) ) +#endif + {} + +#if nssv_HAVE_NULLPTR +# if nssv_HAVE_IS_DELETE + nssv_constexpr basic_string_view( std::nullptr_t ) nssv_noexcept = delete; +# else + private: nssv_constexpr basic_string_view( std::nullptr_t ) nssv_noexcept; public: +# endif +#endif + + // Assignment: + +#if nssv_CPP11_OR_GREATER + nssv_constexpr14 basic_string_view & operator=( basic_string_view const & other ) nssv_noexcept = default; +#else + nssv_constexpr14 basic_string_view & operator=( basic_string_view const & other ) nssv_noexcept + { + data_ = other.data_; + size_ = other.size_; + return *this; + } +#endif + + // 24.4.2.2 Iterator support: + + nssv_constexpr const_iterator begin() const nssv_noexcept { return data_; } + nssv_constexpr const_iterator end() const nssv_noexcept { return data_ + size_; } + + nssv_constexpr const_iterator cbegin() const nssv_noexcept { return begin(); } + nssv_constexpr const_iterator cend() const nssv_noexcept { return end(); } + + nssv_constexpr const_reverse_iterator rbegin() const nssv_noexcept { return const_reverse_iterator( end() ); } + nssv_constexpr const_reverse_iterator rend() const nssv_noexcept { return const_reverse_iterator( begin() ); } + + nssv_constexpr const_reverse_iterator crbegin() const nssv_noexcept { return rbegin(); } + nssv_constexpr const_reverse_iterator crend() const nssv_noexcept { return rend(); } + + // 24.4.2.3 Capacity: + + nssv_constexpr size_type size() const nssv_noexcept { return size_; } + nssv_constexpr size_type length() const nssv_noexcept { return size_; } + nssv_constexpr size_type max_size() const nssv_noexcept { return (std::numeric_limits< size_type >::max)(); } + + // since C++20 + nssv_nodiscard nssv_constexpr bool empty() const nssv_noexcept + { + return 0 == size_; + } + + // 24.4.2.4 Element access: + + nssv_constexpr const_reference operator[]( size_type pos ) const + { + return data_at( pos ); + } + + nssv_constexpr14 const_reference at( size_type pos ) const + { +#if nssv_CONFIG_NO_EXCEPTIONS + assert( pos < size() ); +#else + if ( pos >= size() ) + { + throw std::out_of_range("nonstd::string_view::at()"); + } +#endif + return data_at( pos ); + } + + nssv_constexpr const_reference front() const { return data_at( 0 ); } + nssv_constexpr const_reference back() const { return data_at( size() - 1 ); } + + nssv_constexpr const_pointer data() const nssv_noexcept { return data_; } + + // 24.4.2.5 Modifiers: + + nssv_constexpr14 void remove_prefix( size_type n ) + { + assert( n <= size() ); + data_ += n; + size_ -= n; + } + + nssv_constexpr14 void remove_suffix( size_type n ) + { + assert( n <= size() ); + size_ -= n; + } + + nssv_constexpr14 void swap( basic_string_view & other ) nssv_noexcept + { + const basic_string_view tmp(other); + other = *this; + *this = tmp; + } + + // 24.4.2.6 String operations: + + size_type copy( CharT * dest, size_type n, size_type pos = 0 ) const + { +#if nssv_CONFIG_NO_EXCEPTIONS + assert( pos <= size() ); +#else + if ( pos > size() ) + { + throw std::out_of_range("nonstd::string_view::copy()"); + } +#endif + const size_type rlen = (std::min)( n, size() - pos ); + + (void) Traits::copy( dest, data() + pos, rlen ); + + return rlen; + } + + nssv_constexpr14 basic_string_view substr( size_type pos = 0, size_type n = npos ) const + { +#if nssv_CONFIG_NO_EXCEPTIONS + assert( pos <= size() ); +#else + if ( pos > size() ) + { + throw std::out_of_range("nonstd::string_view::substr()"); + } +#endif + return basic_string_view( data() + pos, (std::min)( n, size() - pos ) ); + } + + // compare(), 6x: + + nssv_constexpr14 int compare( basic_string_view other ) const nssv_noexcept // (1) + { +#if nssv_CPP17_OR_GREATER + if ( const int result = Traits::compare( data(), other.data(), (std::min)( size(), other.size() ) ) ) +#else + if ( const int result = detail::compare( data(), other.data(), (std::min)( size(), other.size() ) ) ) +#endif + { + return result; + } + + return size() == other.size() ? 0 : size() < other.size() ? -1 : 1; + } + + nssv_constexpr int compare( size_type pos1, size_type n1, basic_string_view other ) const // (2) + { + return substr( pos1, n1 ).compare( other ); + } + + nssv_constexpr int compare( size_type pos1, size_type n1, basic_string_view other, size_type pos2, size_type n2 ) const // (3) + { + return substr( pos1, n1 ).compare( other.substr( pos2, n2 ) ); + } + + nssv_constexpr int compare( CharT const * s ) const // (4) + { + return compare( basic_string_view( s ) ); + } + + nssv_constexpr int compare( size_type pos1, size_type n1, CharT const * s ) const // (5) + { + return substr( pos1, n1 ).compare( basic_string_view( s ) ); + } + + nssv_constexpr int compare( size_type pos1, size_type n1, CharT const * s, size_type n2 ) const // (6) + { + return substr( pos1, n1 ).compare( basic_string_view( s, n2 ) ); + } + + // 24.4.2.7 Searching: + + // starts_with(), 3x, since C++20: + + nssv_constexpr bool starts_with( basic_string_view v ) const nssv_noexcept // (1) + { + return size() >= v.size() && compare( 0, v.size(), v ) == 0; + } + + nssv_constexpr bool starts_with( CharT c ) const nssv_noexcept // (2) + { + return starts_with( basic_string_view( &c, 1 ) ); + } + + nssv_constexpr bool starts_with( CharT const * s ) const // (3) + { + return starts_with( basic_string_view( s ) ); + } + + // ends_with(), 3x, since C++20: + + nssv_constexpr bool ends_with( basic_string_view v ) const nssv_noexcept // (1) + { + return size() >= v.size() && compare( size() - v.size(), npos, v ) == 0; + } + + nssv_constexpr bool ends_with( CharT c ) const nssv_noexcept // (2) + { + return ends_with( basic_string_view( &c, 1 ) ); + } + + nssv_constexpr bool ends_with( CharT const * s ) const // (3) + { + return ends_with( basic_string_view( s ) ); + } + + // find(), 4x: + + nssv_constexpr size_type find( basic_string_view v, size_type pos = 0 ) const nssv_noexcept // (1) + { + return assert( v.size() == 0 || v.data() != nssv_nullptr ) + , pos >= size() + ? npos : to_pos( +#if nssv_CPP11_OR_GREATER && ! nssv_CPP17_OR_GREATER + detail::search( substr(pos), v ) +#else + std::search( cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq ) +#endif + ); + } + + nssv_constexpr size_type find( CharT c, size_type pos = 0 ) const nssv_noexcept // (2) + { + return find( basic_string_view( &c, 1 ), pos ); + } + + nssv_constexpr size_type find( CharT const * s, size_type pos, size_type n ) const // (3) + { + return find( basic_string_view( s, n ), pos ); + } + + nssv_constexpr size_type find( CharT const * s, size_type pos = 0 ) const // (4) + { + return find( basic_string_view( s ), pos ); + } + + // rfind(), 4x: + + nssv_constexpr14 size_type rfind( basic_string_view v, size_type pos = npos ) const nssv_noexcept // (1) + { + if ( size() < v.size() ) + { + return npos; + } + + if ( v.empty() ) + { + return (std::min)( size(), pos ); + } + + const_iterator last = cbegin() + (std::min)( size() - v.size(), pos ) + v.size(); + const_iterator result = std::find_end( cbegin(), last, v.cbegin(), v.cend(), Traits::eq ); + + return result != last ? size_type( result - cbegin() ) : npos; + } + + nssv_constexpr14 size_type rfind( CharT c, size_type pos = npos ) const nssv_noexcept // (2) + { + return rfind( basic_string_view( &c, 1 ), pos ); + } + + nssv_constexpr14 size_type rfind( CharT const * s, size_type pos, size_type n ) const // (3) + { + return rfind( basic_string_view( s, n ), pos ); + } + + nssv_constexpr14 size_type rfind( CharT const * s, size_type pos = npos ) const // (4) + { + return rfind( basic_string_view( s ), pos ); + } + + // find_first_of(), 4x: + + nssv_constexpr size_type find_first_of( basic_string_view v, size_type pos = 0 ) const nssv_noexcept // (1) + { + return pos >= size() + ? npos + : to_pos( std::find_first_of( cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq ) ); + } + + nssv_constexpr size_type find_first_of( CharT c, size_type pos = 0 ) const nssv_noexcept // (2) + { + return find_first_of( basic_string_view( &c, 1 ), pos ); + } + + nssv_constexpr size_type find_first_of( CharT const * s, size_type pos, size_type n ) const // (3) + { + return find_first_of( basic_string_view( s, n ), pos ); + } + + nssv_constexpr size_type find_first_of( CharT const * s, size_type pos = 0 ) const // (4) + { + return find_first_of( basic_string_view( s ), pos ); + } + + // find_last_of(), 4x: + + nssv_constexpr size_type find_last_of( basic_string_view v, size_type pos = npos ) const nssv_noexcept // (1) + { + return empty() + ? npos + : pos >= size() + ? find_last_of( v, size() - 1 ) + : to_pos( std::find_first_of( const_reverse_iterator( cbegin() + pos + 1 ), crend(), v.cbegin(), v.cend(), Traits::eq ) ); + } + + nssv_constexpr size_type find_last_of( CharT c, size_type pos = npos ) const nssv_noexcept // (2) + { + return find_last_of( basic_string_view( &c, 1 ), pos ); + } + + nssv_constexpr size_type find_last_of( CharT const * s, size_type pos, size_type count ) const // (3) + { + return find_last_of( basic_string_view( s, count ), pos ); + } + + nssv_constexpr size_type find_last_of( CharT const * s, size_type pos = npos ) const // (4) + { + return find_last_of( basic_string_view( s ), pos ); + } + + // find_first_not_of(), 4x: + + nssv_constexpr size_type find_first_not_of( basic_string_view v, size_type pos = 0 ) const nssv_noexcept // (1) + { + return pos >= size() + ? npos + : to_pos( std::find_if( cbegin() + pos, cend(), not_in_view( v ) ) ); + } + + nssv_constexpr size_type find_first_not_of( CharT c, size_type pos = 0 ) const nssv_noexcept // (2) + { + return find_first_not_of( basic_string_view( &c, 1 ), pos ); + } + + nssv_constexpr size_type find_first_not_of( CharT const * s, size_type pos, size_type count ) const // (3) + { + return find_first_not_of( basic_string_view( s, count ), pos ); + } + + nssv_constexpr size_type find_first_not_of( CharT const * s, size_type pos = 0 ) const // (4) + { + return find_first_not_of( basic_string_view( s ), pos ); + } + + // find_last_not_of(), 4x: + + nssv_constexpr size_type find_last_not_of( basic_string_view v, size_type pos = npos ) const nssv_noexcept // (1) + { + return empty() + ? npos + : pos >= size() + ? find_last_not_of( v, size() - 1 ) + : to_pos( std::find_if( const_reverse_iterator( cbegin() + pos + 1 ), crend(), not_in_view( v ) ) ); + } + + nssv_constexpr size_type find_last_not_of( CharT c, size_type pos = npos ) const nssv_noexcept // (2) + { + return find_last_not_of( basic_string_view( &c, 1 ), pos ); + } + + nssv_constexpr size_type find_last_not_of( CharT const * s, size_type pos, size_type count ) const // (3) + { + return find_last_not_of( basic_string_view( s, count ), pos ); + } + + nssv_constexpr size_type find_last_not_of( CharT const * s, size_type pos = npos ) const // (4) + { + return find_last_not_of( basic_string_view( s ), pos ); + } + + // Constants: + +#if nssv_CPP17_OR_GREATER + static nssv_constexpr size_type npos = size_type(-1); +#elif nssv_CPP11_OR_GREATER + enum : size_type { npos = size_type(-1) }; +#else + enum { npos = size_type(-1) }; +#endif + +private: + struct not_in_view + { + const basic_string_view v; + + nssv_constexpr explicit not_in_view( basic_string_view v_ ) : v( v_ ) {} + + nssv_constexpr bool operator()( CharT c ) const + { + return npos == v.find_first_of( c ); + } + }; + + nssv_constexpr size_type to_pos( const_iterator it ) const + { + return it == cend() ? npos : size_type( it - cbegin() ); + } + + nssv_constexpr size_type to_pos( const_reverse_iterator it ) const + { + return it == crend() ? npos : size_type( crend() - it - 1 ); + } + + nssv_constexpr const_reference data_at( size_type pos ) const + { +#if nssv_BETWEEN( nssv_COMPILER_GNUC_VERSION, 1, 500 ) + return data_[pos]; +#else + return assert( pos < size() ), data_[pos]; +#endif + } + +private: + const_pointer data_; + size_type size_; + +public: +#if nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS + + template< class Allocator > + basic_string_view( std::basic_string const & s ) nssv_noexcept + : data_( s.data() ) + , size_( s.size() ) + {} + +#if nssv_HAVE_EXPLICIT_CONVERSION + + template< class Allocator > + explicit operator std::basic_string() const + { + return to_string( Allocator() ); + } + +#endif // nssv_HAVE_EXPLICIT_CONVERSION + +#if nssv_CPP11_OR_GREATER + + template< class Allocator = std::allocator > + std::basic_string + to_string( Allocator const & a = Allocator() ) const + { + return std::basic_string( begin(), end(), a ); + } + +#else + + std::basic_string + to_string() const + { + return std::basic_string( begin(), end() ); + } + + template< class Allocator > + std::basic_string + to_string( Allocator const & a ) const + { + return std::basic_string( begin(), end(), a ); + } + +#endif // nssv_CPP11_OR_GREATER + +#endif // nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS +}; + +// +// Non-member functions: +// + +// 24.4.3 Non-member comparison functions: +// lexicographically compare two string views (function template): + +template< class CharT, class Traits > +nssv_constexpr bool operator== ( + basic_string_view lhs, + basic_string_view rhs ) nssv_noexcept +{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; } + +template< class CharT, class Traits > +nssv_constexpr bool operator!= ( + basic_string_view lhs, + basic_string_view rhs ) nssv_noexcept +{ return !( lhs == rhs ); } + +template< class CharT, class Traits > +nssv_constexpr bool operator< ( + basic_string_view lhs, + basic_string_view rhs ) nssv_noexcept +{ return lhs.compare( rhs ) < 0; } + +template< class CharT, class Traits > +nssv_constexpr bool operator<= ( + basic_string_view lhs, + basic_string_view rhs ) nssv_noexcept +{ return lhs.compare( rhs ) <= 0; } + +template< class CharT, class Traits > +nssv_constexpr bool operator> ( + basic_string_view lhs, + basic_string_view rhs ) nssv_noexcept +{ return lhs.compare( rhs ) > 0; } + +template< class CharT, class Traits > +nssv_constexpr bool operator>= ( + basic_string_view lhs, + basic_string_view rhs ) nssv_noexcept +{ return lhs.compare( rhs ) >= 0; } + +// Let S be basic_string_view, and sv be an instance of S. +// Implementations shall provide sufficient additional overloads marked +// constexpr and noexcept so that an object t with an implicit conversion +// to S can be compared according to Table 67. + +#if ! nssv_CPP11_OR_GREATER || nssv_BETWEEN( nssv_COMPILER_MSVC_VERSION, 100, 141 ) + +// accommodate for older compilers: + +// == + +template< class CharT, class Traits> +nssv_constexpr bool operator==( + basic_string_view lhs, + CharT const * rhs ) nssv_noexcept +{ return lhs.size() == detail::length( rhs ) && lhs.compare( rhs ) == 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator==( + CharT const * lhs, + basic_string_view rhs ) nssv_noexcept +{ return detail::length( lhs ) == rhs.size() && rhs.compare( lhs ) == 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator==( + basic_string_view lhs, + std::basic_string rhs ) nssv_noexcept +{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator==( + std::basic_string rhs, + basic_string_view lhs ) nssv_noexcept +{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; } + +// != + +template< class CharT, class Traits> +nssv_constexpr bool operator!=( + basic_string_view lhs, + CharT const * rhs ) nssv_noexcept +{ return !( lhs == rhs ); } + +template< class CharT, class Traits> +nssv_constexpr bool operator!=( + CharT const * lhs, + basic_string_view rhs ) nssv_noexcept +{ return !( lhs == rhs ); } + +template< class CharT, class Traits> +nssv_constexpr bool operator!=( + basic_string_view lhs, + std::basic_string rhs ) nssv_noexcept +{ return !( lhs == rhs ); } + +template< class CharT, class Traits> +nssv_constexpr bool operator!=( + std::basic_string rhs, + basic_string_view lhs ) nssv_noexcept +{ return !( lhs == rhs ); } + +// < + +template< class CharT, class Traits> +nssv_constexpr bool operator<( + basic_string_view lhs, + CharT const * rhs ) nssv_noexcept +{ return lhs.compare( rhs ) < 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator<( + CharT const * lhs, + basic_string_view rhs ) nssv_noexcept +{ return rhs.compare( lhs ) > 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator<( + basic_string_view lhs, + std::basic_string rhs ) nssv_noexcept +{ return lhs.compare( rhs ) < 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator<( + std::basic_string rhs, + basic_string_view lhs ) nssv_noexcept +{ return rhs.compare( lhs ) > 0; } + +// <= + +template< class CharT, class Traits> +nssv_constexpr bool operator<=( + basic_string_view lhs, + CharT const * rhs ) nssv_noexcept +{ return lhs.compare( rhs ) <= 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator<=( + CharT const * lhs, + basic_string_view rhs ) nssv_noexcept +{ return rhs.compare( lhs ) >= 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator<=( + basic_string_view lhs, + std::basic_string rhs ) nssv_noexcept +{ return lhs.compare( rhs ) <= 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator<=( + std::basic_string rhs, + basic_string_view lhs ) nssv_noexcept +{ return rhs.compare( lhs ) >= 0; } + +// > + +template< class CharT, class Traits> +nssv_constexpr bool operator>( + basic_string_view lhs, + CharT const * rhs ) nssv_noexcept +{ return lhs.compare( rhs ) > 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator>( + CharT const * lhs, + basic_string_view rhs ) nssv_noexcept +{ return rhs.compare( lhs ) < 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator>( + basic_string_view lhs, + std::basic_string rhs ) nssv_noexcept +{ return lhs.compare( rhs ) > 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator>( + std::basic_string rhs, + basic_string_view lhs ) nssv_noexcept +{ return rhs.compare( lhs ) < 0; } + +// >= + +template< class CharT, class Traits> +nssv_constexpr bool operator>=( + basic_string_view lhs, + CharT const * rhs ) nssv_noexcept +{ return lhs.compare( rhs ) >= 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator>=( + CharT const * lhs, + basic_string_view rhs ) nssv_noexcept +{ return rhs.compare( lhs ) <= 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator>=( + basic_string_view lhs, + std::basic_string rhs ) nssv_noexcept +{ return lhs.compare( rhs ) >= 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator>=( + std::basic_string rhs, + basic_string_view lhs ) nssv_noexcept +{ return rhs.compare( lhs ) <= 0; } + +#else // newer compilers: + +#define nssv_BASIC_STRING_VIEW_I(T,U) typename std::decay< basic_string_view >::type + +#if defined(_MSC_VER) // issue 40 +# define nssv_MSVC_ORDER(x) , int=x +#else +# define nssv_MSVC_ORDER(x) /*, int=x*/ +#endif + +// == + +template< class CharT, class Traits nssv_MSVC_ORDER(1) > +nssv_constexpr bool operator==( + basic_string_view lhs, + nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs ) nssv_noexcept +{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; } + +template< class CharT, class Traits nssv_MSVC_ORDER(2) > +nssv_constexpr bool operator==( + nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, + basic_string_view rhs ) nssv_noexcept +{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; } + +// != + +template< class CharT, class Traits nssv_MSVC_ORDER(1) > +nssv_constexpr bool operator!= ( + basic_string_view < CharT, Traits > lhs, + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept +{ return !( lhs == rhs ); } + +template< class CharT, class Traits nssv_MSVC_ORDER(2) > +nssv_constexpr bool operator!= ( + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, + basic_string_view < CharT, Traits > rhs ) nssv_noexcept +{ return !( lhs == rhs ); } + +// < + +template< class CharT, class Traits nssv_MSVC_ORDER(1) > +nssv_constexpr bool operator< ( + basic_string_view < CharT, Traits > lhs, + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept +{ return lhs.compare( rhs ) < 0; } + +template< class CharT, class Traits nssv_MSVC_ORDER(2) > +nssv_constexpr bool operator< ( + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, + basic_string_view < CharT, Traits > rhs ) nssv_noexcept +{ return lhs.compare( rhs ) < 0; } + +// <= + +template< class CharT, class Traits nssv_MSVC_ORDER(1) > +nssv_constexpr bool operator<= ( + basic_string_view < CharT, Traits > lhs, + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept +{ return lhs.compare( rhs ) <= 0; } + +template< class CharT, class Traits nssv_MSVC_ORDER(2) > +nssv_constexpr bool operator<= ( + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, + basic_string_view < CharT, Traits > rhs ) nssv_noexcept +{ return lhs.compare( rhs ) <= 0; } + +// > + +template< class CharT, class Traits nssv_MSVC_ORDER(1) > +nssv_constexpr bool operator> ( + basic_string_view < CharT, Traits > lhs, + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept +{ return lhs.compare( rhs ) > 0; } + +template< class CharT, class Traits nssv_MSVC_ORDER(2) > +nssv_constexpr bool operator> ( + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, + basic_string_view < CharT, Traits > rhs ) nssv_noexcept +{ return lhs.compare( rhs ) > 0; } + +// >= + +template< class CharT, class Traits nssv_MSVC_ORDER(1) > +nssv_constexpr bool operator>= ( + basic_string_view < CharT, Traits > lhs, + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept +{ return lhs.compare( rhs ) >= 0; } + +template< class CharT, class Traits nssv_MSVC_ORDER(2) > +nssv_constexpr bool operator>= ( + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, + basic_string_view < CharT, Traits > rhs ) nssv_noexcept +{ return lhs.compare( rhs ) >= 0; } + +#undef nssv_MSVC_ORDER +#undef nssv_BASIC_STRING_VIEW_I + +#endif // compiler-dependent approach to comparisons + +// 24.4.4 Inserters and extractors: + +#if ! nssv_CONFIG_NO_STREAM_INSERTION + +namespace detail { + +template< class Stream > +void write_padding( Stream & os, std::streamsize n ) +{ + for ( std::streamsize i = 0; i < n; ++i ) + os.rdbuf()->sputc( os.fill() ); +} + +template< class Stream, class View > +Stream & write_to_stream( Stream & os, View const & sv ) +{ + typename Stream::sentry sentry( os ); + + if ( !sentry ) + return os; + + const std::streamsize length = static_cast( sv.length() ); + + // Whether, and how, to pad: + const bool pad = ( length < os.width() ); + const bool left_pad = pad && ( os.flags() & std::ios_base::adjustfield ) == std::ios_base::right; + + if ( left_pad ) + write_padding( os, os.width() - length ); + + // Write span characters: + os.rdbuf()->sputn( sv.begin(), length ); + + if ( pad && !left_pad ) + write_padding( os, os.width() - length ); + + // Reset output stream width: + os.width( 0 ); + + return os; +} + +} // namespace detail + +template< class CharT, class Traits > +std::basic_ostream & +operator<<( + std::basic_ostream& os, + basic_string_view sv ) +{ + return detail::write_to_stream( os, sv ); +} + +#endif // nssv_CONFIG_NO_STREAM_INSERTION + +// Several typedefs for common character types are provided: + +typedef basic_string_view string_view; +typedef basic_string_view wstring_view; +#if nssv_HAVE_WCHAR16_T +typedef basic_string_view u16string_view; +typedef basic_string_view u32string_view; +#endif + +}} // namespace nonstd::sv_lite + +// +// 24.4.6 Suffix for basic_string_view literals: +// + +#if nssv_HAVE_USER_DEFINED_LITERALS + +namespace nonstd { +nssv_inline_ns namespace literals { +nssv_inline_ns namespace string_view_literals { + +#if nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS + +nssv_constexpr nonstd::sv_lite::string_view operator "" sv( const char* str, size_t len ) nssv_noexcept // (1) +{ + return nonstd::sv_lite::string_view{ str, len }; +} + +nssv_constexpr nonstd::sv_lite::u16string_view operator "" sv( const char16_t* str, size_t len ) nssv_noexcept // (2) +{ + return nonstd::sv_lite::u16string_view{ str, len }; +} + +nssv_constexpr nonstd::sv_lite::u32string_view operator "" sv( const char32_t* str, size_t len ) nssv_noexcept // (3) +{ + return nonstd::sv_lite::u32string_view{ str, len }; +} + +nssv_constexpr nonstd::sv_lite::wstring_view operator "" sv( const wchar_t* str, size_t len ) nssv_noexcept // (4) +{ + return nonstd::sv_lite::wstring_view{ str, len }; +} + +#endif // nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS + +#if nssv_CONFIG_USR_SV_OPERATOR + +nssv_constexpr nonstd::sv_lite::string_view operator "" _sv( const char* str, size_t len ) nssv_noexcept // (1) +{ + return nonstd::sv_lite::string_view{ str, len }; +} + +nssv_constexpr nonstd::sv_lite::u16string_view operator "" _sv( const char16_t* str, size_t len ) nssv_noexcept // (2) +{ + return nonstd::sv_lite::u16string_view{ str, len }; +} + +nssv_constexpr nonstd::sv_lite::u32string_view operator "" _sv( const char32_t* str, size_t len ) nssv_noexcept // (3) +{ + return nonstd::sv_lite::u32string_view{ str, len }; +} + +nssv_constexpr nonstd::sv_lite::wstring_view operator "" _sv( const wchar_t* str, size_t len ) nssv_noexcept // (4) +{ + return nonstd::sv_lite::wstring_view{ str, len }; +} + +#endif // nssv_CONFIG_USR_SV_OPERATOR + +}}} // namespace nonstd::literals::string_view_literals + +#endif + +// +// Extensions for std::string: +// + +#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS + +namespace nonstd { +namespace sv_lite { + +// Exclude MSVC 14 (19.00): it yields ambiguous to_string(): + +#if nssv_CPP11_OR_GREATER && nssv_COMPILER_MSVC_VERSION != 140 + +template< class CharT, class Traits, class Allocator = std::allocator > +std::basic_string +to_string( basic_string_view v, Allocator const & a = Allocator() ) +{ + return std::basic_string( v.begin(), v.end(), a ); +} + +#else + +template< class CharT, class Traits > +std::basic_string +to_string( basic_string_view v ) +{ + return std::basic_string( v.begin(), v.end() ); +} + +template< class CharT, class Traits, class Allocator > +std::basic_string +to_string( basic_string_view v, Allocator const & a ) +{ + return std::basic_string( v.begin(), v.end(), a ); +} + +#endif // nssv_CPP11_OR_GREATER + +template< class CharT, class Traits, class Allocator > +basic_string_view +to_string_view( std::basic_string const & s ) +{ + return basic_string_view( s.data(), s.size() ); +} + +}} // namespace nonstd::sv_lite + +#endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS + +// +// make types and algorithms available in namespace nonstd: +// + +namespace nonstd { + +using sv_lite::basic_string_view; +using sv_lite::string_view; +using sv_lite::wstring_view; + +#if nssv_HAVE_WCHAR16_T +using sv_lite::u16string_view; +#endif +#if nssv_HAVE_WCHAR32_T +using sv_lite::u32string_view; +#endif + +// literal "sv" + +using sv_lite::operator==; +using sv_lite::operator!=; +using sv_lite::operator<; +using sv_lite::operator<=; +using sv_lite::operator>; +using sv_lite::operator>=; + +#if ! nssv_CONFIG_NO_STREAM_INSERTION +using sv_lite::operator<<; +#endif + +#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS +using sv_lite::to_string; +using sv_lite::to_string_view; +#endif + +} // namespace nonstd + +// 24.4.5 Hash support (C++11): + +// Note: The hash value of a string view object is equal to the hash value of +// the corresponding string object. + +#if nssv_HAVE_STD_HASH + +#include + +namespace std { + +template<> +struct hash< nonstd::string_view > +{ +public: + std::size_t operator()( nonstd::string_view v ) const nssv_noexcept + { + return std::hash()( std::string( v.data(), v.size() ) ); + } +}; + +template<> +struct hash< nonstd::wstring_view > +{ +public: + std::size_t operator()( nonstd::wstring_view v ) const nssv_noexcept + { + return std::hash()( std::wstring( v.data(), v.size() ) ); + } +}; + +template<> +struct hash< nonstd::u16string_view > +{ +public: + std::size_t operator()( nonstd::u16string_view v ) const nssv_noexcept + { + return std::hash()( std::u16string( v.data(), v.size() ) ); + } +}; + +template<> +struct hash< nonstd::u32string_view > +{ +public: + std::size_t operator()( nonstd::u32string_view v ) const nssv_noexcept + { + return std::hash()( std::u32string( v.data(), v.size() ) ); + } +}; + +} // namespace std + +#endif // nssv_HAVE_STD_HASH + +nssv_RESTORE_WARNINGS() + +#endif // nssv_HAVE_STD_STRING_VIEW +#endif // NONSTD_SV_LITE_H_INCLUDED