6

[Day 5] - 100 days of code em C

Hoje iniciei o processo de começar a escrever testes e com isso dei uma separada melhor no código, apesar de ainda estar horrível basicamente agora consigo brincar com tópicos do C e ir aprendendo com isso, basicamente criei um register.h para colocar as structs relacionadas aos registradores e suas assinaturas relacionadas:

#pragma once
#include <stdint.h>

typedef struct {
    uint16_t PC;
    uint16_t SP;
    uint8_t A;
    uint8_t B;
    uint8_t C;
    uint8_t D;
    uint8_t E;
    uint8_t F;
    uint8_t H;
    uint8_t L;
} Registers;

uint16_t get_af_register(const Registers* reg);
void set_af_register(Registers* reg, const uint8_t value_a, const uint8_t value_f);

uint16_t get_bc_register(const Registers* reg);
void set_bc_register(Registers* reg, const uint8_t value_b, const uint8_t value_c);

uint16_t get_de_register(const Registers* reg);
void set_de_register(Registers* reg, const uint8_t value_d, const uint8_t value_e);

uint16_t get_hl_register(const Registers* reg);
void set_hl_register(Registers* reg, const uint8_t value_l, const uint8_t value_h);

Depois movi a implementação para o register.c que ontem estava na main.c o que não faz muito sentido:

#include "registers.h"
#include <stdint.h>

uint16_t get_af_register(const Registers* reg) {
    uint16_t virtual_register = (((uint16_t)reg->A) << 8) | reg->F;
    return virtual_register;
}

void set_af_register(Registers* reg, const uint8_t value_a, const uint8_t value_f) {
    reg->A = value_a;
    reg->F = value_f;
}

uint16_t get_bc_register(const Registers* reg) {
    uint16_t virtual_register = (((uint16_t)reg->B) << 8) | reg->C;
    return virtual_register;
}

void set_bc_register(Registers* reg, const uint8_t value_b, const uint8_t value_c) {
    reg->B = value_b;
    reg->C = value_c;
}

uint16_t get_de_register(const Registers* reg) {
    uint16_t virtual_register = (((uint16_t)reg->D) << 8) | reg->E;
    return virtual_register;
}

void set_de_register(Registers* reg, const uint8_t value_d, const uint8_t value_e) {
    reg->D = value_d;
    reg->E = value_e;
}

uint16_t get_hl_register(const Registers* reg) {
    const uint16_t virtual_register = (((uint16_t)reg->H) << 8) | reg->L;
    return virtual_register;
}

void set_hl_register(Registers* reg, const uint8_t value_l, const uint8_t value_h) {
    reg->H = value_h;
    reg->L = value_l;
}

Mais pra frente vai nascer um cpu.c onde isso vai ser efetivamente utilizado, porém nesse momento criei um test_registers.c para provar os conceitos relacionado a libs de testes que eu estava interessado, e isso já demonstra que vai ser uma sidequest muito legal para fazer, me questionei coisas sobre coisas do tipo: como vou fazer para deixar isso generalista o suficiente para poder testar qualquer coisa?, como vou fazer para o texto ficar claro o suficiente para quem for debugar e ter formas diferentes? Como fazer para o uso ser prático?; dito tudo isso YAGNI simplismente fiz o teste simples que eu queria para começar a brincar depois eu penso em fazer algo mais estruturado, nas minhas brincadeiras anteriores eu queria usar o assert() porém ele não retorna booleano e eu não curtir muito dessa forma, então só não liguei pra isso no momento e vou ver como outras pessoas fazem isso depois, no mais sinto que colocar testes no código é o que me faz eu mais brincar e forçar a implementação de formas diferentes para ver o que vai acontecer, na real acho que seria bem maneiro nas linguagens terem coisas como o Learn Go with Tests, muito da cultura de dojôs é algo que tem me ajudado muito a aprender melhor ao longo do tempo, dito isso até o momento a implementação ficou assim:

#include <stdio.h>
#include <assert.h>
#include "registers.h"

void test_af_register() {
    printf("Testing virtual 16 btis registers \n");
    Registers reg = {
        .PC = 0x0000,
        .SP = 0x0000,
        .A = 0x00,
        .B = 0x00,
        .C = 0x00,
        .D = 0x00,
        .E = 0x00,
        .F = 0x00,
        .H = 0x00,
        .L = 0x00,
    };

    printf("    Getters and setters AF with normal values: ");
    set_af_register(&reg, 0xAA, 0x1A);
    uint16_t expected = 0xAA1A;
    uint16_t got = get_af_register(&reg);
    if (got == expected) {
        printf("    PASS\n");
    } else {
        printf("    FAIL\n");
    }

    printf("    Getters and setters AF with zero values: ");
    set_af_register(&reg, 0x00, 0x00);
    expected = 0x0000;
    got = get_af_register(&reg);
    if (got == expected) {
        printf("    PASS\n");
    } else {
        printf("    FAIL\n");
        printf("    expected: 0x%04X, got: 0x%04X\n", expected, got);
    }

    printf("    Getters and setters AF with values greater than 8 bits: ");
    set_af_register(&reg, 0xAAAF, 0x00);
    expected = 0x0000;
    got = get_af_register(&reg);
    if (got == expected) {
        printf("    PASS\n");
    } else {
        printf("    FAIL\n");
        printf("    expected: 0x%04X, got: 0x%04X\n", expected, got);
    }
}

int main() {
    printf("Init tests... \n");
    test_af_register();
    printf("Finish tests... \n");
    return 0;
}

É interessante pois no caso de testes acima de 8 bits por mais que o valor do registrador A seja de 8 bits não há uma quebra por tipagem ou algo do tipo, e o valor recebido é 0xAF00, vou pensar com mais carinho sobre isso depois sobre as implicações disso, mas nesse momento foi só eu forçando a barra tentando ver o que acontece quando brinco com valores estranhos como o próprio NULL ou valores maiores que isso, outras coisas que não fiz foi colocar valores na mão direto usando valores negativos ou coisas do gênero para entender como o C lida com isso e ver se preciso fazer tratamentos. No mais essa forma de fazer testes certamente vai mudar ao longo do tempo principalmente por que gosto bastante de escrever comportamentos no estilo gherkin.

Carregando publicação patrocinada...