2019-01-04 02:50:49 +01:00
|
|
|
/* RETRO ------------------------------------------------------
|
2018-01-09 14:58:34 +01:00
|
|
|
A personal, minimalistic forth
|
2019-01-03 15:18:17 +01:00
|
|
|
Copyright (c) 2016 - 2019 Charles Childers
|
2017-10-16 18:09:39 +02:00
|
|
|
|
2018-11-05 03:34:53 +01:00
|
|
|
This is `rre`, short for `run retro and exit`. It's the basic
|
|
|
|
interface layer for Retro on FreeBSD, Linux and macOS.
|
2018-01-09 14:58:34 +01:00
|
|
|
|
2019-01-04 02:50:49 +01:00
|
|
|
rre embeds the image file into the binary, so the compiled
|
|
|
|
version of this is all you need to have a functional system.
|
2018-01-09 14:58:34 +01:00
|
|
|
|
|
|
|
I'll include commentary throughout the source, so read on.
|
2019-01-04 02:50:49 +01:00
|
|
|
---------------------------------------------------------- */
|
2018-11-23 21:38:46 +01:00
|
|
|
|
2019-01-04 02:50:49 +01:00
|
|
|
/* ------------------------------------------------------------
|
2018-01-09 14:58:34 +01:00
|
|
|
Begin by including the various C headers needed.
|
2019-01-04 02:50:49 +01:00
|
|
|
---------------------------------------------------------- */
|
2017-10-16 18:09:39 +02:00
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <string.h>
|
2017-10-20 03:11:18 +02:00
|
|
|
#include <math.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <netdb.h>
|
2017-11-04 22:22:36 +01:00
|
|
|
#include <errno.h>
|
2017-11-05 03:46:38 +01:00
|
|
|
#include <sys/wait.h>
|
2017-11-05 03:51:13 +01:00
|
|
|
#include <signal.h>
|
2017-10-20 03:11:18 +02:00
|
|
|
|
2019-01-15 22:31:41 +01:00
|
|
|
#include "image-functions.h"
|
|
|
|
|
2018-11-23 21:38:46 +01:00
|
|
|
#define USE_TERMIOS
|
|
|
|
|
2018-04-04 18:11:44 +02:00
|
|
|
#ifdef USE_TERMIOS
|
|
|
|
#include <termios.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#endif
|
2017-10-20 03:30:55 +02:00
|
|
|
|
2018-11-23 21:38:46 +01:00
|
|
|
|
|
|
|
void generic_output();
|
|
|
|
void generic_output_query();
|
|
|
|
void io_keyboard_handler();
|
|
|
|
void io_keyboard_query();
|
|
|
|
void io_filesystem_query();
|
|
|
|
void io_filesystem_handler();
|
|
|
|
void io_unix_query();
|
|
|
|
void io_unix_handler();
|
|
|
|
void io_floatingpoint_query();
|
|
|
|
void io_floatingpoint_handler();
|
|
|
|
void io_gopher_query();
|
|
|
|
void io_gopher_handler();
|
|
|
|
void io_scripting_handler();
|
|
|
|
void io_scripting_query();
|
2019-02-06 14:09:35 +01:00
|
|
|
void io_image();
|
|
|
|
void io_image_query();
|
2018-11-23 21:38:46 +01:00
|
|
|
|
2019-02-06 14:09:35 +01:00
|
|
|
#define NUM_DEVICES 8
|
2018-11-22 01:05:28 +01:00
|
|
|
|
|
|
|
typedef void (*Handler)(void);
|
|
|
|
|
2018-11-23 21:38:46 +01:00
|
|
|
Handler IO_deviceHandlers[NUM_DEVICES + 1] = {
|
|
|
|
generic_output,
|
|
|
|
io_keyboard_handler,
|
|
|
|
io_filesystem_handler,
|
|
|
|
io_floatingpoint_handler,
|
|
|
|
io_scripting_handler,
|
|
|
|
io_unix_handler,
|
2019-02-06 14:09:35 +01:00
|
|
|
io_gopher_handler,
|
|
|
|
io_image
|
2018-11-23 21:38:46 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
Handler IO_queryHandlers[NUM_DEVICES + 1] = {
|
|
|
|
generic_output_query,
|
|
|
|
io_keyboard_query,
|
|
|
|
io_filesystem_query,
|
|
|
|
io_floatingpoint_query,
|
|
|
|
io_scripting_query,
|
|
|
|
io_unix_query,
|
2019-02-06 14:09:35 +01:00
|
|
|
io_gopher_query,
|
|
|
|
io_image_query
|
2018-11-23 21:38:46 +01:00
|
|
|
};
|
2018-11-22 01:05:28 +01:00
|
|
|
|
2018-01-09 14:58:34 +01:00
|
|
|
|
|
|
|
CELL sp, rp, ip; /* Data, address, instruction pointers */
|
|
|
|
CELL data[STACK_DEPTH]; /* The data stack */
|
|
|
|
CELL address[ADDRESSES]; /* The address stack */
|
|
|
|
CELL memory[IMAGE_SIZE + 1]; /* The memory for the image */
|
|
|
|
|
2019-01-04 02:50:49 +01:00
|
|
|
/* ------------------------------------------------------------
|
|
|
|
RRE embeds the image into the binary. This includes the image
|
|
|
|
data (converted to a .c file by an external tool).
|
|
|
|
---------------------------------------------------------- */
|
2018-01-09 22:59:00 +01:00
|
|
|
|
2019-01-04 06:10:10 +01:00
|
|
|
#include "rre_image.c"
|
2018-01-09 22:59:00 +01:00
|
|
|
|
|
|
|
|
2019-01-15 22:31:41 +01:00
|
|
|
void rre_execute(CELL cell, int silent);
|
|
|
|
void rre_evaluate(char *s, int silent);
|
2017-10-20 03:11:18 +02:00
|
|
|
int not_eol(int ch);
|
|
|
|
void read_token(FILE *file, char *token_buffer, int echo);
|
2018-04-30 15:08:52 +02:00
|
|
|
void include_file(char *fname, int run_tests);
|
2017-10-20 03:11:18 +02:00
|
|
|
CELL ngaLoadImage(char *imageFile);
|
|
|
|
void ngaPrepare();
|
|
|
|
|
2017-10-20 03:30:55 +02:00
|
|
|
|
2019-01-04 02:50:49 +01:00
|
|
|
/* ------------------------------------------------------------
|
2018-01-09 22:59:00 +01:00
|
|
|
Declare global variables related to I/O.
|
2019-01-04 02:50:49 +01:00
|
|
|
---------------------------------------------------------- */
|
2018-01-09 22:59:00 +01:00
|
|
|
|
2017-10-20 03:11:18 +02:00
|
|
|
char **sys_argv;
|
|
|
|
int sys_argc;
|
2018-11-22 05:32:11 +01:00
|
|
|
int silence_input;
|
2017-10-20 03:11:18 +02:00
|
|
|
|
|
|
|
|
2018-01-09 14:58:34 +01:00
|
|
|
/*---------------------------------------------------------------------
|
|
|
|
Now on to I/O and extensions!
|
|
|
|
|
|
|
|
RRE provides a lot of additional functionality over the base RETRO
|
|
|
|
system. First up is support for files.
|
|
|
|
|
|
|
|
The RRE file model is intended to be similar to that of the standard
|
|
|
|
C libraries and wraps fopen(), fclose(), etc.
|
|
|
|
---------------------------------------------------------------------*/
|
|
|
|
|
2018-11-22 01:05:28 +01:00
|
|
|
void generic_output() {
|
|
|
|
putc(stack_pop(), stdout);
|
|
|
|
fflush(stdout);
|
|
|
|
}
|
|
|
|
|
|
|
|
void generic_output_query() {
|
|
|
|
stack_push(0);
|
|
|
|
stack_push(0);
|
|
|
|
}
|
|
|
|
|
2018-11-22 05:32:11 +01:00
|
|
|
void io_keyboard_handler() {
|
|
|
|
stack_push(getc(stdin));
|
|
|
|
if (TOS == 127) TOS = 8;
|
|
|
|
#ifdef USE_TERMIOS
|
|
|
|
if (silence_input != -1) {
|
|
|
|
putc(TOS, stdout);
|
|
|
|
fflush(stdout);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void io_keyboard_query() {
|
|
|
|
stack_push(0);
|
|
|
|
stack_push(1);
|
|
|
|
}
|
|
|
|
|
2018-01-09 14:58:34 +01:00
|
|
|
|
2018-04-04 18:11:44 +02:00
|
|
|
/*---------------------------------------------------------------------
|
|
|
|
---------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
#ifdef USE_TERMIOS
|
|
|
|
struct termios new_termios, old_termios;
|
|
|
|
|
|
|
|
void prepare_term() {
|
|
|
|
tcgetattr(0, &old_termios);
|
|
|
|
new_termios = old_termios;
|
|
|
|
new_termios.c_iflag &= ~(BRKINT+ISTRIP+IXON+IXOFF);
|
|
|
|
new_termios.c_iflag |= (IGNBRK+IGNPAR);
|
|
|
|
new_termios.c_lflag &= ~(ICANON+ISIG+IEXTEN+ECHO);
|
|
|
|
new_termios.c_cc[VMIN] = 1;
|
|
|
|
new_termios.c_cc[VTIME] = 0;
|
|
|
|
tcsetattr(0, TCSANOW, &new_termios);
|
|
|
|
}
|
|
|
|
|
|
|
|
void restore_term() {
|
|
|
|
tcsetattr(0, TCSANOW, &old_termios);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
2018-11-23 17:05:40 +01:00
|
|
|
void scripting_arg() {
|
|
|
|
CELL a, b;
|
|
|
|
a = stack_pop();
|
|
|
|
b = stack_pop();
|
|
|
|
stack_push(string_inject(sys_argv[a + 2], b));
|
|
|
|
}
|
|
|
|
|
|
|
|
void scripting_arg_count() {
|
|
|
|
stack_push(sys_argc - 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
void scripting_include() {
|
|
|
|
include_file(string_extract(stack_pop()), 0);
|
|
|
|
}
|
|
|
|
|
2019-03-06 18:22:56 +01:00
|
|
|
void scripting_name() {
|
|
|
|
stack_push(string_inject(sys_argv[1], stack_pop()));
|
|
|
|
}
|
|
|
|
|
2018-11-23 17:05:40 +01:00
|
|
|
Handler ScriptingActions[] = {
|
|
|
|
scripting_arg_count,
|
2018-11-23 21:38:46 +01:00
|
|
|
scripting_arg,
|
2019-03-06 18:22:56 +01:00
|
|
|
scripting_include,
|
|
|
|
scripting_name
|
2018-11-23 17:05:40 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
void io_scripting_query() {
|
|
|
|
stack_push(0);
|
|
|
|
stack_push(9);
|
|
|
|
}
|
|
|
|
|
|
|
|
void io_scripting_handler() {
|
|
|
|
ScriptingActions[stack_pop()]();
|
|
|
|
}
|
|
|
|
|
2019-02-06 14:09:35 +01:00
|
|
|
void io_image() {
|
|
|
|
FILE *fp;
|
|
|
|
char *f = string_extract(stack_pop());
|
|
|
|
if ((fp = fopen(f, "wb")) == NULL) {
|
|
|
|
printf("Unable to save the image: %s!\n", f);
|
|
|
|
exit(2);
|
|
|
|
}
|
|
|
|
fwrite(&memory, sizeof(CELL), memory[3] + 1, fp);
|
|
|
|
fclose(fp);
|
|
|
|
}
|
|
|
|
|
|
|
|
void io_image_query() {
|
|
|
|
stack_push(0);
|
|
|
|
stack_push(1000);
|
|
|
|
}
|
|
|
|
|
2018-01-09 22:36:33 +01:00
|
|
|
/*---------------------------------------------------------------------
|
|
|
|
With these out of the way, I implement `execute`, which takes an
|
|
|
|
address and runs the code at it. This has a couple of interesting
|
|
|
|
bits.
|
|
|
|
|
|
|
|
Nga uses packed instruction bundles, with up to four instructions per
|
|
|
|
bundle. Since RETRO requires an additional instruction to handle
|
|
|
|
displaying a character, I define the handler for that here.
|
|
|
|
|
|
|
|
This will also exit if the address stack depth is zero (meaning that
|
|
|
|
the word being run, and it's dependencies) are finished.
|
|
|
|
---------------------------------------------------------------------*/
|
|
|
|
|
2019-01-15 22:31:41 +01:00
|
|
|
void rre_execute(CELL cell, int silent) {
|
2018-10-09 21:48:54 +02:00
|
|
|
CELL a, b, token;
|
2018-01-09 15:47:13 +01:00
|
|
|
CELL opcode;
|
2018-11-22 05:32:11 +01:00
|
|
|
silence_input = silent;
|
2018-01-09 15:47:13 +01:00
|
|
|
rp = 1;
|
|
|
|
ip = cell;
|
2018-10-09 21:48:54 +02:00
|
|
|
token = TIB;
|
2018-01-09 15:47:13 +01:00
|
|
|
while (ip < IMAGE_SIZE) {
|
|
|
|
if (ip == NotFound) {
|
2018-04-09 13:38:14 +02:00
|
|
|
printf("\nERROR: Word Not Found: ");
|
2018-10-09 21:48:54 +02:00
|
|
|
printf("`%s`\n\n", string_extract(token));
|
|
|
|
}
|
|
|
|
if (ip == interpret) {
|
|
|
|
token = TOS;
|
2018-01-09 15:47:13 +01:00
|
|
|
}
|
|
|
|
opcode = memory[ip];
|
|
|
|
if (ngaValidatePackedOpcodes(opcode) != 0) {
|
|
|
|
ngaProcessPackedOpcodes(opcode);
|
|
|
|
} else {
|
2018-11-23 17:05:40 +01:00
|
|
|
printf("Invalid instruction!\n");
|
|
|
|
printf("At %d, opcode %d\n", ip, opcode);
|
2018-04-04 18:11:44 +02:00
|
|
|
#ifdef USE_TERMIOS
|
2018-11-23 17:05:40 +01:00
|
|
|
restore_term();
|
2018-04-04 18:11:44 +02:00
|
|
|
#endif
|
2018-11-23 17:05:40 +01:00
|
|
|
exit(1);
|
2018-01-09 15:47:13 +01:00
|
|
|
}
|
|
|
|
ip++;
|
|
|
|
if (rp == 0)
|
|
|
|
ip = IMAGE_SIZE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-01-09 22:36:33 +01:00
|
|
|
|
2018-01-09 15:47:13 +01:00
|
|
|
/*---------------------------------------------------------------------
|
2018-01-09 22:36:33 +01:00
|
|
|
RETRO's `interpret` word expects a token on the stack. This next
|
|
|
|
function copies a token to the `TIB` (text input buffer) and then
|
|
|
|
calls `interpret` to process it.
|
2018-01-09 15:47:13 +01:00
|
|
|
---------------------------------------------------------------------*/
|
|
|
|
|
2019-01-15 22:31:41 +01:00
|
|
|
void rre_evaluate(char *s, int silent) {
|
|
|
|
//update_rx();
|
2018-01-09 15:47:13 +01:00
|
|
|
if (strlen(s) == 0)
|
|
|
|
return;
|
|
|
|
string_inject(s, TIB);
|
|
|
|
stack_push(TIB);
|
2019-01-15 22:31:41 +01:00
|
|
|
rre_execute(interpret, silent);
|
2018-01-09 15:47:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------
|
2018-01-09 22:36:33 +01:00
|
|
|
`read_token` reads a token from the specified file. It will stop on
|
|
|
|
a whitespace or newline. It also tries to handle backspaces, though
|
|
|
|
the success of this depends on how your terminal is configured.
|
2018-01-09 15:47:13 +01:00
|
|
|
---------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
int not_eol(int ch) {
|
|
|
|
return (ch != (char)10) && (ch != (char)13) && (ch != (char)32) && (ch != EOF) && (ch != 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void read_token(FILE *file, char *token_buffer, int echo) {
|
|
|
|
int ch = getc(file);
|
2018-01-27 21:18:29 +01:00
|
|
|
int count = 0;
|
2018-01-09 15:47:13 +01:00
|
|
|
if (echo != 0)
|
|
|
|
putchar(ch);
|
|
|
|
while (not_eol(ch))
|
|
|
|
{
|
|
|
|
if ((ch == 8 || ch == 127) && count > 0) {
|
|
|
|
count--;
|
|
|
|
if (echo != 0) {
|
|
|
|
putchar(8);
|
|
|
|
putchar(32);
|
|
|
|
putchar(8);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
token_buffer[count++] = ch;
|
|
|
|
}
|
|
|
|
ch = getc(file);
|
|
|
|
if (echo != 0)
|
|
|
|
putchar(ch);
|
|
|
|
}
|
|
|
|
token_buffer[count] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-01-09 14:58:34 +01:00
|
|
|
/*---------------------------------------------------------------------
|
|
|
|
---------------------------------------------------------------------*/
|
2017-10-16 18:09:39 +02:00
|
|
|
void dump_stack() {
|
|
|
|
CELL i;
|
|
|
|
if (sp == 0)
|
|
|
|
return;
|
|
|
|
printf("\nStack: ");
|
|
|
|
for (i = 1; i <= sp; i++) {
|
|
|
|
if (i == sp)
|
|
|
|
printf("[ TOS: %d ]", data[i]);
|
|
|
|
else
|
|
|
|
printf("%d ", data[i]);
|
|
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-01-09 14:58:34 +01:00
|
|
|
/*---------------------------------------------------------------------
|
2018-01-09 22:59:00 +01:00
|
|
|
RRE is primarily intended to be used in a batch or scripting model.
|
|
|
|
The `include_file()` function will be used to read the code in the
|
|
|
|
file, evaluating it as encountered.
|
|
|
|
|
|
|
|
I enforce a literate model, with code in fenced blocks. E.g.,
|
|
|
|
|
|
|
|
# This is a test
|
|
|
|
|
|
|
|
Display "Hello, World!"
|
|
|
|
|
|
|
|
~~~
|
|
|
|
'Hello,_World! puts nl
|
|
|
|
~~~
|
|
|
|
|
|
|
|
RRE will ignore anything outside the `~~~` blocks. To identify if the
|
|
|
|
current token is the start or end of a block, I provide a `fenced()`
|
|
|
|
function.
|
2018-01-09 14:58:34 +01:00
|
|
|
---------------------------------------------------------------------*/
|
2018-01-09 22:59:00 +01:00
|
|
|
|
2017-10-16 18:09:39 +02:00
|
|
|
int fenced(char *s)
|
|
|
|
{
|
|
|
|
int a = strcmp(s, "```");
|
|
|
|
int b = strcmp(s, "~~~");
|
2018-04-30 15:08:52 +02:00
|
|
|
if (a == 0) return 2;
|
2017-10-16 18:09:39 +02:00
|
|
|
if (b == 0) return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-01-09 14:58:34 +01:00
|
|
|
/*---------------------------------------------------------------------
|
2018-01-09 22:59:00 +01:00
|
|
|
And now for the actual `include_file()` function.
|
2018-01-09 14:58:34 +01:00
|
|
|
---------------------------------------------------------------------*/
|
2018-01-09 22:59:00 +01:00
|
|
|
|
2018-04-30 15:08:52 +02:00
|
|
|
void include_file(char *fname, int run_tests) {
|
2018-01-09 22:59:00 +01:00
|
|
|
int inBlock = 0; /* Tracks status of in/out of block */
|
|
|
|
char source[64 * 1024]; /* Line buffer [about 64K] */
|
|
|
|
char fence[4]; /* Used with `fenced()` */
|
|
|
|
|
|
|
|
FILE *fp; /* Open the file. If not found, */
|
|
|
|
fp = fopen(fname, "r"); /* exit. */
|
2017-10-16 18:09:39 +02:00
|
|
|
if (fp == NULL)
|
|
|
|
return;
|
2018-01-09 22:59:00 +01:00
|
|
|
|
|
|
|
while (!feof(fp)) /* Loop through the file */
|
2017-10-16 18:09:39 +02:00
|
|
|
{
|
|
|
|
read_token(fp, source, 0);
|
2018-01-09 22:59:00 +01:00
|
|
|
strncpy(fence, source, 3); /* Copy the first three characters */
|
|
|
|
fence[3] = '\0'; /* into `fence` to see if we are in */
|
2018-04-30 15:08:52 +02:00
|
|
|
if (fenced(fence) > 0) { /* a code block. */
|
|
|
|
if (fenced(fence) == 2 && run_tests == 0) {
|
|
|
|
} else {
|
|
|
|
if (inBlock == 0)
|
|
|
|
inBlock = 1;
|
|
|
|
else
|
|
|
|
inBlock = 0;
|
|
|
|
}
|
2017-10-16 18:09:39 +02:00
|
|
|
} else {
|
2018-01-09 22:59:00 +01:00
|
|
|
if (inBlock == 1) /* If we are, evaluate token */
|
2019-01-15 22:31:41 +01:00
|
|
|
rre_evaluate(source, -1);
|
2017-10-16 18:09:39 +02:00
|
|
|
}
|
|
|
|
}
|
2018-01-09 22:59:00 +01:00
|
|
|
|
2017-10-16 18:09:39 +02:00
|
|
|
fclose(fp);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-01-09 14:58:34 +01:00
|
|
|
/*---------------------------------------------------------------------
|
2018-01-10 14:36:08 +01:00
|
|
|
`help()` displays a summary of the command line arguments RRE allows.
|
|
|
|
|
|
|
|
This is invoked using `rre -h`
|
2018-01-09 14:58:34 +01:00
|
|
|
---------------------------------------------------------------------*/
|
2018-01-09 22:59:00 +01:00
|
|
|
|
2018-11-05 03:34:53 +01:00
|
|
|
void help(char *exename) {
|
|
|
|
printf("Scripting Usage: %s filename\n\n", exename);
|
2018-11-14 14:52:30 +01:00
|
|
|
printf("Interactive Usage: %s [-h] [-i] [-c] [-s] [-f filename] [-t]\n\n", exename);
|
2018-01-09 22:59:00 +01:00
|
|
|
printf("Valid Arguments:\n\n");
|
|
|
|
printf(" -h\n");
|
|
|
|
printf(" Display this help text\n\n");
|
|
|
|
printf(" -i\n");
|
|
|
|
printf(" Launches in interactive mode (line buffered)\n\n");
|
|
|
|
printf(" -c\n");
|
|
|
|
printf(" Launches in interactive mode (character buffered)\n\n");
|
2018-11-14 14:52:30 +01:00
|
|
|
printf(" -s\n");
|
|
|
|
printf(" Suppress the 'ok' prompt and keyboard echo in interactive mode\n\n");
|
2018-05-01 20:08:13 +02:00
|
|
|
printf(" -f filename\n");
|
|
|
|
printf(" Run the contents of the specified file\n\n");
|
2019-01-22 15:35:31 +01:00
|
|
|
printf(" -u filename\n");
|
|
|
|
printf(" Use the image in the specified file instead of the internal one\n\n");
|
2018-05-01 20:08:13 +02:00
|
|
|
printf(" -t\n");
|
|
|
|
printf(" Run tests (in ``` blocks) in any loaded files\n\n");
|
2018-01-09 22:59:00 +01:00
|
|
|
}
|
|
|
|
|
2018-01-10 14:36:08 +01:00
|
|
|
|
|
|
|
/*---------------------------------------------------------------------
|
|
|
|
`initialize()` sets up Nga and loads the image (from the array in
|
|
|
|
`image.c`) to memory.
|
|
|
|
---------------------------------------------------------------------*/
|
|
|
|
|
2018-01-09 22:59:00 +01:00
|
|
|
void initialize() {
|
2018-07-11 02:39:44 +02:00
|
|
|
CELL i;
|
2017-10-16 18:09:39 +02:00
|
|
|
ngaPrepare();
|
|
|
|
for (i = 0; i < ngaImageCells; i++)
|
|
|
|
memory[i] = ngaImage[i];
|
|
|
|
update_rx();
|
2018-01-09 22:59:00 +01:00
|
|
|
}
|
2017-10-16 18:09:39 +02:00
|
|
|
|
2018-01-10 14:36:08 +01:00
|
|
|
|
|
|
|
/*---------------------------------------------------------------------
|
|
|
|
`arg_is()` exists to aid in readability. It compares the first actual
|
|
|
|
command line argument to a string and returns a boolean flag.
|
|
|
|
---------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
int arg_is(char *t) {
|
|
|
|
return strcmp(sys_argv[1], t) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------
|
|
|
|
---------------------------------------------------------------------*/
|
|
|
|
|
2018-05-01 20:08:13 +02:00
|
|
|
enum flags {
|
2018-11-14 14:52:30 +01:00
|
|
|
FLAG_HELP, FLAG_RUN_TESTS, FLAG_INCLUDE, FLAG_INTERACTIVE, FLAG_CBREAK, FLAG_SILENT
|
2018-05-01 20:08:13 +02:00
|
|
|
};
|
|
|
|
|
2018-01-09 22:59:00 +01:00
|
|
|
int main(int argc, char **argv) {
|
2018-05-01 20:08:13 +02:00
|
|
|
int i;
|
|
|
|
int modes[32];
|
|
|
|
char *files[16];
|
|
|
|
int fsp;
|
|
|
|
|
2018-04-30 15:08:52 +02:00
|
|
|
int run_tests;
|
2018-05-01 20:08:13 +02:00
|
|
|
|
2018-11-05 03:34:53 +01:00
|
|
|
if (argc <= 1) return 0; /* Guard clause: exit if no */
|
|
|
|
/* arguments are passed. */
|
|
|
|
|
2018-01-10 14:36:08 +01:00
|
|
|
initialize(); /* Initialize Nga & image */
|
2017-11-28 19:57:51 +01:00
|
|
|
|
2018-01-10 14:36:08 +01:00
|
|
|
sys_argc = argc; /* Point the global argc and */
|
|
|
|
sys_argv = argv; /* argv to the actual ones */
|
2017-10-16 18:09:39 +02:00
|
|
|
|
2018-05-01 20:08:13 +02:00
|
|
|
if (argc >= 2 && argv[1][0] != '-') {
|
|
|
|
include_file(argv[1], 0); /* If no flags were passed, */
|
|
|
|
if (sp >= 1) dump_stack(); /* load the file specified, */
|
|
|
|
exit(0); /* and exit */
|
2017-11-28 19:57:51 +01:00
|
|
|
}
|
2018-01-09 22:59:00 +01:00
|
|
|
|
2018-05-01 20:08:13 +02:00
|
|
|
for (i = 0; i < 32; i++)
|
|
|
|
modes[i] = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < 16; i++)
|
|
|
|
files[i] = "\0";
|
|
|
|
|
|
|
|
run_tests = 0;
|
|
|
|
fsp = 0;
|
|
|
|
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
|
|
if (strcmp(argv[i], "-h") == 0) {
|
2018-11-05 03:34:53 +01:00
|
|
|
help(argv[0]);
|
|
|
|
exit(0);
|
2018-05-01 20:08:13 +02:00
|
|
|
} else if (strcmp(argv[i], "-i") == 0) {
|
|
|
|
modes[FLAG_INTERACTIVE] = 1;
|
|
|
|
} else if (strcmp(argv[i], "-c") == 0) {
|
|
|
|
modes[FLAG_INTERACTIVE] = 1;
|
|
|
|
modes[FLAG_CBREAK] = 1;
|
2018-11-14 14:52:30 +01:00
|
|
|
} else if (strcmp(argv[i], "-s") == 0) {
|
|
|
|
modes[FLAG_SILENT] = 1;
|
2018-05-01 20:08:13 +02:00
|
|
|
} else if (strcmp(argv[i], "-f") == 0) {
|
|
|
|
files[fsp] = argv[i + 1];
|
|
|
|
fsp++;
|
|
|
|
i++;
|
2019-01-22 15:35:31 +01:00
|
|
|
} else if (strcmp(argv[i], "-u") == 0) {
|
|
|
|
i++;
|
|
|
|
ngaLoadImage(argv[i]);
|
2018-05-01 20:08:13 +02:00
|
|
|
} else if (strcmp(argv[i], "-t") == 0) {
|
|
|
|
modes[FLAG_RUN_TESTS] = 1;
|
|
|
|
run_tests = 1;
|
2018-04-30 15:08:52 +02:00
|
|
|
}
|
2018-05-01 20:08:13 +02:00
|
|
|
}
|
|
|
|
|
2018-05-07 17:43:25 +02:00
|
|
|
for (i = 0; i < fsp; i++) {
|
2018-05-01 20:08:13 +02:00
|
|
|
if (strcmp(files[i], "\0") != 0)
|
|
|
|
include_file(files[i], run_tests);
|
|
|
|
}
|
2018-04-30 15:08:52 +02:00
|
|
|
|
2018-11-14 14:52:30 +01:00
|
|
|
|
|
|
|
if (modes[FLAG_SILENT] == 1) {
|
|
|
|
memory[d_xt_for("NoEcho", Dictionary)] = -1;
|
|
|
|
}
|
|
|
|
|
2018-05-01 20:08:13 +02:00
|
|
|
if (modes[FLAG_INTERACTIVE] == 1) {
|
2019-01-15 22:31:41 +01:00
|
|
|
rre_execute(d_xt_for("banner", Dictionary), 0);
|
2018-04-04 18:11:44 +02:00
|
|
|
#ifdef USE_TERMIOS
|
2018-05-01 20:08:13 +02:00
|
|
|
if (modes[FLAG_CBREAK] == 1) prepare_term();
|
2018-04-04 18:11:44 +02:00
|
|
|
#endif
|
2019-01-15 22:31:41 +01:00
|
|
|
if (modes[FLAG_CBREAK] == 1) while (1) rre_execute(0, 0);
|
|
|
|
if (modes[FLAG_CBREAK] == 0) while (1) rre_execute(0, -1);
|
2018-04-04 18:11:44 +02:00
|
|
|
#ifdef USE_TERMIOS
|
2018-05-01 20:08:13 +02:00
|
|
|
if (modes[FLAG_CBREAK] == 1) restore_term();
|
2018-04-04 18:11:44 +02:00
|
|
|
#endif
|
2018-01-09 22:59:00 +01:00
|
|
|
exit(0);
|
|
|
|
}
|
2017-10-16 18:09:39 +02:00
|
|
|
}
|
|
|
|
|
2018-01-09 14:58:34 +01:00
|
|
|
|
2017-10-20 03:11:18 +02:00
|
|
|
/* Nga ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2018-07-11 02:39:44 +02:00
|
|
|
Copyright (c) 2008 - 2018, Charles Childers
|
2017-10-20 03:11:18 +02:00
|
|
|
Copyright (c) 2009 - 2010, Luke Parrish
|
|
|
|
Copyright (c) 2010, Marc Simpson
|
|
|
|
Copyright (c) 2010, Jay Skeer
|
|
|
|
Copyright (c) 2011, Kenneth Keating
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
|
|
|
|
2018-01-09 14:58:34 +01:00
|
|
|
enum vm_opcode {
|
|
|
|
VM_NOP, VM_LIT, VM_DUP, VM_DROP, VM_SWAP, VM_PUSH, VM_POP,
|
|
|
|
VM_JUMP, VM_CALL, VM_CCALL, VM_RETURN, VM_EQ, VM_NEQ, VM_LT,
|
|
|
|
VM_GT, VM_FETCH, VM_STORE, VM_ADD, VM_SUB, VM_MUL, VM_DIVMOD,
|
2018-11-22 01:05:28 +01:00
|
|
|
VM_AND, VM_OR, VM_XOR, VM_SHIFT, VM_ZRET, VM_END, VM_IE,
|
|
|
|
VM_IQ, VM_II
|
2018-01-09 14:58:34 +01:00
|
|
|
};
|
2018-11-22 01:05:28 +01:00
|
|
|
#define NUM_OPS VM_II + 1
|
|
|
|
|
|
|
|
#ifndef NUM_DEVICES
|
|
|
|
#define NUM_DEVICES 0
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//Handler IO_deviceHandlers[NUM_DEVICES + 1];
|
|
|
|
//Handler IO_queryHandlers[NUM_DEVICES + 1];
|
2018-01-09 14:58:34 +01:00
|
|
|
|
2017-10-20 03:11:18 +02:00
|
|
|
CELL ngaLoadImage(char *imageFile) {
|
|
|
|
FILE *fp;
|
|
|
|
CELL imageSize;
|
|
|
|
long fileLen;
|
2018-11-22 01:05:28 +01:00
|
|
|
CELL i;
|
2017-10-20 03:11:18 +02:00
|
|
|
if ((fp = fopen(imageFile, "rb")) != NULL) {
|
|
|
|
/* Determine length (in cells) */
|
|
|
|
fseek(fp, 0, SEEK_END);
|
|
|
|
fileLen = ftell(fp) / sizeof(CELL);
|
|
|
|
rewind(fp);
|
|
|
|
/* Read the file into memory */
|
|
|
|
imageSize = fread(&memory, sizeof(CELL), fileLen, fp);
|
|
|
|
fclose(fp);
|
|
|
|
}
|
|
|
|
else {
|
2018-11-22 01:05:28 +01:00
|
|
|
for (i = 0; i < ngaImageCells; i++)
|
|
|
|
memory[i] = ngaImage[i];
|
|
|
|
imageSize = i;
|
2017-10-20 03:11:18 +02:00
|
|
|
}
|
|
|
|
return imageSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ngaPrepare() {
|
|
|
|
ip = sp = rp = 0;
|
|
|
|
for (ip = 0; ip < IMAGE_SIZE; ip++)
|
|
|
|
memory[ip] = VM_NOP;
|
|
|
|
for (ip = 0; ip < STACK_DEPTH; ip++)
|
|
|
|
data[ip] = 0;
|
|
|
|
for (ip = 0; ip < ADDRESSES; ip++)
|
|
|
|
address[ip] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_nop() {
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_lit() {
|
|
|
|
sp++;
|
|
|
|
ip++;
|
|
|
|
TOS = memory[ip];
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_dup() {
|
|
|
|
sp++;
|
|
|
|
data[sp] = NOS;
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_drop() {
|
|
|
|
data[sp] = 0;
|
2018-11-22 01:05:28 +01:00
|
|
|
if (--sp < 0)
|
|
|
|
ip = IMAGE_SIZE;
|
2017-10-20 03:11:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void inst_swap() {
|
2018-07-11 02:39:44 +02:00
|
|
|
CELL a;
|
2017-10-20 03:11:18 +02:00
|
|
|
a = TOS;
|
|
|
|
TOS = NOS;
|
|
|
|
NOS = a;
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_push() {
|
|
|
|
rp++;
|
|
|
|
TORS = TOS;
|
|
|
|
inst_drop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_pop() {
|
|
|
|
sp++;
|
|
|
|
TOS = TORS;
|
2018-11-22 01:05:28 +01:00
|
|
|
rp--;
|
2017-10-20 03:11:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void inst_jump() {
|
|
|
|
ip = TOS - 1;
|
|
|
|
inst_drop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_call() {
|
|
|
|
rp++;
|
|
|
|
TORS = ip;
|
|
|
|
ip = TOS - 1;
|
|
|
|
inst_drop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_ccall() {
|
2018-07-11 02:39:44 +02:00
|
|
|
CELL a, b;
|
2017-10-20 03:11:18 +02:00
|
|
|
a = TOS; inst_drop(); /* False */
|
|
|
|
b = TOS; inst_drop(); /* Flag */
|
|
|
|
if (b != 0) {
|
|
|
|
rp++;
|
|
|
|
TORS = ip;
|
|
|
|
ip = a - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_return() {
|
|
|
|
ip = TORS;
|
2018-11-22 01:05:28 +01:00
|
|
|
rp--;
|
2017-10-20 03:11:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void inst_eq() {
|
|
|
|
NOS = (NOS == TOS) ? -1 : 0;
|
|
|
|
inst_drop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_neq() {
|
|
|
|
NOS = (NOS != TOS) ? -1 : 0;
|
|
|
|
inst_drop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_lt() {
|
|
|
|
NOS = (NOS < TOS) ? -1 : 0;
|
|
|
|
inst_drop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_gt() {
|
|
|
|
NOS = (NOS > TOS) ? -1 : 0;
|
|
|
|
inst_drop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_fetch() {
|
|
|
|
switch (TOS) {
|
|
|
|
case -1: TOS = sp - 1; break;
|
|
|
|
case -2: TOS = rp; break;
|
|
|
|
case -3: TOS = IMAGE_SIZE; break;
|
|
|
|
default: TOS = memory[TOS]; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_store() {
|
2017-11-21 13:20:55 +01:00
|
|
|
if (TOS <= IMAGE_SIZE && TOS >= 0) {
|
2017-11-21 13:19:00 +01:00
|
|
|
memory[TOS] = NOS;
|
|
|
|
inst_drop();
|
|
|
|
inst_drop();
|
|
|
|
} else {
|
2018-11-22 01:05:28 +01:00
|
|
|
ip = IMAGE_SIZE;
|
2017-11-21 13:19:00 +01:00
|
|
|
}
|
2017-10-20 03:11:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void inst_add() {
|
|
|
|
NOS += TOS;
|
|
|
|
inst_drop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_sub() {
|
|
|
|
NOS -= TOS;
|
|
|
|
inst_drop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_mul() {
|
|
|
|
NOS *= TOS;
|
|
|
|
inst_drop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_divmod() {
|
2018-07-11 02:39:44 +02:00
|
|
|
CELL a, b;
|
2017-10-20 03:11:18 +02:00
|
|
|
a = TOS;
|
|
|
|
b = NOS;
|
|
|
|
TOS = b / a;
|
|
|
|
NOS = b % a;
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_and() {
|
|
|
|
NOS = TOS & NOS;
|
|
|
|
inst_drop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_or() {
|
|
|
|
NOS = TOS | NOS;
|
|
|
|
inst_drop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_xor() {
|
|
|
|
NOS = TOS ^ NOS;
|
|
|
|
inst_drop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_shift() {
|
|
|
|
CELL y = TOS;
|
|
|
|
CELL x = NOS;
|
|
|
|
if (TOS < 0)
|
|
|
|
NOS = NOS << (TOS * -1);
|
|
|
|
else {
|
|
|
|
if (x < 0 && y > 0)
|
|
|
|
NOS = x >> y | ~(~0U >> y);
|
|
|
|
else
|
|
|
|
NOS = x >> y;
|
|
|
|
}
|
|
|
|
inst_drop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_zret() {
|
|
|
|
if (TOS == 0) {
|
|
|
|
inst_drop();
|
|
|
|
ip = TORS;
|
|
|
|
rp--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_end() {
|
|
|
|
ip = IMAGE_SIZE;
|
|
|
|
}
|
|
|
|
|
2018-11-22 01:05:28 +01:00
|
|
|
void inst_ie() {
|
|
|
|
sp++;
|
|
|
|
TOS = NUM_DEVICES;
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_iq() {
|
|
|
|
CELL Device = TOS;
|
|
|
|
inst_drop();
|
|
|
|
IO_queryHandlers[Device]();
|
|
|
|
}
|
|
|
|
|
|
|
|
void inst_ii() {
|
|
|
|
CELL Device = TOS;
|
|
|
|
inst_drop();
|
|
|
|
IO_deviceHandlers[Device]();
|
|
|
|
}
|
|
|
|
|
2017-10-20 03:11:18 +02:00
|
|
|
Handler instructions[NUM_OPS] = {
|
|
|
|
inst_nop, inst_lit, inst_dup, inst_drop, inst_swap, inst_push, inst_pop,
|
|
|
|
inst_jump, inst_call, inst_ccall, inst_return, inst_eq, inst_neq, inst_lt,
|
|
|
|
inst_gt, inst_fetch, inst_store, inst_add, inst_sub, inst_mul, inst_divmod,
|
2018-11-22 01:05:28 +01:00
|
|
|
inst_and, inst_or, inst_xor, inst_shift, inst_zret, inst_end, inst_ie,
|
|
|
|
inst_iq, inst_ii
|
2017-10-20 03:11:18 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
void ngaProcessOpcode(CELL opcode) {
|
2018-02-03 17:07:25 +01:00
|
|
|
if (opcode != 0)
|
|
|
|
instructions[opcode]();
|
2017-10-20 03:11:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int ngaValidatePackedOpcodes(CELL opcode) {
|
|
|
|
CELL raw = opcode;
|
|
|
|
CELL current;
|
|
|
|
int valid = -1;
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
|
|
current = raw & 0xFF;
|
2018-11-22 01:05:28 +01:00
|
|
|
if (!(current >= 0 && current <= 29))
|
2017-10-20 03:11:18 +02:00
|
|
|
valid = 0;
|
|
|
|
raw = raw >> 8;
|
|
|
|
}
|
|
|
|
return valid;
|
|
|
|
}
|
|
|
|
|
2018-07-11 02:39:44 +02:00
|
|
|
void ngaProcessPackedOpcodes(CELL opcode) {
|
2017-10-20 03:11:18 +02:00
|
|
|
CELL raw = opcode;
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
|
|
ngaProcessOpcode(raw & 0xFF);
|
|
|
|
raw = raw >> 8;
|
|
|
|
}
|
|
|
|
}
|