#include <PugsParser.hpp>

#include <PugsAssert.hpp>

#include <fstream>
#include <iostream>
#include <unordered_map>
#include <variant>

#include <rang.hpp>

#include <pegtl.hpp>
#include <pegtl/analyze.hpp>
#include <pegtl/contrib/parse_tree.hpp>
#include <pegtl/contrib/parse_tree_to_dot.hpp>

using namespace TAO_PEGTL_NAMESPACE;

namespace language
{
// clang-format off

struct slashslash : TAO_PEGTL_STRING("//") {};
struct slashstar  : TAO_PEGTL_STRING("/*") {};
struct starslash  : TAO_PEGTL_STRING("*/") {};

struct comment
    : sor< if_must< slashslash, until< eolf > >,
           try_catch< slashstar, until< starslash> >,
           // error management
           if_must<at<slashstar>,raise<slashstar>, until< eof> > > {};

struct ignored : star< sor< space, comment>  >{};

struct integer
    :  plus< digit > {};
struct INTEGER : seq< integer, ignored >{};

struct real
    : seq< sor< seq<
                  plus< digit >,
                  one < '.' >,
                  star< digit >
                  >,
                seq<
                  one < '.' >,
                  plus< digit >
                  >
                >,
           opt<
             seq<
               one< 'E',
                    'e' >,
               opt< one< '+',
                         '-' >
                    >,
               plus<digit>
               >
             >
           >{};

struct semicol : one< ';' >{};

struct REAL : seq< real, ignored >{};

struct B_set : one< 'B' >{};
struct N_set : one< 'N' >{};
struct Z_set : one< 'Z' >{};
struct R_set : one< 'R' >{};

struct basic_type : sor < B_set, R_set, Z_set, N_set > {};

struct TYPESPECIFIER : seq< basic_type, ignored> {};

struct and_kw :  TAO_PEGTL_KEYWORD("and") {};
struct or_kw :  TAO_PEGTL_KEYWORD("or") {};

struct xor_kw :  TAO_PEGTL_KEYWORD("xor") {};
struct bitand_kw :  TAO_PEGTL_KEYWORD("bitand") {};
struct bitor_kw :  TAO_PEGTL_KEYWORD("bitor") {};

struct and_eq_kw :  TAO_PEGTL_KEYWORD("and_eq") {};
struct xor_eq_kw :  TAO_PEGTL_KEYWORD("xor_eq") {};
struct or_eq_kw :  TAO_PEGTL_KEYWORD("or_eq") {};

struct not_kw :  TAO_PEGTL_KEYWORD("not") {};
struct true_kw :  TAO_PEGTL_KEYWORD("true") {};
struct false_kw :  TAO_PEGTL_KEYWORD("false") {};

struct BOOL : seq< sor<true_kw, false_kw>, ignored > {};

struct do_kw : TAO_PEGTL_KEYWORD("do") {};
struct DO : seq < do_kw, ignored > {};

struct while_kw : TAO_PEGTL_KEYWORD("while") {};
struct WHILE : seq < while_kw, ignored > {};

struct for_kw : TAO_PEGTL_KEYWORD("for") {};
struct FOR : seq < for_kw, ignored > {};

struct if_kw : TAO_PEGTL_KEYWORD("if") {};
struct IF : seq < if_kw, ignored > {};

struct else_kw : TAO_PEGTL_KEYWORD("else") {};
struct ELSE : seq < else_kw, ignored > {};

struct break_kw : TAO_PEGTL_KEYWORD("break") {};
struct BREAK : seq < break_kw, ignored > {};

struct continue_kw : TAO_PEGTL_KEYWORD("continue") {};
struct CONTINUE : seq < continue_kw, ignored > {};

struct cout_kw : TAO_PEGTL_KEYWORD("cout") {};
struct cerr_kw : TAO_PEGTL_KEYWORD("cerr") {};
struct clog_kw : TAO_PEGTL_KEYWORD("clog") {};

struct keywork : sor < basic_type, true_kw, false_kw, do_kw, while_kw, for_kw, if_kw, else_kw, and_kw, or_kw, xor_kw,  bitand_kw, bitor_kw, and_eq_kw, xor_eq_kw, or_eq_kw, break_kw, continue_kw, cout_kw, cerr_kw, clog_kw > {};

struct name : minus< identifier, keywork >  {};
struct NAME : seq < name, ignored > {};

struct SEMICOL : seq< semicol , ignored > {};

struct open_parent : seq< one< '(' >, ignored > {};
struct close_parent : seq< one< ')' >, ignored > {};

struct expression;
struct parented_expression : if_must< open_parent, expression, close_parent > {};

struct primary_expression : sor< BOOL, REAL, INTEGER , NAME, parented_expression > {};

struct unary_plusplus : TAO_PEGTL_STRING("++") {};
struct unary_minusminus : TAO_PEGTL_STRING("--") {};

struct unary_plus :  one< '+' > {};
struct unary_minus : one< '-' > {};

struct unary_not : sor< one< '!'> , not_kw > {};

struct unary_operator : seq< sor< unary_plusplus, unary_minusminus, unary_plus, unary_minus, unary_not>, ignored > {};

struct post_plusplus : TAO_PEGTL_STRING("++") {};
struct post_minusminus : TAO_PEGTL_STRING("--") {};

struct postfix_operator : seq< sor< post_plusplus, post_minusminus>, ignored > {};

struct postfix_expression : seq< primary_expression, star<postfix_operator> > {};

struct unary_expression : sor< seq< unary_operator, unary_expression >,
                               postfix_expression > {};

struct and_op : seq< sor<TAO_PEGTL_STRING("&&"), and_kw>, ignored > {};
struct or_op : seq< sor<TAO_PEGTL_STRING("||"), or_kw>, ignored > {};

struct xor_op : seq< sor< one< '^' >, xor_kw>, ignored >{};
struct bitand_op : seq< sor< seq< one< '&' >, not_at< one< '&' > > >, bitand_kw>, ignored >{};
struct bitor_op : seq< sor< seq< one< '|' >, not_at< one< '|' > > >, bitor_kw>, ignored >{};

struct eqeq_op : seq< TAO_PEGTL_STRING("=="), ignored > {};
struct not_eq_op : seq< TAO_PEGTL_STRING("!="), ignored > {};

struct lesser_op : seq< one< '<' >, not_at< one< '<' > >, ignored > {};
struct lesser_or_eq_op : seq< TAO_PEGTL_STRING("<="), ignored > {};
struct greater_op : seq< one< '>' >, not_at< one< '>' > >, ignored > {};
struct greater_or_eq_op : seq< TAO_PEGTL_STRING(">="), ignored > {};

struct shift_left_op : seq< TAO_PEGTL_STRING("<<"), ignored > {};
struct shift_right_op : seq< TAO_PEGTL_STRING(">>"), ignored > {};

struct plus_op : seq< one< '+' >, not_at< one< '+' > >, ignored > {};
struct minus_op : seq< one< '-' >, not_at< one< '-' > >, ignored > {};
struct multiply_op : seq< one< '*' >, ignored > {};
struct divide_op : seq< one< '/' >, ignored > {};

struct eq_op : seq< one<'='>, not_at< one< '=' > >, ignored > {};
struct multiplyeq_op : seq< TAO_PEGTL_STRING("*="), ignored > {};
struct divideeq_op : seq< TAO_PEGTL_STRING("/="), ignored > {};
struct pluseq_op : seq< TAO_PEGTL_STRING("+="), ignored > {};
struct minuseq_op : seq< TAO_PEGTL_STRING("-="), ignored > {};

struct bit_andeq_op : seq< sor< TAO_PEGTL_STRING("&="), and_eq_kw >, ignored > {};
struct bit_xoreq_op : seq< sor< TAO_PEGTL_STRING("^="), xor_eq_kw >, ignored > {};
struct bit_oreq_op : seq< sor< TAO_PEGTL_STRING("|="), or_eq_kw >, ignored > {};

struct product : list_must< unary_expression, sor< multiply_op, divide_op > > {};

struct sum : list_must< product, sor< plus_op, minus_op > > {};

struct compare : list_must<sum, sor< lesser_or_eq_op, greater_or_eq_op, lesser_op, greater_op > >{};

struct equality : list_must< compare, sor< eqeq_op, not_eq_op > >{};

struct bitwise_and : list_must < equality, bitand_op >{};

struct bitwise_xor : list_must< bitwise_and, xor_op >{};

struct bitwise_or : list_must< bitwise_xor, bitor_op >{};

struct logical_and : list_must< bitwise_or, and_op >{};

struct logical_or : list_must< logical_and, or_op >{};

struct expression : logical_or {};

struct affect_op : sor< eq_op, multiplyeq_op, divideeq_op, pluseq_op, minuseq_op,  bit_andeq_op,
                        bit_xoreq_op, bit_oreq_op > {};

struct affectation : seq< NAME , if_must< affect_op, expression > >{};

struct declaration : if_must<TYPESPECIFIER, NAME, opt<if_must<seq<one<'='>,ignored>, expression>> >{};

struct open_brace : seq< one< '{' >, ignored >{};
struct close_brace : seq< one< '}' >, ignored >{};

struct instruction_list;

struct braced_instruction_list
    :  sor<try_catch< open_brace, instruction_list, close_brace >,
           // non matching braces management
           if_must< at< one< '{' > >, raise<open_brace>, until<eof> > >{};

struct bloc : braced_instruction_list {};

struct statement_bloc;

struct if_statement : if_must< IF, parented_expression,  statement_bloc, opt< if_must<ELSE, statement_bloc > > >{};

struct do_while_statement : if_must< DO, statement_bloc, WHILE, parented_expression >{};

struct while_statement : if_must< WHILE, parented_expression, statement_bloc >{};

struct for_init : opt< sor< declaration, affectation, expression > >{};
struct for_test : opt< sor< affectation, expression > >{};
struct for_post : opt< sor< affectation, expression > >{};

struct for_statement_bloc;

struct for_statement : if_must< FOR, open_parent, for_init, SEMICOL, for_test, SEMICOL, for_post, close_parent, for_statement_bloc >{};

struct ostream_object : seq< sor< cout_kw, cerr_kw, clog_kw >, ignored >{};
struct ostream_statement : seq< ostream_object, star< if_must< shift_left_op, expression, ignored > > >{};

struct instruction
    : sor<if_must< declaration, semicol >,
          if_must< affectation, semicol >,
          if_statement,
          if_must<do_while_statement, semicol>,
          while_statement,
          for_statement,
          if_must< ostream_statement, semicol >,
          if_must< BREAK, semicol >,
          if_must< CONTINUE, semicol >,
          if_must< expression, semicol >,
          bloc,
          semicol>
{};

struct INSTRUCTION : seq<instruction, ignored> {};
struct statement_bloc : seq< sor< bloc, instruction >, ignored >{};

struct for_statement_bloc : seq< sor< braced_instruction_list,
                                      instruction >,
                                 ignored >{};

struct instruction_list : star< INSTRUCTION >{};
struct grammar : must<ignored, instruction_list, eof>{};

template <typename Rule>
struct errors : public normal<Rule>
{
  static const std::string error_message;

  template <typename Input, typename... States>
  static void
  raise(const Input& in, States&&... /*unused*/)
  {
    throw parse_error(error_message, std::vector{in.position()});
  }
};

template <typename Rule>
inline const std::string errors<Rule>::error_message = "parse error matching "+ internal::demangle< Rule >();

template <>
inline const std::string errors<language::semicol>::error_message = "parse error, missing ';'";
template <>
inline const std::string errors<language::SEMICOL>::error_message = "parse error, missing ';'";

template <>
inline const std::string errors<language::name>::error_message = "parse error, missing identifier";
template <>
inline const std::string errors<language::NAME>::error_message = "parse error, missing identifier";

template <>
inline const std::string errors<language::expression>::error_message = "parse error, missing expression";
template <>
inline const std::string errors<language::product>::error_message = "parse error, missing expression";
template <>
inline const std::string errors<language::sum>::error_message = "parse error, missing expression";
template <>
inline const std::string errors<language::compare>::error_message = "parse error, missing expression";
template <>
inline const std::string errors<language::equality>::error_message = "parse error, missing expression";
template <>
inline const std::string errors<language::bitwise_and>::error_message = "parse error, missing expression";
template <>
inline const std::string errors<language::bitwise_xor>::error_message = "parse error, missing expression";
template <>
inline const std::string errors<language::bitwise_or>::error_message = "parse error, missing expression";
template <>
inline const std::string errors<language::logical_and>::error_message = "parse error, missing expression";
template <>
inline const std::string errors<language::logical_or>::error_message = "parse error, missing expression";

template <>
inline const std::string errors<language::parented_expression>::error_message = "parse error, missing parented expression";

template <>
inline const std::string errors<language::statement_bloc>::error_message = "parse error, missing instruction";

template <>
inline const std::string errors<language::WHILE>::error_message = "parse error, missing 'while' statement";
template <>
inline const std::string errors<language::while_kw>::error_message = "parse error, missing 'while' statement";

template <>
inline const std::string errors<language::open_parent>::error_message = "parse error, missing open parent '('";

template <>
inline const std::string errors<language::for_statement_bloc>::error_message = "parse error, missing for-loop body";

template <>
inline const std::string errors<language::slashstar>::error_message = "bloc comment was never terminated, missing '*/'";

template <>
inline const std::string errors<language::open_brace>::error_message = "open brace was never closed, missing '}'";

// clang-format on

enum class DataType
{
  undefined_t    = -1,
  bool_t         = 0,
  unsigned_int_t = 1,
  int_t          = 2,
  double_t       = 3,
  typename_t     = 10,
  void_t         = 9999
};

std::string
dataTypeName(const DataType& data_type)
{
  std::string name;
  switch (data_type) {
  case DataType::undefined_t:
    name = "undefined";
    break;
  case DataType::bool_t:
    name = "B";
    break;
  case DataType::unsigned_int_t:
    name = "N";
    break;
  case DataType::int_t:
    name = "Z";
    break;
  case DataType::double_t:
    name = "R";
    break;
  case DataType::typename_t:
    name = "typename";
    break;
  case DataType::void_t:
    name = "void";
    break;
  }
  return name;
}

DataType
dataTypePromotion(const DataType& data_type_1, const DataType& data_type_2)
{
  if (data_type_1 == data_type_2) {
    return data_type_1;
  } else if ((std::max(data_type_1, data_type_2) <= DataType::double_t) and
             (std::min(data_type_1, data_type_2) >= DataType::bool_t)) {
    return std::max(data_type_1, data_type_2);
  } else {
    return DataType::undefined_t;
  }
}

using DataVariant = std::variant<std::monostate, bool, uint64_t, int64_t, double>;

struct ExecUntilBreakOrContinue
{
  enum class JumpType
  {
    no_jump,
    break_jump,
    continue_jump
  };

 private:
  JumpType m_jump_type{JumpType::no_jump};
  bool m_exec{true};

 public:
  PUGS_INLINE
  bool
  exec() const
  {
    return m_exec;
  }

  PUGS_INLINE
  JumpType
  jumpType() const
  {
    return m_jump_type;
  }

  ExecUntilBreakOrContinue& operator=(const ExecUntilBreakOrContinue&) = delete;
  ExecUntilBreakOrContinue& operator=(ExecUntilBreakOrContinue&&) = default;

  ExecUntilBreakOrContinue() = default;

  constexpr ExecUntilBreakOrContinue(const JumpType& jump_type)
    : m_jump_type(jump_type), m_exec((jump_type == JumpType::no_jump))
  {
    ;
  }
};

class SymbolTable;
class INodeProcessor
{
 public:
  virtual void execute(ExecUntilBreakOrContinue& exec_policy) = 0;

  INodeProcessor(const INodeProcessor& node) = delete;

  INodeProcessor() {}

  virtual ~INodeProcessor() {}
};

struct Node : public parse_tree::basic_node<Node>
{
  std::shared_ptr<SymbolTable> m_symbol_table;
  std::unique_ptr<INodeProcessor> m_node_processor;

  PUGS_INLINE
  void
  execute(ExecUntilBreakOrContinue& exec_policy)
  {
    Assert(static_cast<bool>(m_node_processor));
    if (exec_policy.exec()) {
      m_node_processor->execute(exec_policy);
    }
  }

  DataType m_data_type{DataType::undefined_t};
  DataVariant m_value;
};

struct rearrange : parse_tree::apply<rearrange>
{
  template <typename... States>
  static void
  transform(std::unique_ptr<Node>& n, States&&... st)
  {
    if (n->children.size() == 1) {
      n = std::move(n->children.back());
    } else {
      // First we rearrange tree
      {
        n->remove_content();
        auto& children = n->children;
        auto rhs       = std::move(children.back());
        children.pop_back();
        auto op = std::move(children.back());
        children.pop_back();
        op->children.emplace_back(std::move(n));
        op->children.emplace_back(std::move(rhs));
        n = std::move(op);
        transform(n->children.front(), st...);
      }
      // Then we eventually simplify operations
      {
        if (n->is<language::minus_op>()) {
          Assert(n->children.size() == 2);
          auto& rhs = n->children[1];
          if (rhs->is<language::unary_minus>()) {
            rhs->remove_content();
            n->id = typeid(language::plus_op);
            rhs   = std::move(rhs->children[0]);
          }
        } else if (n->is<language::plus_op>()) {
          Assert(n->children.size() == 2);
          auto& rhs = n->children[1];
          if (rhs->is<language::unary_minus>()) {
            rhs->remove_content();
            n->id = typeid(language::minus_op);
            rhs   = std::move(rhs->children[0]);
          }
        }
      }
    }
  }
};

struct simplify_unary : parse_tree::apply<simplify_unary>
{
  template <typename... States>
  static void
  transform(std::unique_ptr<Node>& n, States&&... st)
  {
    if (n->children.size() == 1) {
      if (n->is<unary_expression>()) {
        n->remove_content();
        n = std::move(n->children.back());
        transform(n, st...);
      } else if (n->is<unary_minus>()) {
        auto& child = n->children[0];
        if (child->is<unary_minus>()) {
          n->remove_content();
          child->remove_content();
          n = std::move(child->children[0]);
          transform(n, st...);
        }
      } else if (n->is<unary_not>()) {
        auto& child = n->children[0];
        if (child->is<unary_not>()) {
          n->remove_content();
          child->remove_content();
          n = std::move(child->children[0]);
          transform(n, st...);
        }
      }
    } else if (n->children.size() == 2) {
      if (n->children[0]->is<language::unary_plus>()) {
        n->remove_content();
        n = std::move(n->children[1]);
        transform(n, st...);
      } else if (n->children[0]->is<language::unary_minus>() or n->children[0]->is<language::unary_not>() or
                 n->children[0]->is<language::unary_minusminus>() or n->children[0]->is<language::unary_plusplus>()) {
        n->remove_content();
        auto expression     = std::move(n->children[1]);
        auto unary_operator = std::move(n->children[0]);
        unary_operator->children.emplace_back(std::move(expression));
        n = std::move(unary_operator);
        n->remove_content();
        transform(n, st...);
      } else if (n->children[1]->is<language::post_minusminus>() or n->children[1]->is<language::post_plusplus>()) {
        n->remove_content();
        auto expression     = std::move(n->children[0]);
        auto unary_operator = std::move(n->children[1]);
        unary_operator->children.emplace_back(std::move(expression));
        n = std::move(unary_operator);
        n->remove_content();
        transform(n, st...);
      }
    }
  }
};

struct simplify_statement_bloc : parse_tree::apply<simplify_statement_bloc>
{
  template <typename... States>
  static void
  transform(std::unique_ptr<Node>& n, States&&... st)
  {
    if (n->children.size() == 1) {
      if (not n->children[0]->is<language::declaration>()) {
        n->remove_content();
        n = std::move(n->children.back());
        transform(n, st...);
      } else {
        n->id = typeid(language::bloc);
      }
    }
  }
};

struct simplify_for_statement_bloc : parse_tree::apply<simplify_for_statement_bloc>
{
  template <typename... States>
  static void
  transform(std::unique_ptr<Node>& n, States&&... st)
  {
    if (n->children.size() == 1) {
      n->remove_content();
      n = std::move(n->children.back());
      transform(n, st...);
    }
  }
};

struct simplify_for_init : parse_tree::apply<simplify_for_init>
{
  template <typename... States>
  static void
  transform(std::unique_ptr<Node>& n, States&&...)
  {
    Assert(n->children.size() <= 1);
    if (n->children.size() == 1) {
      n->remove_content();
      n = std::move(n->children.back());
    }
  }
};

struct simplify_for_test : parse_tree::apply<simplify_for_test>
{
  template <typename... States>
  static void
  transform(std::unique_ptr<Node>& n, States&&...)
  {
    Assert(n->children.size() <= 1);
    if (n->children.size() == 1) {
      n->remove_content();
      n = std::move(n->children.back());
    }
  }
};

struct simplify_for_post : parse_tree::apply<simplify_for_post>
{
  template <typename... States>
  static void
  transform(std::unique_ptr<Node>& n, States&&...)
  {
    Assert(n->children.size() <= 1);
    if (n->children.size() == 1) {
      n->remove_content();
      n = std::move(n->children.back());
    }
  }
};

struct simplify_stream_statement : parse_tree::apply<simplify_stream_statement>
{
  template <typename... States>
  static void
  transform(std::unique_ptr<Node>& n, States&&...)
  {
    for (size_t i = 1; i < n->children.size(); ++i) {
      n->children[0]->children.emplace_back(std::move(n->children[i]));
    }
    n->remove_content();
    n = std::move(n->children[0]);
  }
};

template <typename Rule>
using selector = parse_tree::selector<Rule,
                                      parse_tree::store_content::on<true_kw,
                                                                    false_kw,
                                                                    integer,
                                                                    real,
                                                                    name,
                                                                    B_set,
                                                                    N_set,
                                                                    Z_set,
                                                                    R_set,
                                                                    cout_kw,
                                                                    cerr_kw,
                                                                    clog_kw,
                                                                    declaration,
                                                                    if_statement,
                                                                    do_while_statement,
                                                                    while_statement,
                                                                    for_statement,
                                                                    break_kw,
                                                                    continue_kw>,
                                      rearrange::on<product, affectation, expression>,
                                      simplify_unary::on<unary_minus, unary_plus, unary_not, unary_expression>,
                                      parse_tree::remove_content::on<plus_op,
                                                                     minus_op,
                                                                     multiply_op,
                                                                     divide_op,
                                                                     lesser_op,
                                                                     lesser_or_eq_op,
                                                                     greater_op,
                                                                     greater_or_eq_op,
                                                                     eqeq_op,
                                                                     not_eq_op,
                                                                     and_op,
                                                                     or_op,
                                                                     xor_op,
                                                                     bitand_op,
                                                                     bitor_op,
                                                                     eq_op,
                                                                     multiplyeq_op,
                                                                     divideeq_op,
                                                                     pluseq_op,
                                                                     minuseq_op,
                                                                     // shift_left_op,
                                                                     // shift_right_op,
                                                                     bit_andeq_op,
                                                                     bit_xoreq_op,
                                                                     bit_oreq_op,
                                                                     unary_plusplus,
                                                                     unary_minusminus,
                                                                     post_minusminus,
                                                                     post_plusplus>,
                                      simplify_for_statement_bloc::on<for_statement_bloc>,
                                      parse_tree::discard_empty::on<ignored, semicol, bloc>,
                                      simplify_statement_bloc::on<statement_bloc>,
                                      simplify_for_init::on<for_init>,
                                      simplify_for_test::on<for_test>,
                                      simplify_for_post::on<for_post>,
                                      simplify_stream_statement::on<ostream_statement>>;

class SymbolTable
{
  class Attributes
  {
    bool m_is_initialized{false};
    DataType m_data_type{DataType::undefined_t};
    DataVariant m_value;

   public:
    auto&
    value()
    {
      return m_value;
    }

    const auto&
    value() const
    {
      return m_value;
    }

    const bool&
    isInitialized() const
    {
      return m_is_initialized;
    }

    void
    setIsInitialized()
    {
      m_is_initialized = true;
    }

    const DataType&
    dataType() const
    {
      return m_data_type;
    }

    void
    setDataType(const DataType& data_type)
    {
      m_data_type = data_type;
    }

    friend std::ostream&
    operator<<(std::ostream& os, const Attributes& attributes)
    {
      std::visit(
        [&](const auto& value) {
          using T = std::decay_t<decltype(value)>;
          if constexpr (std::is_same_v<T, std::monostate>) {
            os << "--";
          } else {
            os << value;
          }
        },
        attributes.m_value);

      return os;
    }
  };

 private:
  std::vector<std::pair<std::string, Attributes>> m_symbol_list;
  std::shared_ptr<SymbolTable> m_parent_table;

 public:
  friend std::ostream&
  operator<<(std::ostream& os, const SymbolTable& symbol_table)
  {
    os << "-- Symbol table state -- parent : " << symbol_table.m_parent_table.get() << "\n";
    for (auto i_symbol : symbol_table.m_symbol_list) {
      os << ' ' << i_symbol.first << ": " << std::boolalpha << i_symbol.second << '\n';
    }
    os << "------------------------\n";
    return os;
  }

  auto
  find(const std::string& symbol)
  {
    auto i_symbol = m_symbol_list.end();

    for (auto i_stored_symbol = m_symbol_list.begin(); i_stored_symbol != m_symbol_list.end(); ++i_stored_symbol) {
      if (i_stored_symbol->first == symbol) {
        i_symbol = i_stored_symbol;
        break;
      }
    }

    if (i_symbol != m_symbol_list.end()) {
      return std::make_pair(i_symbol, true);
    } else {
      if (m_parent_table) {
        return m_parent_table->find(symbol);
      } else {
        return std::make_pair(i_symbol, false);
      }
    }
  }

  auto
  add(const std::string& symbol)
  {
    for (auto i_stored_symbol = m_symbol_list.begin(); i_stored_symbol != m_symbol_list.end(); ++i_stored_symbol) {
      if (i_stored_symbol->first == symbol) {
        return std::make_pair(i_stored_symbol, false);
      }
    }
    return std::make_pair(m_symbol_list.emplace(m_symbol_list.end(), std::make_pair(symbol, Attributes())), true);
  }

  SymbolTable(const std::shared_ptr<SymbolTable>& parent_table = nullptr) : m_parent_table(parent_table)
  {
    ;
  }
};

class NodeList final : public INodeProcessor
{
  Node& m_node;

 public:
  NodeList(Node& node) : m_node{node} {}

  void
  execute(ExecUntilBreakOrContinue& exec_policy)
  {
    for (auto& child : m_node.children) {
      child->execute(exec_policy);
    }
  }
};

template <typename Op>
struct AffOp;

template <>
struct AffOp<language::eq_op>
{
  template <typename A, typename B>
  PUGS_INLINE void
  eval(A& a, const B& b)
  {
    a = b;
  }
};

template <>
struct AffOp<language::multiplyeq_op>
{
  template <typename A, typename B>
  PUGS_INLINE void
  eval(A& a, const B& b)
  {
    a *= b;
  }
};

template <>
struct AffOp<language::divideeq_op>
{
  template <typename A, typename B>
  PUGS_INLINE void
  eval(A& a, const B& b)
  {
    a /= b;
  }
};

template <>
struct AffOp<language::pluseq_op>
{
  template <typename A, typename B>
  PUGS_INLINE void
  eval(A& a, const B& b)
  {
    a += b;
  }
};

template <>
struct AffOp<language::minuseq_op>
{
  template <typename A, typename B>
  PUGS_INLINE void
  eval(A& a, const B& b)
  {
    a -= b;
  }
};

template <>
struct AffOp<language::bit_andeq_op>
{
  template <typename A, typename B>
  PUGS_INLINE void
  eval(A& a, const B& b)
  {
    a &= b;
  }
};

template <>
struct AffOp<language::bit_xoreq_op>
{
  template <typename A, typename B>
  PUGS_INLINE void
  eval(A& a, const B& b)
  {
    a ^= b;
  }
};

template <>
struct AffOp<language::bit_oreq_op>
{
  template <typename A, typename B>
  PUGS_INLINE void
  eval(A& a, const B& b)
  {
    a |= b;
  }
};

template <typename OperatorT, typename ValueT, typename DataT>
class AffectationProcessor final : public INodeProcessor
{
 private:
  Node& m_node;
  DataVariant* p_value{nullptr};

  static inline const bool _is_defined{[] {
    if constexpr (std::is_same_v<OperatorT, language::bit_andeq_op> or
                  std::is_same_v<OperatorT, language::bit_xoreq_op> or
                  std::is_same_v<OperatorT, language::bit_oreq_op>) {
      return std::is_same_v<std::decay_t<ValueT>, std::decay_t<DataT>> and std::is_integral_v<std::decay_t<ValueT>>;
    } else if constexpr (std::is_same_v<std::decay_t<ValueT>, bool>) {
      if constexpr (not std::is_same_v<OperatorT, language::eq_op>) {
        return false;
      }
    }
    return true;
  }()};

 public:
  AffectationProcessor(Node& node) : m_node{node}
  {
    if constexpr (_is_defined) {
      const std::string& symbol = m_node.children[0]->string();
      auto [i_symbol, found]    = m_node.m_symbol_table->find(symbol);
      Assert(found);
      p_value = &i_symbol->second.value();
    } else {
      throw parse_error("invalid operands to binary expression", std::vector{m_node.begin()});
    }
  }

  void
  execute(ExecUntilBreakOrContinue& exec_policy)
  {
    if constexpr (_is_defined) {
      m_node.children[1]->execute(exec_policy);

      if constexpr (std::is_same_v<OperatorT, language::eq_op>) {
        if constexpr (std::is_same_v<ValueT, DataT>) {
          *p_value = m_node.children[1]->m_value;
        } else {
          *p_value = static_cast<ValueT>(std::get<DataT>(m_node.children[1]->m_value));
        }
      } else {
        AffOp<OperatorT>().eval(std::get<ValueT>(*p_value), std::get<DataT>(m_node.children[1]->m_value));
      }
    }
  }
};

class NoProcess final : public INodeProcessor
{
 public:
  NoProcess() {}

  PUGS_INLINE
  void
  execute(ExecUntilBreakOrContinue&)
  {
    ;
  }
};

template <typename Op>
struct UnaryOp;

template <>
struct UnaryOp<language::unary_minus>
{
  template <typename A>
  PUGS_INLINE A
  eval(const A& a)
  {
    return -a;
  }
};

template <>
struct UnaryOp<language::unary_not>
{
  template <typename A>
  PUGS_INLINE bool
  eval(const A& a)
  {
    return not a;
  }
};

template <typename UnaryOpT, typename ValueT, typename DataT>
class UnaryExpressionProcessor final : public INodeProcessor
{
  Node& m_node;

 public:
  PUGS_INLINE ValueT
  eval(const DataVariant& a)
  {
    return UnaryOp<UnaryOpT>().eval(std::get<DataT>(a));
  }

 public:
  UnaryExpressionProcessor(Node& node) : m_node{node} {}

  void
  execute(ExecUntilBreakOrContinue& exec_policy)
  {
    m_node.children[0]->execute(exec_policy);
    m_node.m_value = eval(m_node.children[0]->m_value);
  }
};

template <typename Op>
struct IncDecOp;

template <>
struct IncDecOp<language::unary_minusminus>
{
  template <typename A>
  PUGS_INLINE A
  eval(A& a)
  {
    return --a;
  }
};

template <>
struct IncDecOp<language::unary_plusplus>
{
  template <typename A>
  PUGS_INLINE A
  eval(A& a)
  {
    return ++a;
  }
};

template <>
struct IncDecOp<language::post_minusminus>
{
  template <typename A>
  PUGS_INLINE A
  eval(A& a)
  {
    return a--;
  }
};

template <>
struct IncDecOp<language::post_plusplus>
{
  template <typename A>
  PUGS_INLINE A
  eval(A& a)
  {
    return a++;
  }
};

template <typename IncDecOpT, typename ValueT, typename DataT>
class IncDecExpressionProcessor final : public INodeProcessor
{
  Node& m_node;
  DataVariant* p_value{nullptr};

  static inline const bool _is_defined{[] {
    if constexpr (std::is_same_v<IncDecOpT, language::unary_minusminus> or
                  std::is_same_v<IncDecOpT, language::unary_plusplus> or
                  std::is_same_v<IncDecOpT, language::post_minusminus> or
                  std::is_same_v<IncDecOpT, language::post_plusplus>) {
      return not std::is_same_v<std::decay_t<DataT>, bool>;
    }
    return true;
  }()};

 public:
  IncDecExpressionProcessor(Node& node) : m_node{node}
  {
    if constexpr (_is_defined) {
      Assert(m_node.children[0]->is<language::name>());
      // It is sure at this point that children 0 is a variable name
      const std::string& symbol = m_node.children[0]->string();
      auto [i_symbol, found]    = m_node.m_symbol_table->find(symbol);
      Assert(found);
      p_value = &i_symbol->second.value();
    } else {
      throw parse_error("invalid operand to unary operator", std::vector{m_node.begin()});
    }
  }
  void
  execute(ExecUntilBreakOrContinue&)
  {
    if constexpr (_is_defined) {
      m_node.m_value = IncDecOp<IncDecOpT>().eval(std::get<DataT>(*p_value));
    }
  }
};

template <typename Op>
struct BinOp;

template <>
struct BinOp<language::and_op>
{
  template <typename A, typename B>
  PUGS_INLINE auto
  eval(const A& a, const B& b) -> decltype(a and b)
  {
    return a and b;
  }
};

template <>
struct BinOp<language::or_op>
{
  template <typename A, typename B>
  PUGS_INLINE auto
  eval(const A& a, const B& b) -> decltype(a or b)
  {
    return a or b;
  }
};

template <>
struct BinOp<language::xor_op>
{
  template <typename A, typename B>
  PUGS_INLINE auto
  eval(const A& a, const B& b) -> decltype(a xor b)
  {
    return a xor b;
  }
};

template <>
struct BinOp<language::bitand_op>
{
  template <typename A, typename B>
  PUGS_INLINE auto
  eval(const A& a, const B& b) -> decltype(a & b)
  {
    return a & b;
  }
};

template <>
struct BinOp<language::bitor_op>
{
  template <typename A, typename B>
  PUGS_INLINE auto
  eval(const A& a, const B& b) -> decltype(a | b)
  {
    return a | b;
  }
};

template <>
struct BinOp<language::eqeq_op>
{
  template <typename A, typename B>
  PUGS_INLINE auto
  eval(const A& a, const B& b) -> decltype(a == b)
  {
    return a == b;
  }
};

template <>
struct BinOp<language::not_eq_op>
{
  template <typename A, typename B>
  PUGS_INLINE auto
  eval(const A& a, const B& b) -> decltype(a != b)
  {
    return a != b;
  }
};

template <>
struct BinOp<language::lesser_op>
{
  template <typename A, typename B>
  PUGS_INLINE auto
  eval(const A& a, const B& b) -> decltype(a < b)
  {
    return a < b;
  }
};

template <>
struct BinOp<language::lesser_or_eq_op>
{
  template <typename A, typename B>
  PUGS_INLINE auto
  eval(const A& a, const B& b) -> decltype(a <= b)
  {
    return a <= b;
  }
};

template <>
struct BinOp<language::greater_op>
{
  template <typename A, typename B>
  PUGS_INLINE auto
  eval(const A& a, const B& b) -> decltype(a > b)
  {
    return a > b;
  }
};

template <>
struct BinOp<language::greater_or_eq_op>
{
  template <typename A, typename B>
  PUGS_INLINE auto
  eval(const A& a, const B& b) -> decltype(a >= b)
  {
    return a >= b;
  }
};

template <>
struct BinOp<language::plus_op>
{
  template <typename A, typename B>
  PUGS_INLINE auto
  eval(const A& a, const B& b) -> decltype(a + b)
  {
    return a + b;
  }
};

template <>
struct BinOp<language::minus_op>
{
  template <typename A, typename B>
  PUGS_INLINE auto
  eval(const A& a, const B& b) -> decltype(a - b)
  {
    return a - b;
  }
};

template <>
struct BinOp<language::multiply_op>
{
  template <typename A, typename B>
  PUGS_INLINE auto
  eval(const A& a, const B& b) -> decltype(a * b)
  {
    return a * b;
  }
};

template <>
struct BinOp<language::divide_op>
{
  template <typename A, typename B>
  PUGS_INLINE auto
  eval(const A& a, const B& b) -> decltype(a / b)
  {
    return a / b;
  }
};

template <typename BinaryOpT, typename ValueT, typename A_DataT, typename B_DataT>
class BinaryExpressionProcessor final : public INodeProcessor
{
  Node& m_node;

  PUGS_INLINE ValueT
  eval(const DataVariant& a, const DataVariant& b)
  {
    // Add 'signed' when necessary to avoid signed/unsigned comparison warnings
    if constexpr ((not(std::is_same_v<A_DataT, bool> or std::is_same_v<B_DataT, bool>)) and
                  (std::is_same_v<BinaryOpT, language::and_op> or std::is_same_v<BinaryOpT, language::or_op> or
                   std::is_same_v<BinaryOpT, language::xor_op> or std::is_same_v<BinaryOpT, language::bitand_op> or
                   std::is_same_v<BinaryOpT, language::bitor_op> or std::is_same_v<BinaryOpT, language::eqeq_op> or
                   std::is_same_v<BinaryOpT, language::not_eq_op> or std::is_same_v<BinaryOpT, language::lesser_op> or
                   std::is_same_v<BinaryOpT, language::lesser_or_eq_op> or
                   std::is_same_v<BinaryOpT, language::greater_op> or
                   std::is_same_v<BinaryOpT, language::greater_or_eq_op>) and
                  (std::is_signed_v<A_DataT> xor std::is_signed_v<B_DataT>)) {
      if constexpr (std::is_unsigned_v<A_DataT>) {
        using signed_A_DataT          = std::make_signed_t<A_DataT>;
        const signed_A_DataT signed_a = static_cast<signed_A_DataT>(std::get<A_DataT>(a));
        return BinOp<BinaryOpT>().eval(signed_a, std::get<B_DataT>(b));
      } else {
        using signed_B_DataT          = std::make_signed_t<B_DataT>;
        const signed_B_DataT signed_b = static_cast<signed_B_DataT>(std::get<B_DataT>(b));
        return BinOp<BinaryOpT>().eval(std::get<A_DataT>(a), signed_b);
      }
    } else {
      return BinOp<BinaryOpT>().eval(std::get<A_DataT>(a), std::get<B_DataT>(b));
    }
  }

  static inline const bool _is_defined{[] {
    if constexpr (std::is_same_v<BinaryOpT, language::bitand_op> or std::is_same_v<BinaryOpT, language::xor_op> or
                  std::is_same_v<BinaryOpT, language::bitor_op>) {
      return std::is_same_v<std::decay_t<A_DataT>, std::decay_t<B_DataT>> and std::is_integral_v<std::decay_t<A_DataT>>;
    }
    return true;
  }()};

 public:
  BinaryExpressionProcessor(Node& node) : m_node{node}
  {
    if constexpr (not _is_defined) {
      throw parse_error("invalid operands to binary expression", std::vector{m_node.begin()});
    }
  }

  void
  execute(ExecUntilBreakOrContinue& exec_policy)
  {
    if constexpr (_is_defined) {
      m_node.children[0]->execute(exec_policy);
      m_node.children[1]->execute(exec_policy);

      m_node.m_value = eval(m_node.children[0]->m_value, m_node.children[1]->m_value);
    }
  }
};

class IfStatement final : public INodeProcessor
{
  Node& m_node;

 public:
  IfStatement(Node& node) : m_node{node} {}

  void
  execute(ExecUntilBreakOrContinue& exec_policy)
  {
    m_node.children[0]->execute(exec_policy);
    const bool is_true = static_cast<bool>(std::visit(
      [](const auto& value) -> bool {
        using T = std::decay_t<decltype(value)>;
        if constexpr (std::is_same_v<T, std::monostate>) {
          return false;
        } else {
          return value;
        }
      },
      m_node.children[0]->m_value));
    if (is_true) {
      Assert(m_node.children[1] != nullptr);
      m_node.children[1]->execute(exec_policy);
    } else {
      if (m_node.children.size() == 3) {
        // else statement
        Assert(m_node.children[2] != nullptr);
        m_node.children[2]->execute(exec_policy);
      }
    }
  }
};

class DoWhileStatement final : public INodeProcessor
{
  Node& m_node;

 public:
  DoWhileStatement(Node& node) : m_node{node} {}

  void
  execute(ExecUntilBreakOrContinue& exec_policy)
  {
    bool continuation_test = true;
    ExecUntilBreakOrContinue exec_until_jump;
    do {
      m_node.children[0]->execute(exec_until_jump);
      if (not exec_until_jump.exec()) {
        if (exec_until_jump.jumpType() == ExecUntilBreakOrContinue::JumpType::break_jump) {
          break;
        } else if (exec_until_jump.jumpType() == ExecUntilBreakOrContinue::JumpType::continue_jump) {
          exec_until_jump = ExecUntilBreakOrContinue{};   // getting ready for next loop traversal
        }
      }
      m_node.children[1]->execute(exec_policy);
      continuation_test = static_cast<bool>(std::visit(
        [](const auto& value) -> bool {
          using T = std::decay_t<decltype(value)>;
          if constexpr (std::is_same_v<T, std::monostate>) {
            return false;
          } else {
            return value;
          }
        },
        m_node.children[1]->m_value));
    } while (continuation_test);
  }
};

class WhileStatement final : public INodeProcessor
{
  Node& m_node;

 public:
  WhileStatement(Node& node) : m_node{node} {}

  void
  execute(ExecUntilBreakOrContinue& exec_policy)
  {
    ExecUntilBreakOrContinue exec_until_jump;
    while ([&]() {
      m_node.children[0]->execute(exec_policy);
      return static_cast<bool>(std::visit(
        [](const auto& value) -> bool {
          using T = std::decay_t<decltype(value)>;
          if constexpr (std::is_same_v<T, std::monostate>) {
            return false;
          } else {
            return value;
          }
        },
        m_node.children[0]->m_value));
    }()) {
      m_node.children[1]->execute(exec_until_jump);
      if (not exec_until_jump.exec()) {
        if (exec_until_jump.jumpType() == ExecUntilBreakOrContinue::JumpType::break_jump) {
          break;
        } else if (exec_until_jump.jumpType() == ExecUntilBreakOrContinue::JumpType::continue_jump) {
          exec_until_jump = ExecUntilBreakOrContinue{};   // getting ready for next loop traversal
        }
      }
    }
  }
};

class ForStatement final : public INodeProcessor
{
  Node& m_node;

 public:
  ForStatement(Node& node) : m_node{node} {}

  void
  execute(ExecUntilBreakOrContinue& exec_policy)
  {
    ExecUntilBreakOrContinue exec_until_jump;
    m_node.children[0]->execute(exec_policy);
    while ([&]() {
      m_node.children[1]->execute(exec_policy);
      return static_cast<bool>(std::visit(
        [](const auto& value) -> bool {
          using T = std::decay_t<decltype(value)>;
          if constexpr (std::is_same_v<T, std::monostate>) {
            return false;
          } else {
            return value;
          }
        },
        m_node.children[1]->m_value));
    }()) {
      m_node.children[3]->execute(exec_until_jump);
      if (not exec_until_jump.exec()) {
        if (exec_until_jump.jumpType() == ExecUntilBreakOrContinue::JumpType::break_jump) {
          break;
        } else if (exec_until_jump.jumpType() == ExecUntilBreakOrContinue::JumpType::continue_jump) {
          exec_until_jump = ExecUntilBreakOrContinue{};   // getting ready for next loop traversal
        }
      }

      m_node.children[2]->execute(exec_policy);
    }
  }
};

class NameExpression final : public INodeProcessor
{
  Node& m_node;
  DataVariant* p_value{nullptr};

 public:
  NameExpression(Node& node) : m_node{node}
  {
    const std::string& symbol = m_node.string();
    auto [i_symbol, found]    = m_node.m_symbol_table->find(symbol);
    Assert(found);
    p_value = &(i_symbol->second.value());
  }

  void
  execute(ExecUntilBreakOrContinue&)
  {
    m_node.m_value = *p_value;
  }
};

class BreakExpression final : public INodeProcessor
{
 public:
  BreakExpression() {}

  void
  execute(ExecUntilBreakOrContinue& exec_policy)
  {
    exec_policy = ExecUntilBreakOrContinue(ExecUntilBreakOrContinue::JumpType::break_jump);
  }
};

class ContinueExpression final : public INodeProcessor
{
 public:
  ContinueExpression() {}

  void
  execute(ExecUntilBreakOrContinue& exec_policy)
  {
    exec_policy = ExecUntilBreakOrContinue(ExecUntilBreakOrContinue::JumpType::continue_jump);
  }
};

class OStreamObject final : public INodeProcessor
{
  Node& m_node;
  std::ostream& m_os;

 public:
  OStreamObject(Node& node, std::ostream& os) : m_node{node}, m_os(os)
  {
    ;
  }

  void
  execute(ExecUntilBreakOrContinue& exec_policy)
  {
    for (size_t i = 0; i < m_node.children.size(); ++i) {
      m_node.children[i]->execute(exec_policy);
      std::visit(
        [&](auto&& value) {
          using ValueT = std::decay_t<decltype(value)>;
          if constexpr (not std::is_same_v<std::monostate, ValueT>) {
            if constexpr (std::is_same_v<bool, ValueT>) {
              m_os << std::boolalpha << value;
            } else {
              m_os << value;
            }
          }
        },
        m_node.children[i]->m_value);
    }
  }
};

namespace internal
{
void
build_node_type(Node& n)
{
  auto set_unary_operator_processor = [](Node& n, const auto& operator_v) {
    auto set_unary_operator_processor_for_data = [&](const auto& value, const DataType& data_type) {
      using OperatorT = std::decay_t<decltype(operator_v)>;
      using ValueT    = std::decay_t<decltype(value)>;
      switch (data_type) {
      case DataType::bool_t: {
        n.m_node_processor = std::make_unique<UnaryExpressionProcessor<OperatorT, ValueT, bool>>(n);
        break;
      }
      case DataType::unsigned_int_t: {
        n.m_node_processor = std::make_unique<UnaryExpressionProcessor<OperatorT, ValueT, uint64_t>>(n);
        break;
      }
      case DataType::int_t: {
        n.m_node_processor = std::make_unique<UnaryExpressionProcessor<OperatorT, ValueT, int64_t>>(n);
        break;
      }
      case DataType::double_t: {
        n.m_node_processor = std::make_unique<UnaryExpressionProcessor<OperatorT, ValueT, double>>(n);
        break;
      }
      default: {
        throw parse_error("undefined operand type for unary operator", std::vector{n.children[0]->begin()});
      }
      }
    };

    auto set_unary_operator_processor_for_value = [&](const DataType& value_type) {
      const DataType data_type = n.children[0]->m_data_type;
      switch (value_type) {
      case DataType::bool_t: {
        set_unary_operator_processor_for_data(bool{}, data_type);
        break;
      }
      case DataType::unsigned_int_t: {
        set_unary_operator_processor_for_data(uint64_t{}, data_type);
        break;
      }
      case DataType::int_t: {
        set_unary_operator_processor_for_data(int64_t{}, data_type);
        break;
      }
      case DataType::double_t: {
        set_unary_operator_processor_for_data(double{}, data_type);
        break;
      }
      default: {
        throw parse_error("undefined value type for unary operator", std::vector{n.begin()});
      }
      }
    };

    set_unary_operator_processor_for_value(n.m_data_type);
  };

  auto set_inc_dec_operator_processor = [](Node& n, const auto& operator_v) {
    auto set_inc_dec_operator_processor_for_data = [&](const auto& value, const DataType& data_type) {
      using OperatorT = std::decay_t<decltype(operator_v)>;
      using ValueT    = std::decay_t<decltype(value)>;
      switch (data_type) {
      case DataType::bool_t: {
        n.m_node_processor = std::make_unique<IncDecExpressionProcessor<OperatorT, ValueT, bool>>(n);
        break;
      }
      case DataType::unsigned_int_t: {
        n.m_node_processor = std::make_unique<IncDecExpressionProcessor<OperatorT, ValueT, uint64_t>>(n);
        break;
      }
      case DataType::int_t: {
        n.m_node_processor = std::make_unique<IncDecExpressionProcessor<OperatorT, ValueT, int64_t>>(n);
        break;
      }
      case DataType::double_t: {
        n.m_node_processor = std::make_unique<IncDecExpressionProcessor<OperatorT, ValueT, double>>(n);
        break;
      }
      default: {
        throw parse_error("undefined operand type for unary operator", std::vector{n.children[0]->begin()});
      }
      }
    };

    auto set_inc_dec_processor_for_value = [&](const DataType& value_type) {
      const DataType data_type = n.children[0]->m_data_type;
      switch (value_type) {
      case DataType::bool_t: {
        set_inc_dec_operator_processor_for_data(bool{}, data_type);
        break;
      }
      case DataType::unsigned_int_t: {
        set_inc_dec_operator_processor_for_data(uint64_t{}, data_type);
        break;
      }
      case DataType::int_t: {
        set_inc_dec_operator_processor_for_data(int64_t{}, data_type);
        break;
      }
      case DataType::double_t: {
        set_inc_dec_operator_processor_for_data(double{}, data_type);
        break;
      }
      default: {
        throw parse_error("undefined value type for unary operator", std::vector{n.begin()});
      }
      }
    };

    if (not n.children[0]->is<language::name>()) {
      throw parse_error("invalid operand type for unary operator", std::vector{n.begin()});
    }

    set_inc_dec_processor_for_value(n.m_data_type);
  };

  auto set_binary_operator_processor = [](Node& n, const auto& operator_v) {
    auto set_binary_operator_processor_for_data_b = [&](const auto value, const auto data_a,
                                                        const DataType& data_type_b) {
      using OperatorT = std::decay_t<decltype(operator_v)>;
      using ValueT    = std::decay_t<decltype(value)>;
      using DataTA    = std::decay_t<decltype(data_a)>;
      switch (data_type_b) {
      case DataType::bool_t: {
        n.m_node_processor = std::make_unique<BinaryExpressionProcessor<OperatorT, ValueT, DataTA, bool>>(n);
        break;
      }
      case DataType::unsigned_int_t: {
        n.m_node_processor = std::make_unique<BinaryExpressionProcessor<OperatorT, ValueT, DataTA, uint64_t>>(n);
        break;
      }
      case DataType::int_t: {
        n.m_node_processor = std::make_unique<BinaryExpressionProcessor<OperatorT, ValueT, DataTA, int64_t>>(n);
        break;
      }
      case DataType::double_t: {
        n.m_node_processor = std::make_unique<BinaryExpressionProcessor<OperatorT, ValueT, DataTA, double>>(n);
        break;
      }
      default: {
        throw parse_error("undefined operand type for binary operator", std::vector{n.children[1]->begin()});
      }
      }
    };

    auto set_binary_operator_processor_for_data_a = [&](const auto value, const DataType& data_type_a) {
      const DataType data_type_b = n.children[1]->m_data_type;
      switch (data_type_a) {
      case DataType::bool_t: {
        set_binary_operator_processor_for_data_b(value, bool{}, data_type_b);
        break;
      }
      case DataType::unsigned_int_t: {
        set_binary_operator_processor_for_data_b(value, uint64_t{}, data_type_b);
        break;
      }
      case DataType::int_t: {
        set_binary_operator_processor_for_data_b(value, int64_t{}, data_type_b);
        break;
      }
      case DataType::double_t: {
        set_binary_operator_processor_for_data_b(value, double{}, data_type_b);
        break;
      }
      default: {
        throw parse_error("undefined operand type for binary operator", std::vector{n.children[0]->begin()});
      }
      }
    };

    auto set_binary_operator_processor_for_value = [&](const DataType& value_type) {
      const DataType data_type_a = n.children[0]->m_data_type;
      switch (value_type) {
      case DataType::bool_t: {
        set_binary_operator_processor_for_data_a(bool{}, data_type_a);
        break;
      }
      case DataType::unsigned_int_t: {
        set_binary_operator_processor_for_data_a(uint64_t{}, data_type_a);
        break;
      }
      case DataType::int_t: {
        set_binary_operator_processor_for_data_a(int64_t{}, data_type_a);
        break;
      }
      case DataType::double_t: {
        set_binary_operator_processor_for_data_a(double{}, data_type_a);
        break;
      }
      default: {
        throw parse_error("undefined value type for binary operator", std::vector{n.begin()});
      }
      }
    };

    set_binary_operator_processor_for_value(n.m_data_type);
  };

  auto set_affectation_processor = [](Node& n, const auto& operator_v) {
    auto set_affectation_processor_for_data = [&](const auto& value, const DataType& data_type) {
      using OperatorT = std::decay_t<decltype(operator_v)>;
      using ValueT    = std::decay_t<decltype(value)>;
      switch (data_type) {
      case DataType::bool_t: {
        n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, bool>>(n);
        break;
      }
      case DataType::unsigned_int_t: {
        n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, uint64_t>>(n);
        break;
      }
      case DataType::int_t: {
        n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, int64_t>>(n);
        break;
      }
      case DataType::double_t: {
        n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, double>>(n);
        break;
      }
      default: {
        throw parse_error("undefined operand type for affectation", std::vector{n.children[0]->begin()});
      }
      }
    };

    auto set_affectation_processor_for_value = [&](const DataType& value_type) {
      const DataType data_type = n.children[1]->m_data_type;
      switch (value_type) {
      case DataType::bool_t: {
        set_affectation_processor_for_data(bool{}, data_type);
        break;
      }
      case DataType::unsigned_int_t: {
        set_affectation_processor_for_data(uint64_t{}, data_type);
        break;
      }
      case DataType::int_t: {
        set_affectation_processor_for_data(int64_t{}, data_type);
        break;
      }
      case DataType::double_t: {
        set_affectation_processor_for_data(double{}, data_type);
        break;
      }
      default: {
        throw parse_error("undefined value type for affectation", std::vector{n.begin()});
      }
      }
    };

    set_affectation_processor_for_value(n.m_data_type);
  };

  if (n.is_root()) {
    n.m_node_processor = std::make_unique<NodeList>(n);
  } else if (n.is<language::bloc>()) {
    n.m_node_processor = std::make_unique<NodeList>(n);
  } else if (n.is<language::declaration>()) {
    n.m_node_processor = std::make_unique<NoProcess>();
  } else if (n.is<language::eq_op>()) {
    set_affectation_processor(n, language::eq_op{});
  } else if (n.is<language::multiplyeq_op>()) {
    set_affectation_processor(n, language::multiplyeq_op{});
  } else if (n.is<language::divideeq_op>()) {
    set_affectation_processor(n, language::divideeq_op{});
  } else if (n.is<language::pluseq_op>()) {
    set_affectation_processor(n, language::pluseq_op{});
  } else if (n.is<language::minuseq_op>()) {
    set_affectation_processor(n, language::minuseq_op{});
  } else if (n.is<language::bit_andeq_op>()) {
    set_affectation_processor(n, language::bit_andeq_op{});
  } else if (n.is<language::bit_xoreq_op>()) {
    set_affectation_processor(n, language::bit_xoreq_op{});
  } else if (n.is<language::bit_oreq_op>()) {
    set_affectation_processor(n, language::bit_oreq_op{});

  } else if (n.is<language::real>()) {
    n.m_node_processor = std::make_unique<NoProcess>();
  } else if (n.is<language::integer>()) {
    n.m_node_processor = std::make_unique<NoProcess>();

  } else if (n.is<language::name>()) {
    n.m_node_processor = std::make_unique<NameExpression>(n);

  } else if (n.is<language::unary_minus>()) {
    set_unary_operator_processor(n, language::unary_minus{});
  } else if (n.is<language::unary_not>()) {
    set_unary_operator_processor(n, language::unary_not{});

  } else if (n.is<language::unary_minusminus>()) {
    set_inc_dec_operator_processor(n, language::unary_minusminus{});
  } else if (n.is<language::unary_plusplus>()) {
    set_inc_dec_operator_processor(n, language::unary_plusplus{});
  } else if (n.is<language::post_minusminus>()) {
    set_inc_dec_operator_processor(n, language::post_minusminus{});
  } else if (n.is<language::post_plusplus>()) {
    set_inc_dec_operator_processor(n, language::post_plusplus{});

  } else if (n.is<language::multiply_op>()) {
    set_binary_operator_processor(n, language::multiply_op{});
  } else if (n.is<language::divide_op>()) {
    set_binary_operator_processor(n, language::divide_op{});
  } else if (n.is<language::plus_op>()) {
    set_binary_operator_processor(n, language::plus_op{});
  } else if (n.is<language::minus_op>()) {
    set_binary_operator_processor(n, language::minus_op{});
  } else if (n.is<language::or_op>()) {
    set_binary_operator_processor(n, language::or_op{});
  } else if (n.is<language::and_op>()) {
    set_binary_operator_processor(n, language::and_op{});

  } else if (n.is<language::xor_op>()) {
    set_binary_operator_processor(n, language::xor_op{});
  } else if (n.is<language::bitand_op>()) {
    set_binary_operator_processor(n, language::bitand_op{});
  } else if (n.is<language::bitor_op>()) {
    set_binary_operator_processor(n, language::bitor_op{});

  } else if (n.is<language::greater_op>()) {
    set_binary_operator_processor(n, language::greater_op{});
  } else if (n.is<language::greater_or_eq_op>()) {
    set_binary_operator_processor(n, language::greater_or_eq_op{});
  } else if (n.is<language::lesser_op>()) {
    set_binary_operator_processor(n, language::lesser_op{});
  } else if (n.is<language::lesser_or_eq_op>()) {
    set_binary_operator_processor(n, language::lesser_or_eq_op{});
  } else if (n.is<language::eqeq_op>()) {
    set_binary_operator_processor(n, language::eqeq_op{});
  } else if (n.is<language::not_eq_op>()) {
    set_binary_operator_processor(n, language::not_eq_op{});
  } else if (n.is<language::B_set>()) {
  } else if (n.is<language::N_set>()) {
  } else if (n.is<language::Z_set>()) {
  } else if (n.is<language::cout_kw>()) {
    n.m_node_processor = std::make_unique<OStreamObject>(n, std::cout);
  } else if (n.is<language::cerr_kw>()) {
    n.m_node_processor = std::make_unique<OStreamObject>(n, std::cerr);
  } else if (n.is<language::clog_kw>()) {
    n.m_node_processor = std::make_unique<OStreamObject>(n, std::clog);
  } else if (n.is<language::R_set>()) {
  } else if (n.is<language::if_statement>()) {
    n.m_node_processor = std::make_unique<IfStatement>(n);
  } else if (n.is<language::statement_bloc>()) {
    n.m_node_processor = std::make_unique<NodeList>(n);
  } else if (n.is<language::do_while_statement>()) {
    n.m_node_processor = std::make_unique<DoWhileStatement>(n);
  } else if (n.is<language::while_statement>()) {
    n.m_node_processor = std::make_unique<WhileStatement>(n);
  } else if (n.is<language::for_statement>()) {
    n.m_node_processor = std::make_unique<ForStatement>(n);
  } else if (n.is<language::for_statement_bloc>()) {
    n.m_node_processor = std::make_unique<NodeList>(n);
  } else if (n.is<language::for_init>()) {
    n.m_node_processor = std::make_unique<NoProcess>();
  } else if (n.is<language::for_post>()) {
    n.m_node_processor = std::make_unique<NoProcess>();
  } else if (n.is<language::for_test>()) {
    n.m_node_processor = std::make_unique<NoProcess>();
  } else if (n.is<language::break_kw>()) {
    n.m_node_processor = std::make_unique<BreakExpression>();
  } else if (n.is<language::continue_kw>()) {
    n.m_node_processor = std::make_unique<ContinueExpression>();
  } else if (n.is<language::true_kw>()) {
    n.m_node_processor = std::make_unique<NoProcess>();
  } else if (n.is<language::false_kw>()) {
    n.m_node_processor = std::make_unique<NoProcess>();
  } else {
    std::ostringstream error_message;
    error_message << "undefined node type '" << rang::fgB::red << n.name() << rang::fg::reset << "'";
    throw parse_error{error_message.str(), std::vector{n.begin()}};
  }

  for (auto& child : n.children) {
    internal::build_node_type(*child);
  }
}
}   // namespace internal

void
build_node_type(Node& n)
{
  Assert(n.is_root());
  Assert(n.is<void>());
  n.m_node_processor = std::make_unique<NodeList>(n);
  for (auto& child : n.children) {
    internal::build_node_type(*child);
  }
  std::cout << " - build node types\n";
}

namespace internal
{
void
print_dot(std::ostream& os, const Node& n)
{
  if (n.is_root()) {
    os << "  x" << &n << " [ label=\"root \\n" << dataTypeName(n.m_data_type) << "\" ]\n";
  } else {
    if (n.has_content()) {
      os << "  x" << &n << " [ label=\"" << n.name() << "\\n"
         << n.string_view() << "\\n"
         << dataTypeName(n.m_data_type) << "\" ]\n";
    } else {
      os << "  x" << &n << " [ label=\"" << n.name() << "\\n" << dataTypeName(n.m_data_type) << "\" ]\n";
    }
  }
  if (!n.children.empty()) {
    os << "  x" << &n << " -> { ";
    for (auto& child : n.children) {
      os << "x" << child.get() << ((child == n.children.back()) ? " }\n" : ", ");
    }
    for (auto& child : n.children) {
      print_dot(os, *child);
    }
  }
}

}   // namespace internal

void
print_dot(std::ostream& os, const Node& n)
{
  Assert(n.is_root());
  os << "digraph parse_tree\n{\n";
  internal::print_dot(os, n);
  os << "}\n";
}

namespace internal
{
void
build_symbol_table_and_check_declarations(Node& n, std::shared_ptr<SymbolTable>& symbol_table)
{
  if (n.is<language::bloc>() or (n.is<language::for_statement>())) {
    if (!n.children.empty()) {
      std::shared_ptr bloc_symbol_table = std::make_shared<SymbolTable>(symbol_table);
      n.m_symbol_table                  = bloc_symbol_table;
      for (auto& child : n.children) {
        build_symbol_table_and_check_declarations(*child, bloc_symbol_table);
      }
    }
  } else {
    n.m_symbol_table = symbol_table;
    if (n.has_content()) {
      if (n.is<language::declaration>()) {
        const std::string& symbol = n.children[1]->string();
        auto [i_symbol, success]  = symbol_table->add(symbol);
        if (not success) {
          std::ostringstream error_message;
          error_message << "symbol '" << rang::fg::red << symbol << rang::fg::reset << '\'' << " was already defined!";
          throw parse_error(error_message.str(), std::vector{n.begin()});
        }
      } else if (n.is<language::name>()) {
        auto [i_symbol, found] = symbol_table->find(n.string());
        if (not found) {
          std::ostringstream error_message;
          error_message << "undefined symbol '" << rang::fg::red << n.string() << rang::fg::reset << '\'';
          throw parse_error(error_message.str(), std::vector{n.begin()});
        }
      }
    }

    for (auto& child : n.children) {
      build_symbol_table_and_check_declarations(*child, symbol_table);
    }
  }
}
}   // namespace internal

void
build_symbol_table_and_check_declarations(Node& n)
{
  Assert(n.is_root());
  std::shared_ptr symbol_table = std::make_shared<SymbolTable>();
  n.m_symbol_table             = symbol_table;
  internal::build_symbol_table_and_check_declarations(n, symbol_table);
  std::cout << " - checked symbols declaration\n";
}

namespace internal
{
void
check_symbol_initialization(const Node& n, std::shared_ptr<SymbolTable>& symbol_table)
{
  if (n.is<language::bloc>() or n.is<language::for_statement>()) {
    if (!n.children.empty()) {
      std::shared_ptr bloc_symbol_table = std::make_shared<SymbolTable>(symbol_table);
      for (auto& child : n.children) {
        check_symbol_initialization(*child, bloc_symbol_table);
      }
    }
  } else {
    if (n.is<language::declaration>()) {
      const std::string& symbol = n.children[1]->string();
      auto [i_symbol, success]  = symbol_table->add(symbol);
      Assert(success, "unexpected error, should have been detected through declaration checking");
      if (n.children.size() == 3) {
        check_symbol_initialization(*n.children[2], symbol_table);
        i_symbol->second.setIsInitialized();
      }
    } else if (n.is<language::eq_op>()) {
      // first checks for right hand side
      check_symbol_initialization(*n.children[1], symbol_table);
      // then marks left hand side as initialized
      const std::string& symbol = n.children[0]->string();
      auto [i_symbol, found]    = symbol_table->find(symbol);
      Assert(found, "unexpected error, should have been detected through declaration checking");
      i_symbol->second.setIsInitialized();
    } else if (n.is<language::name>()) {
      auto [i_symbol, found] = symbol_table->find(n.string());
      Assert(found, "unexpected error, should have been detected through declaration checking");
      if (not i_symbol->second.isInitialized()) {
        std::ostringstream error_message;
        error_message << "uninitialized symbol '" << rang::fg::red << n.string() << rang::fg::reset << '\'';
        throw parse_error(error_message.str(), std::vector{n.begin()});
      }
    }

    if ((not n.is<language::declaration>()) and (not n.is<language::eq_op>())) {
      for (auto& child : n.children) {
        check_symbol_initialization(*child, symbol_table);
      }
    }
  }
}
}   // namespace internal

void
check_symbol_initialization(const Node& n)
{
  std::cerr << rang::fgB::yellow << "warning:" << rang::fg::reset
            << " symbol initialization checking not finished"
               "if and loops statements are not correctly evaluated\n";
  Assert(n.is_root());
  std::shared_ptr symbol_table = std::make_shared<SymbolTable>();
  internal::check_symbol_initialization(n, symbol_table);
  std::cout << " - checked symbols initialization\n";
}

namespace internal
{
void
build_node_data_types(Node& n)
{
  if (n.is<language::bloc>() or n.is<for_statement>()) {
    if (!n.children.empty()) {
      for (auto& child : n.children) {
        build_node_data_types(*child);
      }
    }
    n.m_data_type = DataType::void_t;
  } else {
    if (n.has_content()) {
      if (n.is<language::true_kw>() or n.is<language::false_kw>() or n.is<language::do_kw>()) {
        n.m_data_type = DataType::bool_t;
      } else if (n.is<language::real>()) {
        n.m_data_type = DataType::double_t;
      } else if (n.is<language::integer>()) {
        n.m_data_type = DataType::int_t;
      } else if (n.is<language::cout_kw>() or n.is<language::cerr_kw>() or n.is<language::clog_kw>()) {
        n.m_data_type = DataType::void_t;
      } else if (n.is<language::declaration>()) {
        auto& type_node = *(n.children[0]);
        DataType data_type{DataType::undefined_t};
        if (type_node.is<language::B_set>()) {
          data_type = DataType::bool_t;
        } else if (type_node.is<language::Z_set>()) {
          data_type = DataType::int_t;
        } else if (type_node.is<language::N_set>()) {
          data_type = DataType::unsigned_int_t;
        } else if (type_node.is<language::R_set>()) {
          data_type = DataType::double_t;
        }
        if (data_type == DataType::undefined_t) {
          throw parse_error("unexpected error: invalid datatype", type_node.begin());
        }
        type_node.m_data_type      = DataType::void_t;
        n.children[1]->m_data_type = data_type;
        const std::string& symbol  = n.children[1]->string();

        std::shared_ptr<SymbolTable>& symbol_table = n.m_symbol_table;

        auto [i_symbol, found] = symbol_table->find(symbol);
        Assert(found);
        i_symbol->second.setDataType(data_type);
        n.m_data_type = data_type;
      } else if (n.is<language::name>()) {
        std::shared_ptr<SymbolTable>& symbol_table = n.m_symbol_table;

        auto [i_symbol, found] = symbol_table->find(n.string());
        Assert(found);
        n.m_data_type = i_symbol->second.dataType();
      }
    }
    for (auto& child : n.children) {
      build_node_data_types(*child);
    }

    if (n.is<language::break_kw>() or n.is<language::continue_kw>()) {
      n.m_data_type = DataType::void_t;
    } else if (n.is<language::eq_op>() or n.is<language::multiplyeq_op>() or n.is<language::divideeq_op>() or
               n.is<language::pluseq_op>() or n.is<language::minuseq_op>() or n.is<language::bit_andeq_op>() or
               n.is<language::bit_xoreq_op>() or n.is<language::bit_oreq_op>()) {
      n.m_data_type = n.children[0]->m_data_type;
    } else if (n.is<language::for_statement>()) {
      n.m_data_type = DataType::void_t;
    } else if (n.is<language::for_post>() or n.is<language::for_init>() or n.is<language::for_statement_bloc>()) {
      n.m_data_type = DataType::void_t;
    } else if (n.is<language::for_test>()) {
      n.m_data_type = DataType::bool_t;
    } else if (n.is<language::statement_bloc>()) {
      n.m_data_type = DataType::void_t;
    } else if (n.is<language::if_statement>() or n.is<language::while_statement>()) {
      n.m_data_type = DataType::void_t;
      if ((n.children[0]->m_data_type > DataType::double_t) or (n.children[0]->m_data_type < DataType::bool_t)) {
        const DataType type_0 = n.children[0]->m_data_type;
        std::ostringstream message;
        message << "Cannot convert data type to boolean value\n"
                << "note: incompatible operand '" << n.children[0]->string() << " of type ' (" << dataTypeName(type_0)
                << ')' << std::ends;
        throw parse_error(message.str(), n.children[0]->begin());
      }
    } else if (n.is<language::do_while_statement>()) {
      n.m_data_type = DataType::void_t;
      if ((n.children[1]->m_data_type > DataType::double_t) or (n.children[1]->m_data_type < DataType::bool_t)) {
        const DataType type_0 = n.children[1]->m_data_type;
        std::ostringstream message;
        message << "Cannot convert data type to boolean value\n"
                << "note: incompatible operand '" << n.children[1]->string() << " of type ' (" << dataTypeName(type_0)
                << ')' << std::ends;
        throw parse_error(message.str(), n.children[1]->begin());
      }
    } else if (n.is<language::unary_not>() or n.is<language::lesser_op>() or n.is<language::lesser_or_eq_op>() or
               n.is<language::greater_op>() or n.is<language::greater_or_eq_op>() or n.is<language::eqeq_op>() or
               n.is<language::not_eq_op>() or n.is<language::and_op>() or n.is<language::or_op>() or
               n.is<language::xor_op>() or n.is<language::bitand_op>() or n.is<language::bitor_op>()) {
      n.m_data_type = DataType::bool_t;
    } else if (n.is<language::unary_minus>() or n.is<language::unary_plus>() or n.is<language::unary_plusplus>() or
               n.is<language::unary_minusminus>()) {
      n.m_data_type = n.children[0]->m_data_type;
    } else if (n.is<language::plus_op>() or n.is<language::minus_op>() or n.is<language::multiply_op>() or
               n.is<language::divide_op>()) {
      const DataType type_0 = n.children[0]->m_data_type;
      const DataType type_1 = n.children[1]->m_data_type;

      n.m_data_type = dataTypePromotion(type_0, type_1);
      if (n.m_data_type == DataType::undefined_t) {
        std::ostringstream message;
        message << "undefined binary operator\n"
                << "note: incompatible operand types " << n.children[0]->string() << " (" << dataTypeName(type_0)
                << ") and " << n.children[1]->string() << " (" << dataTypeName(type_1) << ')' << std::ends;
        throw parse_error(message.str(), n.begin());
      }
    }
  }
}
}   // namespace internal

void
build_node_data_types(Node& n)
{
  Assert(n.is_root());
  n.m_data_type = DataType::void_t;

  internal::build_node_data_types(n);
  std::cout << " - build node data types\n";
}

namespace internal
{
void
check_node_data_types(const Node& n)
{
  if (n.m_data_type == DataType::undefined_t) {
    throw parse_error("unexpected error: undefined datatype for AST node for " + n.name(), n.begin());
  }

  for (auto& child : n.children) {
    check_node_data_types(*child);
  }
}
}   // namespace internal

void
check_node_data_types(const Node& n)
{
  Assert(n.is_root());
  internal::check_node_data_types(n);
  std::cout << " - checked node data types\n";
}

namespace internal
{
void
build_node_values(Node& n, std::shared_ptr<SymbolTable>& symbol_table)
{
  if (n.is<language::bloc>()) {
    if (!n.children.empty()) {
      std::shared_ptr bloc_symbol_table = std::make_shared<SymbolTable>(symbol_table);
      for (auto& child : n.children) {
        build_node_values(*child, bloc_symbol_table);
      }
    }
    n.m_data_type = DataType::void_t;
  } else {
    for (auto& child : n.children) {
      build_node_values(*child, symbol_table);
    }

    if (n.has_content()) {
      if (n.is<language::real>()) {
        std::stringstream ss(n.string());
        double v;
        ss >> v;
        n.m_value = v;
      } else if (n.is<language::integer>()) {
        std::stringstream ss(n.string());
        int64_t v;
        ss >> v;
        n.m_value = v;
      } else if (n.is<language::for_test>()) {
        // if AST contains a for_test statement, it means that no test were
        // given to the for-loop, so its value is always true
        n.m_value = true;
      } else if (n.is<language::true_kw>()) {
        n.m_value = true;
      } else if (n.is<language::false_kw>()) {
        n.m_value = false;
      }
    }
  }
}
}   // namespace internal

void
build_node_values(Node& n)
{
  Assert(n.is_root());
  n.m_data_type                = DataType::void_t;
  std::shared_ptr symbol_table = std::make_shared<SymbolTable>();
  internal::build_node_values(n, symbol_table);
  std::cout << " - build node data types\n";
}

namespace internal
{
void
check_break_or_continue_placement(const Node& n, bool is_inside_loop)
{
  if (n.is<language::for_statement>() or n.is<language::do_while_statement>() or n.is<language::while_statement>()) {
    for (auto& child : n.children) {
      check_break_or_continue_placement(*child, true);
    }
  } else if (n.is<language::break_kw>() or n.is<language::continue_kw>()) {
    if (not is_inside_loop) {
      std::ostringstream error_message;
      error_message << "unexpected '" << rang::fgB::red << n.string() << rang::fg::reset
                    << "' outside of loop or switch statement";
      throw parse_error(error_message.str(), std::vector{n.begin()});
    }
  } else {
    for (auto& child : n.children) {
      check_break_or_continue_placement(*child, is_inside_loop);
    }
  }
}
}   // namespace internal

void
check_break_or_continue_placement(const Node& n)
{
  Assert(n.is_root());
  internal::check_break_or_continue_placement(n, false);
}

namespace internal
{
void
simplify_declarations(Node& n)
{
  if (n.is<language::declaration>()) {
    if (n.children.size() == 3) {
      n.children[0] = std::move(n.children[1]);
      n.children[1] = std::move(n.children[2]);
      n.children.resize(2);
      n.id = typeid(language::eq_op);
    }
  } else {
    for (auto& child : n.children) {
      simplify_declarations(*child);
    }
  }
}
}   // namespace internal

void
simplify_declarations(Node& n)
{
  Assert(n.is_root());
  internal::simplify_declarations(n);
}

void print(const Node& n);

std::string prefix;
std::vector<int> last_prefix_size;
const std::string T_junction(" \u251c\u2500\u2500");
const std::string L_junction(" \u2514\u2500\u2500");

const std::string pipe_space(" \u2502  ");
const std::string space_space("    ");

template <typename NodeVector>
void
print(const NodeVector& node_list)
{
  for (size_t i_child = 0; i_child < node_list.size(); ++i_child) {
    if (i_child != node_list.size() - 1) {
      std::cout << rang::fgB::green << prefix << T_junction << rang::fg::reset;
    } else {
      std::cout << rang::fgB::green << prefix << L_junction << rang::fg::reset;
    }
    auto& child = *(node_list[i_child]);
    if (not child.children.empty()) {
      last_prefix_size.push_back(prefix.size());
      if (i_child != node_list.size() - 1) {
        prefix += pipe_space;
      } else {
        prefix += space_space;
      }

      print(*(node_list[i_child]));

      prefix.resize(last_prefix_size[last_prefix_size.size() - 1]);
      last_prefix_size.pop_back();
    } else {
      print(*(node_list[i_child]));
    }
  }
}

void
print(const Node& n)
{
  std::cout << '(' << rang::fgB::yellow;
  if (n.is_root()) {
    std::cout << "root";
  } else {
    std::cout << n.name();
  }
  std::cout << rang::fg::reset << ':';
  std::cout << dataTypeName(n.m_data_type) << ':';

  std::cout << rang::fgB::cyan;
  std::visit(
    [](const auto& value) {
      using T = std::decay_t<decltype(value)>;
      if constexpr (std::is_same_v<T, std::monostate>) {
        std::cout << "--";
      } else {
        std::cout << value;
      }
    },
    n.m_value);
  std::cout << rang::fg::reset;

  std::cout << ")\n";

  if (not n.children.empty()) {
    print(n.children);
  }
}

}   // namespace language

void
parser(const std::string& filename)
{
  const size_t grammar_issues = analyze<language::grammar>();

  std::cout << rang::fgB::yellow << "grammar_issues=" << rang::fg::reset << grammar_issues << '\n';

  std::cout << rang::style::bold << "Parsing file " << rang::style::reset << rang::style::underline << filename
            << rang::style::reset << " ...\n";

  std::unique_ptr<language::Node> root_node;
  read_input in(filename);
  try {
    root_node = parse_tree::parse<language::grammar, language::Node, language::selector, nothing, language::errors>(in);
    std::cout << " - AST is built ...... [done]\n";

    language::build_symbol_table_and_check_declarations(*root_node);
    language::check_symbol_initialization(*root_node);
    {
      std::string dot_filename{"parse_tree.dot"};
      std::ofstream fout(dot_filename);
      language::print_dot(fout, *root_node);
      std::cout << "   AST dot file: " << dot_filename << '\n';
    }

    language::build_node_data_types(*root_node);

    language::check_node_data_types(*root_node);
    language::build_node_values(*root_node);

    language::check_break_or_continue_placement(*root_node);

    // optimizations
    language::simplify_declarations(*root_node);

    language::build_node_type(*root_node);

    language::print(*root_node);

    language::ExecUntilBreakOrContinue exec_all;
    root_node->execute(exec_all);
    std::cout << *(root_node->m_symbol_table) << '\n';
  }
  catch (const parse_error& e) {
    const auto p = e.positions.front();
    std::cerr << rang::style::bold << p.source << ':' << p.line << ':' << p.byte_in_line << ": " << rang::style::reset
              << rang::fgB::red << "error: " << rang::fg::reset << rang::style::bold << e.what() << rang::style::reset
              << '\n'
              << in.line_at(p) << '\n'
              << std::string(p.byte_in_line, ' ') << rang::fgB::yellow << '^' << rang::fg::reset << std::endl;
    std::exit(1);
  }

  std::cout << "Parsed: " << filename << '\n';
}