proc lisp_object_equal(Lisp_Object* n1, Lisp_Object* n2) -> bool {
    if (n1 == n2)
        return true;
    if (n1->type != n2->type)
        return false;

    switch (n1->type) {
    case Lisp_Object_Type::CFunction:
        // TODO(Felix): make comparing work again.
        return false;
        /* return n1->value.built_in_function->type */
        /*     == n2->value.built_in_function->type; */
    case Lisp_Object_Type::Function:
        // if they have the same pointer, true is
        // returned a few lines above
        return false;
    case Lisp_Object_Type::Keyword:
        return string_equal(
            n1->value.keyword->identifier,
            n2->value.keyword->identifier);
    case Lisp_Object_Type::T:
    case Lisp_Object_Type::Nil:
        return true;
    case Lisp_Object_Type::Number:
        return
            n1->value.number->value ==
            n2->value.number->value;
    case Lisp_Object_Type::Pair:
        create_error(Error_Type::Not_Yet_Implemented, n1->sourceCodeLocation);
        return false;
    case Lisp_Object_Type::String:
        return string_equal(n1->value.string, n2->value.string);
    case Lisp_Object_Type::Symbol:
        return string_equal(
            n1->value.symbol->identifier,
            n2->value.symbol->identifier);
    }

    // we should never reach here
    return false;
}

proc built_in_load(String* file_name, Environment* env) -> Lisp_Object* {
    char* file_content = read_entire_file(Memory::get_c_str(file_name));
    if (file_content) {
        Lisp_Object* result = Memory::nil;
        Lisp_Object_Array_List* program;
        try {
            program = Parser::parse_program(file_name, file_content);
        }
        for (int i = 0; i < program->next_index; ++i) {
            try {
                result = eval_expr(program->data[i], env);
            }
        }
        return result;
    } else {
        create_error(Error_Type::File_Not_Found, nullptr);
        return nullptr;
    }
}

proc load_built_ins_into_environment(Environment* env) -> void {
    int arguments_length;
    Lisp_Object* evaluated_arguments;

#define cLambda [=](Lisp_Object* arguments, Environment* env) mutable -> Lisp_Object*
#define report_error(_type) {                                         \
        if (log_level == Log_Level::Debug) {                          \
            printf("Error occurred\nin %s:%d\n", __FILE__, __LINE__); \
        }                                                             \
        create_error(_type, current_source_code_location);            \
        return nullptr;                                               \
    }

    proc defun = [&](const char* name, auto fun) {
        define_symbol(
            Memory::get_or_create_lisp_object_symbol(name),
            Memory::create_lisp_object_cfunction(fun),
            env);
    };

    proc parse_lambda_starting_from_args = [&](Lisp_Object* arguments, Environment* env) -> Lisp_Object* {
        Function* function = new(Function);
        function->parent_environment = env;
        function->type = Function_Type::Lambda;

        // if parameters were specified
        if (arguments->value.pair->first->type != Lisp_Object_Type::Nil) {
            try {
                assert_type(arguments->value.pair->first, Lisp_Object_Type::Pair);
            }
            try {
                parse_argument_list(arguments->value.pair->first, function);
            }
        } else {
            function->positional_arguments = create_positional_argument_list(1);
            function->keyword_arguments = create_keyword_argument_list(1);
            function->rest_argument = nullptr;
        }

        arguments = arguments->value.pair->rest;
        // if there is a docstring, use it
        if (arguments->value.pair->first->type == Lisp_Object_Type::String) {
            function->docstring = arguments->value.pair->first->value.string;
            arguments = arguments->value.pair->rest;
        } else {
            function->docstring = nullptr;
        }

        // we are now in the function body, just wrap it in an
        // implicit prog
        function->body = Memory::create_lisp_object_pair(
            Memory::get_or_create_lisp_object_symbol("prog"),
            arguments);

        Lisp_Object* ret = Memory::create_lisp_object();
        ret->type = Lisp_Object_Type::Function;
        ret->value.function = function;
        return ret;
    };

    defun("=",  cLambda {
            int arguments_length;
            try {
                arguments = eval_arguments(arguments, env, &arguments_length);
            }

            if (arguments->type == Lisp_Object_Type::Nil)
                return Memory::t;

            Lisp_Object* first = arguments->value.pair->first;

            while (arguments->type == Lisp_Object_Type::Pair) {
                if (!lisp_object_equal(arguments->value.pair->first, first))
                    return Memory::nil;
                arguments = arguments->value.pair->rest;
            }

            return Memory::t;
        });
    defun(">",  cLambda {
            int arguments_length;
            try {
                arguments = eval_arguments(arguments, env, &arguments_length);
            }

            double last_number = strtod("Inf", NULL);

            while (arguments->type == Lisp_Object_Type::Pair) {
                try {
                    assert_type(arguments->value.pair->first, Lisp_Object_Type::Number);
                }

                if (arguments->value.pair->first->value.number->value >= last_number)
                    return Memory::nil;

                last_number = arguments->value.pair->first->value.number->value;
                arguments = arguments->value.pair->rest;
            }

            return Memory::t;
        });
    defun(">=", cLambda {
            int arguments_length;
            try {
                arguments = eval_arguments(arguments, env, &arguments_length);
            }

            double last_number = strtod("Inf", NULL);

            while (arguments->type == Lisp_Object_Type::Pair) {
                try {
                    assert_type(arguments->value.pair->first, Lisp_Object_Type::Number);
                }

                if (arguments->value.pair->first->value.number->value > last_number)
                    return Memory::nil;

                last_number = arguments->value.pair->first->value.number->value;
                arguments = arguments->value.pair->rest;
            }

            return Memory::t;
        });
    defun("<",  cLambda {
            int arguments_length;
            try {
                arguments = eval_arguments(arguments, env, &arguments_length);
            }

            double last_number = strtod("-Inf", NULL);

            while (arguments->type == Lisp_Object_Type::Pair) {
                try {
                    assert_type(arguments->value.pair->first, Lisp_Object_Type::Number);
                }

                if (arguments->value.pair->first->value.number->value <= last_number)
                    return Memory::nil;

                last_number = arguments->value.pair->first->value.number->value;
                arguments = arguments->value.pair->rest;
            }

            return Memory::t;
        });
    defun("<=", cLambda {
            int arguments_length;
            try {
                arguments = eval_arguments(arguments, env, &arguments_length);
            }

            double last_number = strtod("-Inf", NULL);

            while (arguments->type == Lisp_Object_Type::Pair) {
                try {
                    assert_type(arguments->value.pair->first, Lisp_Object_Type::Number);
                }

                if (arguments->value.pair->first->value.number->value < last_number)
                    return Memory::nil;

                last_number = arguments->value.pair->first->value.number->value;
                arguments = arguments->value.pair->rest;
            }

            return Memory::t;
        });
    defun("+",  cLambda {
            int arguments_length;
            try {
                arguments = eval_arguments(arguments, env, &arguments_length);
            }

            double sum = 0;
            while (arguments->type == Lisp_Object_Type::Pair) {
                try {
                    assert_type(arguments->value.pair->first, Lisp_Object_Type::Number);
                }
                sum += arguments->value.pair->first->value.number->value;
                arguments = arguments->value.pair->rest;
            }

            return Memory::create_lisp_object_number(sum);
        });
    defun("-",  cLambda {
            int arguments_length;
            try {
                arguments = eval_arguments(arguments, env, &arguments_length);
            }

            try {
                assert_type(arguments->value.pair->first, Lisp_Object_Type::Number);
            }
            double difference = arguments->value.pair->first->value.number->value;

            arguments = arguments->value.pair->rest;
            while (arguments->type == Lisp_Object_Type::Pair) {
                try {
                    assert_type(arguments->value.pair->first, Lisp_Object_Type::Number);
                }

                difference -= arguments->value.pair->first->value.number->value;
                arguments = arguments->value.pair->rest;
            }
            return Memory::create_lisp_object_number(difference);
        });
    defun("*",  cLambda {
            int arguments_length;
            try {
                arguments = eval_arguments(arguments, env, &arguments_length);
            }

            try {
                assert_type(arguments->value.pair->first, Lisp_Object_Type::Number);
            }
            double product = arguments->value.pair->first->value.number->value;

            arguments = arguments->value.pair->rest;
            while (arguments->type == Lisp_Object_Type::Pair) {
                try {
                    assert_type(arguments->value.pair->first, Lisp_Object_Type::Number);
                }

                product *= arguments->value.pair->first->value.number->value;
                arguments = arguments->value.pair->rest;
            }
            return Memory::create_lisp_object_number(product);
        });
    defun("/",  cLambda {
            int arguments_length;
            try {
                arguments = eval_arguments(arguments, env, &arguments_length);
            }

            try {
                assert_type(arguments->value.pair->first, Lisp_Object_Type::Number);
            }
            double quotient = arguments->value.pair->first->value.number->value;

            arguments = arguments->value.pair->rest;
            while (arguments->type == Lisp_Object_Type::Pair) {
                try {
                    assert_type(arguments->value.pair->first, Lisp_Object_Type::Number);
                }

                quotient /= arguments->value.pair->first->value.number->value;
                arguments = arguments->value.pair->rest;
            }
            return Memory::create_lisp_object_number(quotient);
        });
    defun("**",  cLambda {
            int arguments_length;
            try {
                arguments = eval_arguments(arguments, env, &arguments_length);
            }

            if (arguments_length != 2) {
                create_error(Error_Type::Wrong_Number_Of_Arguments, arguments->sourceCodeLocation);
                return nullptr;
            }

            try {
                assert_type(arguments->value.pair->first, Lisp_Object_Type::Number);
            }

            double base = arguments->value.pair->first->value.number->value;

            arguments = arguments->value.pair->rest;

            try {
                assert_type(arguments->value.pair->first, Lisp_Object_Type::Number);
            }

            double exponent = arguments->value.pair->first->value.number->value;

            return Memory::create_lisp_object_number(pow(base, exponent));
        });
    defun("assert", cLambda {
            int arguments_length;

            try {
                arguments = eval_arguments(arguments, env, &arguments_length);
            }

            if (arguments_length != 1) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }

            if (is_truthy(arguments->value.pair->first, env))
                return Memory::t;

            report_error(Error_Type::Assertion_Error);
            return nullptr;
        });
    defun("define", cLambda {
            try {
                arguments_length = list_length(arguments);
            }

            if (arguments_length < 2) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }

            Lisp_Object* symbol = arguments->value.pair->first;
            Lisp_Object* value;

            if (symbol->type == Lisp_Object_Type::Pair) {
                /*
                   1: arguments
                   2: symbol
                   3: real_symbol

                    (define   (f x)   "docs"  (+ 1 x))

                    [ | ] -> [1| ] -> [ | ] -> [ |/]
                     |        |        |        |
                     V        |        V        V
                   define     |      "docs"    [ | ] -> [ | ] -> [ |/]
                              |                 |        |        |
                              V                 V        V        V
                             [2| ] -> [ |/]     +        1        x
                              |        |
                              V        V
                             f(3)      x
                 */

                Lisp_Object* real_symbol = symbol->value.pair->first;

                try {
                    assert_type(real_symbol, Lisp_Object_Type::Symbol);
                }

                Lisp_Object* fake_lambda = Memory::create_lisp_object_pair(
                    symbol   ->value.pair->rest,
                    arguments->value.pair->rest);

                value = parse_lambda_starting_from_args(fake_lambda, env);
                symbol = real_symbol;
            } else {
                if (arguments_length > 2) {
                    report_error(Error_Type::Wrong_Number_Of_Arguments);
                }
                if (symbol->type != Lisp_Object_Type::Symbol) {
                    report_error(Error_Type::Type_Missmatch);
                }

                value = arguments->value.pair->rest->value.pair->first;
                try {
                    value = eval_expr(value, env);
                }
            }

            define_symbol(symbol, value, env);

            return value;
        });
    // defun("define-upwards", cLambda {
    //         try {
    //             arguments_length = list_length(arguments);
    //         }

    //         if (arguments_length != 2) {
    //             report_error(Error_Type::Wrong_Number_Of_Arguments);
    //         }

    //         Lisp_Object* symbol = arguments->value.pair->first;

    //         if (symbol->type == Lisp_Object_Type::Pair) {
    //             try {
    //                 symbol = eval_expr(symbol, env);
    //             }
    //         }

    //         if (symbol->type != Lisp_Object_Type::Symbol) {
    //             report_error(Error_Type::Type_Missmatch);
    //         }

    //         if (!env->parent) {
    //             report_error(Error_Type::Unknown_Error);
    //         }


    //         Lisp_Object* value = arguments->value.pair->rest->value.pair->first;
    //         try {
    //             value = eval_expr(value, env);
    //         }

    //         define_symbol(symbol, value, env->parent);

    //         return value;
    //     });
    defun("mutate", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }

            if (arguments_length != 2)
                report_error(Error_Type::Wrong_Number_Of_Arguments);

            if (evaluated_arguments->value.pair->first->type == Lisp_Object_Type::Nil ||
                evaluated_arguments->value.pair->first->type == Lisp_Object_Type::Keyword)
            {
                report_error(Error_Type::Type_Missmatch);
            }

            Lisp_Object* target = evaluated_arguments->value.pair->first;
            Lisp_Object* source = evaluated_arguments->value.pair->rest->value.pair->first;

            *target = *source;
            return target;
        });
    defun("if", cLambda {
            try {
                arguments_length = list_length(arguments);
            }

            if (arguments_length != 2 && arguments_length != 3) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }

            Lisp_Object* condition = arguments->value.pair->first;
            Lisp_Object* then_part = arguments->value.pair->rest;
            Lisp_Object* else_part = then_part->value.pair->rest;

            bool truthy;
            try {
                truthy = is_truthy(condition, env);
            }

            Lisp_Object* result;

            if (truthy)
                try{
                    result = eval_expr(then_part->value.pair->first, env);
                }
            else if (arguments_length == 3)
                try {
                    result = eval_expr(else_part->value.pair->first, env);
                }
            else return Memory::nil;
            return result;
        });
    defun("quote", cLambda {
            try {
                arguments_length = list_length(arguments);
            }

            if (arguments_length != 1) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }

            return arguments->value.pair->first;
        });
    defun("quasiquote", cLambda {
            try {
                arguments_length = list_length(arguments);
            }

            if (arguments_length != 1) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }
            // print(arguments);
            // printf("\n");

            /* recursive lambdas in lambdas yay!! */
            // NOTE(Felix): first we have to initialize the variable
            // with a garbage lambda, so that we can then overwrite it
            // a recursive lambda
            std::function<Lisp_Object*(Lisp_Object*)> unquoteSomeExpressions; // = [] (Lisp_Object* expr) -> Lisp_Object* {return nullptr;};
            unquoteSomeExpressions = [&unquoteSomeExpressions, &env] (Lisp_Object* expr) -> Lisp_Object* {
                // if it is an atom, return it
                if (expr->type != Lisp_Object_Type::Pair)
                    return Memory::copy_lisp_object(expr);

                // it is a pair!
                Lisp_Object* originalPair = expr->value.pair->first;
                if (originalPair->type == Lisp_Object_Type::Symbol &&
                    string_equal(originalPair->value.symbol->identifier, "unquote"))
                {
                    // eval replace the stuff
                    return eval_expr(expr->value.pair->rest->value.pair->first, env);
                }

                // it is a list but not starting with the symbol
                // unquote, so search in there for stuff to unquote.
                // While copying the list

                //NOTE(Felix): Of fucking course we have to copy the
                // list. The quasiquote will be part of the body of a
                // funciton, we can't jsut modify it because otherwise
                // we modify the body of the function and would bake
                // in the result...
                Lisp_Object* newPair = Memory::create_lisp_object_pair(nullptr, nullptr);
                Lisp_Object* newPairHead = newPair;
                Lisp_Object* head = expr;
                while (head->type == Lisp_Object_Type::Pair) {
                    newPairHead->value.pair->first = unquoteSomeExpressions(head->value.pair->first);

                    if (head->value.pair->rest->type != Lisp_Object_Type::Pair)
                        break;

                    newPairHead->value.pair->rest  = Memory::create_lisp_object_pair(nullptr, nullptr);

                    newPairHead = newPairHead->value.pair->rest;
                    head = head->value.pair->rest;
                }
                newPairHead->value.pair->rest = Memory::nil;

                return newPair;
            };

            Lisp_Object* ret = arguments->value.pair->first;
            Lisp_Object* head = ret;

            ret = unquoteSomeExpressions(ret);
            return ret;
        });
    defun("and", cLambda {
            bool result = true;
            while (arguments->type != Lisp_Object_Type::Nil) {
                if (arguments->type != Lisp_Object_Type::Pair) {
                    report_error(Error_Type::Ill_Formed_List);
                }
                try {
                    result &= is_truthy(arguments->value.pair->first, env);
                }
                arguments = arguments->value.pair->rest;

                if (!result) return Memory::nil;
            }

            return Memory::t;
        });
    defun("or", cLambda {
            bool result = false;
            while (arguments->type != Lisp_Object_Type::Nil) {
                if (arguments->type != Lisp_Object_Type::Pair) {
                    report_error(Error_Type::Ill_Formed_List);
                }
                try {
                    result |= is_truthy(arguments->value.pair->first, env);
                }
                arguments = arguments->value.pair->rest;

                if (result) return Memory::t;
            }

            return Memory::nil;
        });
    defun("not", cLambda {
            try {
                arguments_length = list_length(arguments);
            }

            if (arguments_length != 1) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }
            bool truthy;
            try {
                truthy = is_truthy(arguments->value.pair->first, env);
            }
            if (truthy)
                return Memory::nil;
            return Memory::t;
        });
    defun("while", cLambda {
            try {
                arguments_length = list_length(arguments);
            }

            if (arguments_length < 2) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }
            Lisp_Object* condition_part = arguments->value.pair->first;
            Lisp_Object* condition;
            Lisp_Object* then_part = arguments->value.pair->rest;
            Lisp_Object* result = Memory::nil;

            while (true) {
                try {
                    condition = eval_expr(condition_part, env);
                }
                if (condition->type == Lisp_Object_Type::Nil) {
                    break;
                }
                try {
                    result = eval_expr(then_part->value.pair->first, env);
                }
            }
            return result;

        });
    defun("let", cLambda {
            // (let ((a 10)(b 20)) (body1) (body2))
            try {
                arguments_length = list_length(arguments);
            }

            if (arguments_length < 1)
                report_error(Error_Type::Wrong_Number_Of_Arguments);

            Environment* let_env = Memory::create_child_environment(env);
            Lisp_Object* bindings = arguments->value.pair->first;
            while (true) {
                if (bindings->type == Lisp_Object_Type::Nil) {
                    break;
                } else if (bindings->type != Lisp_Object_Type::Pair) {
                    report_error(Error_Type::Ill_Formed_Arguments);
                }

                Lisp_Object* sym = bindings->value.pair->first->value.pair->first;
                if(sym->type != Lisp_Object_Type::Symbol) {
                    report_error(Error_Type::Ill_Formed_Arguments);
                }
                Lisp_Object* rest_sym = bindings->value.pair->first->value.pair->rest;
                if (rest_sym->type != Lisp_Object_Type::Pair) {
                    report_error(Error_Type::Ill_Formed_Arguments);
                }
                if (rest_sym->value.pair->rest->type != Lisp_Object_Type::Nil) {
                    report_error(Error_Type::Ill_Formed_Arguments);
                }

                Lisp_Object* value = eval_expr(rest_sym->value.pair->first, env);

                // NOTE(Felix): We have to copy the value here because
                // if the let body modifies the value, it would bake
                // in... bad bad...
                define_symbol(sym, Memory::copy_lisp_object(value), let_env);

                bindings = bindings->value.pair->rest;
            }

            arguments = arguments->value.pair->rest;

            Lisp_Object* evaluated_arguments;
            try {
                evaluated_arguments = eval_arguments(arguments, let_env, &arguments_length);
            }

            if (evaluated_arguments->type == Lisp_Object_Type::Nil)
                return evaluated_arguments;

            // skip to the last evaluated operand and return it,
            // we use eval_arguments here instead of doing it
            // manually, because we want to increase code reuse,
            // but at the cost that we have to find the end of the
            // list again
            while (evaluated_arguments->value.pair->rest->type == Lisp_Object_Type::Pair) {
                evaluated_arguments = evaluated_arguments->value.pair->rest;
            }
            return evaluated_arguments->value.pair->first;
        });
    defun("lambda", cLambda {
            /*  TODO(Felix): first one crashes
             *  (lambda ())
             *  (lambda (x d) (+ 1 2) (- 1 2) (* 1 2))
             */
            try {
                arguments_length = list_length(arguments);
            }

            if (arguments_length == 0)
                report_error(Error_Type::Wrong_Number_Of_Arguments);

            Lisp_Object* function = parse_lambda_starting_from_args(arguments, env);
            // parse lambda starting from arguments


            return function;
        });
    defun("special-lambda", cLambda {
            /*
             *  (special-lambda ())
             *  (special-lambda (x d) (+ 1 2) (- 1 2) (* 1 2))
             */
            try {
                arguments_length = list_length(arguments);
            }

            if (arguments_length == 0)
                report_error(Error_Type::Wrong_Number_Of_Arguments);

            Function* function = new(Function);
            function->parent_environment = env;
            function->type = Function_Type::Special_Lambda;

            // if parameters were specified
            if (arguments->value.pair->first->type != Lisp_Object_Type::Nil) {
                try {
                    assert_type(arguments->value.pair->first, Lisp_Object_Type::Pair);
                }
                try {
                    parse_argument_list(arguments->value.pair->first, function);
                }
            } else {
                function->positional_arguments = create_positional_argument_list(1);
                function->keyword_arguments = create_keyword_argument_list(1);
                function->rest_argument = nullptr;
            }

            arguments = arguments->value.pair->rest;
            // if there is a docstring, use it
            if (arguments->value.pair->first->type == Lisp_Object_Type::String) {
                function->docstring = arguments->value.pair->first->value.string;
                arguments = arguments->value.pair->rest;
            } else {
                function->docstring = nullptr;
            }

            // we are now in the function body, just wrap it in an
            // implicit prog
            function->body = Memory::create_lisp_object_pair(
                Memory::get_or_create_lisp_object_symbol("prog"),
                arguments);

            Lisp_Object* ret = Memory::create_lisp_object();
            ret->type = Lisp_Object_Type::Function;
            ret->value.function = function;
            return ret;
        });
    defun("eval", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }
            if (arguments_length != 1) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }
            Lisp_Object* result;
            try {
                result = eval_expr(evaluated_arguments->value.pair->first, env);
            }
            return result;
        });


    defun("prog", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }

            if (evaluated_arguments->type == Lisp_Object_Type::Nil)
                return evaluated_arguments;

            // skip to the last evaluated operand and return it,
            // we use eval_arguments here instead of doing it
            // manually, because we want to increase code reuse,
            // but at the cost that we have to find the end of the
            // list again
            while (evaluated_arguments->value.pair->rest->type == Lisp_Object_Type::Pair) {
                evaluated_arguments = evaluated_arguments->value.pair->rest;
            }
            return evaluated_arguments->value.pair->first;
        });
    defun("list", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }
            return evaluated_arguments;
        });
    defun("pair", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }

            if (arguments_length != 2) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }
            return Memory::create_lisp_object_pair(evaluated_arguments->value.pair->first, evaluated_arguments->value.pair->rest->value.pair->first);
        });
    defun("first", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }
            if (arguments_length != 1) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }
            if (evaluated_arguments->value.pair->first->type == Lisp_Object_Type::Nil)
                return Memory::nil;
            if (evaluated_arguments->value.pair->first->type != Lisp_Object_Type::Pair)
                report_error(Error_Type::Type_Missmatch);

            return evaluated_arguments->value.pair->first->value.pair->first;
        });
    defun("rest", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }
            if (arguments_length != 1) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }
            if (evaluated_arguments->value.pair->first->type == Lisp_Object_Type::Nil)
                return Memory::nil;
            if (evaluated_arguments->value.pair->first->type != Lisp_Object_Type::Pair)
                report_error(Error_Type::Type_Missmatch);

            return evaluated_arguments->value.pair->first->value.pair->rest;
        });
    defun("set-type", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }
            if (arguments_length != 2) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }

            Lisp_Object* object = evaluated_arguments->value.pair->first;
            Lisp_Object* type = evaluated_arguments->value.pair->rest->value.pair->first;

            try {
                assert_type(type, Lisp_Object_Type::Keyword);
            }

            evaluated_arguments->value.pair->first->userType = type;
            return type;
        });
    defun("delete-type", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }
            if (arguments_length != 1) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }
            evaluated_arguments->value.pair->first->userType = nullptr;
            return  Memory::t;
        });
    defun("type", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }
            if (arguments_length != 1) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }

            if (evaluated_arguments->value.pair->first->userType) {
                return evaluated_arguments->value.pair->first->userType;
            }

            Lisp_Object_Type type  = evaluated_arguments->value.pair->first->type;
            switch (type) {
            case Lisp_Object_Type::CFunction: return Memory::get_or_create_lisp_object_keyword("cfunction");
            case Lisp_Object_Type::Function: {
                Function* fun = evaluated_arguments->value.pair->first->value.function;
                if (fun->type == Function_Type::Lambda)
                    return Memory::get_or_create_lisp_object_keyword("lambda");
                else if (fun->type == Function_Type::Special_Lambda)
                    return Memory::get_or_create_lisp_object_keyword("special-lambda");
                else if (fun->type == Function_Type::Macro)
                    return Memory::get_or_create_lisp_object_keyword("macro");
                else return Memory::get_or_create_lisp_object_keyword("unknown");
            }
            case Lisp_Object_Type::Keyword: return Memory::get_or_create_lisp_object_keyword("keyword");
            case Lisp_Object_Type::Nil:     return Memory::get_or_create_lisp_object_keyword("nil");
            case Lisp_Object_Type::T:       return Memory::get_or_create_lisp_object_keyword("t");
            case Lisp_Object_Type::Number:  return Memory::get_or_create_lisp_object_keyword("number");
            case Lisp_Object_Type::Pair:    return Memory::get_or_create_lisp_object_keyword("pair");
            case Lisp_Object_Type::String:  return Memory::get_or_create_lisp_object_keyword("string");
            case Lisp_Object_Type::Symbol:  return Memory::get_or_create_lisp_object_keyword("symbol");
            }
            return Memory::get_or_create_lisp_object_keyword("unknown");
        });
    defun("info", cLambda {
            try {
                arguments_length = list_length(arguments);
            }

            if (arguments_length != 1) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }

            print(arguments->value.pair->first);

            Lisp_Object* type = eval_expr(
                Memory::create_lisp_object_pair(
                    Memory::get_or_create_lisp_object_symbol("type"),
                    Memory::create_lisp_object_pair(arguments->value.pair->first, Memory::nil)),
                env);

            if (type) {
                printf(" is of type ");
                print(type);
                printf("\n\n");

                // TODO(Felix): Maybe don't compare strings here?? Wtf
                if (type->type == Lisp_Object_Type::Keyword &&
                    (string_equal(type->value.keyword->identifier, "lambda") ||
                     string_equal(type->value.keyword->identifier, "special-lambda") ||
                     string_equal(type->value.keyword->identifier, "macro")))
                {
                    Lisp_Object* fun = eval_expr(arguments->value.pair->first, env);

                    if (fun->value.function->docstring)
                        printf("Docstring:\n==========\n%s\n\n", Memory::get_c_str(fun->value.function->docstring));
                    else
                        printf("No docstring avaliable\n");

                    printf("Arguments:\n==========\n");
                    printf("Postitional: {");
                    if (fun->value.function->positional_arguments->next_index != 0) {
                        printf("%s",
                               Memory::get_c_str(fun->value.function->positional_arguments->identifiers[0]));
                        for (int i = 1; i < fun->value.function->positional_arguments->next_index; ++i) {
                            printf(", %s",
                                   Memory::get_c_str(fun->value.function->positional_arguments->identifiers[i]));
                        }
                    }
                    printf("}\n");
                    printf("Keyword:     {");
                    if (fun->value.function->keyword_arguments->next_index != 0) {
                        printf("%s",
                               Memory::get_c_str(fun->value.function->keyword_arguments->identifiers[0]));
                        if (fun->value.function->keyword_arguments->values->data[0]) {
                            printf(" (");
                            print(fun->value.function->keyword_arguments->values->data[0]);
                            printf(")");
                        }
                        for (int i = 1; i < fun->value.function->keyword_arguments->next_index; ++i) {
                            printf(", %s",
                                   Memory::get_c_str(fun->value.function->keyword_arguments->identifiers[i]));
                            if (fun->value.function->keyword_arguments->values->data[i]) {
                                printf(" (");
                                print(fun->value.function->keyword_arguments->values->data[i]);
                                printf(")");
                            }
                        }
                    }
                    printf("}\n");
                    printf("Rest:        {");
                    if (fun->value.function->rest_argument)
                        printf("%s",
                               Memory::get_c_str(fun->value.function->rest_argument));
                    printf("}\n");

                }
            } else {
                printf(" is not defined\n");
                delete_error();
            }

            return Memory::nil;
        });
    defun("show", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }
            if (arguments_length != 1) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }
            if (evaluated_arguments->value.pair->first->type != Lisp_Object_Type::Function) {
                report_error(Error_Type::Type_Missmatch);
            }

            puts("body:\n");
            print(evaluated_arguments->value.pair->first->value.function->body);
            puts("\n");

            return Memory::nil;
        });
    defun("print", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }
            if (arguments_length != 1) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }
            print(evaluated_arguments->value.pair->first);
            // printf("\n");
            return Memory::nil;
        });
    defun("read", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }

            if (arguments_length > 1) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }

            if (arguments_length == 1) {
                Lisp_Object* prompt = evaluated_arguments->value.pair->first;
                /* if (prompt->type == Lisp_Object_Type::String) */
                    /* printf("%s", prompt->value.string->value); */
                /* else */
                print(evaluated_arguments->value.pair->first);
            }
            char* line = read_line();
            String* strLine = Memory::create_string(line);
            free(line);
            return Memory::create_lisp_object_string(strLine);
        });
    defun("exit", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }

            if (arguments_length > 1) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }

            if (arguments_length == 1) {
                Lisp_Object* error_code = evaluated_arguments->value.pair->first;
                if (error_code->type != Lisp_Object_Type::Number)
                    report_error(Error_Type::Type_Missmatch);

                exit((int)error_code->value.number->value);
            }
            exit(0);
        });
    defun("break", cLambda {
            print_environment(env);
            debug_break();
            return Memory::nil;
        });
    defun("memstat", cLambda {
            Memory::print_status();
            return Memory::nil;
        });
    defun("try", cLambda {
            try {
                arguments_length = list_length(arguments);
            }

            if (arguments_length != 2) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }

            Lisp_Object* try_part   = arguments->value.pair->first;
            Lisp_Object* catch_part = arguments->value.pair->rest->value.pair->first;
            Lisp_Object* result;

            result = eval_expr(try_part, env);
            if (error) {
                delete_error();
                try {
                    result = eval_expr(catch_part, env);
                }
            }
            return result;
        });
    defun("load", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }

            if (arguments_length != 1)
                report_error(Error_Type::Wrong_Number_Of_Arguments);

            if (evaluated_arguments->value.pair->first->type != Lisp_Object_Type::String)
                report_error(Error_Type::Type_Missmatch);

            Lisp_Object* result;
            try {
                result = built_in_load(evaluated_arguments->value.pair->first->value.string, env);
            }
            return result;

        });
    defun("copy", cLambda {

            // TODO(Felix): if we are copying string nodes, then
            // shouldn't the string itself also get copied??
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }

            if (arguments_length != 1)
                report_error(Error_Type::Wrong_Number_Of_Arguments);

            if (evaluated_arguments->value.pair->first->type == Lisp_Object_Type::Nil ||
                evaluated_arguments->value.pair->first->type == Lisp_Object_Type::Keyword)
            {
                report_error(Error_Type::Type_Missmatch);
            }

            Lisp_Object* target = Memory::create_lisp_object();
            Lisp_Object* source = evaluated_arguments->value.pair->first;

            *target = *source;
            return target;
        });
    defun("error", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }
            if (arguments_length != 0) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }
            report_error(Error_Type::Unknown_Error);
        });
    defun("symbol->keyword", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }

            if (arguments_length != 1) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }

            Lisp_Object* source = evaluated_arguments->value.pair->first;

            if (source->type != Lisp_Object_Type::Symbol) {
                report_error(Error_Type::Type_Missmatch);
            }

            return Memory::get_or_create_lisp_object_keyword(source->value.symbol->identifier);
        });
    defun("string->symbol", cLambda {

            // TODO(Felix): do some sanity checks on the string. For
            // example, numbers are not valid symbols.

            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }

            if (arguments_length != 1) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }

            Lisp_Object* source = evaluated_arguments->value.pair->first;

            if (source->type != Lisp_Object_Type::String) {
                report_error(Error_Type::Type_Missmatch);
            }

            return Memory::get_or_create_lisp_object_symbol(Memory::duplicate_string(source->value.string));
        });
    defun("symbol->string", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }

            if (arguments_length != 1) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }

            Lisp_Object* source = evaluated_arguments->value.pair->first;

            if (source->type != Lisp_Object_Type::Symbol) {
                report_error(Error_Type::Type_Missmatch);
            }
            return Memory::create_lisp_object_string(Memory::duplicate_string(source->value.symbol->identifier));
        });
    defun("concat-strings", cLambda {
            try {
                evaluated_arguments = eval_arguments(arguments, env, &arguments_length);
            }

            if (arguments_length < 1) {
                report_error(Error_Type::Wrong_Number_Of_Arguments);
            }

            int resulting_string_len = 0;

            Lisp_Object* head = evaluated_arguments;

            while (head->type == Lisp_Object_Type::Pair) {
                try {
                    assert_type(head->value.pair->first, Lisp_Object_Type::String);
                }
                resulting_string_len += head->value.pair->first->value.string->length;

                head = head->value.pair->rest;
            }

            head = evaluated_arguments;

            String* resulting_string = Memory::create_string("", resulting_string_len);
            int index_in_string = 0;

            while (head->type == Lisp_Object_Type::Pair) {
                strcpy((&resulting_string->data)+index_in_string,
                       Memory::get_c_str(head->value.pair->first->value.string));
                index_in_string += head->value.pair->first->value.string->length;
                head = head->value.pair->rest;
            }

            return Memory::create_lisp_object_string(resulting_string);
        });

#undef report_error
#undef cLambda
}
