text: // Copyright (c) 2020 Pantor. All rights reserved.
  
  #ifndef INCLUDE_INJA_NODE_HPP_
  #define INCLUDE_INJA_NODE_HPP_
  
  #include <string>
  #include <utility>
  
  #include <nlohmann/json.hpp>
  
  #include "function_storage.hpp"
  #include "string_view.hpp"
  
  
  namespace inja {
  
  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;
  class SetStatementNode;
  
  
  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;
    virtual void visit(const SetStatementNode& node) = 0;
  };
  
  /*!
   * \brief Base node class for the abstract syntax tree (AST).
   */
  class AstNode {
  public:
    virtual void accept(NodeVisitor& v) const = 0;
  
    size_t 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:
    const size_t length;
  
    explicit TextNode(size_t pos, size_t length): AstNode(pos), length(length) { }
  
    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:
    const 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:
    const std::string name;
    const json::json_pointer ptr;
  
    static std::string convert_dot_to_json_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 JsonNode(nonstd::string_view ptr_name, size_t pos) : ExpressionNode(pos), name(ptr_name), ptr(json::json_pointer(convert_dot_to_json_ptr(ptr_name))) { }
  
    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;
    int number_args; // Should also be negative -> -1 for unknown number
    std::vector<std::shared_ptr<ExpressionNode>> arguments;
    CallbackFunction callback;
  
    explicit FunctionNode(nonstd::string_view name, size_t pos) : ExpressionNode(pos), precedence(8), 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: {
          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);
    }
  };
  
  class ExpressionListNode : public AstNode {
  public:
    std::shared_ptr<ExpressionNode> root;
  
    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 *const parent;
  
    ForStatementNode(BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent) { }
  
    virtual void accept(NodeVisitor& v) const = 0;
  };
  
  class ForArrayStatementNode : public ForStatementNode {
  public:
    const std::string 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);
    }
  };
  
  class ForObjectStatementNode : public ForStatementNode {
  public:
    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) { }
  
    void accept(NodeVisitor& v) const {
      v.visit(*this);
    }
  };
  
  class IfStatementNode : public StatementNode {
  public:
    ExpressionListNode condition;
    BlockNode true_statement;
    BlockNode false_statement;
    BlockNode *const parent;
  
    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(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);
    }
  };
  
  class IncludeStatementNode : public StatementNode {
  public:
    const std::string file;
  
    explicit IncludeStatementNode(const std::string& file, size_t pos) : StatementNode(pos), file(file) { }
  
    void accept(NodeVisitor& v) const {
      v.visit(*this);
    };
  };
  
  class SetStatementNode : public StatementNode {
  public:
    const std::string key;
    ExpressionListNode expression;
  
    explicit SetStatementNode(const std::string& key, size_t pos) : StatementNode(pos), key(key) { }
  
    void accept(NodeVisitor& v) const {
      v.visit(*this);
    };
  };
  
  } // namespace inja
  
  #endif // INCLUDE_INJA_NODE_HPP_
  