[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(®, 0xAA, 0x1A);
uint16_t expected = 0xAA1A;
uint16_t got = get_af_register(®);
if (got == expected) {
printf(" PASS\n");
} else {
printf(" FAIL\n");
}
printf(" Getters and setters AF with zero values: ");
set_af_register(®, 0x00, 0x00);
expected = 0x0000;
got = get_af_register(®);
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(®, 0xAAAF, 0x00);
expected = 0x0000;
got = get_af_register(®);
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.