/* Visitor generating a GENERIC tree.
   Copyright (C) 2025 Free Software Foundation, Inc.

GCC is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

GCC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */

#include <array>

#include "elna/gcc/elna-generic.h"
#include "elna/gcc/elna-diagnostic.h"
#include "elna/gcc/elna1.h"

#include "ggc.h"
#include "function.h"
#include "cgraph.h"
#include "gimplify.h"
#include "stringpool.h"
#include "diagnostic.h"
#include "realmpfr.h"
#include "stor-layout.h"
#include "varasm.h"
#include "fold-const.h"
#include "langhooks.h"
#include <set>

namespace elna
{
namespace gcc
{
    declaration_visitor::declaration_visitor(std::shared_ptr<symbol_table> symbol_table)
        : symbols(symbol_table)
    {
    }

    void declaration_visitor::visit(boot::program *program)
    {
        for (boot::type_definition *const type : program->types)
        {
            tree type_node = NULL_TREE;

            if (type->body().is_record())
            {
                type_node = make_node(RECORD_TYPE);
            }
            else if (type->body().is_union())
            {
                type_node = make_node(UNION_TYPE);
            }
            if (type_node != NULL_TREE)
            {
                this->symbols->enter(type->identifier, type_node);
            }
        }
    }

    generic_visitor::generic_visitor(std::shared_ptr<symbol_table> symbol_table)
    {
        this->symbols = symbol_table;
    }

    void generic_visitor::build_procedure_call(location_t call_location,
            tree procedure_address, const std::vector<boot::expression *>& arguments)
    {
        vec<tree, va_gc> *argument_trees = nullptr;
        tree symbol_type = TREE_TYPE(TREE_TYPE(procedure_address));

        tree current_parameter = TYPE_ARG_TYPES(symbol_type);

        vec_alloc(argument_trees, arguments.size());
        for (boot::expression *const argument : arguments)
        {
            location_t argument_location = get_location(&argument->position());
            if (is_void_type(TREE_VALUE(current_parameter)))
            {
                error_at(argument_location, "too many arguments, expected %i, got %lu",
                        list_length(TYPE_ARG_TYPES(symbol_type)) - 1, arguments.size());
                this->current_expression = error_mark_node;
                break;
            }
            argument->accept(this);
            this->current_expression = prepare_rvalue(this->current_expression);
            if (!is_assignable_from(TREE_VALUE(current_parameter), this->current_expression))
            {
                error_at(argument_location,
                        "cannot assign value of type '%s' to variable of type '%s'",
                        print_type(TREE_TYPE(this->current_expression)).c_str(),
                        print_type(TREE_VALUE(current_parameter)).c_str());
                this->current_expression = error_mark_node;
            }
            current_parameter = TREE_CHAIN(current_parameter);
            argument_trees->quick_push(this->current_expression);
        }
        tree stmt = fold_build_call_array_loc(call_location, TREE_TYPE(symbol_type),
                procedure_address, vec_safe_length(argument_trees), vec_safe_address(argument_trees));

        if (!is_void_type(TREE_VALUE(current_parameter)))
        {
                error_at(call_location, "too few arguments, expected %i, got %lu",
                        list_length(TYPE_ARG_TYPES(symbol_type)) - 1, arguments.size());
                this->current_expression = error_mark_node;
        }
        else
        {
            this->current_expression = stmt;
        }
    }

    void generic_visitor::build_record_call(location_t call_location,
            tree symbol, const std::vector<boot::expression *>& arguments)
    {
        vec<constructor_elt, va_gc> *tree_arguments = nullptr;
        tree record_fields = TYPE_FIELDS(symbol);
        for (boot::expression *const argument : arguments)
        {
            location_t argument_location = get_location(&argument->position());

            if (is_void_type(record_fields))
            {
                error_at(argument_location, "too many arguments, expected %i, got %lu",
                        list_length(TYPE_FIELDS(symbol)), arguments.size());
                this->current_expression = error_mark_node;
                break;
            }
            argument->accept(this);
            tree unqualified_field = get_qualified_type(TREE_TYPE(record_fields), TYPE_UNQUALIFIED);
            if (!is_assignable_from(unqualified_field, this->current_expression))
            {
                error_at(argument_location,
                        "cannot assign value of type '%s' to variable of type '%s'",
                        print_type(TREE_TYPE(this->current_expression)).c_str(),
                        print_type(TREE_TYPE(record_fields)).c_str());
                this->current_expression = error_mark_node;
            }
            CONSTRUCTOR_APPEND_ELT(tree_arguments, record_fields, this->current_expression);
            record_fields = TREE_CHAIN(record_fields);
        }
        if (!is_void_type(record_fields))
        {
                error_at(call_location, "too few arguments, expected %i, got %lu",
                        list_length(TYPE_FIELDS(symbol)), arguments.size());
                this->current_expression = error_mark_node;
        }
        else
        {
            this->current_expression = build_constructor(symbol, tree_arguments);
        }
    }

    void generic_visitor::visit(boot::procedure_call *call)
    {
        location_t call_location = get_location(&call->position());
        call->callable().accept(this);

        tree expression_type = TYPE_P(this->current_expression)
            ? this->current_expression
            : TREE_TYPE(this->current_expression);

        if (TYPE_P(this->current_expression) && TREE_CODE(expression_type) == RECORD_TYPE)
        {
            build_record_call(call_location, this->current_expression, call->arguments);
        }
        else if (TREE_CODE(expression_type) == FUNCTION_TYPE)
        {
            this->current_expression = build1(ADDR_EXPR,
                    build_pointer_type_for_mode(expression_type, VOIDmode, true), this->current_expression);
            build_procedure_call(call_location, this->current_expression, call->arguments);
        }
        else if (is_pointer_type(expression_type) && TREE_CODE(TREE_TYPE(expression_type)) == FUNCTION_TYPE)
        {
            build_procedure_call(call_location, this->current_expression, call->arguments);
        }
        else
        {
            error_at(call_location, "'%s' cannot be called, it is neither a procedure nor record",
                    print_type(expression_type).c_str());
            this->current_expression = error_mark_node;
        }
    }

    void generic_visitor::visit(boot::cast_expression *expression)
    {
        expression->target().accept(this);
        tree cast_target = this->current_expression;

        expression->value().accept(this);

        this->current_expression = build1_loc(get_location(&expression->position()), CONVERT_EXPR,
                cast_target, this->current_expression);
    }

    void generic_visitor::visit(boot::program *program)
    {
        for (boot::constant_definition *const constant : program->constants)
        {
            constant->accept(this);
        }
        for (boot::type_definition *const type : program->types)
        {
            type->accept(this);
        }
        for (boot::variable_declaration *const variable : program->variables)
        {
            variable->accept(this);
        }
        for (boot::procedure_definition *const procedure : program->procedures)
        {
            procedure->accept(this);
        }
        tree declaration_type = build_function_type_list(integer_type_node, integer_type_node,
                build_pointer_type(build_pointer_type(char_type_node)), NULL_TREE);
        tree fndecl = build_fn_decl("main", declaration_type);

        tree resdecl = build_decl(UNKNOWN_LOCATION, RESULT_DECL, NULL_TREE, integer_type_node);
        DECL_CONTEXT(resdecl) = fndecl;
        DECL_RESULT(fndecl) = resdecl;

        push_struct_function(fndecl, false);
        DECL_STRUCT_FUNCTION(fndecl)->language = ggc_cleared_alloc<language_function>();

        enter_scope();

        tree parameter_type = TYPE_ARG_TYPES(declaration_type);
        for (const char *argument_name : std::array<const char *, 2>{ "count", "parameters" })
        {
            tree declaration_tree = build_decl(UNKNOWN_LOCATION, PARM_DECL,
                    get_identifier(argument_name), TREE_VALUE(parameter_type));
            DECL_CONTEXT(declaration_tree) = fndecl;
            DECL_ARG_TYPE(declaration_tree) = TREE_VALUE(parameter_type);

            this->symbols->enter(argument_name, declaration_tree);
            DECL_ARGUMENTS(fndecl) = chainon(DECL_ARGUMENTS(fndecl), declaration_tree);
            parameter_type = TREE_CHAIN(parameter_type);
        }
        visit_statements(program->body);
        tree set_result = build2(INIT_EXPR, void_type_node, DECL_RESULT(fndecl),
                build_int_cst_type(integer_type_node, 0));
        tree return_stmt = build1(RETURN_EXPR, void_type_node, set_result);
        append_statement(return_stmt);
        tree mapping = leave_scope();

        BLOCK_SUPERCONTEXT(BIND_EXPR_BLOCK(mapping)) = fndecl;
        DECL_INITIAL(fndecl) = BIND_EXPR_BLOCK(mapping);
        DECL_SAVED_TREE(fndecl) = mapping;

        DECL_EXTERNAL(fndecl) = 0;
        DECL_PRESERVE_P(fndecl) = 1;

        pop_cfun();
        gimplify_function_tree(fndecl);
        cgraph_node::finalize_function(fndecl, true);
    }

    void generic_visitor::visit(boot::block *block)
    {
        for (boot::constant_definition *const constant : block->constants)
        {
            constant->accept(this);
        }
        for (boot::variable_declaration *const variable : block->variables)
        {
            variable->accept(this);
        }
        visit_statements(block->body);
    }

    void generic_visitor::visit(boot::procedure_definition *definition)
    {
        tree declaration_type = build_procedure_type(definition->heading());
        tree fndecl = build_fn_decl(definition->identifier.c_str(), declaration_type);
        this->symbols->enter(definition->identifier, fndecl);

        if (definition->heading().no_return)
        {
            TREE_THIS_VOLATILE(fndecl) = 1;
        }
        if (definition->body != nullptr)
        {
            tree resdecl = build_decl(UNKNOWN_LOCATION, RESULT_DECL, NULL_TREE, TREE_TYPE(declaration_type));
            DECL_CONTEXT(resdecl) = fndecl;
            DECL_RESULT(fndecl) = resdecl;

            push_struct_function(fndecl, false);
            DECL_STRUCT_FUNCTION(fndecl)->language = ggc_cleared_alloc<language_function>();

            enter_scope();
        }
        tree argument_chain = NULL_TREE;
        function_args_iterator parameter_type;
        function_args_iter_init(&parameter_type, declaration_type);

        for (const boot::variable_declaration *parameter : definition->heading().parameters)
        {
            tree declaration_tree = build_decl(get_location(&parameter->position()), PARM_DECL,
                get_identifier(parameter->identifier.c_str()), function_args_iter_cond(&parameter_type));
            DECL_CONTEXT(declaration_tree) = fndecl;
            DECL_ARG_TYPE(declaration_tree) = function_args_iter_cond(&parameter_type);

            if (definition->body != nullptr)
            {
                this->symbols->enter(parameter->identifier, declaration_tree);
            }
            argument_chain = chainon(argument_chain, declaration_tree);
            function_args_iter_next(&parameter_type);
        }
        DECL_ARGUMENTS(fndecl) = argument_chain;
        TREE_PUBLIC(fndecl) = definition->exported;
        TREE_ADDRESSABLE(fndecl) = 1;

        if (definition->body != nullptr)
        {
            definition->body->accept(this);
            tree mapping = leave_scope();

            BLOCK_SUPERCONTEXT(BIND_EXPR_BLOCK(mapping)) = fndecl;
            DECL_INITIAL(fndecl) = BIND_EXPR_BLOCK(mapping);
            DECL_SAVED_TREE(fndecl) = mapping;

            DECL_EXTERNAL(fndecl) = 0;
            DECL_PRESERVE_P(fndecl) = 1;

            pop_cfun();
            gimplify_function_tree(fndecl);
            cgraph_node::finalize_function(fndecl, true);
        }
        else
        {
            DECL_EXTERNAL(fndecl) = 1;
        }
    }

    void generic_visitor::enter_scope()
    {
        this->symbols = std::make_shared<symbol_table>(this->symbols);

        // Chain the binding levels.
        struct binding_level *new_level = ggc_cleared_alloc<binding_level>();
        new_level->level_chain = f_binding_level;
        new_level->statement_list = alloc_stmt_list();
        f_binding_level = new_level;
    }

    tree generic_visitor::leave_scope()
    {
        // Variables are only defined in the top function scope.
        tree variables = f_binding_level->level_chain == nullptr ? f_names : NULL_TREE;
        tree new_block = build_block(variables, f_binding_level->blocks, NULL_TREE, NULL_TREE);

        for (tree it = f_binding_level->blocks; it != NULL_TREE; it = BLOCK_CHAIN(it))
        {
            BLOCK_SUPERCONTEXT(it) = new_block;
        }
        tree bind_expr = build3(BIND_EXPR, void_type_node, variables, chain_defer(), new_block);
        this->symbols = this->symbols->scope();

        f_binding_level = f_binding_level->level_chain;

        if (f_binding_level != nullptr)
        {
            f_binding_level->blocks = chainon(f_binding_level->blocks, new_block);
        }
        return bind_expr;
    }

    tree generic_visitor::lookup(const std::string& name)
    {
        if (name == "Int")
        {
            return elna_int_type_node;
        }
        if (name == "Word")
        {
            return elna_word_type_node;
        }
        if (name == "Char")
        {
            return elna_char_type_node;
        }
        if (name == "Bool")
        {
            return elna_bool_type_node;
        }
        if (name == "Byte")
        {
            return elna_byte_type_node;
        }
        if (name == "Float")
        {
            return elna_float_type_node;
        }
        if (name == "String")
        {
            return elna_string_type_node;
        }
        return this->symbols->lookup(name);
    }

    void generic_visitor::visit(boot::number_literal<std::int32_t> *literal)
    {
        this->current_expression = build_int_cst(elna_int_type_node, literal->value);
    }

    void generic_visitor::visit(boot::number_literal<std::uint32_t> *literal)
    {
        this->current_expression = build_int_cstu(elna_word_type_node, literal->value);
    }

    void generic_visitor::visit(boot::number_literal<double> *literal)
    {
        REAL_VALUE_TYPE real_value1;

        mpfr_t number;
        mpfr_init2(number, SIGNIFICAND_BITS);
        mpfr_set_d(number, literal->value, MPFR_RNDN);

        real_from_mpfr(&real_value1, number, double_type_node, MPFR_RNDN);

        this->current_expression = build_real(double_type_node, real_value1);

        mpfr_clear(number);
    }

    void generic_visitor::visit(boot::number_literal<bool> *boolean)
    {
        this->current_expression = boolean->value ? boolean_true_node : boolean_false_node;
    }

    void generic_visitor::visit(boot::number_literal<unsigned char> *character)
    {
        this->current_expression = build_int_cstu(elna_char_type_node, character->value);
    }

    void generic_visitor::visit(boot::number_literal<nullptr_t> *)
    {
        this->current_expression = elna_pointer_nil_node;
    }

    void generic_visitor::visit(boot::number_literal<std::string> *string)
    {
        tree index_constant = build_int_cstu(elna_word_type_node, string->value.size());
        tree string_type = build_array_type(elna_char_type_node, build_index_type(index_constant));

        tree string_literal = build_string(string->value.size(), string->value.c_str());
        TREE_TYPE(string_literal) = string_type;
        TREE_CONSTANT(string_literal) = 1;
        TREE_READONLY(string_literal) = 1;
        TREE_STATIC(string_literal) = 1;

        string_type = TREE_TYPE(elna_string_ptr_field_node);
        string_literal = build4(ARRAY_REF, elna_char_type_node,
                string_literal, integer_zero_node, NULL_TREE, NULL_TREE);
        string_literal = build1(ADDR_EXPR, string_type, string_literal);

        vec<constructor_elt, va_gc> *elms = nullptr;
        CONSTRUCTOR_APPEND_ELT(elms, elna_string_ptr_field_node, string_literal);
        CONSTRUCTOR_APPEND_ELT(elms, elna_string_length_field_node, index_constant);

        this->current_expression = build_constructor(elna_string_type_node, elms);
    }

    tree generic_visitor::build_arithmetic_operation(boot::binary_expression *expression,
            tree_code operator_code, tree left, tree right)
    {
        return build_binary_operation(is_numeric_type(TREE_TYPE(left)),
                expression, operator_code, left, right, TREE_TYPE(left));
    }

    tree generic_visitor::build_comparison_operation(boot::binary_expression *expression,
            tree_code operator_code, tree left, tree right)
    {
        return build_binary_operation(is_numeric_type(TREE_TYPE(left)) || is_pointer_type(TREE_TYPE(left)),
                expression, operator_code, left, right, elna_bool_type_node);
    }

    tree generic_visitor::build_bit_logic_operation(boot::binary_expression *expression, tree left, tree right)
    {
        location_t expression_location = get_location(&expression->position());
        tree left_type = TREE_TYPE(left);
        tree right_type = TREE_TYPE(right);
        tree_code logical_code, bit_code;

        if (expression->operation() == boot::binary_operator::conjunction)
        {
            bit_code = BIT_AND_EXPR;
            logical_code = TRUTH_ANDIF_EXPR;
        }
        else if (expression->operation() == boot::binary_operator::disjunction)
        {
            bit_code = BIT_IOR_EXPR;
            logical_code = TRUTH_ORIF_EXPR;
        }
        else if (expression->operation() == boot::binary_operator::exclusive_disjunction)
        {
            bit_code = BIT_XOR_EXPR;
            logical_code = TRUTH_XOR_EXPR;
        }
        else
        {
            gcc_unreachable();
        }

        if (left_type == elna_bool_type_node)
        {
            return build2_loc(expression_location, logical_code, elna_bool_type_node, left, right);
        }
        else if (is_integral_type(left_type))
        {
            return build2_loc(expression_location, bit_code, left_type, left, right);
        }
        else
        {
            error_at(expression_location,
                    "invalid operands of type '%s' and '%s' for operator %s",
                    print_type(left_type).c_str(), print_type(right_type).c_str(),
                    elna::boot::print_binary_operator(expression->operation()));
            return error_mark_node;
        }
    }

    tree generic_visitor::build_equality_operation(boot::binary_expression *expression, tree left, tree right)
    {
        location_t expression_location = get_location(&expression->position());
        tree_code equality_code, combination_code;

        if (expression->operation() == boot::binary_operator::equals)
        {
            equality_code = EQ_EXPR;
            combination_code = TRUTH_ANDIF_EXPR;
        }
        else if (expression->operation() == boot::binary_operator::not_equals)
        {
            equality_code = NE_EXPR;
            combination_code = TRUTH_ORIF_EXPR;
        }
        else
        {
            gcc_unreachable();
        }
        if (TREE_TYPE(left) == elna_string_type_node)
        {
            tree lhs_length = build3(COMPONENT_REF, TREE_TYPE(elna_string_length_field_node),
                    left, elna_string_length_field_node, NULL_TREE);
            tree lhs_ptr = build3(COMPONENT_REF, TREE_TYPE(elna_string_ptr_field_node),
                    left, elna_string_ptr_field_node, NULL_TREE);

            tree rhs_length = build3(COMPONENT_REF, TREE_TYPE(elna_string_length_field_node),
                    right, elna_string_length_field_node, NULL_TREE);
            tree rhs_ptr = build3(COMPONENT_REF, TREE_TYPE(elna_string_ptr_field_node),
                    right, elna_string_ptr_field_node, NULL_TREE);

            tree length_equality = build2(equality_code, elna_bool_type_node, lhs_length, rhs_length);
            tree *memcmp = elna_global_decls->get("__builtin_memcmp");
            gcc_assert(memcmp != nullptr);

            tree fndecl_type = build_function_type(integer_type_node, TYPE_ARG_TYPES(*memcmp));
            tree memcmp_addr = build1(ADDR_EXPR, build_pointer_type(fndecl_type), *memcmp);
            tree memcmp_call = build_call_nary(integer_type_node, memcmp_addr, 3, lhs_ptr, rhs_ptr, lhs_length);

            tree equals_zero = build2(equality_code, elna_bool_type_node, memcmp_call, integer_zero_node);

            return build2(combination_code, elna_bool_type_node, length_equality, equals_zero);
        }
        else
        {
            return build2_loc(expression_location, equality_code, elna_bool_type_node, left, right);
        }
    }

    void generic_visitor::visit(boot::binary_expression *expression)
    {
        expression->lhs().accept(this);
        tree left = this->current_expression;
        tree left_type = get_qualified_type(TREE_TYPE(left), TYPE_UNQUALIFIED);

        expression->rhs().accept(this);
        tree right = this->current_expression;
        tree right_type = get_qualified_type(TREE_TYPE(right), TYPE_UNQUALIFIED);

        location_t expression_location = get_location(&expression->position());

        if ((is_pointer_type(left_type) || is_pointer_type(right_type))
                && (expression->operation() == boot::binary_operator::sum
                    || expression->operation() == boot::binary_operator::subtraction))
        {
            this->current_expression = do_pointer_arithmetic(expression->operation(), left, right);
            if (this->current_expression == error_mark_node)
            {
                error_at(expression_location,
                        "invalid operation %s on a pointer and an integral type",
                        boot::print_binary_operator(expression->operation()));
            }
            else if (TREE_TYPE(this->current_expression) == ssizetype)
            {
                this->current_expression = fold_convert(elna_int_type_node, this->current_expression);
            }
            return;
        }
        if (left_type != right_type
                && !are_compatible_pointers(left_type, right)
                && !are_compatible_pointers(right_type, left)
                && !(is_integral_type(left_type) && right_type == elna_word_type_node))
        {
            error_at(expression_location,
                    "invalid operands of type '%s' and '%s' for operator %s",
                    print_type(left_type).c_str(), print_type(right_type).c_str(),
                    boot::print_binary_operator(expression->operation()));
            this->current_expression = error_mark_node;
            return;
        }
        switch (expression->operation())
        {
            case boot::binary_operator::sum:
                this->current_expression = build_arithmetic_operation(expression, PLUS_EXPR, left, right);
                break;
            case boot::binary_operator::subtraction:
                this->current_expression = build_arithmetic_operation(expression, MINUS_EXPR, left, right);
                break;
            case boot::binary_operator::division:
                this->current_expression = build_arithmetic_operation(expression, TRUNC_DIV_EXPR, left, right);
                break;
            case boot::binary_operator::remainder:
                this->current_expression = build_arithmetic_operation(expression, TRUNC_MOD_EXPR, left, right);
                break;
            case boot::binary_operator::multiplication:
                this->current_expression = build_arithmetic_operation(expression, MULT_EXPR, left, right);
                break;
            case boot::binary_operator::less:
                this->current_expression = build_comparison_operation(expression, LT_EXPR, left, right);
                break;
            case boot::binary_operator::greater:
                this->current_expression = build_comparison_operation(expression, GT_EXPR, left, right);
                break;
            case boot::binary_operator::less_equal:
                this->current_expression = build_comparison_operation(expression, LE_EXPR, left, right);
                break;
            case boot::binary_operator::greater_equal:
                this->current_expression = build_comparison_operation(expression, GE_EXPR, left, right);
                break;
            case boot::binary_operator::conjunction:
                this->current_expression = build_bit_logic_operation(expression, left, right);
                break;
            case boot::binary_operator::disjunction:
                this->current_expression = build_bit_logic_operation(expression, left, right);
                break;
            case boot::binary_operator::exclusive_disjunction:
                this->current_expression = build_bit_logic_operation(expression, left, right);
                break;
            case boot::binary_operator::equals:
                this->current_expression = build_equality_operation(expression, left, right);
                break;
            case boot::binary_operator::not_equals:
                this->current_expression = build_equality_operation(expression, left, right);
                break;
            case boot::binary_operator::shift_left:
                this->current_expression = build_binary_operation(
                        is_numeric_type(left_type) && right_type == elna_word_type_node,
                        expression, LSHIFT_EXPR, left, right, left_type);
                break;
            case boot::binary_operator::shift_right:
                this->current_expression = build_binary_operation(
                        is_numeric_type(left_type) && right_type == elna_word_type_node,
                        expression, RSHIFT_EXPR, left, right, left_type);
                break;
        }
    }

    void generic_visitor::visit(boot::unary_expression *expression)
    {
        expression->operand().accept(this);
        location_t location = get_location(&expression->position());

        switch (expression->operation())
        {
            case boot::unary_operator::reference:
                this->current_expression = prepare_rvalue(this->current_expression);
                TREE_ADDRESSABLE(this->current_expression) = 1;
                this->current_expression = build_fold_addr_expr_with_type_loc(location,
                        this->current_expression,
                        build_pointer_type_for_mode(TREE_TYPE(this->current_expression), VOIDmode, true));
                TREE_NO_TRAMPOLINE(this->current_expression) = 1;
                break;
            case boot::unary_operator::negation:
                if (TREE_TYPE(this->current_expression) == elna_bool_type_node)
                {
                    this->current_expression = build1_loc(location, TRUTH_NOT_EXPR,
                            boolean_type_node, this->current_expression);
                }
                else if (is_integral_type(TREE_TYPE(this->current_expression)))
                {
                    this->current_expression = build1_loc(location, BIT_NOT_EXPR,
                            TREE_TYPE(this->current_expression), this->current_expression);
                }
                else
                {
                    error_at(location, "type '%s' cannot be negated",
                            print_type(TREE_TYPE(this->current_expression)).c_str());
                    this->current_expression = error_mark_node;
                }
                break;
            case boot::unary_operator::minus:
                if (is_integral_type(TREE_TYPE(this->current_expression)))
                {
                    this->current_expression = fold_build1(NEGATE_EXPR, TREE_TYPE(this->current_expression),
                            this->current_expression);
                }
                else
                {
                    error_at(location, "type '%s' cannot be negated",
                            print_type(TREE_TYPE(this->current_expression)).c_str());
                    this->current_expression = error_mark_node;
                }
        }
    }

    void generic_visitor::visit(boot::constant_definition *definition)
    {
        location_t definition_location = get_location(&definition->position());
        definition->body().accept(this);

        tree definition_tree = build_decl(definition_location, CONST_DECL,
                get_identifier(definition->identifier.c_str()), TREE_TYPE(this->current_expression));
        auto result = this->symbols->enter(definition->identifier, definition_tree);

        if (result)
        {
            DECL_INITIAL(definition_tree) = this->current_expression;
            TREE_CONSTANT(definition_tree) = 1;
            TREE_READONLY(definition_tree) = 1;
            TREE_PUBLIC(definition_tree) = definition->exported;

            if (!lang_hooks.decls.global_bindings_p())
            {
                auto declaration_statement = build1_loc(definition_location, DECL_EXPR,
                        void_type_node, definition_tree);
                append_statement(declaration_statement);
            }
        }
        else
        {
            error_at(definition_location,
                    "variable '%s' already declared in this scope",
                    definition->identifier.c_str());
        }
        this->current_expression = NULL_TREE;
    }

    void generic_visitor::visit(boot::type_definition *definition)
    {
        location_t definition_location = get_location(&definition->position());
        this->current_expression = this->symbols->lookup(definition->identifier);
        definition->body().accept(this);

        tree definition_tree = build_decl(definition_location, TYPE_DECL,
                get_identifier(definition->identifier.c_str()), this->current_expression);
        auto result = this->symbols->enter(definition->identifier, this->current_expression);

        /* if (result)
        { */
            TREE_PUBLIC(definition_tree) = definition->exported;
            TYPE_NAME(this->current_expression) = get_identifier(definition->identifier.c_str());
        // }
        /* else
        {
            error_at(get_location(&definition->position()),
                    "type '%s' already declared in this scope",
                    definition->identifier.c_str());
        } */
        this->current_expression = NULL_TREE;
    }

    tree generic_visitor::build_procedure_type(boot::procedure_type_expression& type)
    {
        std::vector<tree> parameter_types(type.parameters.size());

        for (std::size_t i = 0; i < type.parameters.size(); ++i)
        {
            boot::type_expression& parameter_type = type.parameters.at(i)->variable_type();
            parameter_type.accept(this);
            parameter_types[i] = this->current_expression;
        }
        tree return_type = void_type_node;

        if (type.return_type != nullptr)
        {
            type.return_type->accept(this);
            return_type = this->current_expression;
        }
        this->current_expression = NULL_TREE;

        return build_function_type_array(return_type, type.parameters.size(), parameter_types.data());
    }

    void generic_visitor::visit(boot::variable_declaration *declaration)
    {
        declaration->variable_type().accept(this);

        location_t declaration_location = get_location(&declaration->position());
        tree declaration_tree = build_decl(declaration_location, VAR_DECL,
                get_identifier(declaration->identifier.c_str()), this->current_expression);
        bool result = this->symbols->enter(declaration->identifier, declaration_tree);

        if (is_pointer_type(this->current_expression))
        {
            DECL_INITIAL(declaration_tree) = elna_pointer_nil_node;
        }
        this->current_expression = NULL_TREE;
        if (!result)
        {
            error_at(declaration_location, "variable '%s' already declared in this scope",
                    declaration->identifier.c_str());
        }
        else if (lang_hooks.decls.global_bindings_p())
        {
            TREE_STATIC(declaration_tree) = 1;
            varpool_node::get_create(declaration_tree);
            varpool_node::finalize_decl(declaration_tree);
        }
        else
        {
            DECL_CONTEXT(declaration_tree) = current_function_decl;
            f_names = chainon(f_names, declaration_tree);

            auto declaration_statement = build1_loc(declaration_location, DECL_EXPR,
                    void_type_node, declaration_tree);
            append_statement(declaration_statement);
        }
    }

    void generic_visitor::visit(boot::variable_expression *expression)
    {
        auto symbol = this->lookup(expression->name);

        if (symbol == NULL_TREE)
        {
            error_at(get_location(&expression->position()),
                    "symbol '%s' not declared in the current scope",
                    expression->name.c_str());
            this->current_expression = error_mark_node;
        }
        else
        {
            this->current_expression = symbol;
        }
    }

    void generic_visitor::visit(boot::array_access_expression *expression)
    {
        expression->base().accept(this);
        tree designator = this->current_expression;
        location_t location = get_location(&expression->position());

        expression->index().accept(this);
        if (!is_integral_type(TREE_TYPE(this->current_expression)))
        {
            error_at(location, "type '%s' cannot be used as index",
                    print_type(TREE_TYPE(this->current_expression)).c_str());
            this->current_expression = error_mark_node;
            return;
        }
        if (this->current_expression != elna_word_type_node)
        {
            this->current_expression = fold_convert(elna_word_type_node, this->current_expression);
        }
        tree offset = build2(MINUS_EXPR, elna_word_type_node,
                this->current_expression, size_one_node);

        if (is_array_type(TREE_TYPE(designator)))
        {
            tree element_type = TREE_TYPE(TREE_TYPE(designator));

            this->current_expression = build4_loc(location,
                    ARRAY_REF, element_type, designator, offset, NULL_TREE, NULL_TREE);
        }
        else if (TREE_TYPE(designator) == elna_string_type_node)
        {
            tree string_ptr = build3_loc(location, COMPONENT_REF, TREE_TYPE(elna_string_ptr_field_node),
                    designator, elna_string_ptr_field_node, NULL_TREE);

            tree target_pointer = do_pointer_arithmetic(boot::binary_operator::sum, string_ptr, offset);

            this->current_expression = build1_loc(location, INDIRECT_REF,
                    elna_char_type_node, target_pointer);
        }
        else
        {
            error_at(location, "indexing is not allowed on type '%s'",
                    print_type(TREE_TYPE(designator)).c_str());
            this->current_expression = error_mark_node;
        }
    }

    void generic_visitor::visit(boot::traits_expression *trait)
    {
        trait->type().accept(this);

        if (trait->name == "size")
        {
            this->current_expression = build1(CONVERT_EXPR, elna_word_type_node,
                    size_in_bytes(this->current_expression));
        }
        else if (trait->name == "alignment")
        {
            this->current_expression = build_int_cstu(elna_word_type_node,
                    TYPE_ALIGN_UNIT(this->current_expression));
        }
        else if (trait->name == "min" && is_integral_type(this->current_expression))
        {
            this->current_expression = TYPE_MIN_VALUE(this->current_expression);
        }
        else if (trait->name == "max" && is_integral_type(this->current_expression))
        {
            this->current_expression = TYPE_MAX_VALUE(this->current_expression);
        }
        else
        {
            error_at(get_location(&trait->position()), "type '%s' does not have property '%s'",
                    print_type(this->current_expression).c_str(), trait->name.c_str());
            this->current_expression = error_mark_node;
        }
    }

    void generic_visitor::visit(boot::field_access_expression *expression)
    {
        expression->base().accept(this);
        location_t expression_location = get_location(&expression->position());

        if (is_aggregate_type(TREE_TYPE(this->current_expression)))
        {
            tree field_declaration = TYPE_FIELDS(TREE_TYPE(this->current_expression));

            while (field_declaration != NULL_TREE)
            {
                tree declaration_name = DECL_NAME(field_declaration);
                const char *identifier_pointer = IDENTIFIER_POINTER(declaration_name);

                if (expression->field() == identifier_pointer)
                {
                    break;
                }
                field_declaration = TREE_CHAIN(field_declaration);
            }
            if (field_declaration == NULL_TREE)
            {
                error_at(expression_location,
                        "record type does not have a field named '%s'",
                        expression->field().c_str());
                this->current_expression = error_mark_node;
            }
            else
            {
                this->current_expression = build3_loc(expression_location, COMPONENT_REF,
                        TREE_TYPE(field_declaration), this->current_expression,
                        field_declaration, NULL_TREE);
            }
        }
        else
        {
            error_at(expression_location, "type '%s' does not have a field named '%s'",
                    print_type(TREE_TYPE(this->current_expression)).c_str(), expression->field().c_str());
            this->current_expression = error_mark_node;
        }
    }

    void generic_visitor::visit(boot::dereference_expression *expression)
    {
        expression->base().accept(this);

        this->current_expression = build1_loc(get_location(&expression->position()), INDIRECT_REF,
                TREE_TYPE(TREE_TYPE(this->current_expression)), this->current_expression);
    }

    void generic_visitor::visit(boot::assign_statement *statement)
    {
        statement->lvalue().accept(this);

        tree lvalue = this->current_expression;
        location_t statement_location = get_location(&statement->position());

        statement->rvalue().accept(this);
        tree rvalue = prepare_rvalue(this->current_expression);

        if (TREE_CODE(lvalue) == CONST_DECL)
        {
            error_at(statement_location, "cannot modify constant '%s'",
                    statement->lvalue().is_variable()->name.c_str());
            this->current_expression = error_mark_node;
        }
        else if (is_assignable_from(TREE_TYPE(lvalue), rvalue))
        {
            tree assignment = build2_loc(statement_location, MODIFY_EXPR, void_type_node, lvalue, rvalue);

            append_statement(assignment);
            this->current_expression = NULL_TREE;
        }
        else
        {
            error_at(statement_location,
                    "cannot assign value of type '%s' to variable of type '%s'",
                    print_type(TREE_TYPE(rvalue)).c_str(),
                    print_type(TREE_TYPE(lvalue)).c_str());
            this->current_expression = error_mark_node;
        }
    }

    void generic_visitor::visit(boot::if_statement *statement)
    {
        tree endif_label_decl = build_label_decl("endif", UNKNOWN_LOCATION);
        tree goto_endif = build1(GOTO_EXPR, void_type_node, endif_label_decl);

        make_if_branch(statement->body(), goto_endif);

        for (const auto branch : statement->branches)
        {
            make_if_branch(*branch, goto_endif);
        }
        if (statement->alternative() != nullptr)
        {
            enter_scope();
            visit_statements(*statement->alternative());
            tree mapping = leave_scope();
            append_statement(mapping);
        }
        tree endif_label_expr = build1(LABEL_EXPR, void_type_node, endif_label_decl);
        append_statement(endif_label_expr);
        this->current_expression = NULL_TREE;
    }

    void generic_visitor::make_if_branch(boot::conditional_statements& branch, tree goto_endif)
    {
        branch.prerequisite().accept(this);

        if (TREE_TYPE(this->current_expression) != elna_bool_type_node)
        {
            error_at(get_location(&branch.prerequisite().position()),
                    "expected expression of boolean type but its type is %s",
                    print_type(TREE_TYPE(this->current_expression)).c_str());
            this->current_expression = error_mark_node;
            return;
        }
        tree then_label_decl = build_label_decl("then", UNKNOWN_LOCATION);
        tree goto_then = build1(GOTO_EXPR, void_type_node, then_label_decl);

        tree else_label_decl = build_label_decl("else", UNKNOWN_LOCATION);
        tree goto_else = build1(GOTO_EXPR, void_type_node, else_label_decl);

        auto cond_expr = build3(COND_EXPR, void_type_node, this->current_expression, goto_then, goto_else);
        append_statement(cond_expr);

        tree then_label_expr = build1(LABEL_EXPR, void_type_node, then_label_decl);
        append_statement(then_label_expr);
        enter_scope();

        visit_statements(branch.statements);
        tree mapping = leave_scope();
        append_statement(mapping);
        append_statement(goto_endif);

        tree else_label_expr = build1(LABEL_EXPR, void_type_node, else_label_decl);
        append_statement(else_label_expr);
    }

    tree generic_visitor::build_label_decl(const char *name, location_t loc)
    {
        auto label_decl = build_decl(loc,
                LABEL_DECL, get_identifier(name), void_type_node);

        DECL_CONTEXT(label_decl) = current_function_decl;

        return label_decl;
    }

    void generic_visitor::visit(boot::while_statement *statement)
    {
        auto prerequisite_location = get_location(&statement->body().prerequisite().position());
        auto prerequisite_label_decl = build_label_decl("while_check", prerequisite_location);
        auto prerequisite_label_expr = build1_loc(prerequisite_location, LABEL_EXPR,
                void_type_node, prerequisite_label_decl);
        auto goto_check = build1(GOTO_EXPR, void_type_node, prerequisite_label_decl);

        append_statement(prerequisite_label_expr);
        make_if_branch(statement->body(), goto_check);

        for (const auto branch : statement->branches)
        {
            make_if_branch(*branch, goto_check);
        }
        this->current_expression = NULL_TREE;
    }

    void generic_visitor::visit_statements(const std::vector<boot::statement *>& statements)
    {
        for (boot::statement *const statement : statements)
        {
            statement->accept(this);

            if (this->current_expression != NULL_TREE && this->current_expression != error_mark_node)
            {
                append_statement(this->current_expression);
                this->current_expression = NULL_TREE;
            }
        }
    }

    void generic_visitor::visit(boot::return_statement *statement)
    {
        boot::expression *return_expression = statement->return_expression();

        if (return_expression == nullptr)
        {
            return;
        }
        return_expression->accept(this);

        tree set_result = build2(INIT_EXPR, void_type_node, DECL_RESULT(current_function_decl),
                this->current_expression);
        tree return_stmt = build1(RETURN_EXPR, void_type_node, set_result);
        append_statement(return_stmt);

        this->current_expression = NULL_TREE;
    }

    void generic_visitor::visit(boot::primitive_type_expression *type)
    {
        tree symbol = this->lookup(type->name);

        if (symbol == NULL_TREE && TYPE_P(symbol))
        {
            error_at(get_location(&type->position()),
                    "type '%s' not declared", type->name.c_str());

            this->current_expression = error_mark_node;
        }
        else
        {
            this->current_expression = symbol;
        }
    }

    void generic_visitor::visit(boot::array_type_expression *type)
    {
        tree lower_bound = build_int_cst_type(integer_type_node, 0);
        tree upper_bound = build_int_cst_type(integer_type_node, type->size);
        type->base().accept(this);

        if (this->current_expression != NULL_TREE && this->current_expression != error_mark_node)
        {
            tree range_type = build_range_type(integer_type_node, lower_bound, upper_bound);

            this->current_expression = build_array_type(this->current_expression, range_type);
        }
    }

    void generic_visitor::visit(boot::pointer_type_expression *type)
    {
        type->base().accept(this);

        if (this->current_expression != NULL_TREE && this->current_expression != error_mark_node)
        {
            this->current_expression = build_pointer_type_for_mode(this->current_expression, VOIDmode, true);
        }
    }

    void generic_visitor::visit(boot::record_type_expression *type)
    {
        std::set<std::string> field_names;
        tree record_type_node = this->current_expression == NULL_TREE
            ? make_node(RECORD_TYPE)
            : this->current_expression;

        for (auto& field : type->fields)
        {
            if (field_names.find(field.first) != field_names.cend())
            {
                error_at(get_location(&field.second->position()), "repeated field name");
                this->current_expression = error_mark_node;
                return;
            }
            field_names.insert(field.first);

            field.second->accept(this);
            if (this->current_expression == NULL_TREE || this->current_expression == error_mark_node)
            {
                return;
            }
            tree field_declaration = build_field(get_location(&field.second->position()),
                    record_type_node, field.first, this->current_expression);
            TYPE_FIELDS(record_type_node) = chainon(TYPE_FIELDS(record_type_node), field_declaration);
            this->current_expression = NULL_TREE;
        }
        layout_type(record_type_node);

        this->current_expression = record_type_node;
    }

    void generic_visitor::visit(boot::union_type_expression *type)
    {
        std::set<std::string> field_names;
        tree union_type_node = this->current_expression == NULL_TREE
            ? make_node(UNION_TYPE)
            : this->current_expression;

        for (auto& field : type->fields)
        {
            if (field_names.find(field.first) != field_names.cend())
            {
                error_at(get_location(&field.second->position()), "repeated field name");
                this->current_expression = error_mark_node;
                return;
            }
            field_names.insert(field.first);

            field.second->accept(this);
            if (this->current_expression == NULL_TREE || this->current_expression == error_mark_node)
            {
                return;
            }
            tree field_declaration = build_field(get_location(&field.second->position()),
                    union_type_node, field.first, this->current_expression);
            TYPE_FIELDS(union_type_node) = chainon(TYPE_FIELDS(union_type_node), field_declaration);
            this->current_expression = NULL_TREE;
        }
        layout_type(union_type_node);

        this->current_expression = union_type_node;
    }

    void generic_visitor::visit(boot::procedure_type_expression *type)
    {
        tree procedure_type_node = build_procedure_type(*type);
        this->current_expression = build_pointer_type_for_mode(procedure_type_node, VOIDmode, true);
    }

    void generic_visitor::visit(boot::defer_statement *statement)
    {
        enter_scope();
        visit_statements(statement->statements);
        defer(leave_scope());
    }
}
}