mirror of
https://github.com/pantor/inja.git
synced 2026-03-31 05:12:46 +00:00
Rewarite core with an AST for statements and RPN for expressions (#149)
* test * improve ast * add if statement * shunting-yard start * renderer as node visitor * improve ast * improve ast further * first functions * improve ast v3 * improve ast v4 * fix parser error location * nested ifs * fix comma, activate more tests * fix line statements * fix some more tests * fix callbacks without arguments * add json literal array and object * use switch in expression * fix default function * fix loop data * improved tests and benchmark * fix minus numbers * improve all * fix warnings, optimizations * fix callbacks argument order * dont move loop parent * a few more test * fix clang-3 * fix pointers * clean * update single include
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2019 Pantor. All rights reserved.
|
||||
// Copyright (c) 2020 Pantor. All rights reserved.
|
||||
|
||||
#ifndef INCLUDE_INJA_NODE_HPP_
|
||||
#define INCLUDE_INJA_NODE_HPP_
|
||||
@@ -8,122 +8,296 @@
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "function_storage.hpp"
|
||||
#include "string_view.hpp"
|
||||
|
||||
|
||||
namespace inja {
|
||||
|
||||
using json = nlohmann::json;
|
||||
class NodeVisitor;
|
||||
class BlockNode;
|
||||
class TextNode;
|
||||
class ExpressionNode;
|
||||
class LiteralNode;
|
||||
class JsonNode;
|
||||
class FunctionNode;
|
||||
class ExpressionListNode;
|
||||
class StatementNode;
|
||||
class ForStatementNode;
|
||||
class ForArrayStatementNode;
|
||||
class ForObjectStatementNode;
|
||||
class IfStatementNode;
|
||||
class IncludeStatementNode;
|
||||
|
||||
struct Node {
|
||||
enum class Op : uint8_t {
|
||||
Nop,
|
||||
// print StringRef (always immediate)
|
||||
PrintText,
|
||||
// print value
|
||||
PrintValue,
|
||||
// push value onto stack (always immediate)
|
||||
Push,
|
||||
|
||||
// builtin functions
|
||||
// result is pushed to stack
|
||||
// args specify number of arguments
|
||||
// all functions can take their "last" argument either immediate
|
||||
// or popped off stack (e.g. if immediate, it's like the immediate was
|
||||
// just pushed to the stack)
|
||||
Not,
|
||||
And,
|
||||
Or,
|
||||
In,
|
||||
Equal,
|
||||
Greater,
|
||||
GreaterEqual,
|
||||
Less,
|
||||
LessEqual,
|
||||
At,
|
||||
Different,
|
||||
DivisibleBy,
|
||||
Even,
|
||||
First,
|
||||
Float,
|
||||
Int,
|
||||
Last,
|
||||
Length,
|
||||
Lower,
|
||||
Max,
|
||||
Min,
|
||||
Odd,
|
||||
Range,
|
||||
Result,
|
||||
Round,
|
||||
Sort,
|
||||
Upper,
|
||||
Exists,
|
||||
ExistsInObject,
|
||||
IsBoolean,
|
||||
IsNumber,
|
||||
IsInteger,
|
||||
IsFloat,
|
||||
IsObject,
|
||||
IsArray,
|
||||
IsString,
|
||||
Default,
|
||||
class NodeVisitor {
|
||||
public:
|
||||
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 JsonNode& 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;
|
||||
};
|
||||
|
||||
// include another template
|
||||
// value is the template name
|
||||
Include,
|
||||
|
||||
// callback function
|
||||
// str is the function name (this means it cannot be a lookup)
|
||||
// args specify number of arguments
|
||||
// as with builtin functions, "last" argument can be immediate
|
||||
Callback,
|
||||
class AstNode {
|
||||
public:
|
||||
virtual void accept(NodeVisitor& v) const = 0;
|
||||
|
||||
// unconditional jump
|
||||
// args is the index of the node to jump to.
|
||||
Jump,
|
||||
|
||||
// conditional jump
|
||||
// value popped off stack is checked for truthyness
|
||||
// if false, args is the index of the node to jump to.
|
||||
// if true, no action is taken (falls through)
|
||||
ConditionalJump,
|
||||
|
||||
// start loop
|
||||
// value popped off stack is what is iterated over
|
||||
// args is index of node after end loop (jumped to if iterable is empty)
|
||||
// immediate value is key name (for maps)
|
||||
// str is value name
|
||||
StartLoop,
|
||||
|
||||
// end a loop
|
||||
// args is index of the first node in the loop body
|
||||
EndLoop,
|
||||
};
|
||||
|
||||
enum Flag {
|
||||
// location of value for value-taking ops (mask)
|
||||
ValueMask = 0x03,
|
||||
// pop value off stack
|
||||
ValuePop = 0x00,
|
||||
// value is immediate rather than on stack
|
||||
ValueImmediate = 0x01,
|
||||
// lookup immediate str (dot notation)
|
||||
ValueLookupDot = 0x02,
|
||||
// lookup immediate str (json pointer notation)
|
||||
ValueLookupPointer = 0x03,
|
||||
};
|
||||
|
||||
Op op {Op::Nop};
|
||||
uint32_t args : 30;
|
||||
uint32_t flags : 2;
|
||||
|
||||
json value;
|
||||
std::string str;
|
||||
size_t pos;
|
||||
|
||||
explicit Node(Op op, unsigned int args, size_t pos) : op(op), args(args), flags(0), pos(pos) {}
|
||||
explicit Node(Op op, nonstd::string_view str, unsigned int flags, size_t pos) : op(op), args(0), flags(flags), str(str), pos(pos) {}
|
||||
explicit Node(Op op, json &&value, unsigned int flags, size_t pos) : op(op), args(0), flags(flags), value(std::move(value)), pos(pos) {}
|
||||
AstNode(size_t pos) : pos(pos) { }
|
||||
virtual ~AstNode() { };
|
||||
};
|
||||
|
||||
|
||||
class BlockNode : public AstNode {
|
||||
public:
|
||||
std::vector<std::shared_ptr<AstNode>> nodes;
|
||||
|
||||
explicit BlockNode() : AstNode(0) {}
|
||||
|
||||
void accept(NodeVisitor& v) const {
|
||||
v.visit(*this);
|
||||
}
|
||||
};
|
||||
|
||||
class TextNode : public AstNode {
|
||||
public:
|
||||
std::string content;
|
||||
|
||||
explicit TextNode(nonstd::string_view content, size_t pos): AstNode(pos), content(content) { }
|
||||
|
||||
void accept(NodeVisitor& v) const {
|
||||
v.visit(*this);
|
||||
}
|
||||
};
|
||||
|
||||
class ExpressionNode : public AstNode {
|
||||
public:
|
||||
explicit ExpressionNode(size_t pos) : AstNode(pos) {}
|
||||
|
||||
void accept(NodeVisitor& v) const {
|
||||
v.visit(*this);
|
||||
}
|
||||
};
|
||||
|
||||
class LiteralNode : public ExpressionNode {
|
||||
public:
|
||||
nlohmann::json value;
|
||||
|
||||
explicit LiteralNode(const nlohmann::json& value, size_t pos) : ExpressionNode(pos), value(value) { }
|
||||
|
||||
void accept(NodeVisitor& v) const {
|
||||
v.visit(*this);
|
||||
}
|
||||
};
|
||||
|
||||
class JsonNode : public ExpressionNode {
|
||||
public:
|
||||
std::string name;
|
||||
std::string ptr {""};
|
||||
|
||||
explicit JsonNode(nonstd::string_view ptr_name, size_t pos) : ExpressionNode(pos), name(ptr_name) {
|
||||
// Convert dot notation to json pointer notation
|
||||
do {
|
||||
nonstd::string_view part;
|
||||
std::tie(part, ptr_name) = string_view::split(ptr_name, '.');
|
||||
ptr.push_back('/');
|
||||
ptr.append(part.begin(), part.end());
|
||||
} while (!ptr_name.empty());
|
||||
}
|
||||
|
||||
void accept(NodeVisitor& v) const {
|
||||
v.visit(*this);
|
||||
}
|
||||
};
|
||||
|
||||
class FunctionNode : public ExpressionNode {
|
||||
using Op = FunctionStorage::Operation;
|
||||
|
||||
public:
|
||||
enum class Associativity {
|
||||
Left,
|
||||
Right,
|
||||
};
|
||||
|
||||
unsigned int precedence;
|
||||
Associativity associativity;
|
||||
|
||||
Op operation;
|
||||
|
||||
std::string name;
|
||||
size_t number_args;
|
||||
CallbackFunction callback;
|
||||
|
||||
explicit FunctionNode(nonstd::string_view name, size_t pos) : ExpressionNode(pos), precedence(5), associativity(Associativity::Left), operation(Op::Callback), name(name), number_args(1) { }
|
||||
explicit FunctionNode(Op operation, size_t pos) : ExpressionNode(pos), operation(operation), number_args(1) {
|
||||
switch (operation) {
|
||||
case Op::Not: {
|
||||
precedence = 4;
|
||||
associativity = Associativity::Left;
|
||||
} break;
|
||||
case Op::And: {
|
||||
precedence = 1;
|
||||
associativity = Associativity::Left;
|
||||
} break;
|
||||
case Op::Or: {
|
||||
precedence = 1;
|
||||
associativity = Associativity::Left;
|
||||
} break;
|
||||
case Op::In: {
|
||||
precedence = 2;
|
||||
associativity = Associativity::Left;
|
||||
} break;
|
||||
case Op::Equal: {
|
||||
precedence = 2;
|
||||
associativity = Associativity::Left;
|
||||
} break;
|
||||
case Op::NotEqual: {
|
||||
precedence = 2;
|
||||
associativity = Associativity::Left;
|
||||
} break;
|
||||
case Op::Greater: {
|
||||
precedence = 2;
|
||||
associativity = Associativity::Left;
|
||||
} break;
|
||||
case Op::GreaterEqual: {
|
||||
precedence = 2;
|
||||
associativity = Associativity::Left;
|
||||
} break;
|
||||
case Op::Less: {
|
||||
precedence = 2;
|
||||
associativity = Associativity::Left;
|
||||
} break;
|
||||
case Op::LessEqual: {
|
||||
precedence = 2;
|
||||
associativity = Associativity::Left;
|
||||
} break;
|
||||
case Op::Add: {
|
||||
precedence = 3;
|
||||
associativity = Associativity::Left;
|
||||
} break;
|
||||
case Op::Subtract: {
|
||||
precedence = 3;
|
||||
associativity = Associativity::Left;
|
||||
} break;
|
||||
case Op::Multiplication: {
|
||||
precedence = 4;
|
||||
associativity = Associativity::Left;
|
||||
} break;
|
||||
case Op::Division: {
|
||||
precedence = 4;
|
||||
associativity = Associativity::Left;
|
||||
} break;
|
||||
case Op::Power: {
|
||||
precedence = 5;
|
||||
associativity = Associativity::Right;
|
||||
} break;
|
||||
case Op::Modulo: {
|
||||
precedence = 4;
|
||||
associativity = Associativity::Left;
|
||||
} break;
|
||||
default: {
|
||||
precedence = 1;
|
||||
associativity = Associativity::Left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void accept(NodeVisitor& v) const {
|
||||
v.visit(*this);
|
||||
}
|
||||
};
|
||||
|
||||
class ExpressionListNode : public AstNode {
|
||||
public:
|
||||
std::vector<std::shared_ptr<ExpressionNode>> rpn_output;
|
||||
|
||||
explicit ExpressionListNode() : AstNode(0) { }
|
||||
explicit ExpressionListNode(size_t pos) : AstNode(pos) { }
|
||||
|
||||
void accept(NodeVisitor& v) const {
|
||||
v.visit(*this);
|
||||
}
|
||||
};
|
||||
|
||||
class StatementNode : public AstNode {
|
||||
public:
|
||||
StatementNode(size_t pos) : AstNode(pos) { }
|
||||
|
||||
virtual void accept(NodeVisitor& v) const = 0;
|
||||
};
|
||||
|
||||
class ForStatementNode : public StatementNode {
|
||||
public:
|
||||
ExpressionListNode condition;
|
||||
BlockNode body;
|
||||
BlockNode *parent;
|
||||
|
||||
ForStatementNode(size_t pos) : StatementNode(pos) { }
|
||||
|
||||
virtual void accept(NodeVisitor& v) const = 0;
|
||||
};
|
||||
|
||||
class ForArrayStatementNode : public ForStatementNode {
|
||||
public:
|
||||
nonstd::string_view value;
|
||||
|
||||
explicit ForArrayStatementNode(nonstd::string_view value, size_t pos) : ForStatementNode(pos), value(value) { }
|
||||
|
||||
void accept(NodeVisitor& v) const {
|
||||
v.visit(*this);
|
||||
}
|
||||
};
|
||||
|
||||
class ForObjectStatementNode : public ForStatementNode {
|
||||
public:
|
||||
nonstd::string_view key;
|
||||
nonstd::string_view value;
|
||||
|
||||
explicit ForObjectStatementNode(nonstd::string_view key, nonstd::string_view value, size_t pos) : ForStatementNode(pos), key(key), value(value) { }
|
||||
|
||||
void accept(NodeVisitor& v) const {
|
||||
v.visit(*this);
|
||||
}
|
||||
};
|
||||
|
||||
class IfStatementNode : public StatementNode {
|
||||
public:
|
||||
ExpressionListNode condition;
|
||||
BlockNode true_statement;
|
||||
BlockNode false_statement;
|
||||
BlockNode *parent;
|
||||
|
||||
bool is_nested;
|
||||
bool has_false_statement {false};
|
||||
|
||||
explicit IfStatementNode(size_t pos) : StatementNode(pos), is_nested(false) { }
|
||||
explicit IfStatementNode(bool is_nested, size_t pos) : StatementNode(pos), is_nested(is_nested) { }
|
||||
|
||||
void accept(NodeVisitor& v) const {
|
||||
v.visit(*this);
|
||||
}
|
||||
};
|
||||
|
||||
class IncludeStatementNode : public StatementNode {
|
||||
public:
|
||||
std::string file;
|
||||
|
||||
explicit IncludeStatementNode(const std::string& file, size_t pos) : StatementNode(pos), file(file) { }
|
||||
|
||||
void accept(NodeVisitor& v) const {
|
||||
v.visit(*this);
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace inja
|
||||
|
||||
Reference in New Issue
Block a user