145 lines
5.7 KiB
C
145 lines
5.7 KiB
C
/**
|
|
* n808: an ultralight numeric-only VM
|
|
* ANSI C89 reference implementation
|
|
* Compile with: cc -std=c89 -O2 -s -lm -o n808 n808.c
|
|
* See the README.md file for all documentation
|
|
* Created by Luxferre in 2025, released into public domain
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
#include <math.h>
|
|
|
|
/* POSIX-specific terminal stuff for unbuffered input for I/O port 2 */
|
|
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
|
|
#define NLC "\n"
|
|
#include <unistd.h>
|
|
#include <termios.h>
|
|
struct termios orig_termios;
|
|
void disable_raw_mode() {
|
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
|
|
}
|
|
void enable_raw_mode() {
|
|
tcgetattr(STDIN_FILENO, &orig_termios);
|
|
atexit(disable_raw_mode);
|
|
struct termios raw = orig_termios;
|
|
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
|
raw.c_oflag &= ~(OPOST);
|
|
raw.c_cflag |= (CS8);
|
|
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
|
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
|
|
}
|
|
#else
|
|
#define NLC "\r\n"
|
|
void disable_raw_mode() {}
|
|
void enable_raw_mode() {}
|
|
#endif
|
|
|
|
/* core memory */
|
|
#define MEMLIMIT 128
|
|
static unsigned int PMEM[MEMLIMIT] = {0};
|
|
static double DMEM[MEMLIMIT] = {0.0};
|
|
#define uchar unsigned char
|
|
|
|
/* port input/output function */
|
|
void portio(uchar port, double *data) {
|
|
if(port == 0) printf("%f" NLC, *data); /* standard numeric output */
|
|
else if(port == 1) scanf("%lf", data); /* standard numeric input */
|
|
else if(port == 2) fputc(((int)floor(*data)) & 255, stdout); /* char output */
|
|
else if(port == 3) { /* character input */
|
|
enable_raw_mode();
|
|
*data = (double) (fgetc(stdin) & 255);
|
|
disable_raw_mode();
|
|
}
|
|
}
|
|
|
|
/* main instruction execution logic */
|
|
void iexec(unsigned short lno) {
|
|
uchar halt = 0, data_override = 0, cmd, p1, p2, p3, i;
|
|
double v1, v2, v3;
|
|
while(halt == 0) { /* decode and execute the current instruction */
|
|
cmd = (PMEM[lno] >> 21) & 7; /* command opcode */
|
|
p1 = (data_override ? ((int)floor(v1)) : (PMEM[lno] >> 14)) & 127; /* parameter 1 */
|
|
p2 = (data_override ? ((int)floor(v2)) : (PMEM[lno] >> 7)) & 127; /* parameter 2 */
|
|
p3 = (data_override ? ((int)floor(v3)) : PMEM[lno]) & 127; /* parameter 3 */
|
|
DMEM[0] = data_override = 0; /* enforce the 0 at the location 0 */
|
|
DMEM[127] = 1; /* enforce the 1 at the location 127 */
|
|
DMEM[126] = -1; /* enforce the -1 at the location 126 */
|
|
v1 = DMEM[p1]; v2 = DMEM[p2]; v3 = DMEM[p3]; /* prefetch the values */
|
|
if(cmd == 1) { /* JMP */
|
|
if(p1 == 14) DMEM[125] = lno + 1;
|
|
if((v2 == 0 && p1 == 0) || (v2 > 0 && p1 == 1) || (v2 < 0 && p1 == 2) \
|
|
|| (v2 >= 0 && p1 == 3) || (v2 <= 0 && p1 == 4) || (v2 != 0 && p1 == 5) \
|
|
|| p1 == 6 || p1 == 14) lno = p3 - 1;
|
|
else if((v2 == 0 && p1 == 7) || (v2 > 0 && p1 == 8) || (v2 < 0 && p1 == 9) \
|
|
|| (v2 >= 0 && p1 == 10) || (v2 <= 0 && p1 == 11) || (v2 != 0 && p1 == 12) \
|
|
|| p1 == 13) lno = (int)floor(v3) - 1;
|
|
} else if(cmd == 2) data_override = 1; /* IAT */
|
|
else if(cmd == 3) for(i=p2;i<=p3;i++) portio(p1, &DMEM[i]); /* INO */
|
|
else if(cmd == 4) { /* CPY */
|
|
if(p1 == 0) DMEM[p3] = p2;
|
|
else if(p1 == 1) DMEM[p3] = v2;
|
|
else if(p1 == 2) DMEM[(int)v3] = p2;
|
|
else if(p1 == 3) DMEM[(int)v3] = v2;
|
|
else if(p1 == 4) DMEM[(int)v3] = DMEM[(int)v2];
|
|
} else if(cmd == 5) DMEM[p3] = p1 * 100 + p2 + v3 / 100.0; /* SET */
|
|
else if(cmd == 6) { /* MAT */
|
|
if(p1 == 0) DMEM[p3] = v2 + v3;
|
|
else if(p1 == 1) DMEM[p3] = v2 - v3;
|
|
else if(p1 == 2) DMEM[p3] = v2 * v3;
|
|
else if(p1 == 3) DMEM[p3] = (v3 == 0) ? 0 : (v2 / v3);
|
|
else if(p1 == 4) DMEM[p3] = (v3 == 0) ? floor(v2) : fmod(v2, v3);
|
|
else if(p1 == 5) DMEM[p3] = fabs(v2);
|
|
else if(p1 == 6) DMEM[p3] = sqrt(fabs(v2));
|
|
else if(p1 == 7) DMEM[p3] = exp(v2);
|
|
else if(p1 == 8) DMEM[p3] = (v2 == 0) ? 0 : log(fabs(v2));
|
|
else if(p1 == 9) DMEM[p3] = sin(v2);
|
|
else if(p1 == 10) DMEM[p3] = cos(v2);
|
|
else if(p1 == 11) DMEM[p3] = atan(v2);
|
|
} else if(cmd == 7) { /* RND */
|
|
v1 = floor(v1);
|
|
DMEM[p3] = v1 + (rand() % (int)(floor(v2) + 1 - v1));
|
|
}
|
|
lno++; /* increment the program counter */
|
|
if(lno >= MEMLIMIT) halt = 1;
|
|
}
|
|
}
|
|
|
|
/* interactive mode entry function */
|
|
void intermode(unsigned int cmd, unsigned int p1, unsigned int p2) {
|
|
unsigned int i;
|
|
if(cmd == 0 && p1 < MEMLIMIT) iexec(p1); /* run from the step */
|
|
else if(cmd == 1) PMEM[p1] = p2; /* enter the instruction into PMEM */
|
|
else if(cmd == 2 && p1 < MEMLIMIT && p2 < MEMLIMIT) /* clear instructions */
|
|
for(i=p1;i<=p2;i++) PMEM[i] = 0;
|
|
else if(cmd == 3 && p1 < MEMLIMIT && p2 < MEMLIMIT) /* clear data */
|
|
for(i=p1;i<=p2;i++) DMEM[i] = 0.0;
|
|
else if(cmd == 4) {puts("Bye!"); exit(0);}
|
|
}
|
|
|
|
/* entry point to the VM REPL */
|
|
void main(int argc, char* argv[]) {
|
|
srand(time(NULL));
|
|
unsigned int cmd, p1, p2, instr, lno = 0; /* commands and their parameters */
|
|
if(argc > 1) { /* preload the file from a command line parameter */
|
|
FILE *fd = fopen(argv[1], "r");
|
|
if(fd != NULL) { /* opened successfully */
|
|
while(!feof(fd)) {
|
|
if(fscanf(fd, "%u", &instr) == 1) { /* read the instruction number */
|
|
intermode(1, lno, instr); /* run the entry routine */
|
|
lno++;
|
|
}
|
|
}
|
|
fclose(fd);
|
|
} else puts("Warning: no file could be preloaded!");
|
|
}
|
|
while(1) { /* main interactive loop */
|
|
printf("> ");
|
|
cmd = p1 = p2 = 0;
|
|
scanf("%u %u %u", &cmd, &p1, &p2); /* read the three numbers */
|
|
while(getchar() != '\n'); /* ignore the rest of the line */
|
|
intermode(cmd, p1, p2); /* run the entry routine */
|
|
}
|
|
}
|