Skip to content
Snippets Groups Projects
Select Git revision
  • ca87f29c6deb2fc03ca40243173fb6d0c0dfbe8d
  • develop default protected
  • feature/gmsh-reader
  • origin/stage/bouguettaia
  • feature/kinetic-schemes
  • feature/reconstruction
  • feature/local-dt-fsi
  • feature/composite-scheme-sources
  • feature/composite-scheme-other-fluxes
  • feature/serraille
  • feature/variational-hydro
  • feature/composite-scheme
  • hyperplastic
  • feature/polynomials
  • feature/gks
  • feature/implicit-solver-o2
  • feature/coupling_module
  • feature/implicit-solver
  • feature/merge-local-dt-fsi
  • master protected
  • feature/escobar-smoother
  • v0.5.0 protected
  • v0.4.1 protected
  • v0.4.0 protected
  • v0.3.0 protected
  • v0.2.0 protected
  • v0.1.0 protected
  • Kidder
  • v0.0.4 protected
  • v0.0.3 protected
  • v0.0.2 protected
  • v0 protected
  • v0.0.1 protected
33 results

AcousticSolver.cpp

Blame
  • PugsParser.cpp 77.41 KiB
    #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';
    }