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:
pantor
2020-07-13 15:20:04 +02:00
committed by GitHub
parent 59d1d6b577
commit 6eb71dd3ea
22 changed files with 3329 additions and 2805 deletions

View File

@@ -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