├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── asm.c ├── bin.c ├── c8c.c ├── emu.c ├── examples ├── Makefile ├── collision.c8 ├── invaders.c8 ├── maze.c8 ├── mul.c8 └── tty.c8 ├── scrots ├── 2018-01-04-003042_512x256_scrot.png ├── 2018-01-04-140651_512x256_scrot.png ├── 2018-01-31-130004_512x256_scrot.png └── c8c.png ├── tasm ├── .gitignore ├── Makefile ├── README ├── flow.asm ├── graphics.asm ├── keypad.asm ├── registers.asm ├── skips.asm ├── storage.asm ├── subroutines.asm └── timers.asm └── tc8c ├── .gitignore ├── Makefile ├── README ├── assignment.c8 ├── branching.c8 ├── logical0.c8 ├── logical1.c8 ├── sizeof.c8 └── while.c8 /.gitignore: -------------------------------------------------------------------------------- 1 | asm 2 | c8c 3 | bin 4 | emu 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Gustav Louw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc -std=c99 2 | 3 | CFLAGS = -Wshadow -Wall -Wpedantic -Wextra 4 | CFLAGS+= -Ofast -march=native 5 | 6 | LDFLAGS = -lSDL2 7 | 8 | all: emu bin asm c8c 9 | make clean -C tasm 10 | make clean -C tc8c 11 | make clean -C examples 12 | make -C tasm 13 | make -C tc8c 14 | make -C examples 15 | 16 | emu: emu.c 17 | $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ 18 | 19 | bin: bin.c 20 | $(CC) $(CFLAGS) $^ -o $@ 21 | 22 | asm: asm.c 23 | $(CC) $(CFLAGS) $^ -o $@ 24 | 25 | c8c: c8c.c 26 | $(CC) $(CFLAGS) $^ -o $@ 27 | 28 | clean: 29 | rm -f c8c 30 | rm -f asm 31 | rm -f bin 32 | rm -f emu 33 | make clean -C tasm 34 | make clean -C tc8c 35 | make clean -C examples 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Screenshot](scrots/c8c.png) 2 | 3 | c8c aims to be a small typeless programming language for the CHIP-8 virtual machine: 4 | 5 | r = { 0x80, 0x40, 0x20, 0x10 }; 6 | l = { 0x20, 0x40, 0x80, 0x10 }; 7 | 8 | main() 9 | { 10 | auto x = 0, xmax = 64, dx = 4; 11 | auto y = 0, ymax = 32, dy = 4; 12 | while(y < ymax) 13 | { 14 | if(rand() & 0x1) 15 | { 16 | draw(x, y, r); 17 | } 18 | else 19 | { 20 | draw(x, y, l); 21 | } 22 | if((x += dx) == xmax) 23 | { 24 | x = 0; 25 | y += dy; 26 | } 27 | } 28 | while(1) 29 | { 30 | // Never leave main. 31 | } 32 | } 33 | 34 | ![Screenshot](scrots/2018-01-04-140651_512x256_scrot.png) 35 | 36 | mul(a, b) 37 | { 38 | if(b == 0) 39 | { 40 | return 0; 41 | } 42 | return a + mul(a, b - 1); 43 | } 44 | 45 | main() 46 | { 47 | putchar(24, 13, mul(9, 9)); 48 | while(1) 49 | { 50 | // Never leave main. 51 | } 52 | } 53 | 54 | ![Screenshot](scrots/2018-01-31-130004_512x256_scrot.png) 55 | 56 | Just run: 57 | 58 | make 59 | 60 | This will build the CHIP-8 virtual machine, binner, assembler, and compiler. 61 | The compiler will then build all the unit tests (tc8c) and example code pieces (examples). 62 | 63 | To run a compiled binary, invoke the virtual machine: 64 | 65 | ./emu examples/maze.bin 66 | 67 | Hit the END key to exit. 68 | 69 | To build your own c8 code, piecewise invoke the toolchain: 70 | 71 | ./c8c main.c8 main.asm 72 | 73 | ./asm main.asm main.hex 74 | 75 | ./bin main.hex main.bin 76 | 77 | ./emu main.bin 78 | -------------------------------------------------------------------------------- /asm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // Input file. 8 | static FILE* fi; 9 | 10 | // Output file. 11 | static FILE* fo; 12 | 13 | // Name of assembly file. 14 | static char* assem; 15 | 16 | // Name of hex file. 17 | static char* hexid; 18 | 19 | // Failure flag goes high if anything went wrong during assembly. 20 | static bool failure; 21 | 22 | // Cleans up files. Uses failure flag to remove output hex file. 23 | static void fshutdown() 24 | { 25 | if(fi) fclose(fi); 26 | if(fo) fclose(fo); 27 | if(failure) 28 | remove(hexid); 29 | } 30 | 31 | // Inits files. 32 | static void finit(char* argv[]) 33 | { 34 | assem = argv[1]; 35 | hexid = argv[2]; 36 | // Input 37 | fi = fopen(argv[1], "r"); 38 | if(fi == NULL) 39 | { 40 | fprintf(stderr, "error: %s does not exist\n", assem); 41 | exit(1); 42 | } 43 | // Output 44 | fo = fopen(argv[2], "w"); 45 | if(fo == NULL) 46 | { 47 | fprintf(stderr, "error: %s cannot be made\n", hexid); 48 | exit(1); 49 | } 50 | atexit(fshutdown); 51 | } 52 | 53 | // Duplicates a string. 54 | static char* dup(char* s) 55 | { 56 | int len = strlen(s) + 1; 57 | char* p = (char*) malloc(len); 58 | return p ? (char*) memcpy(p, s, len) : NULL; 59 | } 60 | 61 | // Rewinds both the input and output files. 62 | static void frewind() 63 | { 64 | rewind(fi); 65 | rewind(fo); 66 | } 67 | 68 | // Binary tree node. 69 | // Keeps track of label names and their addresses. 70 | struct node 71 | { 72 | char* name; 73 | unsigned address; 74 | struct node* l; 75 | struct node* r; 76 | }; 77 | 78 | // New binary tree node. 79 | static struct node* build(char* name, unsigned address) 80 | { 81 | struct node* node = (struct node*) malloc(sizeof(*node)); 82 | node->name = dup(name); 83 | node->address = address; 84 | node->l = NULL; 85 | node->r = NULL; 86 | return node; 87 | } 88 | 89 | // Inserts a new node into a binary tree. 90 | static struct node* insert(struct node* nodes, struct node* node) 91 | { 92 | if(nodes == NULL) 93 | return node; 94 | int difference = strcmp(node->name, nodes->name); 95 | if(difference == 0) 96 | { 97 | free(node->name); 98 | free(node); 99 | failure = true; 100 | } 101 | else if(difference < 0) 102 | nodes->l = insert(nodes->l, node); 103 | else 104 | nodes->r = insert(nodes->r, node); 105 | return nodes; 106 | } 107 | 108 | // Gets a node from a binary tree. Returns NULL if node was not found. 109 | static struct node* get(struct node* nodes, const char* name) 110 | { 111 | if(nodes == NULL) 112 | return NULL; 113 | int difference = strcmp(name, nodes->name); 114 | if(difference == 0) 115 | return nodes; 116 | else if(difference < 0) 117 | return get(nodes->l, name); 118 | else 119 | return get(nodes->r, name); 120 | } 121 | 122 | // Cleans up the binary tree. 123 | static void burn(struct node* nodes) 124 | { 125 | if(nodes == NULL) 126 | return; 127 | burn(nodes->l); 128 | burn(nodes->r); 129 | free(nodes->name); 130 | free(nodes); 131 | } 132 | 133 | static int add(char* operand, struct node* labels) 134 | { 135 | (void) labels; 136 | char* a = strtok(operand, "\t ,"); 137 | char* b = strtok(NULL, "\t "); 138 | // ADD Vx, Vy. 139 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 140 | strlen(b) == 2 && b[0] == 'V' && isxdigit(b[1])) 141 | fprintf(fo, "8%c%c4\n", a[1], b[1]); 142 | // ADD I, Vx. 143 | else 144 | if(strlen(a) == 1 && a[0] == 'I' && 145 | strlen(b) == 2 && b[0] == 'V' && isxdigit(b[1])) 146 | fprintf(fo, "F%c1E\n", b[1]); 147 | // ADD Vx, byte. 148 | else 149 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 150 | strlen(b) == 4 && strncmp(b, "0x", 2) == 0 && 151 | isxdigit(b[2]) && 152 | isxdigit(b[3])) 153 | fprintf(fo, "7%c%c%c\n", a[1], b[2], b[3]); 154 | else 155 | return 1; 156 | return 0; 157 | } 158 | 159 | static int _and(char* operand, struct node* labels) 160 | { 161 | (void) labels; 162 | char* a = strtok(operand, "\t ,"); 163 | char* b = strtok(NULL, "\t "); 164 | // AND Vx, Vy. 165 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 166 | strlen(b) == 2 && b[0] == 'V' && isxdigit(b[1])) 167 | fprintf(fo, "8%c%c2\n", a[1], b[1]); 168 | else 169 | return 1; 170 | return 0; 171 | } 172 | 173 | static int call(char* operand, struct node* labels) 174 | { 175 | char* a = strtok(operand, "\t "); 176 | struct node* found = get(labels, a); 177 | // CALL address. 178 | if(found) 179 | fprintf(fo, "2%03X\n", found->address); 180 | else 181 | return 2; 182 | return 0; 183 | } 184 | 185 | static int cls(char* operand, struct node* labels) 186 | { 187 | (void )operand, (void) labels; 188 | // CLS. 189 | fprintf(fo, "00E0\n"); 190 | return 0; 191 | } 192 | 193 | static int db(char* operand, struct node* labels) 194 | { 195 | (void) labels; 196 | char* a = strtok(operand, "\t "); 197 | // DB (Define Byte). 198 | if(strlen(a) == 4 && strncmp(a, "0x", 2) == 0 && 199 | isxdigit(a[2]) && 200 | isxdigit(a[3])) 201 | fprintf(fo, "%c%c\n", a[2], a[3]); 202 | else 203 | return 1; 204 | return 0; 205 | } 206 | 207 | static int drw(char* operand, struct node* labels) 208 | { 209 | (void) labels; 210 | char* a = strtok(operand, "\t ,"); 211 | char* b = strtok(NULL, "\t ,"); 212 | char* c = strtok(NULL, "\t "); 213 | // DRW Vx, Vy, nibble. 214 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 215 | strlen(b) == 2 && b[0] == 'V' && isxdigit(b[1]) && 216 | strlen(c) == 3 && strncmp(c, "0x", 2) == 0 && isxdigit(c[2])) 217 | fprintf(fo, "D%c%c%c\n", a[1], b[1], c[2]); 218 | else 219 | return 1; 220 | return 0; 221 | } 222 | 223 | static int jp(char* operand, struct node* labels) 224 | { 225 | struct node* found; 226 | char* a = strtok(operand, "\t ,"); 227 | char* b = strtok(NULL, "\t "); 228 | // JP V0, address. 229 | if(strlen(a) == 2 && a[0] == 'V' && a[1] == '0' && 230 | (found = get(labels, b))) 231 | fprintf(fo, "B%03X\n", found->address); 232 | // JP address. 233 | else 234 | if((found = get(labels, a))) 235 | fprintf(fo, "1%03X\n", found->address); 236 | else 237 | return 2; 238 | return 0; 239 | } 240 | 241 | static int ld(char* operand, struct node* labels) 242 | { 243 | struct node* found; 244 | char* a = strtok(operand, "\t ,"); 245 | char* b = strtok(NULL, "\t "); 246 | // LD DT, Vx. 247 | if(strlen(a) == 2 && a[0] == 'D' && a[1] == 'T' && 248 | strlen(b) == 2 && b[0] == 'V' && isxdigit(b[1])) 249 | fprintf(fo, "F%c15\n", b[1]); 250 | // LD ST, Vx. 251 | else 252 | if(strlen(a) == 2 && a[0] == 'S' && a[1] == 'T' && 253 | strlen(b) == 2 && b[0] == 'V' && isxdigit(b[1])) 254 | fprintf(fo, "F%c18\n", b[1]); 255 | // LD F, Vx. 256 | else 257 | if(strlen(a) == 1 && a[0] == 'F' && 258 | strlen(b) == 2 && b[0] == 'V' && isxdigit(b[1])) 259 | fprintf(fo, "F%c29\n", b[1]); 260 | // LD B, Vx. 261 | else 262 | if(strlen(a) == 1 && a[0] == 'B' && 263 | strlen(b) == 2 && b[0] == 'V' && isxdigit(b[1])) 264 | fprintf(fo, "F%c33\n", b[1]); 265 | // LD Vx, DT. 266 | else 267 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 268 | strlen(b) == 2 && b[0] == 'D' && b[1] == 'T') 269 | fprintf(fo, "F%c07\n", a[1]); 270 | // LD Vx, [I]. 271 | else 272 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 273 | strlen(b) == 3 && b[0] == '[' && b[1] == 'I' && b[2] == ']') 274 | fprintf(fo, "F%c65\n", a[1]); 275 | // LD Vx, K. 276 | else 277 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 278 | strlen(b) == 1 && b[0] == 'K') 279 | fprintf(fo, "F%c0A\n", a[1]); 280 | // LD Vx, Vy. 281 | else 282 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 283 | strlen(b) == 2 && b[0] == 'V' && isxdigit(b[1])) 284 | fprintf(fo, "8%c%c0\n", a[1], b[1]); 285 | // LD Vx, byte. 286 | else 287 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 288 | (strlen(b) == 4 && strncmp(b, "0x", 2)) == 0 && 289 | isxdigit(b[2]) && 290 | isxdigit(b[3])) 291 | fprintf(fo, "6%c%c%c\n", a[1], b[2], b[3]); 292 | // LD [I], Vx. 293 | else 294 | if(strlen(a) == 3 && a[0] == '[' && a[1] == 'I' && a[2] == ']' && 295 | strlen(b) == 2 && b[0] == 'V' && isxdigit(b[1])) 296 | fprintf(fo, "F%c55\n", b[1]); 297 | // LD I, address. 298 | else 299 | if(strlen(a) == 1 && a[0] == 'I') 300 | { 301 | if((found = get(labels, b))) 302 | fprintf(fo, "A%03X\n", found->address); 303 | else 304 | return 2; 305 | } 306 | else 307 | return 1; 308 | return 0; 309 | } 310 | 311 | static int _or(char* operand, struct node* labels) 312 | { 313 | (void) labels; 314 | char* a = strtok(operand, "\t ,"); 315 | char* b = strtok(NULL, "\t "); 316 | // OR Vx, Vy. 317 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 318 | strlen(b) == 2 && b[0] == 'V' && isxdigit(b[1])) 319 | fprintf(fo, "8%c%c1\n", a[1], b[1]); 320 | else 321 | return 1; 322 | return 0; 323 | } 324 | 325 | static int ret(char* operand, struct node* labels) 326 | { 327 | (void) operand, (void) labels; 328 | // RET. 329 | fprintf(fo, "00EE\n"); 330 | return 0; 331 | } 332 | 333 | static int rnd(char* operand, struct node* labels) 334 | { 335 | (void) labels; 336 | char* a = strtok(operand, "\t ,"); 337 | char* b = strtok(NULL, "\t "); 338 | // RND Vx, byte. 339 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 340 | strlen(b) == 4 && strncmp(b, "0x", 2) == 0 && 341 | isxdigit(b[2]) && 342 | isxdigit(b[3])) 343 | fprintf(fo, "C%c%c%c\n", a[1], b[2], b[3]); 344 | else 345 | return 1; 346 | return 0; 347 | } 348 | 349 | static int se(char* operand, struct node* labels) 350 | { 351 | (void) labels; 352 | char* a = strtok(operand, "\t ,"); 353 | char* b = strtok(NULL, "\t "); 354 | // SE Vx, Vy. 355 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 356 | strlen(b) == 2 && b[0] == 'V' && isxdigit(b[1])) 357 | fprintf(fo, "5%c%c0\n", a[1], b[1]); 358 | // SE Vx, byte. 359 | else 360 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 361 | strlen(b) == 4 && strncmp(b, "0x", 2) == 0 && 362 | isxdigit(b[2]) && 363 | isxdigit(b[3])) 364 | fprintf(fo, "3%c%c%c\n", a[1], b[2], b[3]); 365 | else 366 | return 1; 367 | return 0; 368 | } 369 | 370 | static int shl(char* operand, struct node* labels) 371 | { 372 | (void) labels; 373 | char* a = strtok(operand, "\t ,"); 374 | char* b = strtok(NULL, "\t "); 375 | // SHL Vx, Vy. 376 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 377 | strlen(b) == 2 && b[0] == 'V' && isxdigit(b[1])) 378 | fprintf(fo, "8%c%cE\n", a[1], b[1]); 379 | else 380 | return 1; 381 | return 0; 382 | } 383 | 384 | static int shr(char* operand, struct node* labels) 385 | { 386 | (void) labels; 387 | char* a = strtok(operand, "\t ,"); 388 | char* b = strtok(NULL, "\t "); 389 | // SHR Vx, Vy. 390 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 391 | strlen(b) == 2 && b[0] == 'V' && isxdigit(b[1])) 392 | fprintf(fo, "8%c%c6\n", a[1], b[1]); 393 | else 394 | return 1; 395 | return 0; 396 | } 397 | 398 | static int sknp(char* operand, struct node* labels) 399 | { 400 | (void) labels; 401 | char* a = strtok(operand, "\t "); 402 | // SKNP Vx. 403 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1])) 404 | fprintf(fo, "E%cA1\n", a[1]); 405 | else 406 | return 1; 407 | return 0; 408 | } 409 | 410 | static int skp(char* operand, struct node* labels) 411 | { 412 | (void) labels; 413 | char* a = strtok(operand, "\t "); 414 | // SKP Vx. 415 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1])) 416 | fprintf(fo, "E%c9E\n", a[1]); 417 | else 418 | return 1; 419 | return 0; 420 | } 421 | 422 | static int sne(char* operand, struct node* labels) 423 | { 424 | (void) labels; 425 | char* a = strtok(operand, "\t ,"); 426 | char* b = strtok(NULL, "\t "); 427 | // SNE Vx, Vy. 428 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 429 | strlen(b) == 2 && b[0] == 'V' && isxdigit(b[1])) 430 | fprintf(fo, "9%c%c0\n", a[1], b[1]); 431 | // SNE Vx, byte. 432 | else 433 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 434 | strlen(b) == 4 && strncmp(b, "0x", 2) == 0 && 435 | isxdigit(b[2]) && 436 | isxdigit(b[3])) 437 | fprintf(fo, "4%c%c%c\n", a[1], b[2], b[3]); 438 | else 439 | return 1; 440 | return 0; 441 | } 442 | 443 | static int sub(char* operand, struct node* labels) 444 | { 445 | (void) labels; 446 | char* a = strtok(operand, "\t ,"); 447 | char* b = strtok(NULL, "\t "); 448 | // SUB Vx, Vy. 449 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 450 | strlen(b) == 2 && b[0] == 'V' && isxdigit(b[1])) 451 | fprintf(fo, "8%c%c5\n", a[1], b[1]); 452 | else 453 | return 1; 454 | return 0; 455 | } 456 | 457 | static int subn(char* operand, struct node* labels) 458 | { 459 | (void) labels; 460 | char* a = strtok(operand, "\t ,"); 461 | char* b = strtok(NULL, "\t "); 462 | // SUBN Vx, Vy. 463 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 464 | strlen(b) == 2 && b[0] == 'V' && isxdigit(b[1])) 465 | fprintf(fo, "8%c%c7\n", a[1], b[1]); 466 | else 467 | return 1; 468 | return 0; 469 | } 470 | 471 | static int _xor(char* operand, struct node* labels) 472 | { 473 | (void) labels; 474 | char* a = strtok(operand, "\t ,"); 475 | char* b = strtok(NULL, "\t "); 476 | // XOR Vx, Vy. 477 | if(strlen(a) == 2 && a[0] == 'V' && isxdigit(a[1]) && 478 | strlen(b) == 2 && b[0] == 'V' && isxdigit(b[1])) 479 | fprintf(fo, "8%c%c3\n", a[1], b[1]); 480 | else 481 | return 1; 482 | return 0; 483 | } 484 | 485 | static int (*functions[])(char* operand, struct node* labels) = { 486 | add, _and, call, cls, db, drw, jp, ld, _or, ret, rnd, se, 487 | shl, shr, sknp, skp, sne, sub, subn, _xor 488 | }; 489 | 490 | static const char* mnemonics[] = { 491 | "ADD","AND","CALL","CLS","DB","DRW","JP","LD","OR","RET","RND","SE", 492 | "SHL","SHR","SKNP","SKP","SNE","SUB","SUBN","XOR" 493 | }; 494 | 495 | static int compare(const void* a, const void* b) 496 | { 497 | return strcmp((char*)a, *(char**)b); 498 | } 499 | 500 | static int assemble(char* mnemonic, char* operand, struct node* labels) 501 | { 502 | const char** found = (const char**) bsearch( 503 | mnemonic, 504 | mnemonics, 505 | sizeof(mnemonics) / sizeof(*mnemonics), 506 | sizeof(*mnemonics), 507 | compare); 508 | if(!found) 509 | return 3; 510 | return (*functions[found - mnemonics])(operand, labels); 511 | } 512 | 513 | static struct node* scan(struct node* labels) 514 | { 515 | unsigned address = 0x0202; 516 | const bool growing = labels == NULL; 517 | char line[320]; 518 | for(unsigned linenumber = 1; fgets(line, sizeof(line), fi); linenumber++) 519 | { 520 | char* label; 521 | char* mnemonic; 522 | char* operand; 523 | char* newline; 524 | char* semicolon; 525 | char* colon; 526 | int error = 0; 527 | newline = strchr(line, '\n'); 528 | if(newline) 529 | *newline = '\0'; 530 | semicolon = strchr(line, ';'); 531 | if(semicolon) 532 | *semicolon = '\0'; 533 | colon = strchr(line, ':'); 534 | if(colon) 535 | { 536 | label = strtok(line, "\t :"); 537 | if(growing) 538 | { 539 | labels = insert(labels, build(label, address)); 540 | if(failure) 541 | { 542 | fprintf(stderr, "error: line %d: %s already defined\n", linenumber, label); 543 | exit(1); 544 | } 545 | } 546 | } 547 | mnemonic = strtok(colon ? NULL : line, "\t "); 548 | operand = strtok(NULL, ""); 549 | if(mnemonic) 550 | { 551 | if(growing) 552 | address += strcmp(mnemonic, "DB") == 0 ? 0x0001 : 0x0002; 553 | else 554 | error = assemble(mnemonic, operand, labels); 555 | } 556 | if(error) 557 | { 558 | const char* types[] = { 559 | /* 0 */ "no error", 560 | /* 1 */ "operand formatting", 561 | /* 2 */ "label not found", 562 | /* 3 */ "unsupported chip8 mnemonic" 563 | }; 564 | failure = true; 565 | fprintf(stderr, "error: line %d: %s\n", linenumber, types[error]); 566 | exit(1); 567 | } 568 | } 569 | return labels; 570 | } 571 | 572 | // Generates the reset vector for the entry label. 573 | static void rvec(struct node* labels, const char* entry) 574 | { 575 | struct node* reset = get(labels, entry); 576 | if(!reset) 577 | { 578 | fprintf(stderr, "error: entry point %s not found\n", entry); 579 | exit(1); 580 | } 581 | fprintf(fo, "1%03X\n", reset->address); 582 | } 583 | 584 | int main(int argc, char* argv[]) 585 | { 586 | if(argc != 3) 587 | { 588 | fprintf(stderr, "expected input and output arguments"); 589 | exit(1); 590 | } 591 | finit(argv); 592 | // First pass. 593 | struct node* labels = NULL; 594 | labels = scan(labels); 595 | frewind(); 596 | // Second pass. 597 | rvec(labels, "main"); 598 | scan(labels); 599 | burn(labels); 600 | exit(0); 601 | } 602 | -------------------------------------------------------------------------------- /bin.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static int hex(const int c) 6 | { 7 | switch(c) 8 | { 9 | case '0': return 0x0; 10 | case '1': return 0x1; 11 | case '2': return 0x2; 12 | case '3': return 0x3; 13 | case '4': return 0x4; 14 | case '5': return 0x5; 15 | case '6': return 0x6; 16 | case '7': return 0x7; 17 | case '8': return 0x8; 18 | case '9': return 0x9; 19 | case 'A': return 0xA; 20 | case 'B': return 0xB; 21 | case 'C': return 0xC; 22 | case 'D': return 0xD; 23 | case 'E': return 0xE; 24 | case 'F': return 0xF; 25 | default: 26 | fprintf(stderr, "unknown char '%c' during conversion\n", c); 27 | exit(1); 28 | } 29 | } 30 | 31 | static int bin(const int lo, const int hi) 32 | { 33 | return (hex(lo) << 4) | hex(hi); 34 | } 35 | 36 | int main(int argc, char* argv[]) 37 | { 38 | if(argc != 3) 39 | { 40 | fprintf(stderr, "expected input and output arguments"); 41 | exit(1); 42 | } 43 | FILE* const fi = fopen(argv[1], "r"); 44 | FILE* const fo = fopen(argv[2], "w"); 45 | for(int c = fgetc(fi); c != EOF; c = fgetc(fi)) 46 | { 47 | if(isspace(c)) 48 | continue; 49 | fprintf(fo, "%c", bin(c, fgetc(fi))); 50 | } 51 | fclose(fi); 52 | fclose(fo); 53 | } 54 | -------------------------------------------------------------------------------- /c8c.c: -------------------------------------------------------------------------------- 1 | // ____ ___ ____ 2 | // / __) ( _ ) / __) 3 | // ( (__ / _ \ ( (__ 4 | // \____) \___/ \____) 5 | // 6 | // C8C is a highly portable C-like compiler for the chip8 platform. 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | static char* term(); 17 | 18 | static void expression(char* overrider, const bool shortable); 19 | 20 | static void dblock(); 21 | 22 | // Variable names. 23 | static int v; 24 | 25 | static char* vars[16]; 26 | 27 | // Array names and function names. 28 | static int l; 29 | 30 | static struct label 31 | { 32 | char* name; 33 | int args; 34 | int height; 35 | } 36 | labels[128]; 37 | 38 | // Counter for branches. 39 | static int branch; 40 | 41 | // The current input file character. 42 | static int now; 43 | 44 | // The line number. 45 | static int nline = 1; 46 | 47 | // Line max length. 48 | static const int lmax = 512; 49 | 50 | // Input file (c8). 51 | static FILE* fi; 52 | 53 | // Output file (asm). 54 | static FILE* fo; 55 | 56 | // Input file name. 57 | static char* c8src; 58 | 59 | // Output file name. 60 | static char* assem; 61 | 62 | // Line buffer. 63 | static char* line; 64 | 65 | // Failure signaler raised by bomb(). Used to remove .c8 file at cleanup. 66 | static bool failure; 67 | 68 | // Writes to the output file. A new line is included. 69 | static void print(const char* msg, ...) 70 | { 71 | va_list args; 72 | va_start(args, msg); 73 | vfprintf(fo, msg, args); 74 | fprintf(fo, "\n"); 75 | va_end(args); 76 | } 77 | 78 | // Writes to standard error. A newline is included. 79 | static void bomb(const char* msg, ...) 80 | { 81 | va_list args; 82 | va_start(args, msg); 83 | fprintf(stderr, "error: line %d: ", nline); 84 | vfprintf(stderr, msg, args); 85 | fprintf(stderr, "\n"); 86 | va_end(args); 87 | failure = true; 88 | exit(1); 89 | } 90 | 91 | // Buffers a new character from the input file. 92 | static void buffer() 93 | { 94 | static int reads; 95 | if(reads == lmax - 1) 96 | bomb("line too long"); 97 | line[reads++] = now == '\n' ? '\0' : now; 98 | if(now == '\n') 99 | { 100 | nline++; 101 | reads = 0; 102 | print(";%s", line); 103 | } 104 | } 105 | 106 | // Gets a new charcter from the input file. Does not ignore comments. 107 | static void step() 108 | { 109 | now = fgetc(fi); 110 | buffer(); 111 | } 112 | 113 | // Removes all variable names. 114 | static void reset() 115 | { 116 | for(int i = 0; i < v; i++) 117 | { 118 | free(vars[i]); 119 | vars[i] = NULL; 120 | } 121 | v = 0; 122 | } 123 | 124 | // Removes all label names. 125 | static void kill() 126 | { 127 | for(int i = 0; i < l; i++) 128 | { 129 | free(labels[i].name); 130 | labels[i].name = NULL; 131 | } 132 | l = 0; 133 | } 134 | 135 | // Gets a new charcter from the input file. Ignores (//) style comments. 136 | static void next() 137 | { 138 | step(); 139 | if(now == '/') 140 | { 141 | step(); 142 | if(now != '/') 143 | bomb("expected '/'"); 144 | while(now != '\n') 145 | step(); 146 | } 147 | } 148 | 149 | // Skips sequential white space. 150 | static void skip() 151 | { 152 | while(isspace(now)) next(); 153 | } 154 | 155 | // Duplicates a string. 156 | static char* dup(const char* s) 157 | { 158 | return strcpy((char*) malloc(strlen(s) + 1), s); 159 | } 160 | 161 | // Shuts down everything. Removes assembly file if something went wrong. 162 | static void shutdown() 163 | { 164 | reset(); 165 | kill(); 166 | free(line); 167 | if(fi) fclose(fi); 168 | if(fo) fclose(fo); 169 | if(failure) 170 | remove(assem); 171 | } 172 | 173 | // Initializes everything. 174 | static void init(char* argv[]) 175 | { 176 | struct label baked[] = { 177 | { dup("draw" ), 3, 0 }, 178 | { dup("putchar"), 3, 0 }, 179 | { dup("rand" ), 0, 0 }, 180 | { dup("getchar"), 0, 0 }, 181 | { dup("cls" ), 0, 0 }, 182 | { dup("sizeof" ), 1, 0 }, 183 | }; 184 | for(unsigned i = 0; i < sizeof(baked) / sizeof(*baked); i++) 185 | labels[l++] = baked[i]; 186 | c8src = argv[1]; 187 | assem = argv[2]; 188 | // Input file. 189 | fi = fopen(argv[1], "r"); 190 | if(fi == NULL) 191 | { 192 | fprintf(stderr, "error: %s does not exist\n", c8src); 193 | exit(1); 194 | } 195 | // Output file. 196 | fo = fopen(argv[2], "w"); 197 | if(fo == NULL) 198 | { 199 | fprintf(stderr, "error: %s cannot be made\n", assem); 200 | exit(1); 201 | } 202 | line = (char*) malloc(lmax * sizeof(char)); 203 | next(); 204 | skip(); 205 | atexit(shutdown); 206 | } 207 | 208 | // Returns true if two strings match, else false. 209 | static bool eql(const char* a, const char* b) 210 | { 211 | if(a == NULL || b == NULL) 212 | return 0; 213 | return strcmp(a, b) == 0; 214 | } 215 | 216 | // Retrusn a string of the 'now' character. 217 | static char* peeks() 218 | { 219 | char n[] = { (char) now, '\0' }; 220 | return dup(n); 221 | } 222 | 223 | // Returns true if 'now' at the end of an expression. 224 | static bool isendexpr() 225 | { 226 | return now == ';' || now == ')' || now == ']' || now == ','; 227 | } 228 | 229 | // Returns true if 'now' at end of operator. 230 | static bool isendop() 231 | { 232 | return isalnum(now) || isspace(now) || isendexpr(); 233 | } 234 | 235 | // Skips all white space and comments, then checks if 'now' matches what is expected. 236 | // Feed is advanced. 237 | static void match(const int c) 238 | { 239 | skip(); 240 | if(now != c) 241 | bomb("expected '%c'", c); 242 | next(); 243 | } 244 | 245 | // Returns a string name. 246 | static char* name() 247 | { 248 | skip(); 249 | // First char must be alpha or underscore 250 | if(!(now == '_' || isalpha(now))) 251 | bomb("names must start with underscores or alpha chars"); 252 | const int size = 128; 253 | char* name = (char*) malloc(size * sizeof(char)); 254 | int i = 0; 255 | // But name can contain alpha an numeric chars. 256 | while(isalnum(now) || now == '_') 257 | { 258 | if(i == size - 1) 259 | bomb("name too long"); 260 | name[i++] = now; 261 | next(); 262 | } 263 | name[i] = '\0'; 264 | return name; 265 | } 266 | 267 | // Returns a string digit. 268 | static char* dig() 269 | { 270 | skip(); 271 | if(!isdigit(now)) 272 | bomb("expected value"); 273 | const int size = 128; 274 | char* d = (char*) malloc(size * sizeof(char)); 275 | int i = 0; 276 | while(isdigit(now) || isxdigit(now) || tolower(now) == 'x') 277 | { 278 | if(i == size - 1) 279 | bomb("digit too long"); 280 | d[i++] = now; 281 | next(); 282 | } 283 | d[i] = '\0'; 284 | return d; 285 | } 286 | 287 | // Returns a string operator. 288 | static char* op() 289 | { 290 | skip(); 291 | const int size = 128; 292 | char* o = (char*) malloc(size * sizeof(char)); 293 | int i = 0; 294 | while(!isendop()) 295 | { 296 | if(i == size - 1) 297 | bomb("operator too long"); 298 | o[i++] = now; 299 | next(); 300 | } 301 | o[i] = '\0'; 302 | return o; 303 | } 304 | 305 | // Increments the v register. 306 | static void incv() 307 | { 308 | if(++v == 0xE) bomb("register stack overflow"); 309 | } 310 | 311 | // Decrements the v register. 312 | static void decv() 313 | { 314 | if(--v < 0x00) bomb("register stack underflow"); 315 | } 316 | 317 | // Removes 'n' variable names. 318 | static void pop(const int n) 319 | { 320 | for(int i = 0; i < n; i++) 321 | { 322 | decv(); 323 | free(vars[v]); 324 | vars[v] = NULL; 325 | } 326 | } 327 | 328 | // Returns a positive integer if a label name was found. 329 | static int find(const char* name) 330 | { 331 | for(int i = 0; i < l; i++) 332 | if(eql(name, labels[i].name)) 333 | return i; 334 | return -1; 335 | } 336 | 337 | // Returns the v-register number of a declared variable. 338 | // Exits if not found. 339 | static int var(char* name) 340 | { 341 | for(int i = 0; i < v; i++) 342 | if(eql(name, vars[i])) 343 | return i; 344 | bomb("variable name '%s' not defined", name); 345 | return 0; /* Keep compiler quiet. */ 346 | } 347 | 348 | // Returns true if a name is already declared, else exits. 349 | // Note that this checks both automatic variables and label names. 350 | static bool isndef(char* name) 351 | { 352 | // Checks vars. 353 | for(int i = 0; i < v; i++) 354 | if(eql(name, vars[i])) 355 | bomb("name '%s' already defined", name); 356 | // Checks labels. 357 | for(int i = 0; i < l; i++) 358 | if(eql(name, labels[i].name)) 359 | bomb("label '%s' already defined", name); 360 | return true; 361 | } 362 | 363 | // Turns a string digit into a long digit. 364 | // All integers are unsigned byte sized and thus clamped 0-255 inclusive. 365 | static uint8_t tobyte(const char* value) 366 | { 367 | return strtoul(value, NULL, 0) % 256; 368 | } 369 | 370 | // Generate negation (-). 371 | static void gneg() 372 | { 373 | print("\tLD VF,0xFF"); 374 | print("\tXOR V%1X,VF", v); 375 | print("\tADD V%1X,0x01", v); 376 | } 377 | 378 | // Generate bitwise inversion (~). 379 | static void ginv() 380 | { 381 | print("\tLD VF,0xFF"); 382 | print("\tXOR V%1X,VF", v); 383 | } 384 | 385 | // Generate logical not (!). 386 | static void gnotl() 387 | { 388 | // Converts a positive number into logical 1. 389 | print("\tSE V%1X,0x00", v); 390 | print("\tLD V%1X,0x01", v); 391 | // Xor the number with logical 1. 392 | print("\tLD VF,0x01"); 393 | print("\tXOR V%1X,VF", v); 394 | } 395 | 396 | // Generate addition (+). 397 | static void gadd() 398 | { 399 | print("\tADD V%1X,V%1X", v - 1, v); 400 | } 401 | 402 | // Generate subtraction (-). 403 | static void gsub() 404 | { 405 | print("\tSUB V%1X,V%1X", v - 1, v); 406 | } 407 | 408 | // Generate bitwise and (&). 409 | static void gand() 410 | { 411 | print("\tAND V%1X,V%1X", v - 1, v); 412 | } 413 | 414 | // Generate bitwise or (|). 415 | static void gor() 416 | { 417 | print("\tOR V%1X,V%1X", v - 1, v); 418 | } 419 | 420 | // Generate bitwise xor (^). 421 | static void gxor() 422 | { 423 | print("\tXOR V%1X,V%1X", v - 1, v); 424 | } 425 | 426 | // Generate move. 427 | static void gmove() 428 | { 429 | print("\tLD V%1X,V%1X", v - 1, v); 430 | } 431 | 432 | // Generate not equal to (!=). 433 | static void gneqlto() 434 | { 435 | print("\tLD VF,0x00"); 436 | print("\tSE V%1X,V%1X", v - 1, v); 437 | print("\tLD VF,0x01"); 438 | print("\tLD V%1X,VF", v - 1); 439 | } 440 | 441 | // Generate equal to (==). 442 | static void geqlto() 443 | { 444 | print("\tLD VF,0x01"); 445 | print("\tSE V%1X,V%1X", v - 1, v); 446 | print("\tLD VF,0x00"); 447 | print("\tLD V%1X,VF", v - 1); 448 | } 449 | 450 | // Generate less than or equal to (<=). 451 | static void glteqlto() 452 | { 453 | print("\tSUBN V%1X,V%1X", v - 1, v); 454 | print("\tLD V%1X,VF", v - 1); 455 | } 456 | 457 | // Generate greater than or equal to (>=). 458 | static void ggteqlto() 459 | { 460 | print("\tSUB V%1X,V%1X", v - 1, v); 461 | print("\tLD V%1X,VF", v - 1); 462 | } 463 | 464 | // Generate less than (<). 465 | static void glt() 466 | { 467 | ggteqlto(); 468 | print("\tLD VF,0x01"); 469 | print("\tXOR V%1X,VF", v - 1); 470 | } 471 | 472 | // Generate greater than (>). 473 | static void ggt() 474 | { 475 | glteqlto(); 476 | print("\tLD VF,0x01"); 477 | print("\tXOR V%1X,VF", v - 1); 478 | } 479 | 480 | // Generate copy. 481 | static void gcp(char* ta) 482 | { 483 | print("\tLD V%1X,V%1X", var(ta), v - 1); 484 | } 485 | 486 | // Generate equal (=). 487 | static void geql(char* ta) 488 | { 489 | gmove(); 490 | gcp(ta); 491 | } 492 | 493 | // Generate add equal (+=). 494 | static void gaddeql(char* ta) 495 | { 496 | gadd(); 497 | gcp(ta); 498 | } 499 | 500 | // Generate subtract equal (-=) 501 | static void gsubeql(char* ta) 502 | { 503 | gsub(); 504 | gcp(ta); 505 | } 506 | 507 | // Generate xor equal (^=) 508 | static void gxoreql(char* ta) 509 | { 510 | gxor(); 511 | gcp(ta); 512 | } 513 | 514 | // Generate and equal (&=) 515 | static void gandeql(char* ta) 516 | { 517 | gand(); 518 | gcp(ta); 519 | } 520 | 521 | // Generate or equal (|=) 522 | static void goreql(char* ta) 523 | { 524 | gor(); 525 | gcp(ta); 526 | } 527 | 528 | // Generate load name. 529 | static void glname(char* ta) 530 | { 531 | print("\tLD V%1X,V%1X", v, var(ta)); 532 | } 533 | 534 | // Generate load digit. 535 | static void gldig(char* tb) 536 | { 537 | print("\tLD V%1X,0x%02X", v, tobyte(tb)); 538 | } 539 | 540 | // Generate frame push. 541 | static void gfpush() 542 | { 543 | print("\tLD F,VE"); 544 | print("\tLD [I],VE"); 545 | print("\tLD VF,0x03"); 546 | print("\tADD VE,VF"); 547 | } 548 | 549 | // Generate frame pop. 550 | static void gfpop() 551 | { 552 | print("\tLD VF,0x03"); 553 | print("\tSUB VE,VF"); 554 | print("\tLD VF,V%1X", v); 555 | print("\tLD F,VE"); 556 | print("\tLD VE,[I]"); 557 | print("\tRET"); 558 | } 559 | 560 | // Unary not. 561 | static char* notl() 562 | { 563 | match('!'); 564 | char* ta = term(); 565 | gnotl(); 566 | return ta; 567 | } 568 | 569 | // Unary positive. 570 | static char* pos() 571 | { 572 | match('+'); 573 | if(now == '+') 574 | bomb("operator '++' not supported"); 575 | return term(); 576 | } 577 | 578 | // Unary bitwise invert. 579 | static char* inv() 580 | { 581 | match('~'); 582 | char* ta = term(); 583 | ginv(); 584 | return ta; 585 | } 586 | 587 | // Unary negate. 588 | static char* neg() 589 | { 590 | match('-'); 591 | if(now == '-') 592 | bomb("operator '--' not supported"); 593 | char* ta = term(); 594 | gneg(); 595 | return ta; 596 | } 597 | 598 | // Force an expression. 599 | static char* fexp() 600 | { 601 | match('('); 602 | expression(NULL, true); 603 | match(')'); 604 | return dup(")"); 605 | } 606 | 607 | // Load digit. 608 | static char* ldig() 609 | { 610 | char* tb = dig(); 611 | gldig(tb); 612 | return tb; 613 | } 614 | 615 | // Saves a copy of this frame then moves this frame to next. 616 | static void move(const int args) 617 | { 618 | gfpush(); 619 | for(int i = 0; i < args; i++) 620 | print("\tLD V%1X,V%1X", i, v + i); 621 | } 622 | 623 | // Return. 624 | static void sret() 625 | { 626 | expression(NULL, true); 627 | match(';'); 628 | gfpop(); 629 | } 630 | 631 | // Get character. 632 | static void getchr() 633 | { 634 | print("getchar:"); 635 | print("\tLD V0,0xFF"); // Return value. 636 | print("\tLD V1,0x00\n\tSKNP V1\n\tLD V0,0x00"); 637 | print("\tLD V1,0x01\n\tSKNP V1\n\tLD V0,0x01"); 638 | print("\tLD V1,0x02\n\tSKNP V1\n\tLD V0,0x02"); 639 | print("\tLD V1,0x03\n\tSKNP V1\n\tLD V0,0x03"); 640 | print("\tLD V1,0x04\n\tSKNP V1\n\tLD V0,0x04"); 641 | print("\tLD V1,0x05\n\tSKNP V1\n\tLD V0,0x05"); 642 | print("\tLD V1,0x06\n\tSKNP V1\n\tLD V0,0x06"); 643 | print("\tLD V1,0x07\n\tSKNP V1\n\tLD V0,0x07"); 644 | print("\tLD V1,0x08\n\tSKNP V1\n\tLD V0,0x08"); 645 | print("\tLD V1,0x09\n\tSKNP V1\n\tLD V0,0x09"); 646 | print("\tLD V1,0x0A\n\tSKNP V1\n\tLD V0,0x0A"); 647 | print("\tLD V1,0x0B\n\tSKNP V1\n\tLD V0,0x0B"); 648 | print("\tLD V1,0x0C\n\tSKNP V1\n\tLD V0,0x0C"); 649 | print("\tLD V1,0x0D\n\tSKNP V1\n\tLD V0,0x0D"); 650 | print("\tLD V1,0x0E\n\tSKNP V1\n\tLD V0,0x0E"); 651 | print("\tLD V1,0x0F\n\tSKNP V1\n\tLD V0,0x0F"); 652 | // Done, restore return value. 653 | gfpop(); 654 | } 655 | 656 | // Put charater. 657 | static void putchr() 658 | { 659 | const int spacing = 1; 660 | const int width = 4; 661 | print("putchar:"); 662 | // Shift up: V0, V1, V2 will be populated by I, I+1, I+2. 663 | print("\tLD V5,V2"); // N 664 | print("\tLD V4,V1"); // Y 665 | print("\tLD V3,V0"); // X 666 | // V6 will serve as a collision flag. 667 | print("\tLD V6,0x00"); 668 | print("\tLD B,V5"); 669 | print("\tLD V2,[I]"); 670 | // First. 671 | print("\tLD F,V0"); 672 | print("\tDRW V3,V4,0x5"); 673 | print("\tOR V6,VF"); 674 | // Second. 675 | print("\tLD F,V1"); 676 | print("\tADD V3,0x%02X", width + spacing); 677 | print("\tDRW V3,V4,0x5"); 678 | print("\tOR V6,VF"); 679 | // Third. 680 | print("\tLD F,V2"); 681 | print("\tADD V3,0x%02X", width + spacing); 682 | print("\tDRW V3,V4,0x5"); 683 | print("\tOR V6,VF"); 684 | // Done. Restore return value. 685 | v = 6; 686 | gfpop(); 687 | } 688 | 689 | // Draw. Hardcoded inline for performance. 690 | static void draw() 691 | { 692 | const int args = 2; 693 | // The first two arguments are expressions for x, y. 694 | for(int i = 0; i < args; i++) 695 | { 696 | expression(NULL, true); 697 | incv(); 698 | match(','); 699 | } 700 | // The third argument is a label name for the sprite to draw. 701 | // Draw is the only function that can take a label as an argument. 702 | char* label = name(); 703 | const int index = find(label); 704 | if(index == -1) 705 | bomb("label '%s' not defined", label); 706 | print("\tLD I,%s", labels[index].name); 707 | // Optionally, appending square brackets to the label will index the sprite. 708 | skip(); 709 | if(now == '[') 710 | { 711 | const int b = branch++; 712 | match('['); 713 | expression(NULL, true); 714 | match(']'); 715 | print("WHILE%d:", b); 716 | print("\tSNE V%X,0x00", v); 717 | print("\tJP OUT%d", b); 718 | print("\tLD VF,0x%02X", labels[index].args); 719 | print("\tADD I,VF"); 720 | print("\tLD VF,0x01"); 721 | print("\tSUB V%X,VF", v); 722 | print("\tJP WHILE%d", b); 723 | print("OUT%d:", b); 724 | } 725 | print("\tDRW V%1X,V%1X,0x%1X", v - 2, v - 1, labels[index].args); 726 | free(label); 727 | v -= args; 728 | } 729 | 730 | // Clear screen. Inlined for performance. 731 | static void clear() 732 | { 733 | print("\tCLS"); 734 | } 735 | 736 | // Random number. Inlined for performance. 737 | static void rnd() 738 | { 739 | print("\tRND VF,0xFF"); 740 | } 741 | 742 | // Generate function call. 743 | static void gfcall(const char* name) 744 | { 745 | const int i = find(name); 746 | if(i == -1) 747 | bomb("function '%s' not defined", name); 748 | int args = 0; 749 | while(now != ')') 750 | { 751 | expression(NULL, true); 752 | incv(); 753 | args++; 754 | skip(); 755 | if(now == ',') 756 | match(','); 757 | else 758 | if(now == ')') 759 | break; 760 | else bomb("unknown symbol in argument list"); 761 | } 762 | if(labels[i].args != args) 763 | bomb("argument mismatch when calling '%s'", name); 764 | v -= args; 765 | move(args); 766 | print("\tCALL %s", name); 767 | } 768 | 769 | // Generate size. 770 | static void szof() 771 | { 772 | char* n = name(); 773 | const int index = find(n); 774 | if(index == -1) 775 | bomb("expected label but got %s", n); 776 | free(n); 777 | skip(); 778 | if(now == '[') 779 | { 780 | match('['); 781 | match(']'); 782 | print("\tLD VF,0x%02X", labels[index].height); 783 | } 784 | else 785 | print("\tLD VF,0x%02X", labels[index].args); 786 | } 787 | 788 | // Function call. 789 | static void fcall(const char* name) 790 | { 791 | match('('); 792 | // These built in functions that will inline. 793 | eql(name, "draw") ? draw() : 794 | eql(name, "sizeof") ? szof() : 795 | eql(name, "rand") ? rnd() : 796 | eql(name, "clear") ? clear() : gfcall(name); 797 | match(')'); 798 | // Load return value. 799 | print("\tLD V%1X,VF", v); 800 | } 801 | 802 | // Returns true if the string is a token. 803 | static bool istoken(char* s) 804 | { 805 | return eql(s, "while") || eql(s, "if") || eql(s, "auto") || eql(s, "return"); 806 | } 807 | 808 | // Load name. 809 | static char* lname() 810 | { 811 | char* ta = name(); 812 | if(istoken(ta)) 813 | return ta; 814 | skip(); 815 | now == '(' ? fcall(ta) : glname(ta); 816 | return ta; 817 | } 818 | 819 | // Terms may start with a prefix modifier, an alpha for a name load 820 | // or a digit for a digit load. A term may be forced into an expression 821 | // with surrounding round brackets. 822 | static char* term() 823 | { 824 | skip(); 825 | return 826 | // Prefix modifiers. 827 | now == '~' ? inv () : 828 | now == '+' ? pos () : 829 | now == '!' ? notl () : 830 | now == '-' ? neg () : 831 | // Name load. 832 | isalpha(now) ? lname() : 833 | // Digit load. 834 | isdigit(now) ? ldig () : 835 | // Term is expression (enclosed in brackets). 836 | now == '(' ? fexp() : peeks(); 837 | /* Returns the string in all cases, even if nothing was done. */ 838 | } 839 | 840 | // Operate. 841 | // Operators work on chained variables from V0 to VD (inclusive). 842 | static void operate(char* o, char* t) 843 | { 844 | eql(o, "=" ) ? geql (t) : // Assignment. 845 | eql(o, "+=") ? gaddeql (t) : // Assignment. 846 | eql(o, "-=") ? gsubeql (t) : // Assignment. 847 | eql(o, "^=") ? gxoreql (t) : // Assignment. 848 | eql(o, "&=") ? gandeql (t) : // Assignment. 849 | eql(o, "|=") ? goreql (t) : // Assignment. 850 | eql(o, "+" ) ? gadd () : // Chain. 851 | eql(o, "-" ) ? gsub () : // Chain. 852 | eql(o, "&" ) ? gand () : // Chain. 853 | eql(o, "^" ) ? gxor () : // Chain. 854 | eql(o, "|" ) ? gor () : // Chain. 855 | eql(o, "<" ) ? glt () : // Comparison. 856 | eql(o, "<=") ? glteqlto () : // Comparison. 857 | eql(o, ">=") ? ggteqlto () : // Comparison. 858 | eql(o, ">" ) ? ggt () : // Comparison. 859 | eql(o, "!=") ? gneqlto () : // Comparison. 860 | eql(o, "==") ? geqlto () : // Comparison. 861 | eql(o, "||") ? gmove () : // Logical. 862 | eql(o, "&&") ? gmove () : // Logical. 863 | eql(o, "!" ) ? gnotl () : // Unary. 864 | bomb("unknown operator '%s'", o); 865 | /* Exits if operator is not found. */ 866 | } 867 | 868 | // Names start with alpha characters or underscores. 869 | static bool isname(const char* s) 870 | { 871 | if(s == NULL) 872 | bomb("derefereced a null pointer."); 873 | return isalpha(s[0]) || s[0] == '_'; 874 | } 875 | 876 | // TA O TB O TA ... 877 | static void expression(char* overrider, const bool shortable) 878 | { 879 | // Assume term is not an L-value. 880 | bool lvalue = false; 881 | // Goes high when using logical operators. 882 | bool shorting = false; 883 | const int b = branch++; 884 | char* ta = overrider ? overrider : term(); 885 | // If term is a name then it is an L-value. 886 | if(isname(ta)) 887 | lvalue = true; 888 | // If the name is a label then it is not an L-value. 889 | if(find(ta) != -1) 890 | lvalue = false; 891 | skip(); 892 | while(!isendexpr()) 893 | { 894 | incv(); 895 | char* o = op(); 896 | // If the operator is an assignment operator, then 897 | // a new expression is computed. lvalue is first checked. 898 | if(eql(o, "=" ) 899 | || eql(o, "+=") 900 | || eql(o, "-=") 901 | || eql(o, "^=") 902 | || eql(o, "&=") 903 | || eql(o, "|=")) 904 | { 905 | if(lvalue == false) 906 | bomb("expected lvalue to the left of operator '%s'", o); 907 | expression(NULL, true); 908 | } 909 | // If the operator is a comparison operator then a new expression is computed. 910 | else if( 911 | eql(o, "<" ) 912 | || eql(o, "<=") 913 | || eql(o, ">=") 914 | || eql(o, ">" ) 915 | || eql(o, "!=") 916 | || eql(o, "==")) 917 | expression(NULL, true); 918 | // If the operator is a short circuit operator a new 919 | // expression is computed. Given this, the expression will turn 920 | // logical at the end of each subsequent expression. 921 | else if( 922 | eql(o, "||") 923 | || eql(o, "&&")) 924 | { 925 | shorting = true; 926 | print("\t%s V%1X,0x00", eql(o, "||") ? "SE" : "SNE", v - 1); 927 | print("\tJP END%d", b); 928 | expression(NULL, false); 929 | } 930 | char* tb = term(); 931 | // If this is the second term of the expression, then there is 932 | // no way that this expression is l-value correct in case a future 933 | // assignment operator is encountered. 934 | lvalue = false; 935 | // Operate on the two terms and let the magic happen. 936 | operate(o, ta); 937 | decv(); 938 | free(o); 939 | free(ta); 940 | ta = tb; 941 | } 942 | if(shorting) 943 | { 944 | print("END%d:", b); 945 | if(shortable) 946 | { 947 | print("\tSE V%1X,0x00", v); 948 | print("\tLD V%1X,0x01", v); 949 | } 950 | } 951 | free(ta); 952 | } 953 | 954 | // Generate array. 955 | // Returns the number of elements in the array. 956 | static int garr() 957 | { 958 | match('{'); 959 | int size = 0; 960 | skip(); 961 | while(now != '}') 962 | { 963 | char* d = dig(); 964 | print("\tDB 0x%02X", tobyte(d)); 965 | size++; 966 | free(d); 967 | skip(); 968 | // Bytes are separated by commas. 969 | // The last byte can be comma but does not have to be. 970 | if(now == ',') 971 | { 972 | match(','); 973 | skip(); 974 | if(now == '}') break; 975 | } 976 | } 977 | if(size > 0xF) 978 | bomb("too many elements in sprite"); 979 | match('}'); 980 | return size; 981 | } 982 | 983 | // Declaring an array (sprite). 984 | static void arr(char* name) 985 | { 986 | isndef(name); 987 | print("%s:", name); 988 | skip(); 989 | int size = 0; 990 | int height = 0; 991 | // Array of sprite arrays. 992 | // eg. sprite[] = { { 0x01, 0x02 ,0x03 }, { 0x01, 0x02 ,0x03 } }; 993 | // In this case, height = 2, and size = 3. 994 | if(now == '[') 995 | { 996 | match('['); 997 | match(']'); 998 | match('='); 999 | match('{'); 1000 | size = garr(); 1001 | skip(); 1002 | height++; 1003 | while(now == ',') 1004 | { 1005 | match(','); 1006 | skip(); 1007 | if(now == '}') 1008 | break; 1009 | if(garr() != size) 1010 | bomb("array elements must be same length"); 1011 | height++; 1012 | skip(); 1013 | } 1014 | match('}'); 1015 | } 1016 | // Simple sprite array. 1017 | // eg. sprite = { 0x01, 0x02, 0x03 }; 1018 | // In this case, height = 1, and size = 3. 1019 | else 1020 | { 1021 | match('='); 1022 | height = 1; 1023 | size = garr(); 1024 | } 1025 | match(';'); 1026 | struct label sprite = { name, size, height }; 1027 | labels[l++] = sprite; 1028 | } 1029 | 1030 | // Declaring an identifier. 1031 | // I really hate pointer integers arguments. 1032 | static void dident(int* const idents) 1033 | { 1034 | do 1035 | { 1036 | char* n = name(); 1037 | isndef(n); 1038 | vars[v] = n; 1039 | // Note equal signs are required when creating identifiers. 1040 | // This will prevent uninitialized variables from cropping up. 1041 | match('='); 1042 | expression(NULL, true); 1043 | incv(); 1044 | *idents += 1; 1045 | // If identifiers are separated by commas, keep going. 1046 | if(now == ',') match(','); 1047 | } 1048 | while(now != ';'); 1049 | match(';'); 1050 | } 1051 | 1052 | // Declaring a while loop statement. 1053 | static void swhile() 1054 | { 1055 | const int b = branch++; 1056 | match('('); 1057 | print("WHILE%d:", b); 1058 | expression(NULL, true); 1059 | print("\tSNE V%1X,0x00", v); 1060 | print("\tJP END%d", b); 1061 | match(')'); 1062 | dblock(); 1063 | print("\tJP WHILE%d", b); 1064 | print("END%d:", b); 1065 | } 1066 | 1067 | // Declaring an if statement. 1068 | static void sif() 1069 | { 1070 | const int b = branch++; 1071 | match('('); 1072 | expression(NULL, true); 1073 | print("\tSNE V%1X,0x00", v); 1074 | print("\tJP ELSE%d", b); 1075 | match(')'); 1076 | dblock(); 1077 | print("\tJP END%d", b); 1078 | print("ELSE%d:", b); 1079 | skip(); 1080 | // The else statement. 1081 | if(now == 'e') 1082 | { 1083 | char* e = name(); 1084 | if(!eql(e, "else")) 1085 | bomb("expected 'else'"); 1086 | free(e); 1087 | skip(); 1088 | // If statements can come right after an else. 1089 | if(now == 'i') 1090 | { 1091 | char* i = name(); 1092 | if(!eql(i, "if")) 1093 | bomb("expected 'if'"); 1094 | free(i); 1095 | sif(); 1096 | } 1097 | else dblock(); 1098 | } 1099 | print("END%d:", b); 1100 | } 1101 | 1102 | // Declaring a block. 1103 | // Blocks hold statements but also declare and define identifiers. 1104 | static void dblock() 1105 | { 1106 | int idents = 0; 1107 | match('{'); 1108 | skip(); 1109 | while(now != '}') 1110 | { 1111 | char* ta = term(); 1112 | eql(ta, "{") ? (free(ta), dblock () ) : 1113 | eql(ta, "while") ? (free(ta), swhile () ) : 1114 | eql(ta, "if") ? (free(ta), sif () ) : 1115 | eql(ta, "auto") ? (free(ta), dident (&idents)) : 1116 | eql(ta, "return") ? (free(ta), sret () ) : (expression(ta, true), match(';')); 1117 | /* An expression is computed as a last restort. */ 1118 | skip(); 1119 | } 1120 | match('}'); 1121 | pop(idents); 1122 | } 1123 | 1124 | // Defining a function. 1125 | static void fun(char* n) 1126 | { 1127 | isndef(n); 1128 | match('('); 1129 | skip(); 1130 | int args = 0; 1131 | while(now != ')') 1132 | { 1133 | char* arg = name(); 1134 | isndef(arg); 1135 | skip(); 1136 | // Arguments can be separated by a comma or a closing paren. 1137 | // If its a closing paren then there are no more args. 1138 | if(now == ',' || now == ')') 1139 | { 1140 | if(now == ',') match(','); 1141 | vars[v] = arg; 1142 | incv(); 1143 | args++; 1144 | } 1145 | else bomb("unknown symbol in argument list"); 1146 | skip(); 1147 | } 1148 | // Labels contain a 'height' field. This only pertains 1149 | // to a collection of sprite arrays for a label. 1150 | struct label label = { n, args, 0 }; 1151 | labels[l++] = label; 1152 | print("%s:", n); 1153 | match(')'); 1154 | // VE must start here for stack space to avoid 1155 | // trampling over the built in font array. 1156 | if(eql(n, "main")) 1157 | print("\tLD VE,0x10"); 1158 | dblock(); 1159 | // Note the popping here occurs after the block statement. The block statement 1160 | // clears up any internal identifiers, so fpop will return V0 into VF. 1161 | gfpop(); 1162 | reset(); 1163 | } 1164 | 1165 | void stdio() 1166 | { 1167 | print(";stdio: Standard Input and Output library"); 1168 | getchr(); 1169 | putchr(); 1170 | } 1171 | 1172 | // Declaring a program. 1173 | // Programs are defined by functions and spirte arrays. 1174 | static void program() 1175 | { 1176 | while(now != EOF) 1177 | { 1178 | char* n = name(); 1179 | skip(); 1180 | switch(now) 1181 | { 1182 | case '[': 1183 | case '=': 1184 | arr(n); 1185 | break; 1186 | case '(': 1187 | fun(n); 1188 | break; 1189 | default: 1190 | bomb("symbol '%c' not known", now); 1191 | break; 1192 | } 1193 | skip(); 1194 | } 1195 | // Libraries to link at compile time. 1196 | stdio(); 1197 | } 1198 | 1199 | // Rock and Roll, baby. 1200 | int main(int argc, char* argv[]) 1201 | { 1202 | if(argc != 3) 1203 | bomb("expected input and output file"); 1204 | init(argv); 1205 | program(); 1206 | } 1207 | -------------------------------------------------------------------------------- /emu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define VROWS (32) 6 | #define VCOLS (64) 7 | #define BYTES (4096) 8 | #define START (0x0200) 9 | #define VSIZE (16) 10 | #define SSIZE (12) 11 | #define BFONT (80) 12 | 13 | static uint64_t vmem[VROWS]; 14 | 15 | static uint16_t pc = START; 16 | static uint16_t I; 17 | static uint16_t s[SSIZE]; 18 | static uint16_t op; 19 | 20 | static uint8_t dt; 21 | static uint8_t st; 22 | static uint8_t sp; 23 | static uint8_t v[VSIZE]; 24 | static uint8_t mem[BYTES]; 25 | static uint8_t charges[VROWS][VCOLS]; 26 | 27 | static const uint8_t* key; 28 | 29 | static SDL_Window* window; 30 | static SDL_Renderer* renderer; 31 | 32 | static int input(const int waiting) 33 | { 34 | do 35 | { 36 | SDL_PumpEvents(); 37 | if(key[SDL_SCANCODE_1]) return 0x01; 38 | if(key[SDL_SCANCODE_2]) return 0x02; 39 | if(key[SDL_SCANCODE_3]) return 0x03; 40 | if(key[SDL_SCANCODE_4]) return 0x0C; 41 | if(key[SDL_SCANCODE_Q]) return 0x04; 42 | if(key[SDL_SCANCODE_W]) return 0x05; 43 | if(key[SDL_SCANCODE_E]) return 0x06; 44 | if(key[SDL_SCANCODE_R]) return 0x0D; 45 | if(key[SDL_SCANCODE_A]) return 0x07; 46 | if(key[SDL_SCANCODE_S]) return 0x08; 47 | if(key[SDL_SCANCODE_D]) return 0x09; 48 | if(key[SDL_SCANCODE_F]) return 0x0E; 49 | if(key[SDL_SCANCODE_Z]) return 0x0A; 50 | if(key[SDL_SCANCODE_X]) return 0x00; 51 | if(key[SDL_SCANCODE_C]) return 0x0B; 52 | if(key[SDL_SCANCODE_V]) return 0x0F; 53 | } 54 | while(waiting); 55 | return -1; 56 | } 57 | 58 | static void _0000() { /* no-op */ } 59 | static void _00E0() { for(int j = 0; j < VROWS; j++) while(vmem[j] >>= 1); } 60 | static void _00EE() { pc = s[--sp]; } 61 | static void _1NNN() { uint16_t nnn = op & 0x0FFF; pc = nnn; } 62 | static void _2NNN() { uint16_t nnn = op & 0x0FFF; s[sp++] = pc; pc = nnn; } 63 | static void _3XNN() { uint16_t x = (op & 0x0F00) >> 8, nn = op & 0x00FF; if(v[x] == nn) pc += 0x0002; } 64 | static void _4XNN() { uint16_t x = (op & 0x0F00) >> 8, nn = op & 0x00FF; if(v[x] != nn) pc += 0x0002; } 65 | static void _5XY0() { uint16_t x = (op & 0x0F00) >> 8, y = (op & 0x00F0) >> 4; if(v[x] == v[y]) pc += 0x0002; } 66 | static void _6XNN() { uint16_t nn = op & 0x00FF, x = (op & 0x0F00) >> 8; v[x] = nn; } 67 | static void _7XNN() { uint16_t nn = op & 0x00FF, x = (op & 0x0F00) >> 8; v[x] += nn; } 68 | static void _8XY0() { uint16_t x = (op & 0x0F00) >> 8, y = (op & 0x00F0) >> 4; v[x] = v[y]; } 69 | static void _8XY1() { uint16_t x = (op & 0x0F00) >> 8, y = (op & 0x00F0) >> 4; v[x] |= v[y]; } 70 | static void _8XY2() { uint16_t x = (op & 0x0F00) >> 8, y = (op & 0x00F0) >> 4; v[x] &= v[y]; } 71 | static void _8XY3() { uint16_t x = (op & 0x0F00) >> 8, y = (op & 0x00F0) >> 4; v[x] ^= v[y]; } 72 | static void _8XY4() { uint16_t x = (op & 0x0F00) >> 8, y = (op & 0x00F0) >> 4; uint8_t flag = v[x] + v[y] > 0xFF ? 0x01 : 0x00; v[x] = v[x] + v[y]; v[0xF] = flag; } 73 | static void _8XY5() { uint16_t x = (op & 0x0F00) >> 8, y = (op & 0x00F0) >> 4; uint8_t flag = v[x] - v[y] < 0x00 ? 0x00 : 0x01; v[x] = v[x] - v[y]; v[0xF] = flag; } 74 | static void _8XY7() { uint16_t x = (op & 0x0F00) >> 8, y = (op & 0x00F0) >> 4; uint8_t flag = v[y] - v[x] < 0x00 ? 0x00 : 0x01; v[x] = v[y] - v[x]; v[0xF] = flag; } 75 | static void _8XY6() { uint16_t x = (op & 0x0F00) >> 8, y = (op & 0x00F0) >> 4; uint8_t flag = (v[y] >> 0) & 0x01; v[x] = v[y] >> 1; v[0xF] = flag; } 76 | static void _8XYE() { uint16_t x = (op & 0x0F00) >> 8, y = (op & 0x00F0) >> 4; uint8_t flag = (v[y] >> 7) & 0x01; v[x] = v[y] << 1; v[0xF] = flag; } 77 | static void _9XY0() { uint16_t x = (op & 0x0F00) >> 8, y = (op & 0x00F0) >> 4; if(v[x] != v[y]) pc += 0x0002; } 78 | static void _ANNN() { uint16_t nnn = op & 0x0FFF; I = nnn; } 79 | static void _BNNN() { uint16_t nnn = op & 0x0FFF; pc = nnn + v[0x0]; } 80 | static void _CXNN() { uint16_t x = (op & 0x0F00) >> 8, nn = op & 0x00FF; v[x] = nn & (rand() % 0x100); } 81 | static void _DXYN() { 82 | uint16_t x = (op & 0x0F00) >> 8; 83 | uint16_t y = (op & 0x00F0) >> 4; 84 | uint16_t n = (op & 0x000F) >> 0; 85 | uint8_t flag = 0; 86 | for(int j = 0; j < n; j++) 87 | { 88 | uint64_t line = (uint64_t) mem[I + j] << (VCOLS - 8); 89 | line >>= v[x]; 90 | if((vmem[v[y] + j] ^ line) != (vmem[v[y] + j] | line)) 91 | flag = 1; 92 | vmem[v[y] + j] ^= line; 93 | } 94 | v[0xF] = flag; 95 | } 96 | static void _EXA1() { uint16_t x = (op & 0x0F00) >> 8; if(v[x] != input(0)) pc += 0x0002; } 97 | static void _EX9E() { uint16_t x = (op & 0x0F00) >> 8; if(v[x] == input(0)) pc += 0x0002; } 98 | static void _FX07() { uint16_t x = (op & 0x0F00) >> 8; v[x] = dt; } 99 | static void _FX0A() { uint16_t x = (op & 0x0F00) >> 8; v[x] = input(1); } 100 | static void _FX15() { uint16_t x = (op & 0x0F00) >> 8; dt = v[x]; } 101 | static void _FX18() { uint16_t x = (op & 0x0F00) >> 8; st = v[x]; } 102 | static void _FX1E() { uint16_t x = (op & 0x0F00) >> 8; I += v[x]; } 103 | static void _FX29() { uint16_t x = (op & 0x0F00) >> 8; I = 5 * v[x]; } 104 | static void _FX33() { uint16_t x = (op & 0x0F00) >> 8; 105 | const int lookup[] = { 100, 10, 1 }; 106 | for(unsigned i = 0; i < sizeof(lookup) / sizeof(*lookup); i++) 107 | mem[I + i] = v[x] / lookup[i] % 10; 108 | } 109 | static void _FX55() { uint16_t x = (op & 0x0F00) >> 8; int i; for(i = 0; i <= x; i++) mem[I + i] = v[i]; I += i; } 110 | static void _FX65() { uint16_t x = (op & 0x0F00) >> 8; int i; for(i = 0; i <= x; i++) v[i] = mem[I + i]; I += i; } 111 | 112 | static void _0___(); 113 | static void _8___(); 114 | static void _E___(); 115 | static void _F___(); 116 | static void (*opsa[])() = { _00E0, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _00EE, _0000 }; 117 | static void (*opsb[])() = { _8XY0, _8XY1, _8XY2, _8XY3, _8XY4, _8XY5, _8XY6, _8XY7, _0000, _0000, _0000, _0000, _0000, _0000, _8XYE, _0000 }; 118 | static void (*opsc[])() = { _0000, _EXA1, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _EX9E, _0000 }; 119 | static void (*opsd[])() = { _0000, _0000, _0000, _0000, _0000, _0000, _0000, _FX07, _0000, _0000, _FX0A, _0000, _0000, _0000, _0000, _0000, 120 | /*************************/ _0000, _0000, _0000, _0000, _0000, _FX15, _0000, _0000, _FX18, _0000, _0000, _0000, _0000, _0000, _FX1E, _0000, 121 | /* */ _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _FX29, _0000, _0000, _0000, _0000, _0000, _0000, 122 | /* */ _0000, _0000, _0000, _FX33, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, 123 | /* CHIP-8 */ _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, 124 | /* */ _0000, _0000, _0000, _0000, _0000, _FX55, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, 125 | /* */ _0000, _0000, _0000, _0000, _0000, _FX65, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, 126 | /*************************/ _0000, _0000, _0000, _0000, _0000, _FX65, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000, _0000 }; 127 | static void (*exec[])() = { _0___, _1NNN, _2NNN, _3XNN, _4XNN, _5XY0, _6XNN, _7XNN, _8___, _9XY0, _ANNN, _BNNN, _CXNN, _DXYN, _E___, _F___ }; 128 | static void _0___() { (*opsa[op & 0x000F])(); } 129 | static void _8___() { (*opsb[op & 0x000F])(); } 130 | static void _E___() { (*opsc[op & 0x000F])(); } 131 | static void _F___() { (*opsd[op & 0x00FF])(); } 132 | 133 | static void load(const char* game) 134 | { 135 | const uint8_t ch[BFONT] = { 136 | 0xF0, 0x90, 0x90, 0x90, 0xF0, 137 | 0x20, 0x60, 0x20, 0x20, 0x70, 138 | 0xF0, 0x10, 0xF0, 0x80, 0xF0, 139 | 0xF0, 0x10, 0xF0, 0x10, 0xF0, 140 | 0x90, 0x90, 0xF0, 0x10, 0x10, 141 | 0xF0, 0x80, 0xF0, 0x10, 0xF0, 142 | 0xF0, 0x80, 0xF0, 0x90, 0xF0, 143 | 0xF0, 0x10, 0x20, 0x40, 0x40, 144 | 0xF0, 0x90, 0xF0, 0x90, 0xF0, 145 | 0xF0, 0x90, 0xF0, 0x10, 0xF0, 146 | 0xF0, 0x90, 0xF0, 0x90, 0x90, 147 | 0xE0, 0x90, 0xE0, 0x90, 0xE0, 148 | 0xF0, 0x80, 0x80, 0x80, 0xF0, 149 | 0xE0, 0x90, 0x90, 0x90, 0xE0, 150 | 0xF0, 0x80, 0xF0, 0x80, 0xF0, 151 | 0xF0, 0x80, 0xF0, 0x80, 0x80, 152 | }; 153 | FILE* const fp = fopen(game, "rb"); 154 | if(fp == NULL) 155 | { 156 | fprintf(stderr, "error: binary '%s' not found\n", game); 157 | exit(1); 158 | } 159 | fseek(fp, 0, SEEK_END); 160 | const long size = ftell(fp); 161 | rewind(fp); 162 | uint8_t buf[BYTES]; 163 | fread(buf, 1, size, fp); 164 | fclose(fp); 165 | for(int i = 0; i < BFONT; i++) 166 | mem[i] = ch[i]; 167 | for(int i = 0; i < size; i++) 168 | mem[i + START] = buf[i]; 169 | } 170 | 171 | static void cycle() 172 | { 173 | if(dt > 0) dt--; 174 | if(st > 0) st--; 175 | if(st) 176 | { 177 | /* Beep */ 178 | } 179 | op = (mem[pc] << 8) + (mem[pc + 1] & 0x00FF); 180 | pc += 0x0002; 181 | (*exec[op >> 12])(); 182 | } 183 | 184 | static void output() 185 | { 186 | for(int j = 0; j < VROWS; j++) 187 | for(int i = 0; i < VCOLS; i++) 188 | { 189 | SDL_SetRenderDrawColor(renderer, charges[j][i], 0x00, 0x00, 0x00); 190 | const int w = 8; 191 | const SDL_Rect rect = { i * w + 1, j * w + 1, w - 2, w - 2 }; 192 | SDL_RenderFillRect(renderer, &rect); 193 | } 194 | SDL_RenderPresent(renderer); 195 | } 196 | 197 | static int charging(const int j, const int i) 198 | { 199 | return (vmem[j] >> (VCOLS - 1 - i)) & 0x1; 200 | } 201 | 202 | static void charge() 203 | { 204 | for(int j = 0; j < VROWS; j++) 205 | for(int i = 0; i < VCOLS; i++) 206 | if(charging(j, i)) 207 | charges[j][i] = 0xFF; 208 | } 209 | 210 | static void discharge() 211 | { 212 | for(int j = 0; j < VROWS; j++) 213 | for(int i = 0; i < VCOLS; i++) 214 | if(!charging(j, i)) 215 | charges[j][i] *= 0.997; 216 | } 217 | 218 | void dump() 219 | { 220 | for(int i = 0; i < VSIZE; i++) 221 | printf("v[%02d]: %d = 0x%02X\n", i, v[i], v[i]); 222 | } 223 | 224 | int main(int argc, char* argv[]) 225 | { 226 | if(argc != 2) 227 | { 228 | fprintf(stderr, "error: too few or too many argmuents\n"); 229 | exit(1); 230 | } 231 | SDL_Init(SDL_INIT_VIDEO); 232 | SDL_CreateWindowAndRenderer(512, 256, 0, &window, &renderer); 233 | SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00); 234 | SDL_SetWindowTitle(window, "Emu-1.0"); 235 | SDL_RenderClear(renderer); 236 | SDL_RenderPresent(renderer); 237 | key = SDL_GetKeyboardState(NULL); 238 | load(argv[1]); 239 | srand(time(0)); 240 | for(int cycles = 0; !key[SDL_SCANCODE_END] && !key[SDL_SCANCODE_ESCAPE]; cycles++) 241 | { 242 | SDL_PumpEvents(); // Cannot poll an SDL_Event -- Too slow! 243 | charge(); 244 | cycle(); 245 | if(cycles % 15 == 0) 246 | output(); 247 | discharge(); 248 | } 249 | SDL_Quit(); 250 | SDL_DestroyRenderer(renderer); 251 | SDL_DestroyWindow(window); 252 | } 253 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | ASM = ../asm 2 | BIN = ../bin 3 | CMP = ../c8c 4 | 5 | BINS = mul.bin maze.bin tty.bin 6 | BINS+= collision.bin invaders.bin 7 | HEXS = $(BINS:.bin=.hex) 8 | ASMS = $(BINS:.bin=.asm) 9 | 10 | all: $(BINS) 11 | 12 | collision.bin: collision.hex 13 | $(BIN) $^ $@ 14 | collision.hex: collision.asm 15 | $(ASM) $^ $@ 16 | collision.asm: collision.c8 17 | $(CMP) $^ $@ 18 | 19 | invaders.bin: invaders.hex 20 | $(BIN) $^ $@ 21 | invaders.hex: invaders.asm 22 | $(ASM) $^ $@ 23 | invaders.asm: invaders.c8 24 | $(CMP) $^ $@ 25 | 26 | tty.bin: tty.hex 27 | $(BIN) $^ $@ 28 | tty.hex: tty.asm 29 | $(ASM) $^ $@ 30 | tty.asm: tty.c8 31 | $(CMP) $^ $@ 32 | 33 | maze.bin: maze.hex 34 | $(BIN) $^ $@ 35 | maze.hex: maze.asm 36 | $(ASM) $^ $@ 37 | maze.asm: maze.c8 38 | $(CMP) $^ $@ 39 | 40 | mul.bin: mul.hex 41 | $(BIN) $^ $@ 42 | mul.hex: mul.asm 43 | $(ASM) $^ $@ 44 | mul.asm: mul.c8 45 | $(CMP) $^ $@ 46 | 47 | clean: 48 | rm -f $(BINS) 49 | rm -f $(HEXS) 50 | rm -f $(ASMS) 51 | -------------------------------------------------------------------------------- /examples/collision.c8: -------------------------------------------------------------------------------- 1 | dot = { 0x80 }; 2 | 3 | main() 4 | { 5 | auto x = 3, xm = 64; 6 | auto y = 3, ym = 32; 7 | auto dir = 1; 8 | // Directional keyboard keys. 9 | auto A = 7, D = 9, W = 5, S = 8; 10 | while(1) 11 | { 12 | // Get move direction. 13 | auto a = getchar(); 14 | if(a == A) { dir = 0; } else 15 | if(a == D) { dir = 1; } else 16 | if(a == W) { dir = 2; } else 17 | if(a == S) { dir = 3; } 18 | // Apply move direction. 19 | if(dir == 0) { x -= 1;} else 20 | if(dir == 1) { x += 1;} else 21 | if(dir == 2) { y -= 1;} else 22 | if(dir == 3) { y += 1;} 23 | // Out of bounds fix. 24 | if(x == xm) { x = 0 ; } else 25 | if(x == -1) { x = xm - 1; } else 26 | if(y == -1) { y = ym - 1; } else 27 | if(y == ym) { y = 0 ; }; 28 | // Draw. If collision, clear the screen. 29 | if(draw(x, y, dot)) { clear(); } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/invaders.c8: -------------------------------------------------------------------------------- 1 | alien = { 0x18, 0x3C, 0x7E, 0xDB, 0xFF, 0x24, 0x5A, 0xA5, }; 2 | 3 | gun = { 0x10, 0x38, }; 4 | 5 | bullet = { 0x10 }; 6 | 7 | move_gun(x) 8 | { 9 | auto dir = getchar(); 10 | auto left = 7; 11 | auto right = 9; 12 | if(dir == left) 13 | { 14 | return x -= 1; 15 | } 16 | else if(dir == right) 17 | { 18 | return x += 1; 19 | } 20 | else 21 | { 22 | return x += 0; 23 | } 24 | } 25 | 26 | shoot_gun() 27 | { 28 | return getchar() == 5; 29 | } 30 | 31 | draw_aliens() 32 | { 33 | auto start = 3; 34 | auto x = 50; 35 | while(x) 36 | { 37 | draw(start + x, 0, alien); 38 | x -= 10; 39 | } 40 | draw(start, 0, alien); 41 | } 42 | 43 | draw_gun(x, ymax, init) 44 | { 45 | auto y = ymax - sizeof(gun); 46 | if(init) 47 | { 48 | draw(x, y, gun); 49 | } 50 | draw(x, y, gun); 51 | x = move_gun(x); 52 | draw(x, y, gun); 53 | return x; 54 | } 55 | 56 | draw_bullet(x, y, tip) 57 | { 58 | if(y == tip) 59 | { 60 | draw(x, y, bullet); 61 | } 62 | draw(x, y, bullet); 63 | if(draw(x, y - 1, bullet)) 64 | { 65 | return 0; 66 | } 67 | else 68 | { 69 | return y -= 1; 70 | } 71 | } 72 | 73 | main() 74 | { 75 | auto x = 0; 76 | auto ymax = 32; 77 | auto tip = ymax - sizeof(gun); 78 | auto bx = 0; 79 | auto by = 0; 80 | auto shooting = 0; 81 | auto init = 1; 82 | draw_aliens(); 83 | while(1) 84 | { 85 | x = draw_gun(x, ymax, init); 86 | if(shoot_gun()) 87 | { 88 | bx = x; 89 | by = tip; 90 | shooting = 1; 91 | } 92 | if(by == 0) 93 | { 94 | shooting = 0; 95 | } 96 | if(shooting) 97 | { 98 | by = draw_bullet(bx, by, tip); 99 | } 100 | init = 0; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /examples/maze.c8: -------------------------------------------------------------------------------- 1 | r = { 0x80, 0x40, 0x20, 0x10 }; 2 | l = { 0x20, 0x40, 0x80, 0x10 }; 3 | 4 | main() 5 | { 6 | auto x = 0, xmax = 64, dx = 4; 7 | auto y = 0, ymax = 32, dy = 4; 8 | while(y < ymax) 9 | { 10 | if(rand() & 0x1) 11 | { 12 | draw(x, y, r); 13 | } 14 | else 15 | { 16 | draw(x, y, l); 17 | } 18 | if((x += dx) == xmax) 19 | { 20 | x = 0; 21 | y += dy; 22 | } 23 | } 24 | while(1) 25 | { 26 | // Never leave main. 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/mul.c8: -------------------------------------------------------------------------------- 1 | mul(a, b) 2 | { 3 | if(b == 0) 4 | { 5 | return 0; 6 | } 7 | return a + mul(a, b - 1); 8 | } 9 | 10 | main() 11 | { 12 | putchar(24, 13, mul(9, 9)); 13 | while(1) 14 | { 15 | // Wait here forever. 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/tty.c8: -------------------------------------------------------------------------------- 1 | main() 2 | { 3 | while(1) 4 | { 5 | auto a = getchar(); 6 | if(a != 0xFF) 7 | { 8 | clear(); 9 | putchar(0, 0, a); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /scrots/2018-01-04-003042_512x256_scrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glouw/c8c/33aa239d549c7fd5f22ec64f7983b75faf82135f/scrots/2018-01-04-003042_512x256_scrot.png -------------------------------------------------------------------------------- /scrots/2018-01-04-140651_512x256_scrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glouw/c8c/33aa239d549c7fd5f22ec64f7983b75faf82135f/scrots/2018-01-04-140651_512x256_scrot.png -------------------------------------------------------------------------------- /scrots/2018-01-31-130004_512x256_scrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glouw/c8c/33aa239d549c7fd5f22ec64f7983b75faf82135f/scrots/2018-01-31-130004_512x256_scrot.png -------------------------------------------------------------------------------- /scrots/c8c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glouw/c8c/33aa239d549c7fd5f22ec64f7983b75faf82135f/scrots/c8c.png -------------------------------------------------------------------------------- /tasm/.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | *.hex 3 | -------------------------------------------------------------------------------- /tasm/Makefile: -------------------------------------------------------------------------------- 1 | ASM = ../asm 2 | BIN = ../bin 3 | 4 | BINS = registers.bin flow.bin subroutines.bin skips.bin 5 | BINS+= timers.bin keypad.bin graphics.bin storage.bin 6 | HEXS = $(BINS:.bin=.hex) 7 | 8 | all: $(BINS) 9 | 10 | registers.bin: registers.hex 11 | $(BIN) $^ $@ 12 | registers.hex: registers.asm 13 | $(ASM) $^ $@ 14 | 15 | flow.bin: flow.hex 16 | $(BIN) $^ $@ 17 | flow.hex: flow.asm 18 | $(ASM) $^ $@ 19 | 20 | subroutines.bin: subroutines.hex 21 | $(BIN) $^ $@ 22 | subroutines.hex: subroutines.asm 23 | $(ASM) $^ $@ 24 | 25 | skips.bin: skips.hex 26 | $(BIN) $^ $@ 27 | skips.hex: skips.asm 28 | $(ASM) $^ $@ 29 | 30 | timers.bin: timers.hex 31 | $(BIN) $^ $@ 32 | timers.hex: timers.asm 33 | $(ASM) $^ $@ 34 | 35 | keypad.bin: keypad.hex 36 | $(BIN) $^ $@ 37 | keypad.hex: keypad.asm 38 | $(ASM) $^ $@ 39 | 40 | graphics.bin: graphics.hex 41 | $(BIN) $^ $@ 42 | graphics.hex: graphics.asm 43 | $(ASM) $^ $@ 44 | 45 | storage.bin: storage.hex 46 | $(BIN) $^ $@ 47 | storage.hex: storage.asm 48 | $(ASM) $^ $@ 49 | 50 | clean: 51 | rm -f $(BINS) 52 | rm -f $(HEXS) 53 | -------------------------------------------------------------------------------- /tasm/README: -------------------------------------------------------------------------------- 1 | These ASM files were hand-written to unit test the virtual machine. 2 | They return 0 if they succeed and 1 if they fail. 3 | -------------------------------------------------------------------------------- /tasm/flow.asm: -------------------------------------------------------------------------------- 1 | AFTER_RESET: 2 | ;----------------------------; 3 | ; JP V0, addr ; 4 | ;----------------------------; 5 | LD V0, 0x02 6 | ; Jumps to the return of JP_TEST 7 | JP V0, JP_TEST 8 | 9 | JP_TEST: 10 | ;----------------------------; 11 | ; JP addr ; 12 | ;----------------------------; 13 | JP AFTER_RESET 14 | ;----------------------------; 15 | ; JP V0, addr ; 16 | ;----------------------------; 17 | RET 18 | 19 | main: 20 | ; VE, if non-zero, indicates test failure 21 | LD VE, 0x00 22 | ; Test all 23 | CALL JP_TEST 24 | ; Finish - Display VE: the test failure status 25 | LD F, VE 26 | LD V0, 0x01 27 | LD V1, 0x01 28 | DRW V0, V1, 0x5 29 | ; All is well; stay here forever 30 | END: 31 | JP END 32 | -------------------------------------------------------------------------------- /tasm/graphics.asm: -------------------------------------------------------------------------------- 1 | ; Counts up with 0x0F mask 2 | SPRITE_0: 3 | DB 0x01 4 | DB 0x02 5 | DB 0x03 6 | DB 0x04 7 | DB 0x05 8 | 9 | ; Counts down with 0xF0 mask 10 | SPRITE_1: 11 | DB 0x50 12 | DB 0x40 13 | DB 0x30 14 | DB 0x20 15 | DB 0x10 16 | 17 | ; Storage for binary coded decimal numbers 18 | BCD: 19 | DB 0x00 20 | DB 0x00 21 | DB 0x00 22 | 23 | ; Binary Coded Decimal drawing test 24 | DRW_BCD_TEST: 25 | CLS 26 | LD I, BCD 27 | LD V3, 0xFF 28 | ;----------------------------; 29 | ; LD B, VX ; 30 | ;----------------------------; 31 | ; Loads BCD of VX into I, I+1, I+2 32 | LD B, V3 33 | ; Populate registers 34 | LD V2, [I] 35 | ; Prepare for drawing 36 | LD V3, 0x01 37 | LD V4, 0x01 38 | ; Sprite width 39 | LD V5, 0x08 40 | ; Draw first 41 | LD F, V0 42 | DRW V3, V4, 0x5 43 | ; Draw second 44 | LD F, V1 45 | ADD V3, V5 46 | DRW V3, V4, 0x5 47 | ; Draw third 48 | LD F, V2 49 | ADD V3, V5 50 | DRW V3, V4, 0x5 51 | ; Wait 52 | LD VA, K 53 | ; Done 54 | RET 55 | 56 | ; Display and wait for each 57 | DRW_FONT_TEST: 58 | LD V3, 0x00 59 | WHILE: 60 | CLS 61 | LD F, V3 62 | LD V0, 0x01 63 | LD V1, 0x01 64 | DRW V0, V1, 0x5 65 | ; Wait here 66 | LD VD, K 67 | ; Loop 68 | ADD V3, 0x01 69 | SE V3, 0x10 70 | JP WHILE 71 | ; Done 72 | RET 73 | 74 | DRW_XOR_TEST: 75 | ;----------------------------; 76 | ; LD I, addr ; 77 | ;----------------------------; 78 | CLS 79 | LD I, SPRITE_0 80 | LD V0, 0x01 81 | LD V1, 0x01 82 | ; Draw and wait 83 | DRW V0, V1, 0x5 84 | LD VA, K 85 | ; Draw and wait 86 | DRW V0, V1, 0x5 87 | LD VA, K 88 | ; Check flag is high for XOR 89 | SE VF, 0x01 90 | LD VE, 0x01 91 | RET 92 | 93 | DRW_TEST: 94 | ;----------------------------; 95 | ; CLS ; 96 | ;----------------------------; 97 | CLS 98 | ;----------------------------; 99 | ; LD I, addr ; 100 | ;----------------------------; 101 | LD I, SPRITE_0 102 | LD V0, 0x01 103 | LD V1, 0x01 104 | DRW V0, V1, 0x5 105 | ; Check flag is low for XOR 106 | SE VF, 0x00 107 | LD VE, 0x01 108 | ; Wait here 109 | LD VA, K 110 | ;----------------------------; 111 | ; ADD I, VX ; 112 | ;----------------------------; 113 | CLS 114 | LD VB, 0x05 115 | ADD I, VB 116 | LD V0, 0x01 117 | LD V1, 0x01 118 | DRW V0, V1, 0x5 119 | ; Check flag is low for XOR 120 | SE VF, 0x00 121 | LD VE, 0x01 122 | ; Wait here 123 | LD VA, K 124 | ; Done 125 | RET 126 | 127 | main: 128 | ; VE, if non-zero, indicates test failure 129 | LD VE, 0x00 130 | ; Test all 131 | CALL DRW_TEST 132 | CALL DRW_XOR_TEST 133 | CALL DRW_FONT_TEST 134 | CALL DRW_BCD_TEST 135 | ; Finish - Display VE: the test failure status 136 | CLS 137 | LD F, VE 138 | LD V0, 0x01 139 | LD V1, 0x01 140 | DRW V0, V1, 0x5 141 | ; All is well; stay here forever 142 | END: 143 | JP END 144 | -------------------------------------------------------------------------------- /tasm/keypad.asm: -------------------------------------------------------------------------------- 1 | LD_TEST: 2 | ;----------------------------; 3 | ; LD VX, K ; 4 | ;----------------------------; 5 | LD VA, K 6 | BACK: 7 | ;----------------------------; 8 | ; SKP VX ; 9 | ;----------------------------; 10 | SKP VA 11 | JP BACK 12 | AGAIN: 13 | ;----------------------------; 14 | ; SKNP VX ; 15 | ;----------------------------; 16 | SKNP VA 17 | JP OUT 18 | JP AGAIN 19 | OUT: 20 | RET 21 | 22 | main: 23 | ; VE, if non-zero, indicates test failure 24 | LD VE, 0x00 25 | ; Test all 26 | CALL LD_TEST 27 | ; Finish - Display VE: the test failure status 28 | LD F, VE 29 | LD V0, 0x01 30 | LD V1, 0x01 31 | DRW V0, V1, 0x5 32 | ; All is well; stay here forever 33 | END: 34 | JP END 35 | -------------------------------------------------------------------------------- /tasm/registers.asm: -------------------------------------------------------------------------------- 1 | LD_TEST: 2 | ;----------------------------; 3 | ; LD Vx, byte ; 4 | ;----------------------------; 5 | LD V0, 0x11 6 | ; Check load 7 | SE V0, 0x11 ; Skip next instruction if equal 8 | LD VE, 0x01 9 | ;----------------------------; 10 | ; LD Vx, Vy ; 11 | ;----------------------------; 12 | LD V1, 0x12 13 | LD V0, V1 14 | ; Check load 15 | SE V0, 0x12 16 | LD VE, 0x01 17 | RET 18 | 19 | ; VF goes high with overflows 20 | ADD_TEST: 21 | ;----------------------------; 22 | ; ADD Vx, byte ; 23 | ;----------------------------; 24 | LD V0, 0x13 25 | ADD V0, 0x01 26 | SE V0, 0x14 27 | LD VE, 0x01 28 | ;----------------------------; 29 | ; ADD Vx, Vy without carry ; 30 | ;----------------------------; 31 | LD V0, 0x15 32 | LD V1, 0x01 33 | ADD V0, V1 34 | ; Check addition 35 | SE V0, 0x16 36 | LD VE, 0x01 37 | ; Check carry does not occur 38 | SE VF, 0x00 39 | LD VE, 0x01 40 | ;----------------------------; 41 | ; ADD Vx, Vy with carry ; 42 | ;----------------------------; 43 | LD V0, 0xFF 44 | LD V1, 0x01 45 | ADD V0, V1 46 | ; Check addition (Overflowed occured) 47 | SE V0, 0x00 48 | LD VE, 0x01 49 | ; Check carry does occur 50 | SE VF, 0x01 51 | LD VE, 0x01 52 | RET 53 | 54 | ; VF goes low with borrows 55 | SUB_TEST: 56 | ;----------------------------; 57 | ; SUB Vx, Vy without borrow ; 58 | ;----------------------------; 59 | LD V0, 0x15 60 | LD V1, 0x01 61 | SUB V0, V1 62 | ; Check subtraction 63 | SE V0, 0x14 64 | LD VE, 0x01 65 | ; Check borrow does not occur 66 | SE VF, 0x01 67 | LD VE, 0x01 68 | ;----------------------------; 69 | ; SUB Vx, Vy with borrow ; 70 | ;----------------------------; 71 | LD V0, 0x00 72 | LD V1, 0x01 73 | SUB V0, V1 74 | ; Check subtraction 75 | SE V0, 0xFF 76 | LD VE, 0x01 77 | ; Check borrow does occur 78 | SE VF, 0x00 79 | LD VE, 0x01 80 | ;----------------------------; 81 | ; SUBN Vx, Vy without borrow ; 82 | ;----------------------------; 83 | LD V0, 0x01 84 | LD V1, 0x15 85 | SUBN V0, V1 86 | ; Check subtraction 87 | SE V0, 0x14 88 | LD VE, 0x01 89 | ; Check borrow does not occur 90 | SE VF, 0x01 91 | LD VE, 0x01 92 | ;----------------------------; 93 | ; SUBN Vx, Vy with borrow ; 94 | ;----------------------------; 95 | LD V0, 0x01 96 | LD V1, 0x00 97 | SUBN V0, V1 98 | ; Check subtraction 99 | SE V0, 0xFF 100 | LD VE, 0x01 101 | ; Check borrow does occur 102 | SE VF, 0x00 103 | LD VE, 0x01 104 | RET 105 | 106 | AND_TEST: 107 | ;----------------------------; 108 | ; AND Vx, Vy ; 109 | ;----------------------------; 110 | LD V0, 0xFF 111 | LD V1, 0x0F 112 | AND V0, V1 113 | ; Check AND 114 | SE V0, 0x0F 115 | LD VE, 0x01 116 | RET 117 | 118 | OR_TEST: 119 | ;----------------------------; 120 | ; OR Vx, Vy ; 121 | ;----------------------------; 122 | LD V0, 0xAA 123 | LD V1, 0x0F 124 | OR V0, V1 125 | ; Check OR 126 | SE V0, 0xAF 127 | LD VE, 0x01 128 | RET 129 | 130 | XOR_TEST: 131 | ;----------------------------; 132 | ; XOR Vx, Vy ; 133 | ;----------------------------; 134 | LD V0, 0xAA 135 | LD V1, 0x0F 136 | XOR V0, V1 137 | ; Check XOR 138 | SE V0, 0xA5 139 | LD VE, 0x01 140 | RET 141 | 142 | SHR_TEST: 143 | ;----------------------------; 144 | ; SHR Vx, Vy w/o outshifting ; 145 | ;----------------------------; 146 | LD V0, 0x00 147 | LD V1, 0x02 148 | SHR V0, V1 149 | ; Check SHR 150 | SE V0, 0x01 151 | LD VE, 0x01 152 | ; Check shifted is zero in VF 153 | SE VF, 0x00 154 | LD VE, 0x01 155 | ;----------------------------; 156 | ; SHR Vx, Vx w/ outshifting ; 157 | ;----------------------------; 158 | SHR V0, V0 159 | ; Check SHR 160 | SE V0, 0x00 161 | LD VE, 0x01 162 | ; Check shifted is one in VF 163 | SE VF, 0x01 164 | LD VE, 0x01 165 | RET 166 | 167 | SHL_TEST: 168 | ;----------------------------; 169 | ; SHL Vx, Vy w/o outshifting ; 170 | ;----------------------------; 171 | LD V0, 0x00 172 | LD V1, 0x40 173 | SHL V0, V1 174 | ; Check SHL 175 | SE V0, 0x80 176 | LD VE, 0x01 177 | ; Check shifted is zero in VF 178 | SE VF, 0x00 179 | LD VE, 0x01 180 | ;----------------------------; 181 | ; SHL Vx, Vx w/ outshifting ; 182 | ;----------------------------; 183 | SHL V0, V0 184 | ; Check SHL 185 | SE V0, 0x00 186 | LD VE, 0x01 187 | ; Check shifted is one in VF 188 | SE VF, 0x01 189 | LD VE, 0x01 190 | RET 191 | 192 | RND_TEST: 193 | ;----------------------------; 194 | ; RND Vx, Byte ; 195 | ;----------------------------; 196 | LD V0, 0x00 197 | RND V0, 0x0F 198 | ; Manual check - Push any key to continue 199 | LD VF, K 200 | LD V0, 0x00 201 | RND V0, 0xF0 202 | ; Manual check - Push any key to continue 203 | LD VF, K 204 | LD V0, 0x00 205 | RND V0, 0xFF 206 | ; Manual check - Push any key to continue 207 | LD VF, K 208 | RET 209 | 210 | main: 211 | ; VE, if non-zero, indicates test failure 212 | LD VE, 0x00 213 | ; Test all 214 | CALL LD_TEST 215 | CALL ADD_TEST 216 | CALL SUB_TEST 217 | CALL AND_TEST 218 | CALL OR_TEST 219 | CALL XOR_TEST 220 | CALL SHR_TEST 221 | CALL SHL_TEST 222 | CALL RND_TEST 223 | ; Finish - Display VE: the test failure status 224 | LD F, VE 225 | LD V0, 0x01 226 | LD V1, 0x01 227 | DRW V0, V1, 0x5 228 | ; All is well; stay here forever 229 | END: 230 | JP END 231 | -------------------------------------------------------------------------------- /tasm/skips.asm: -------------------------------------------------------------------------------- 1 | SE_TEST: 2 | ;----------------------------; 3 | ; SE VX, byte ; 4 | ;----------------------------; 5 | LD V0, 0xFF 6 | ; Skip the raising the failure if equal 7 | SE V0, 0xFF 8 | ; Failure flag 9 | LD VE, 0x01 10 | ;----------------------------; 11 | ; SE VX, VY ; 12 | ;----------------------------; 13 | LD V0, 0xEE 14 | LD V1, 0xEE 15 | ; Skip the raising the failure if equal 16 | SE V0, V1 17 | ; Failure flag 18 | LD VE, 0x02 19 | RET 20 | 21 | SNE_TEST: 22 | ;----------------------------; 23 | ; SNE VX, byte ; 24 | ;----------------------------; 25 | LD V0, 0xDD 26 | ; Skip the raising failure if not equal 27 | SNE V0, 0xCC 28 | ; Failure flag 29 | LD VE, 0x03 30 | ;----------------------------; 31 | ; SNE VX, VY ; 32 | ;----------------------------; 33 | LD V0, 0xAA 34 | LD V1, 0xBB 35 | ; Skip the raising failure if not equal 36 | SNE V0, 0xEE 37 | ; Failure flag 38 | LD VE, 0x04 39 | RET 40 | 41 | main: 42 | ; VE, if non-zero, indicates test failure 43 | LD VE, 0x00 44 | ; Test all 45 | CALL SE_TEST 46 | CALL SNE_TEST 47 | ; Finish - Display VE: the test failure status 48 | LD F, VE 49 | LD V0, 0x01 50 | LD V1, 0x01 51 | DRW V0, V1, 0x5 52 | ; All is well; stay here forever 53 | END: 54 | JP END 55 | -------------------------------------------------------------------------------- /tasm/storage.asm: -------------------------------------------------------------------------------- 1 | STACK: 2 | DB 0x00 3 | DB 0x00 4 | DB 0x00 5 | DB 0x00 6 | DB 0x00 7 | DB 0x00 8 | DB 0x00 9 | DB 0x00 10 | DB 0x00 11 | DB 0x00 12 | DB 0x00 13 | DB 0x00 14 | DB 0x00 15 | DB 0x00 16 | DB 0x00 17 | DB 0x00 18 | 19 | LD_TEST: 20 | LD V0, 0x00 21 | LD V1, 0x01 22 | LD V2, 0x02 23 | LD V3, 0x03 24 | LD V4, 0x04 25 | LD V5, 0x05 26 | LD V6, 0x06 27 | LD V7, 0x07 28 | LD V8, 0x08 29 | LD V9, 0x09 30 | LD VA, 0x0A 31 | LD VB, 0x0B 32 | LD VC, 0x0C 33 | LD VD, 0x0D 34 | LD VE, 0x0E 35 | LD VF, 0x0F 36 | ;----------------------------; 37 | ; LD [I], VX ; 38 | ;----------------------------; 39 | LD I, STACK 40 | LD [I], VF 41 | ; Clear them all 42 | LD V0, 0x00 43 | LD V1, 0x00 44 | LD V2, 0x00 45 | LD V3, 0x00 46 | LD V4, 0x00 47 | LD V5, 0x00 48 | LD V6, 0x00 49 | LD V7, 0x00 50 | LD V8, 0x00 51 | LD V9, 0x00 52 | LD VA, 0x00 53 | LD VB, 0x00 54 | LD VC, 0x00 55 | LD VD, 0x00 56 | LD VE, 0x00 57 | LD VF, 0x00 58 | ;----------------------------; 59 | ; LD VX, [I] ; 60 | ;----------------------------; 61 | ; Load them back 62 | LD I, STACK 63 | LD VF, [I] 64 | ; Check them all 65 | ; V0 66 | SE V0, 0x00 67 | LD VE, 0x01 68 | ; V1 69 | SE V1, 0x01 70 | LD VE, 0x01 71 | ; V2 72 | SE V2, 0x02 73 | LD VE, 0x01 74 | ; V3 75 | SE V3, 0x03 76 | LD VE, 0x01 77 | ; V4 78 | SE V4, 0x04 79 | LD VE, 0x01 80 | ; V5 81 | SE V5, 0x05 82 | LD VE, 0x01 83 | ; V6 84 | SE V6, 0x06 85 | LD VE, 0x01 86 | ; V7 87 | SE V7, 0x07 88 | LD VE, 0x01 89 | ; V8 90 | SE V8, 0x08 91 | LD VE, 0x01 92 | ; V9 93 | SE V9, 0x09 94 | LD VE, 0x01 95 | ; VA 96 | SE VA, 0x0A 97 | LD VE, 0x01 98 | ; VB 99 | SE VB, 0x0B 100 | LD VE, 0x01 101 | ; VC 102 | SE VC, 0x0C 103 | LD VE, 0x01 104 | ; VD 105 | SE VD, 0x0D 106 | LD VE, 0x01 107 | ; VE 108 | SE VE, 0x0E 109 | LD VE, 0x01 110 | ; VF 111 | SE VF, 0x0F 112 | LD VE, 0x01 113 | ; Done -- Need to reset VE back to 0x0 since this passed 114 | LD VE, 0x00 115 | RET 116 | 117 | main: 118 | ; VE, if non-zero, indicates test failure 119 | LD VE, 0x00 120 | ; Test all 121 | CALL LD_TEST 122 | ; Finish - Display VE: the test failure status 123 | LD F, VE 124 | LD V0, 0x01 125 | LD V1, 0x01 126 | DRW V0, V1, 0x5 127 | ; All is well; stay here forever 128 | END: 129 | JP END 130 | -------------------------------------------------------------------------------- /tasm/subroutines.asm: -------------------------------------------------------------------------------- 1 | SUBROUTINE: 2 | ; Tests RET as well 3 | RET 4 | 5 | CALL_TEST: 6 | ;----------------------------; 7 | ; CALL addr ; 8 | ;----------------------------; 9 | CALL SUBROUTINE 10 | RET 11 | 12 | main: 13 | ; VE, if non-zero, indicates test failure 14 | LD VE, 0x00 15 | ; Test all 16 | CALL CALL_TEST 17 | ; Finish - Display VE: the test failure status 18 | LD F, VE 19 | LD V0, 0x01 20 | LD V1, 0x01 21 | DRW V0, V1, 0x5 22 | ; All is well; stay here forever 23 | END: 24 | JP END 25 | -------------------------------------------------------------------------------- /tasm/timers.asm: -------------------------------------------------------------------------------- 1 | LD_TEST: 2 | ;----------------------------; 3 | ; LD DT, VX ; 4 | ;----------------------------; 5 | LD V0, 0xFF 6 | LD DT, V0 ; 0xFF 7 | ;----------------------------; 8 | ; LD VX, DT ; 9 | ;----------------------------; 10 | LD V1, DT ; 0xFE 11 | SE V1, 0xFE ; 0xFE 12 | ; Flag 13 | LD VE, 0x01 14 | ;----------------------------; 15 | ; LD ST, VX ; 16 | ;----------------------------; 17 | LD V2, 0xFF 18 | LD ST, V2 ; How to test this? 19 | RET 20 | 21 | main: 22 | ; VE, if non-zero, indicates test failure 23 | LD VE, 0x00 24 | ; Test all 25 | CALL LD_TEST 26 | ; Finish - Display VE: the test failure status 27 | LD F, VE 28 | LD V0, 0x01 29 | LD V1, 0x01 30 | DRW V0, V1, 0x5 31 | ; All is well; stay here forever 32 | DONE: 33 | JP DONE 34 | -------------------------------------------------------------------------------- /tc8c/.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | *.hex 3 | *.asm 4 | -------------------------------------------------------------------------------- /tc8c/Makefile: -------------------------------------------------------------------------------- 1 | ASM = ../asm 2 | BIN = ../bin 3 | CMP = ../c8c 4 | 5 | BINS = logical0.bin logical1.bin assignment.bin 6 | BINS+= sizeof.bin branching.bin while.bin 7 | HEXS = $(BINS:.bin=.hex) 8 | ASMS = $(BINS:.bin=.asm) 9 | 10 | all: $(BINS) 11 | 12 | while.bin: while.hex 13 | $(BIN) $^ $@ 14 | while.hex: while.asm 15 | $(ASM) $^ $@ 16 | while.asm: while.c8 17 | $(CMP) $^ $@ 18 | 19 | branching.bin: branching.hex 20 | $(BIN) $^ $@ 21 | branching.hex: branching.asm 22 | $(ASM) $^ $@ 23 | branching.asm: branching.c8 24 | $(CMP) $^ $@ 25 | 26 | sizeof.bin: sizeof.hex 27 | $(BIN) $^ $@ 28 | sizeof.hex: sizeof.asm 29 | $(ASM) $^ $@ 30 | sizeof.asm: sizeof.c8 31 | $(CMP) $^ $@ 32 | 33 | assignment.bin: assignment.hex 34 | $(BIN) $^ $@ 35 | assignment.hex: assignment.asm 36 | $(ASM) $^ $@ 37 | assignment.asm: assignment.c8 38 | $(CMP) $^ $@ 39 | 40 | logical0.bin: logical0.hex 41 | $(BIN) $^ $@ 42 | logical0.hex: logical0.asm 43 | $(ASM) $^ $@ 44 | logical0.asm: logical0.c8 45 | $(CMP) $^ $@ 46 | 47 | logical1.bin: logical1.hex 48 | $(BIN) $^ $@ 49 | logical1.hex: logical1.asm 50 | $(ASM) $^ $@ 51 | logical1.asm: logical1.c8 52 | $(CMP) $^ $@ 53 | 54 | clean: 55 | rm -f $(BINS) 56 | rm -f $(HEXS) 57 | rm -f $(ASMS) 58 | -------------------------------------------------------------------------------- /tc8c/README: -------------------------------------------------------------------------------- 1 | These are c8 source files for use with the c8c compiler. 2 | They compile down to CHIP-8 ASM. 3 | Basic functionality is tested. 4 | -------------------------------------------------------------------------------- /tc8c/assignment.c8: -------------------------------------------------------------------------------- 1 | number[] = { 2 | { 0x00, 0x00, 0x20, 0x50, 0x50, 0x50, 0x20, 0x00 }, 3 | { 0x00, 0x00, 0x20, 0x60, 0x20, 0x20, 0x70, 0x00 }, 4 | }; 5 | 6 | ttoggle(y) 7 | { 8 | auto x = 0, dx = 8; 9 | auto t = 0; 10 | auto a = (t ^= 9) == 9; 11 | auto b = (t ^= 9) == 0; 12 | draw(x, y, number[a]); x += dx; 13 | draw(x, y, number[b]); x += dx; 14 | return a && b; 15 | } 16 | 17 | tassignment(y) 18 | { 19 | auto x = 0, dx = 8; 20 | auto t = 0; 21 | auto a = (t += 9) == 9; 22 | auto b = (t -= 9) == 0; 23 | auto c = (t |= 3) == 3; 24 | auto d = (t &= 0) == 0; 25 | draw(x, y, number[a]); x += dx; 26 | draw(x, y, number[b]); x += dx; 27 | draw(x, y, number[c]); x += dx; 28 | draw(x, y, number[d]); x += dx; 29 | return a && b && c && d; 30 | } 31 | 32 | delay(t) 33 | { 34 | while(t) 35 | { 36 | t -= 1; 37 | } 38 | } 39 | 40 | flash(x, y, value) 41 | { 42 | while(1) 43 | { 44 | draw(x, y, number[value]); 45 | delay(40); 46 | } 47 | } 48 | 49 | place(x, y, value) 50 | { 51 | draw(x, y, number[value]); 52 | while(1) 53 | { 54 | } 55 | } 56 | 57 | main() 58 | { 59 | auto dy = sizeof(number); 60 | auto y = -dy; 61 | auto pass = 1; 62 | pass &= tassignment(y += dy); 63 | pass &= ttoggle (y += dy); 64 | clear(); 65 | // Roughly middle of screen. 66 | auto xmid = 32 - 4; 67 | auto ymid = 16 - 4; 68 | // If the test passed, 1 will be drawn to 69 | // the middle of the screen. Otherwise, 70 | // 0 will flash on screen. 71 | if(pass) 72 | { 73 | place(xmid, ymid, pass); 74 | } 75 | else 76 | { 77 | flash(xmid, ymid, pass); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tc8c/branching.c8: -------------------------------------------------------------------------------- 1 | number[] = { 2 | { 0x00, 0x00, 0x20, 0x50, 0x50, 0x50, 0x20, 0x00 }, 3 | { 0x00, 0x00, 0x20, 0x60, 0x20, 0x20, 0x70, 0x00 }, 4 | }; 5 | 6 | tbranch(y) 7 | { 8 | auto x = 0, dx = 8; 9 | auto t = 0; 10 | if(0 == 4) { 11 | t = 4; 12 | } else if(0 == 3) { 13 | t = 3; 14 | } else if(0 == 2) { 15 | t = 2; 16 | } else if(0 == 1) { 17 | t = 1; 18 | } else if(0 == 0) { 19 | t = 0xAA; 20 | } else { 21 | t = 0xFF; 22 | } 23 | auto a = t == 0xAA; 24 | draw(x, y, number[a]); x += dx; 25 | // Nested testing. 26 | if(0 == 4) { 27 | t = 4; 28 | } else if(0 == 3) { 29 | t = 3; 30 | } else if(0 == 2) { 31 | t = 2; 32 | } else if(0 == 1) { 33 | t = 1; 34 | } else if(0 == 0) { 35 | auto z = 3; 36 | if(z == 3) { 37 | if(z == 3) { 38 | if(z != 3) { 39 | } else if(z == 4) { 40 | } else { 41 | t = 0xBB; 42 | } 43 | } 44 | } 45 | } else { 46 | t = 0xFF; 47 | } 48 | auto b = t == 0xBB; 49 | draw(x, y, number[a]); x += dx; 50 | // Reaching the else. 51 | if(0 == 4) { 52 | putchar(32, 16, 255); 53 | } else if(0 == 3) { 54 | putchar(32, 16, 255); 55 | } else if(0 == 2) { 56 | putchar(32, 16, 255); 57 | } else if(0 == 1) { 58 | putchar(32, 16, 255); 59 | } else { 60 | t = 0xFF; 61 | } 62 | auto c = t == 0xFF; 63 | draw(x, y, number[c]); x += dx; 64 | return a && b && c; 65 | } 66 | 67 | delay(t) 68 | { 69 | while(t) 70 | { 71 | t -= 1; 72 | } 73 | } 74 | 75 | flash(x, y, value) 76 | { 77 | while(1) 78 | { 79 | draw(x, y, number[value]); 80 | delay(40); 81 | } 82 | } 83 | 84 | place(x, y, value) 85 | { 86 | draw(x, y, number[value]); 87 | while(1) 88 | { 89 | } 90 | } 91 | 92 | main() 93 | { 94 | auto dy = sizeof(number); 95 | auto y = -dy; 96 | auto pass = 1; 97 | pass &= tbranch(y += dy); 98 | clear(); 99 | // Roughly middle of screen. 100 | auto xmid = 32 - 4; 101 | auto ymid = 16 - 4; 102 | // If the test passed, 1 will be drawn to 103 | // the middle of the screen. Otherwise, 104 | // 0 will flash on screen. 105 | if(pass) 106 | { 107 | place(xmid, ymid, pass); 108 | } 109 | else 110 | { 111 | flash(xmid, ymid, pass); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /tc8c/logical0.c8: -------------------------------------------------------------------------------- 1 | number[] = { 2 | { 0x00, 0x00, 0x20, 0x50, 0x50, 0x50, 0x20, 0x00 }, 3 | { 0x00, 0x00, 0x20, 0x60, 0x20, 0x20, 0x70, 0x00 }, 4 | }; 5 | 6 | // Tests logical OR. 7 | tor(y) 8 | { 9 | auto x = 0, dx = 8; 10 | auto a = (0 || 0 || 0 || 0) == 0; 11 | auto b = (0 || 0 || 0 || 1) == 1; 12 | auto c = (0 || 0 || 2 || 0) == 1; 13 | auto d = (0 || 3 || 0 || 0) == 1; 14 | auto e = (4 || 0 || 0 || 0) == 1; 15 | draw(x, y, number[a]); x += dx; 16 | draw(x, y, number[b]); x += dx; 17 | draw(x, y, number[c]); x += dx; 18 | draw(x, y, number[d]); x += dx; 19 | draw(x, y, number[e]); x += dx; 20 | return a && b && c && d && e; 21 | } 22 | 23 | // Tests logical OR with forced expressions. 24 | torf(y) 25 | { 26 | auto x = 0, dx = 8; 27 | auto a = ((0 || 0) || (0 || 0)) == 0; 28 | auto b = ((0 || 0) || (0 || 1)) == 1; 29 | auto c = ((0 || 0) || (2 || 0)) == 1; 30 | auto d = ((0 || 3) || (0 || 0)) == 1; 31 | auto e = ((4 || 0) || (0 || 0)) == 1; 32 | draw(x, y, number[a]); x += dx; 33 | draw(x, y, number[b]); x += dx; 34 | draw(x, y, number[c]); x += dx; 35 | draw(x, y, number[d]); x += dx; 36 | draw(x, y, number[e]); x += dx; 37 | return a && b && c && d && e; 38 | } 39 | 40 | // Tests and logical AND. 41 | tand(y) 42 | { 43 | auto x = 0, dx = 8; 44 | auto a = (5 && 9 && 4 && 1) == 1; 45 | auto b = (0 && 0 && 0 && 1) == 0; 46 | auto c = (0 && 0 && 2 && 0) == 0; 47 | auto d = (0 && 3 && 0 && 0) == 0; 48 | auto e = (4 && 0 && 0 && 0) == 0; 49 | draw(x, y, number[a]); x += dx; 50 | draw(x, y, number[b]); x += dx; 51 | draw(x, y, number[c]); x += dx; 52 | draw(x, y, number[d]); x += dx; 53 | draw(x, y, number[e]); x += dx; 54 | return a && b && c && d && e; 55 | } 56 | 57 | // Tests logical AND with forced expressions. 58 | tandf(y) 59 | { 60 | auto x = 0, dx = 8; 61 | auto a = ((5 && 9) && (4 && 1)) == 1; 62 | auto b = ((0 && 0) && (0 && 1)) == 0; 63 | auto c = ((0 && 0) && (2 && 0)) == 0; 64 | auto d = ((0 && 3) && (0 && 0)) == 0; 65 | auto e = ((4 && 0) && (0 && 0)) == 0; 66 | draw(x, y, number[a]); x += dx; 67 | draw(x, y, number[b]); x += dx; 68 | draw(x, y, number[c]); x += dx; 69 | draw(x, y, number[d]); x += dx; 70 | draw(x, y, number[e]); x += dx; 71 | return a && b && c && d && e; 72 | } 73 | 74 | delay(t) 75 | { 76 | while(t) 77 | { 78 | t -= 1; 79 | } 80 | } 81 | 82 | flash(x, y, value) 83 | { 84 | while(1) 85 | { 86 | draw(x, y, number[value]); 87 | delay(40); 88 | } 89 | } 90 | 91 | place(x, y, value) 92 | { 93 | draw(x, y, number[value]); 94 | while(1) 95 | { 96 | } 97 | } 98 | 99 | main() 100 | { 101 | auto dy = sizeof(number); 102 | auto y = -dy; 103 | auto pass = 1; 104 | pass &= tor (y += dy); 105 | pass &= torf (y += dy); 106 | pass &= tand (y += dy); 107 | pass &= tandf(y += dy); 108 | clear(); 109 | // Roughly middle of screen. 110 | auto xmid = 32 - 4; 111 | auto ymid = 16 - 4; 112 | // If the test passed, 1 will be drawn to 113 | // the middle of the screen. Otherwise, 114 | // 0 will flash on screen. 115 | if(pass) 116 | { 117 | place(xmid, ymid, pass); 118 | } 119 | else 120 | { 121 | flash(xmid, ymid, pass); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /tc8c/logical1.c8: -------------------------------------------------------------------------------- 1 | number[] = { 2 | { 0x00, 0x00, 0x20, 0x50, 0x50, 0x50, 0x20, 0x00 }, 3 | { 0x00, 0x00, 0x20, 0x60, 0x20, 0x20, 0x70, 0x00 }, 4 | }; 5 | 6 | // Tests the not operator 7 | tnot(y) 8 | { 9 | auto x = 0, dx = 8; 10 | auto a = !0 == 1; 11 | auto b = !1 == 0; 12 | auto c = !9 == 0; 13 | draw(x, y, number[a]); x += dx; 14 | draw(x, y, number[b]); x += dx; 15 | draw(x, y, number[c]); x += dx; 16 | return a && b && c; 17 | } 18 | 19 | // Tests equality and non-equality. 20 | tequal(y) 21 | { 22 | auto x = 0, dx = 8; 23 | auto a = (7 == 7) == 1; 24 | auto b = (7 != 7) == 0; 25 | auto c = (1 == 1) == 1; 26 | auto d = (0 == 0) == 1; 27 | auto e = (0 == 1) == 0; 28 | draw(x, y, number[a]); x += dx; 29 | draw(x, y, number[b]); x += dx; 30 | draw(x, y, number[c]); x += dx; 31 | draw(x, y, number[d]); x += dx; 32 | draw(x, y, number[e]); x += dx; 33 | return a && b && c && d && e; 34 | } 35 | 36 | // Tests long equality. 37 | tlequal(y) 38 | { 39 | auto x = 0, dx = 8; 40 | auto a = 4 + 3 + 2 == 4 + 3 + 2; 41 | auto b = 4 + 3 + 1 != 4 + 3 + 2; 42 | auto c = 4 + 3 + 1 <= 4 + 3 + 2; 43 | draw(x, y, number[a]); x += dx; 44 | draw(x, y, number[b]); x += dx; 45 | draw(x, y, number[c]); x += dx; 46 | return a && b && c; 47 | } 48 | tlequal2(y) 49 | { 50 | auto x = 0, dx = 8; 51 | auto d = 4 + 3 + 3 >= 4 + 3 + 2; 52 | auto e = 4 + 3 + 2 <= 4 + 3 + 2; 53 | auto f = 4 + 3 + 2 >= 4 + 3 + 2; 54 | auto g = 4 + 3 < 4 + 3 + 2; 55 | auto h = 4 + 3 + 2 > 4 + 3; 56 | draw(x, y, number[d]); x += dx; 57 | draw(x, y, number[e]); x += dx; 58 | draw(x, y, number[f]); x += dx; 59 | draw(x, y, number[g]); x += dx; 60 | draw(x, y, number[h]); x += dx; 61 | return d && e && f && g && h; 62 | } 63 | 64 | // Tests comparison. 65 | tcompare(y) 66 | { 67 | auto x = 0, dx = 8; 68 | auto a = (0 < 0) == 0; 69 | auto b = (0 <= 0) == 1; 70 | auto c = (0 > 0) == 0; 71 | auto d = (0 >= 0) == 1; 72 | draw(x, y, number[a]); x += dx; 73 | draw(x, y, number[b]); x += dx; 74 | draw(x, y, number[c]); x += dx; 75 | draw(x, y, number[d]); x += dx; 76 | return a && b && c && d; 77 | } 78 | 79 | delay(t) 80 | { 81 | while(t) 82 | { 83 | t -= 1; 84 | } 85 | } 86 | 87 | flash(x, y, value) 88 | { 89 | while(1) 90 | { 91 | draw(x, y, number[value]); 92 | delay(40); 93 | } 94 | } 95 | 96 | place(x, y, value) 97 | { 98 | draw(x, y, number[value]); 99 | while(1) 100 | { 101 | } 102 | } 103 | 104 | main() 105 | { 106 | auto dy = sizeof(number); 107 | auto y = -dy; 108 | auto pass = 1; 109 | pass &= tequal (y += dy); 110 | pass &= tcompare(y += dy); 111 | pass &= tnot (y += dy); 112 | pass &= tlequal (y += dy); 113 | clear(); 114 | y = -dy; 115 | pass &= tlequal (y += dy); 116 | pass &= tlequal2(y += dy); 117 | clear(); 118 | // Roughly middle of screen. 119 | auto xmid = 32 - 4; 120 | auto ymid = 16 - 4; 121 | // If the test passed, 1 will be drawn to 122 | // the middle of the screen. Otherwise, 123 | // 0 will flash on screen. 124 | if(pass) 125 | { 126 | place(xmid, ymid, pass); 127 | } 128 | else 129 | { 130 | flash(xmid, ymid, pass); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /tc8c/sizeof.c8: -------------------------------------------------------------------------------- 1 | number[] = { 2 | { 0x00, 0x00, 0x20, 0x50, 0x50, 0x50, 0x20, 0x00 }, 3 | { 0x00, 0x00, 0x20, 0x60, 0x20, 0x20, 0x70, 0x00 }, 4 | }; 5 | 6 | tsizeof(y) 7 | { 8 | auto x = 0, dx = 8; 9 | auto t = 0; 10 | auto a = (sizeof(number)) == 8; 11 | auto b = (sizeof(number[])) == 2; 12 | draw(x, y, number[a]); x += dx; 13 | draw(x, y, number[b]); x += dx; 14 | return a && b; 15 | } 16 | 17 | delay(t) 18 | { 19 | while(t) 20 | { 21 | t -= 1; 22 | } 23 | } 24 | 25 | flash(x, y, value) 26 | { 27 | while(1) 28 | { 29 | draw(x, y, number[value]); 30 | delay(40); 31 | } 32 | } 33 | 34 | place(x, y, value) 35 | { 36 | draw(x, y, number[value]); 37 | while(1) 38 | { 39 | } 40 | } 41 | 42 | main() 43 | { 44 | auto dy = sizeof(number); 45 | auto y = -dy; 46 | auto pass = 1; 47 | pass &= tsizeof(y += dy); 48 | clear(); 49 | // Roughly middle of screen. 50 | auto xmid = 32 - 4; 51 | auto ymid = 16 - 4; 52 | // If the test passed, 1 will be drawn to 53 | // the middle of the screen. Otherwise, 54 | // 0 will flash on screen. 55 | if(pass) 56 | { 57 | place(xmid, ymid, pass); 58 | } 59 | else 60 | { 61 | flash(xmid, ymid, pass); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tc8c/while.c8: -------------------------------------------------------------------------------- 1 | number[] = { 2 | { 0x00, 0x00, 0x20, 0x50, 0x50, 0x50, 0x20, 0x00 }, 3 | { 0x00, 0x00, 0x20, 0x60, 0x20, 0x20, 0x70, 0x00 }, 4 | }; 5 | 6 | delay(t) 7 | { 8 | while(t) 9 | { 10 | t -= 1; 11 | } 12 | } 13 | 14 | twhile(y) 15 | { 16 | auto x = 0, dx = 8; 17 | auto a = 255; 18 | while(a) 19 | { 20 | clear(); 21 | putchar(0, 0, a); 22 | a -= 1; 23 | } 24 | return a == 0; 25 | } 26 | 27 | flash(x, y, value) 28 | { 29 | while(1) 30 | { 31 | draw(x, y, number[value]); 32 | delay(40); 33 | } 34 | } 35 | 36 | place(x, y, value) 37 | { 38 | draw(x, y, number[value]); 39 | while(1) 40 | { 41 | } 42 | } 43 | 44 | main() 45 | { 46 | auto dy = sizeof(number); 47 | auto y = -dy; 48 | auto pass = 1; 49 | pass &= twhile(y += dy); 50 | clear(); 51 | // Roughly middle of screen. 52 | auto xmid = 32 - 4; 53 | auto ymid = 16 - 4; 54 | // If the test passed, 1 will be drawn to 55 | // the middle of the screen. Otherwise, 56 | // 0 will flash on screen. 57 | if(pass) 58 | { 59 | place(xmid, ymid, pass); 60 | } 61 | else 62 | { 63 | flash(xmid, ymid, pass); 64 | } 65 | } 66 | --------------------------------------------------------------------------------