075fbd6500
FossilOrigin-Name: fccadfe87aee74d062c39ad8c6eca33e031afae801c35ed2a2c050b5f6553cec
1670 lines
44 KiB
C
1670 lines
44 KiB
C
/**************************************************************
|
|
_ __ _ _
|
|
_ __ ___| |_ _ __ ___ / _| ___ _ __| |_| |__
|
|
| '__/ _ \ __| '__/ _ \| |_ / _ \| '__| __| '_ \
|
|
| | | __/ |_| | | (_) | _| (_) | | | |_| | | |
|
|
|_| \___|\__|_| \___/|_| \___/|_| \__|_| |_|
|
|
for nga
|
|
|
|
(c) Charles Childers, Luke Parrish, Marc Simpsonn,
|
|
Jay Skeer, Kenneth Keating
|
|
|
|
**************************************************************/
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <signal.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "config.h"
|
|
|
|
#define TIB vm->memory[7] /* Location of TIB */
|
|
|
|
#define MAX_DEVICES 32
|
|
#define MAX_OPEN_FILES 32
|
|
|
|
#include "image.c"
|
|
|
|
typedef struct NgaState NgaState;
|
|
|
|
typedef void (*Handler)(NgaState *);
|
|
|
|
struct NgaCore {
|
|
CELL sp, rp, ip; /* Stack & instruction pointers */
|
|
CELL active; /* Is core active? */
|
|
CELL u; /* Should next operation be */
|
|
/* unsigned? */
|
|
CELL data[STACK_DEPTH]; /* The data stack */
|
|
CELL address[ADDRESSES]; /* The address stack */
|
|
|
|
#ifdef ENABLE_MULTICORE
|
|
CELL registers[24]; /* Internal Registers */
|
|
#endif
|
|
};
|
|
|
|
struct NgaState {
|
|
/* System Memory */
|
|
CELL memory[IMAGE_SIZE + 1];
|
|
|
|
/* CPU Cores */
|
|
struct NgaCore cpu[CORES];
|
|
int active;
|
|
|
|
/* I/O Devices */
|
|
int devices;
|
|
Handler IO_deviceHandlers[MAX_DEVICES];
|
|
Handler IO_queryHandlers[MAX_DEVICES];
|
|
|
|
CELL Dictionary, NotFound, interpret; /* Interfacing */
|
|
char string_data[8192];
|
|
|
|
#ifdef ENABLE_FLOATS
|
|
double Floats[256], AFloats[256]; /* Floating Point */
|
|
CELL fsp, afsp;
|
|
#endif
|
|
|
|
#ifdef ENABLE_BLOCKS
|
|
char BlockFile[1025];
|
|
#endif
|
|
|
|
#ifdef ENABLE_ERROR
|
|
CELL ErrorHandlers[64];
|
|
#endif
|
|
|
|
/* Scripting */
|
|
char **sys_argv;
|
|
int sys_argc;
|
|
char scripting_sources[64][8192];
|
|
char line[4096];
|
|
int current_source;
|
|
int perform_abort;
|
|
int interactive;
|
|
|
|
CELL currentLine;
|
|
CELL ignoreToEOL, ignoreToEOF;
|
|
|
|
/* Configuration of code & test fences for Unu */
|
|
char code_start[256], code_end[256];
|
|
char test_start[256], test_end[256];
|
|
int codeBlocks;
|
|
|
|
FILE *OpenFileHandles[MAX_OPEN_FILES];
|
|
};
|
|
|
|
|
|
/* Function Prototypes ----------------------------------------------- */
|
|
void handle_error(NgaState *, CELL);
|
|
|
|
CELL stack_pop(NgaState *);
|
|
void stack_push(NgaState *, CELL);
|
|
CELL string_inject(NgaState *, char *, CELL);
|
|
char *string_extract(NgaState *, CELL);
|
|
void update_rx(NgaState *);
|
|
void include_file(NgaState *, char *, int);
|
|
|
|
void register_device(NgaState *, void *, void *);
|
|
|
|
void io_output(NgaState *); void query_output(NgaState *);
|
|
void io_keyboard(NgaState *); void query_keyboard(NgaState *);
|
|
void query_filesystem(NgaState *); void io_filesystem(NgaState *);
|
|
void io_clock(NgaState *); void query_clock(NgaState *);
|
|
void io_scripting(NgaState *); void query_scripting(NgaState *);
|
|
void io_rng(NgaState *); void query_rng(NgaState *);
|
|
void io_unsigned(NgaState *); void query_unsigned(NgaState *);
|
|
|
|
#ifdef ENABLE_UNIX
|
|
void query_unix(NgaState *); void io_unix(NgaState *);
|
|
#endif
|
|
|
|
#ifdef ENABLE_FLOATS
|
|
void io_floatingpoint(NgaState *); void query_floatingpoint(NgaState *);
|
|
#endif
|
|
|
|
#ifdef ENABLE_SOCKETS
|
|
void io_socket(NgaState *); void query_socket(NgaState *);
|
|
#endif
|
|
|
|
#ifdef ENABLE_MALLOC
|
|
#ifdef BIT64
|
|
void io_malloc(NgaState *); void query_malloc(NgaState *);
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef ENABLE_BLOCKS
|
|
void io_blocks(NgaState *); void query_blocks(NgaState *);
|
|
#endif
|
|
|
|
void io_image(NgaState *); void query_image(NgaState *);
|
|
|
|
void load_embedded_image(NgaState *);
|
|
CELL load_image(NgaState *, char *);
|
|
void prepare_vm(NgaState *);
|
|
void process_opcode_bundle(NgaState *, CELL);
|
|
int validate_opcode_bundle(CELL);
|
|
|
|
#ifdef NEEDS_STRL
|
|
size_t strlcat(char *dst, const char *src, size_t dsize);
|
|
size_t strlcpy(char *dst, const char *src, size_t dsize);
|
|
#endif
|
|
|
|
void inst_no(NgaState *); void inst_li(NgaState *);
|
|
void inst_du(NgaState *); void inst_dr(NgaState *);
|
|
void inst_sw(NgaState *); void inst_pu(NgaState *);
|
|
void inst_po(NgaState *); void inst_ju(NgaState *);
|
|
void inst_ca(NgaState *); void inst_cc(NgaState *);
|
|
void inst_re(NgaState *); void inst_eq(NgaState *);
|
|
void inst_ne(NgaState *); void inst_lt(NgaState *);
|
|
void inst_gt(NgaState *); void inst_fe(NgaState *);
|
|
void inst_st(NgaState *); void inst_ad(NgaState *);
|
|
void inst_su(NgaState *); void inst_mu(NgaState *);
|
|
void inst_di(NgaState *); void inst_an(NgaState *);
|
|
void inst_or(NgaState *); void inst_xo(NgaState *);
|
|
void inst_sh(NgaState *); void inst_zr(NgaState *);
|
|
void inst_ha(NgaState *); void inst_ie(NgaState *);
|
|
void inst_iq(NgaState *); void inst_ii(NgaState *);
|
|
|
|
|
|
/* Image, Stack, and VM variables ------------------------------------ */
|
|
#define TOS vm->cpu[vm->active].data[vm->cpu[vm->active].sp]
|
|
#define NOS vm->cpu[vm->active].data[vm->cpu[vm->active].sp-1]
|
|
#define TORS vm->cpu[vm->active].address[vm->cpu[vm->active].rp]
|
|
|
|
/* Global Variables -------------------------------------------------- */
|
|
|
|
int verbose;
|
|
|
|
void guard(NgaState *vm, int n, int m, int diff) {
|
|
if (vm->cpu[vm->active].sp < n) {
|
|
#ifdef ENABLE_ERROR
|
|
if (vm->ErrorHandlers[1] != 0) {
|
|
handle_error(vm, 1);
|
|
}
|
|
#else
|
|
printf("E: Data Stack Underflow");
|
|
vm->cpu[vm->active].sp = 0;
|
|
return;
|
|
#endif
|
|
}
|
|
if (((vm->cpu[vm->active].sp + m) - n) > (STACK_DEPTH - 1)) {
|
|
#ifdef ENABLE_ERROR
|
|
if (vm->ErrorHandlers[2] != 0) {
|
|
handle_error(vm, 2);
|
|
}
|
|
#else
|
|
printf("E: Data Stack Overflow");
|
|
vm->cpu[vm->active].sp = 0;
|
|
return;
|
|
#endif
|
|
}
|
|
if (diff) {
|
|
if (vm->cpu[vm->active].rp + diff < 0) {
|
|
#ifdef ENABLE_ERROR
|
|
if (vm->ErrorHandlers[3] != 0) {
|
|
handle_error(vm, 3);
|
|
}
|
|
#else
|
|
return;
|
|
#endif
|
|
}
|
|
if (vm->cpu[vm->active].rp + diff > (ADDRESSES - 1)) {
|
|
#ifdef ENABLE_ERROR
|
|
if (vm->ErrorHandlers[1] != 4) {
|
|
handle_error(vm, 4);
|
|
}
|
|
#else
|
|
return;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Dynamic Memory / `malloc` support --------------------------------- */
|
|
#ifdef ENABLE_MALLOC
|
|
#ifdef BIT64
|
|
#include "dev-malloc.c"
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef ENABLE_ERROR
|
|
#include "dev-error.c"
|
|
#endif
|
|
|
|
/* Block Storage -------------------------------------------- */
|
|
#ifdef ENABLE_BLOCKS
|
|
void io_blocks(NgaState *vm) {
|
|
CELL op, buffer, block;
|
|
int32_t m[1024];
|
|
op = stack_pop(vm);
|
|
|
|
if (op == 0) {
|
|
buffer = stack_pop(vm);
|
|
block = stack_pop(vm);
|
|
int fp = open(vm->BlockFile, O_RDONLY, 0666);
|
|
lseek(fp, 4096 * block, SEEK_SET);
|
|
read(fp, m, 4096);
|
|
for (int i = 0; i < 1024; i++) {
|
|
vm->memory[buffer + i] = (CELL)m[i];
|
|
}
|
|
close(fp);
|
|
}
|
|
|
|
if (op == 1) {
|
|
buffer = stack_pop(vm);
|
|
block = stack_pop(vm);
|
|
int fp = open(vm->BlockFile, O_WRONLY, 0666);
|
|
lseek(fp, 4096 * block, SEEK_SET);
|
|
for (int i = 0; i < 1024; i++) {
|
|
m[i] = (int32_t)vm->memory[buffer + i];
|
|
}
|
|
write(fp, m, 4096);
|
|
close(fp);
|
|
}
|
|
|
|
if (op == 2) {
|
|
buffer = stack_pop(vm);
|
|
strlcpy(vm->BlockFile, string_extract(vm, buffer), 1024);
|
|
}
|
|
}
|
|
|
|
void query_blocks(NgaState *vm) {
|
|
stack_push(vm, 0);
|
|
stack_push(vm, 3);
|
|
}
|
|
#endif
|
|
|
|
#include "dev-files.c"
|
|
|
|
/* Multi Core Support ------------------------------------------------ */
|
|
#ifdef ENABLE_MULTICORE
|
|
#include "dev-multicore.c"
|
|
#endif
|
|
|
|
#ifdef ENABLE_FFI
|
|
#include "dev-ffi.c"
|
|
#endif
|
|
|
|
#ifdef ENABLE_FLOATS
|
|
#include "dev-float.c"
|
|
#endif
|
|
|
|
#ifdef ENABLE_CLOCK
|
|
#include "dev-clock.c"
|
|
#endif
|
|
|
|
#ifdef ENABLE_RNG
|
|
#include "dev-rng.c"
|
|
#endif
|
|
|
|
#ifdef ENABLE_SOCKETS
|
|
#include "dev-sockets.c"
|
|
#endif
|
|
|
|
#ifdef ENABLE_UNIX
|
|
#include "dev-unix.c"
|
|
#endif
|
|
|
|
|
|
/*---------------------------------------------------------------------
|
|
Now on to I/O and extensions!
|
|
---------------------------------------------------------------------*/
|
|
#ifdef USE_UTF32
|
|
void display_utf8(const unsigned char* utf8_bytes, int num_bytes) {
|
|
if (write(STDOUT_FILENO, utf8_bytes, num_bytes) == -1) {
|
|
perror("Error writing to /dev/stdout");
|
|
}
|
|
}
|
|
|
|
void utf32_to_utf8(uint32_t utf32_char, unsigned char* utf8_bytes, int* num_bytes) {
|
|
if (utf32_char < 0x80) {
|
|
utf8_bytes[0] = (unsigned char)utf32_char;
|
|
*num_bytes = 1;
|
|
} else if (utf32_char < 0x800) {
|
|
utf8_bytes[0] = (unsigned char)(0xC0 | (utf32_char >> 6));
|
|
utf8_bytes[1] = (unsigned char)(0x80 | (utf32_char & 0x3F));
|
|
*num_bytes = 2;
|
|
} else if (utf32_char < 0x10000) {
|
|
utf8_bytes[0] = (unsigned char)(0xE0 | (utf32_char >> 12));
|
|
utf8_bytes[1] = (unsigned char)(0x80 | ((utf32_char >> 6) & 0x3F));
|
|
utf8_bytes[2] = (unsigned char)(0x80 | (utf32_char & 0x3F));
|
|
*num_bytes = 3;
|
|
} else if (utf32_char < 0x110000) {
|
|
utf8_bytes[0] = (unsigned char)(0xF0 | (utf32_char >> 18));
|
|
utf8_bytes[1] = (unsigned char)(0x80 | ((utf32_char >> 12) & 0x3F));
|
|
utf8_bytes[2] = (unsigned char)(0x80 | ((utf32_char >> 6) & 0x3F));
|
|
utf8_bytes[3] = (unsigned char)(0x80 | (utf32_char & 0x3F));
|
|
*num_bytes = 4;
|
|
} else {
|
|
*num_bytes = 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void io_output(NgaState *vm) {
|
|
#ifdef USE_UTF32
|
|
unsigned char utf8_bytes[4];
|
|
int num_bytes;
|
|
utf32_to_utf8(stack_pop(vm), utf8_bytes, &num_bytes);
|
|
display_utf8(utf8_bytes, num_bytes);
|
|
#else
|
|
putc(stack_pop(vm), stdout);
|
|
#endif
|
|
fflush(stdout);
|
|
}
|
|
|
|
void query_output(NgaState *vm) {
|
|
stack_push(vm, 0);
|
|
stack_push(vm, 0);
|
|
}
|
|
|
|
|
|
/*=====================================================================*/
|
|
|
|
#ifdef USE_UTF32
|
|
int read_character(int from) {
|
|
unsigned char utf8_bytes[4] = { 0 };
|
|
int utf32_char, i, num_bytes;
|
|
|
|
if (read(from, &utf8_bytes[0], 1) != 1) { return 0; }
|
|
if ((utf8_bytes[0] & 0x80) == 0x00) {
|
|
num_bytes = 1;
|
|
} else if ((utf8_bytes[0] & 0xE0) == 0xC0) {
|
|
num_bytes = 2;
|
|
} else if ((utf8_bytes[0] & 0xF0) == 0xE0) {
|
|
num_bytes = 3;
|
|
} else if ((utf8_bytes[0] & 0xF8) == 0xF0) {
|
|
num_bytes = 4;
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
for (i = 1; i < num_bytes; i++) {
|
|
if (read(from, &utf8_bytes[i], 1) != 1) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (num_bytes == 1) {
|
|
utf32_char = utf8_bytes[0];
|
|
} else if (num_bytes == 2) {
|
|
utf32_char = ((uint32_t)(utf8_bytes[0] & 0x1F) << 6) |
|
|
(utf8_bytes[1] & 0x3F);
|
|
} else if (num_bytes == 3) {
|
|
utf32_char = ((uint32_t)(utf8_bytes[0] & 0x0F) << 12) |
|
|
((uint32_t)(utf8_bytes[1] & 0x3F) << 6) |
|
|
(utf8_bytes[2] & 0x3F);
|
|
} else if (num_bytes == 4) {
|
|
utf32_char = ((uint32_t)(utf8_bytes[0] & 0x07) << 18) |
|
|
((uint32_t)(utf8_bytes[1] & 0x3F) << 12) |
|
|
((uint32_t)(utf8_bytes[2] & 0x3F) << 6) |
|
|
(utf8_bytes[3] & 0x3F);
|
|
} else {
|
|
return 0;
|
|
}
|
|
return utf32_char;
|
|
}
|
|
|
|
int fread_character(FILE *from) {
|
|
unsigned char utf8_bytes[4] = { 0 };
|
|
int utf32_char, i, num_bytes;
|
|
|
|
if (fread(&utf8_bytes[0], 1, 1, from) != 1) { return 0; }
|
|
if ((utf8_bytes[0] & 0x80) == 0x00) {
|
|
num_bytes = 1;
|
|
} else if ((utf8_bytes[0] & 0xE0) == 0xC0) {
|
|
num_bytes = 2;
|
|
} else if ((utf8_bytes[0] & 0xF0) == 0xE0) {
|
|
num_bytes = 3;
|
|
} else if ((utf8_bytes[0] & 0xF8) == 0xF0) {
|
|
num_bytes = 4;
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
for (i = 1; i < num_bytes; i++) {
|
|
if (fread(&utf8_bytes[i], 1, 1, from) != 1) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (num_bytes == 1) {
|
|
utf32_char = utf8_bytes[0];
|
|
} else if (num_bytes == 2) {
|
|
utf32_char = ((uint32_t)(utf8_bytes[0] & 0x1F) << 6) |
|
|
(utf8_bytes[1] & 0x3F);
|
|
} else if (num_bytes == 3) {
|
|
utf32_char = ((uint32_t)(utf8_bytes[0] & 0x0F) << 12) |
|
|
((uint32_t)(utf8_bytes[1] & 0x3F) << 6) |
|
|
(utf8_bytes[2] & 0x3F);
|
|
} else if (num_bytes == 4) {
|
|
utf32_char = ((uint32_t)(utf8_bytes[0] & 0x07) << 18) |
|
|
((uint32_t)(utf8_bytes[1] & 0x3F) << 12) |
|
|
((uint32_t)(utf8_bytes[2] & 0x3F) << 6) |
|
|
(utf8_bytes[3] & 0x3F);
|
|
} else {
|
|
return 0;
|
|
}
|
|
return utf32_char;
|
|
}
|
|
#endif
|
|
|
|
void io_keyboard(NgaState *vm) {
|
|
#ifdef USE_UTF32
|
|
stack_push(vm, read_character(STDIN_FILENO));
|
|
#else
|
|
stack_push(vm, getc(stdin));
|
|
#endif
|
|
if (TOS == 127) TOS = 8;
|
|
}
|
|
|
|
void query_keyboard(NgaState *vm) {
|
|
stack_push(vm, 0);
|
|
stack_push(vm, 1);
|
|
}
|
|
|
|
/*=====================================================================*/
|
|
|
|
#ifdef ENABLE_UNSIGNED
|
|
void io_unsigned(NgaState *vm) {
|
|
int x, y, z;
|
|
long c;
|
|
switch (stack_pop(vm)) {
|
|
case 0: vm->cpu[vm->active].u = 1; break;
|
|
case 1:
|
|
c = 0;
|
|
z = stack_pop(vm);
|
|
y = stack_pop(vm);
|
|
x = stack_pop(vm);
|
|
if (vm->cpu[vm->active].u != 0) {
|
|
c = (unsigned)x * (unsigned)y;
|
|
stack_push(vm, (unsigned)c % (unsigned)z);
|
|
stack_push(vm, (unsigned)c / (unsigned)z);
|
|
}
|
|
else {
|
|
c = x * y;
|
|
stack_push(vm, c % z);
|
|
stack_push(vm, c / z);
|
|
}
|
|
vm->cpu[vm->active].u = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void query_unsigned(NgaState *vm) {
|
|
stack_push(vm, 0);
|
|
stack_push(vm, 8101);
|
|
}
|
|
#endif
|
|
|
|
/*=====================================================================*/
|
|
|
|
void io_image(NgaState *vm) {
|
|
FILE *fp;
|
|
char *f = string_extract(vm, stack_pop(vm));
|
|
if ((fp = fopen(f, "wb")) == NULL) {
|
|
printf("\nERROR (nga/io_image): Unable to save the image: %s!\n", f);
|
|
exit(2);
|
|
}
|
|
fwrite(vm->memory, sizeof(CELL), vm->memory[3] + 1, fp);
|
|
fclose(fp);
|
|
}
|
|
|
|
void query_image(NgaState *vm) {
|
|
stack_push(vm, 0);
|
|
stack_push(vm, 1000);
|
|
}
|
|
|
|
|
|
/*=====================================================================*/
|
|
|
|
|
|
/*---------------------------------------------------------------------
|
|
Scripting Support
|
|
---------------------------------------------------------------------*/
|
|
|
|
void scripting_arg(NgaState *vm) {
|
|
CELL a, b;
|
|
a = stack_pop(vm);
|
|
b = stack_pop(vm);
|
|
stack_push(vm, string_inject(vm, vm->sys_argv[a + 2], b));
|
|
}
|
|
|
|
void scripting_arg_count(NgaState *vm) {
|
|
stack_push(vm, vm->sys_argc - 2);
|
|
}
|
|
|
|
void scripting_include(NgaState *vm) {
|
|
include_file(vm, string_extract(vm, stack_pop(vm)), 0);
|
|
}
|
|
|
|
void scripting_name(NgaState *vm) {
|
|
stack_push(vm, string_inject(vm, vm->sys_argv[1], stack_pop(vm)));
|
|
}
|
|
|
|
/* addeded in scripting i/o device, revision 1 */
|
|
void scripting_source(NgaState *vm) {
|
|
stack_push(vm, string_inject(vm, vm->scripting_sources[vm->current_source], stack_pop(vm)));
|
|
}
|
|
|
|
void scripting_line(NgaState *vm) {
|
|
stack_push(vm, vm->currentLine);
|
|
}
|
|
|
|
void scripting_ignore_to_eol(NgaState *vm) {
|
|
vm->ignoreToEOL = -1;
|
|
}
|
|
|
|
void scripting_ignore_to_eof(NgaState *vm) {
|
|
vm->ignoreToEOF = -1;
|
|
}
|
|
|
|
void scripting_abort(NgaState *vm) {
|
|
scripting_ignore_to_eol(vm);
|
|
scripting_ignore_to_eof(vm);
|
|
vm->perform_abort = -1;
|
|
}
|
|
|
|
void carry_out_abort(NgaState *vm) {
|
|
vm->cpu[vm->active].ip = IMAGE_SIZE + 1;
|
|
vm->cpu[vm->active].rp = 0;
|
|
vm->cpu[vm->active].sp = 0;
|
|
#ifdef ENABLE_FLOATS
|
|
vm->fsp = 0;
|
|
vm->afsp = 0;
|
|
#endif
|
|
|
|
if (vm->current_source > 0) {
|
|
scripting_abort(vm);
|
|
return;
|
|
}
|
|
|
|
vm->perform_abort = 0;
|
|
vm->current_source = 0;
|
|
}
|
|
|
|
void scripting_line_text(NgaState *vm) {
|
|
CELL target = stack_pop(vm);
|
|
string_inject(vm, vm->line, target);
|
|
}
|
|
|
|
Handler ScriptingActions[] = {
|
|
scripting_arg_count, scripting_arg,
|
|
scripting_include, scripting_name,
|
|
scripting_source, scripting_line,
|
|
scripting_ignore_to_eol, scripting_ignore_to_eof,
|
|
scripting_abort, scripting_line_text
|
|
};
|
|
|
|
void query_scripting(NgaState *vm) {
|
|
stack_push(vm, 2);
|
|
stack_push(vm, 9);
|
|
}
|
|
|
|
void io_scripting(NgaState *vm) {
|
|
ScriptingActions[stack_pop(vm)](vm);
|
|
}
|
|
|
|
|
|
/*=====================================================================*/
|
|
|
|
/*---------------------------------------------------------------------
|
|
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.
|
|
|
|
This will also exit if the address stack depth is zero (meaning that
|
|
the word being run, and it's dependencies) are finished.
|
|
---------------------------------------------------------------------*/
|
|
|
|
void invalid_opcode(NgaState *vm, CELL opcode) {
|
|
CELL a, i;
|
|
printf("\nERROR (nga/execute): Invalid instruction!\n");
|
|
printf("At %lld, opcode %lld\n", (long long)vm->cpu[vm->active].ip, (long long)opcode);
|
|
printf("Instructions: ");
|
|
a = opcode;
|
|
for (i = 0; i < 4; i++) {
|
|
printf("%lldd ", (long long)a & 0xFF);
|
|
a = a >> 8;
|
|
}
|
|
printf("\n");
|
|
exit(1);
|
|
}
|
|
|
|
void execute(NgaState *vm, CELL cell) {
|
|
CELL token;
|
|
CELL opcode;
|
|
if (vm->cpu[vm->active].rp == 0)
|
|
vm->cpu[vm->active].rp = 1;
|
|
vm->cpu[vm->active].ip = cell;
|
|
token = TIB;
|
|
while (vm->cpu[vm->active].ip < IMAGE_SIZE) {
|
|
if (vm->perform_abort == 0) {
|
|
/* FIXME
|
|
if (vm->cpu[vm->active].ip == vm->NotFound) {
|
|
printf("\nERROR: Word Not Found: ");
|
|
printf("`%s`\n\n", string_extract(vm, token));
|
|
}
|
|
*/
|
|
if (vm->cpu[vm->active].ip == vm->interpret) {
|
|
token = TOS;
|
|
}
|
|
opcode = vm->memory[vm->cpu[vm->active].ip];
|
|
if (validate_opcode_bundle(opcode) != 0) {
|
|
process_opcode_bundle(vm, opcode);
|
|
} else {
|
|
invalid_opcode(vm, opcode);
|
|
}
|
|
#ifndef ENABLE_ERROR
|
|
if (vm->cpu[vm->active].sp < 0 || vm->cpu[vm->active].sp > STACK_DEPTH) {
|
|
printf("\nERROR (nga/execute): Stack Limits Exceeded!\n");
|
|
printf("At %lld, opcode %lld. sp = %lld, core = %lld\n", (long long)vm->cpu[vm->active].ip, (long long)opcode, (long long)vm->cpu[vm->active].sp, (long long)vm->active);
|
|
exit(1);
|
|
}
|
|
if (vm->cpu[vm->active].rp < 0 || vm->cpu[vm->active].rp > ADDRESSES) {
|
|
printf("\nERROR (nga/execute): Address Stack Limits Exceeded!\n");
|
|
printf("At %lld, opcode %lld. rp = %lld\n", (long long)vm->cpu[vm->active].ip, (long long)opcode, (long long)vm->cpu[vm->active].rp);
|
|
exit(1);
|
|
}
|
|
#endif
|
|
vm->cpu[vm->active].ip++;
|
|
#ifdef ENABLE_MULTICORE
|
|
switch_core(vm);
|
|
#endif
|
|
if (vm->cpu[vm->active].rp == 0)
|
|
vm->cpu[vm->active].ip = IMAGE_SIZE;
|
|
} else {
|
|
carry_out_abort(vm);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*---------------------------------------------------------------------
|
|
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.
|
|
---------------------------------------------------------------------*/
|
|
|
|
void evaluate(NgaState *vm, char *s) {
|
|
if (strlen(s) == 0) return;
|
|
string_inject(vm, s, TIB);
|
|
stack_push(vm, TIB);
|
|
execute(vm, vm->interpret);
|
|
}
|
|
|
|
|
|
/*---------------------------------------------------------------------
|
|
`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.
|
|
---------------------------------------------------------------------*/
|
|
|
|
int not_eol(int c) {
|
|
return (c != 9) && (c != 10) && (c != 13) && (c != 32) && (c != EOF) && (c != 0);
|
|
}
|
|
|
|
void read_token(FILE *file, char *token_buffer) {
|
|
#ifdef USE_UTF32
|
|
int ch = fread_character(file);
|
|
#else
|
|
int ch = getc(file);
|
|
#endif
|
|
int count = 0;
|
|
while (not_eol(ch)) {
|
|
if ((ch == 8 || ch == 127) && count > 0) {
|
|
count--;
|
|
} else {
|
|
token_buffer[count++] = ch;
|
|
}
|
|
#ifdef USE_UTF32
|
|
ch = fread_character(file);
|
|
#else
|
|
ch = getc(file);
|
|
#endif
|
|
}
|
|
token_buffer[count] = '\0';
|
|
}
|
|
|
|
|
|
void skip_indent(FILE *fp) {
|
|
int ch = getc(fp);
|
|
while (ch == ' ') {
|
|
ch = getc(fp);
|
|
}
|
|
ungetc(ch, fp);
|
|
}
|
|
|
|
/*---------------------------------------------------------------------
|
|
Display the Stack Contents
|
|
---------------------------------------------------------------------*/
|
|
|
|
void dump_stack(NgaState *vm) {
|
|
CELL i;
|
|
if (vm->cpu[vm->active].sp == 0) return;
|
|
printf("\nStack: ");
|
|
for (i = 1; i <= vm->cpu[vm->active].sp; i++) {
|
|
if (i == vm->cpu[vm->active].sp)
|
|
printf("[ TOS: %lld ]", (long long)vm->cpu[vm->active].data[i]);
|
|
else
|
|
printf("%lld ", (long long)vm->cpu[vm->active].data[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
void dump_astack(NgaState *vm) {
|
|
CELL i;
|
|
if (vm->cpu[vm->active].rp == 0) return;
|
|
printf("\nAddress Stack: ");
|
|
for (i = 1; i <= vm->cpu[vm->active].rp; i++) {
|
|
if (i == vm->cpu[vm->active].rp)
|
|
printf("[ TOS: %lld ]", (long long)vm->cpu[vm->active].address[i]);
|
|
else
|
|
printf("%lld ", (long long)vm->cpu[vm->active].address[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
/*---------------------------------------------------------------------
|
|
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.
|
|
---------------------------------------------------------------------*/
|
|
|
|
/* Check to see if a line is a fence boundary.
|
|
This will check code blocks in all cases, and test blocks
|
|
if tests_enabled is set to a non-zero value. */
|
|
|
|
int fence_boundary(NgaState *vm, char *buffer, int tests_enabled) {
|
|
int flag = 1;
|
|
if (strcmp(buffer, vm->code_start) == 0) { flag = -1; }
|
|
if (strcmp(buffer, vm->code_end) == 0) { flag = -1; }
|
|
if (strcmp(buffer, vm->test_start) == 0) {
|
|
if (vm->codeBlocks == 0) { vm->codeBlocks++; }
|
|
}
|
|
if (tests_enabled == 0) { return flag; }
|
|
if (strcmp(buffer, vm->test_start) == 0) { flag = -1; }
|
|
if (strcmp(buffer, vm->test_end) == 0) { flag = -1; }
|
|
return flag;
|
|
}
|
|
|
|
|
|
/*---------------------------------------------------------------------
|
|
And now for the actual `include_file()` function.
|
|
---------------------------------------------------------------------*/
|
|
|
|
void read_line(NgaState *vm, FILE *file, char *token_buffer) {
|
|
int ch = getc(file);
|
|
int count = 0;
|
|
token_buffer[0] = '\0';
|
|
while ((ch != 10) && (ch != 13) && (ch != EOF) && (ch != 0)) {
|
|
token_buffer[count++] = ch;
|
|
#ifdef USE_UTF32
|
|
ch = fread_character(file);
|
|
#else
|
|
ch = getc(file);
|
|
#endif
|
|
}
|
|
token_buffer[count] = '\0';
|
|
}
|
|
|
|
int count_tokens(char *line) {
|
|
int count = 1;
|
|
while (*line++) {
|
|
if (isspace(line[0]))
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
void include_file(NgaState *vm, char *fname, int run_tests) {
|
|
int inBlock = 0; /* Tracks status of in/out of block */
|
|
int priorBlocks = 0;
|
|
char source[64 * 1024]; /* Token buffer [about 64K] */
|
|
char fence[33]; /* Used with `fence_boundary()` */
|
|
|
|
CELL ReturnStack[ADDRESSES];
|
|
CELL arp, aip;
|
|
|
|
long offset = 0;
|
|
CELL at = 0;
|
|
int tokens = 0;
|
|
FILE *fp; /* Open the file. If not found, */
|
|
fp = fopen(fname, "r"); /* exit. */
|
|
if (fp == NULL) {
|
|
printf("File `%s` not found. Exiting.\n", fname);
|
|
exit(1);
|
|
}
|
|
|
|
priorBlocks = vm->codeBlocks;
|
|
vm->codeBlocks = 0;
|
|
|
|
arp = vm->cpu[vm->active].rp;
|
|
aip = vm->cpu[vm->active].ip;
|
|
for(vm->cpu[vm->active].rp = 0; vm->cpu[vm->active].rp <= arp; vm->cpu[vm->active].rp++)
|
|
ReturnStack[vm->cpu[vm->active].rp] = vm->cpu[vm->active].address[vm->cpu[vm->active].rp];
|
|
vm->cpu[vm->active].rp = 0;
|
|
|
|
vm->current_source++;
|
|
strlcpy(vm->scripting_sources[vm->current_source], fname, 8192);
|
|
|
|
vm->ignoreToEOF = 0;
|
|
|
|
while (!feof(fp) && (vm->ignoreToEOF == 0)) { /* Loop through the file */
|
|
|
|
vm->ignoreToEOL = 0;
|
|
|
|
offset = ftell(fp);
|
|
read_line(vm, fp, vm->line);
|
|
at++;
|
|
fseek(fp, offset, SEEK_SET);
|
|
skip_indent(fp);
|
|
|
|
tokens = count_tokens(vm->line);
|
|
|
|
while (tokens > 0 && vm->ignoreToEOL == 0) {
|
|
tokens--;
|
|
read_token(fp, source);
|
|
strlcpy(fence, source, 32); /* Copy the first three characters */
|
|
if (fence_boundary(vm, fence, run_tests) == -1) {
|
|
if (inBlock == 0) {
|
|
inBlock = 1;
|
|
vm->codeBlocks++;
|
|
} else {
|
|
inBlock = 0;
|
|
}
|
|
} else {
|
|
if (inBlock == 1) {
|
|
vm->currentLine = at;
|
|
evaluate(vm, source);
|
|
vm->currentLine = at;
|
|
}
|
|
}
|
|
}
|
|
if (vm->ignoreToEOL == -1) {
|
|
read_line(vm, fp, vm->line);
|
|
}
|
|
}
|
|
|
|
vm->current_source--;
|
|
vm->ignoreToEOF = 0;
|
|
fclose(fp);
|
|
if (vm->perform_abort == -1) {
|
|
carry_out_abort(vm);
|
|
}
|
|
for(vm->cpu[vm->active].rp = 0; vm->cpu[vm->active].rp <= arp; vm->cpu[vm->active].rp++)
|
|
vm->cpu[vm->active].address[vm->cpu[vm->active].rp] = ReturnStack[vm->cpu[vm->active].rp];
|
|
vm->cpu[vm->active].rp = arp;
|
|
vm->cpu[vm->active].ip = aip;
|
|
|
|
if (vm->codeBlocks == 0) {
|
|
printf("warning: no code or test blocks found!\n");
|
|
printf(" filename: %s\n", fname);
|
|
printf(" see http://unu.retroforth.org for a brief summary of\n");
|
|
printf(" the unu code format used by retro\n");
|
|
|
|
}
|
|
vm->codeBlocks = priorBlocks;
|
|
}
|
|
|
|
|
|
/*---------------------------------------------------------------------
|
|
`initialize()` sets up Nga and loads the image (from the array in
|
|
`image.c`) to memory.
|
|
---------------------------------------------------------------------*/
|
|
|
|
void initialize(NgaState *vm) {
|
|
prepare_vm(vm);
|
|
load_embedded_image(vm);
|
|
}
|
|
|
|
|
|
/*---------------------------------------------------------------------
|
|
`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 *argv, char *t) {
|
|
return strcmp(argv, t) == 0;
|
|
}
|
|
|
|
|
|
void help(char *exename) {
|
|
printf("Scripting Usage: %s filename\n\n", exename);
|
|
printf("Interactive Usage: %s [-h] [-i] [-f filename] [-t filename]\n\n", exename);
|
|
printf("Valid Arguments:\n\n");
|
|
printf(" -h\n");
|
|
printf(" Display this help text\n");
|
|
printf(" -i\n");
|
|
printf(" Launches in interactive mode\n");
|
|
printf(" -f filename\n");
|
|
printf(" Run the contents of the specified file\n");
|
|
printf(" -u filename\n");
|
|
printf(" Use the image in the specified file instead of the internal one\n");
|
|
printf(" -r filename\n");
|
|
printf(" Use the image in the specified file instead of the internal one and run the code in it\n");
|
|
printf(" -t filename\n");
|
|
printf(" Run the contents of the specified file, including any tests (in ``` blocks)\n\n");
|
|
printf(" -v\n");
|
|
printf(" Run in verbose mode\n");
|
|
}
|
|
|
|
/* Signal Handler -----------------------------------------------------*/
|
|
|
|
#ifdef ENABLE_SIGNALS
|
|
static void sig_handler(int _)
|
|
{
|
|
printf("\nCaught: %d\n", _);
|
|
exit(1);
|
|
}
|
|
#endif
|
|
|
|
/* Main Entry Point ---------------------------------------------------*/
|
|
enum flags {
|
|
FLAG_HELP, FLAG_INTERACTIVE,
|
|
};
|
|
|
|
int main(int argc, char **argv) {
|
|
int i;
|
|
int modes[16];
|
|
NgaState *vm = calloc(sizeof(NgaState), sizeof(char));
|
|
verbose = 0;
|
|
|
|
#ifdef ENABLE_SIGNALS
|
|
signal(SIGHUP, sig_handler);
|
|
signal(SIGINT, sig_handler);
|
|
signal(SIGILL, sig_handler);
|
|
signal(SIGBUS, sig_handler);
|
|
signal(SIGFPE, sig_handler);
|
|
#endif
|
|
|
|
initialize(vm); /* Initialize Nga & image */
|
|
vm->interactive = 0;
|
|
|
|
register_device(vm, io_output, query_output);
|
|
register_device(vm, io_keyboard, query_keyboard);
|
|
register_device(vm, io_filesystem, query_filesystem);
|
|
register_device(vm, io_image, query_image);
|
|
#ifdef ENABLE_FLOATS
|
|
register_device(vm, io_floatingpoint, query_floatingpoint);
|
|
#endif
|
|
#ifdef ENABLE_UNIX
|
|
register_device(vm, io_unix, query_unix);
|
|
#endif
|
|
#ifdef ENABLE_MALLOC
|
|
#ifdef BIT64
|
|
register_device(vm, io_malloc, query_malloc);
|
|
#endif
|
|
#endif
|
|
#ifdef ENABLE_BLOCKS
|
|
register_device(vm, io_blocks, query_blocks);
|
|
#endif
|
|
#ifdef ENABLE_CLOCK
|
|
register_device(vm, io_clock, query_clock);
|
|
#endif
|
|
register_device(vm, io_scripting, query_scripting);
|
|
#ifdef ENABLE_RNG
|
|
register_device(vm, io_rng, query_rng);
|
|
#endif
|
|
#ifdef ENABLE_SOCKETS
|
|
register_device(vm, io_socket, query_socket);
|
|
#endif
|
|
#ifdef ENABLE_MULTICORE
|
|
register_device(vm, io_multicore, query_multicore);
|
|
#endif
|
|
#ifdef ENABLE_FFI
|
|
register_device(vm, io_ffi, query_ffi);
|
|
nlibs = 0;
|
|
nffi = 0;
|
|
#endif
|
|
#ifdef ENABLE_UNSIGNED
|
|
register_device(vm, io_unsigned, query_unsigned);
|
|
#endif
|
|
#ifdef ENABLE_ERROR
|
|
register_device(vm, io_error, query_error);
|
|
#endif
|
|
|
|
strlcpy(vm->code_start, "~~~", 256);
|
|
strlcpy(vm->code_end, "~~~", 256);
|
|
strlcpy(vm->test_start, "```", 256);
|
|
strlcpy(vm->test_end, "```", 256);
|
|
|
|
/* Setup variables related to the scripting device */
|
|
vm->currentLine = 0; /* Current Line # for script */
|
|
vm->current_source = 0; /* Current file being run */
|
|
vm->perform_abort = 0; /* Carry out abort procedure */
|
|
vm->sys_argc = argc; /* Point the global argc and */
|
|
vm->sys_argv = argv; /* argv to the actual ones */
|
|
strlcpy(vm->scripting_sources[0], "/dev/stdin", 8192);
|
|
vm->ignoreToEOL = 0;
|
|
vm->ignoreToEOF = 0;
|
|
vm->codeBlocks = 0;
|
|
|
|
if (argc >= 2 && argv[1][0] != '-') {
|
|
update_rx(vm);
|
|
include_file(vm, argv[1], 0);
|
|
/* If no flags were passed, */
|
|
/* load the file specified, */
|
|
/* and exit */
|
|
if (vm->cpu[vm->active].sp >= 1) dump_stack(vm);
|
|
exit(0);
|
|
}
|
|
|
|
/* Clear startup modes */
|
|
for (i = 0; i < 16; i++)
|
|
modes[i] = 0;
|
|
|
|
if (argc <= 1) modes[FLAG_INTERACTIVE] = 1;
|
|
|
|
update_rx(vm);
|
|
|
|
/* Process Arguments */
|
|
for (i = 1; i < argc; i++) {
|
|
if (strcmp(argv[i], "-h") == 0) {
|
|
help(argv[0]);
|
|
exit(0);
|
|
} else if (strcmp(argv[i], "-v") == 0) {
|
|
verbose = 1;
|
|
} else if (strcmp(argv[i], "-i") == 0) {
|
|
modes[FLAG_INTERACTIVE] = 1;
|
|
vm->interactive = -1;
|
|
} else if (strcmp(argv[i], "-f") == 0) {
|
|
include_file(vm, argv[i + 1], 0);
|
|
i++;
|
|
} else if (strcmp(argv[i], "-u") == 0) {
|
|
i++;
|
|
load_image(vm, argv[i]);
|
|
update_rx(vm);
|
|
} else if (strcmp(argv[i], "-r") == 0) {
|
|
i++;
|
|
load_image(vm, argv[i]);
|
|
modes[FLAG_INTERACTIVE] = 1;
|
|
update_rx(vm);
|
|
} else if (strcmp(argv[i], "-t") == 0) {
|
|
include_file(vm, argv[i + 1], 1);
|
|
i++;
|
|
} else if (arg_is(argv[i], "--code-start") || arg_is(argv[i], "-cs")) {
|
|
i++;
|
|
strlcpy(vm->code_start, argv[i], 256);
|
|
} else if (arg_is(argv[i], "--code-end") || arg_is(argv[i], "-ce")) {
|
|
i++;
|
|
strlcpy(vm->code_end, argv[i], 256);
|
|
} else if (arg_is(argv[i], "--test-start") || arg_is(argv[i], "-ts")) {
|
|
i++;
|
|
strlcpy(vm->test_start, argv[i], 256);
|
|
} else if (arg_is(argv[i], "--test-end") || arg_is(argv[i], "-te")) {
|
|
i++;
|
|
strlcpy(vm->test_end, argv[i], 256);
|
|
}
|
|
}
|
|
|
|
/* Run the Listener (if interactive mode was set) */
|
|
if (modes[FLAG_INTERACTIVE] == 1) {
|
|
execute(vm, 0);
|
|
}
|
|
|
|
/* Dump Stack */
|
|
if (vm->cpu[vm->active].sp >= 1) dump_stack(vm);
|
|
|
|
free(vm);
|
|
}
|
|
|
|
|
|
/*=====================================================================*/
|
|
|
|
|
|
/*---------------------------------------------------------------------
|
|
Interfacing With The Image
|
|
---------------------------------------------------------------------*/
|
|
|
|
/*---------------------------------------------------------------------
|
|
Stack push/pop is easy. I could avoid these, but it aids in keeping
|
|
the code readable, so it's worth the slight overhead.
|
|
---------------------------------------------------------------------*/
|
|
|
|
CELL stack_pop(NgaState *vm) {
|
|
vm->cpu[vm->active].sp--;
|
|
return vm->cpu[vm->active].data[vm->cpu[vm->active].sp + 1];
|
|
}
|
|
|
|
void stack_push(NgaState *vm, CELL value) {
|
|
vm->cpu[vm->active].sp++;
|
|
vm->cpu[vm->active].data[vm->cpu[vm->active].sp] = value;
|
|
}
|
|
|
|
|
|
/*---------------------------------------------------------------------
|
|
Strings are next. RETRO uses C-style NULL terminated strings. So I
|
|
can easily inject or extract a string. Injection iterates over the
|
|
string, copying it into the image. This also takes care to ensure
|
|
that the NULL terminator is added.
|
|
---------------------------------------------------------------------*/
|
|
|
|
CELL string_inject(NgaState *vm, char *str, CELL buffer) {
|
|
CELL m, i;
|
|
if (!str) {
|
|
vm->memory[buffer] = 0;
|
|
return 0;
|
|
}
|
|
m = strlen(str);
|
|
i = 0;
|
|
while (m > 0) {
|
|
vm->memory[buffer + i] = (CELL)str[i];
|
|
vm->memory[buffer + i + 1] = 0;
|
|
m--; i++;
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
|
|
/*---------------------------------------------------------------------
|
|
Extracting a string is similar, but I have to iterate over the VM
|
|
memory instead of a C string and copy the charaters into a buffer.
|
|
This uses a static buffer (`string_data`) as I prefer to avoid using
|
|
`malloc()`.
|
|
---------------------------------------------------------------------*/
|
|
|
|
char *string_extract(NgaState *vm, CELL at) {
|
|
CELL starting = at;
|
|
CELL i = 0;
|
|
while(vm->memory[starting] && i < 8192)
|
|
vm->string_data[i++] = (char)vm->memory[starting++];
|
|
vm->string_data[i] = 0;
|
|
return (char *)vm->string_data;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------
|
|
This interface tracks a few words and variables in the image. These
|
|
are:
|
|
|
|
Dictionary - the latest dictionary header
|
|
NotFound - called when a word is not found
|
|
interpret - the heart of the interpreter/compiler
|
|
|
|
I have to call this periodically, as the Dictionary will change as
|
|
new words are defined, and the user might write a new error handler
|
|
or interpreter.
|
|
---------------------------------------------------------------------*/
|
|
|
|
void update_rx(NgaState *vm) {
|
|
vm->Dictionary = vm->memory[2];
|
|
vm->interpret = vm->memory[5];
|
|
vm->NotFound = vm->memory[6];
|
|
if (vm->memory[10] != 0) { execute(vm, vm->memory[10]); }
|
|
}
|
|
|
|
/*=====================================================================*/
|
|
|
|
void register_device(NgaState *vm, void *handler, void *query) {
|
|
vm->IO_deviceHandlers[vm->devices] = handler;
|
|
vm->IO_queryHandlers[vm->devices] = query;
|
|
vm->devices++;
|
|
}
|
|
|
|
void load_embedded_image(NgaState *vm) {
|
|
int i;
|
|
for (i = 0; i < ngaImageCells; i++)
|
|
vm->memory[i] = ngaImage[i];
|
|
}
|
|
|
|
CELL load_image(NgaState *vm, char *imageFile) {
|
|
FILE *fp;
|
|
CELL imageSize = 0;
|
|
long fileLen;
|
|
if ((fp = fopen(imageFile, "rb")) != NULL) {
|
|
/* Determine length (in cells) */
|
|
fseek(fp, 0, SEEK_END);
|
|
fileLen = ftell(fp) / sizeof(CELL);
|
|
if (fileLen > IMAGE_SIZE) {
|
|
fclose(fp);
|
|
printf("\nERROR (nga/ngaLoadImage): Image is larger than alloted space!\n");
|
|
exit(1);
|
|
}
|
|
rewind(fp);
|
|
for (int i = 0; i < IMAGE_SIZE; i++)
|
|
vm->memory[i] = 0; /* NO - nop instruction */
|
|
/* Read the file into memory */
|
|
imageSize = fread(vm->memory, sizeof(CELL), fileLen, fp);
|
|
fclose(fp);
|
|
}
|
|
return imageSize;
|
|
}
|
|
|
|
void prepare_vm(NgaState *vm) {
|
|
vm->active = 0;
|
|
vm->cpu[vm->active].ip = vm->cpu[vm->active].sp = vm->cpu[vm->active].rp = vm->cpu[vm->active].u = 0;
|
|
vm->cpu[vm->active].active = -1;
|
|
for (vm->cpu[vm->active].ip = 0; vm->cpu[vm->active].ip < IMAGE_SIZE; vm->cpu[vm->active].ip++)
|
|
vm->memory[vm->cpu[vm->active].ip] = 0; /* NO - nop instruction */
|
|
for (vm->cpu[vm->active].ip = 0; vm->cpu[vm->active].ip < STACK_DEPTH; vm->cpu[vm->active].ip++)
|
|
vm->cpu[vm->active].data[vm->cpu[vm->active].ip] = 0;
|
|
for (vm->cpu[vm->active].ip = 0; vm->cpu[vm->active].ip < ADDRESSES; vm->cpu[vm->active].ip++)
|
|
vm->cpu[vm->active].address[vm->cpu[vm->active].ip] = 0;
|
|
}
|
|
|
|
void inst_no(NgaState *vm) {
|
|
guard(vm, 0, 0, 0);
|
|
}
|
|
|
|
void inst_li(NgaState *vm) {
|
|
guard(vm, 0, 1, 0);
|
|
vm->cpu[vm->active].sp++;
|
|
vm->cpu[vm->active].ip++;
|
|
TOS = vm->memory[vm->cpu[vm->active].ip];
|
|
}
|
|
|
|
void inst_du(NgaState *vm) {
|
|
guard(vm, 1, 2, 0);
|
|
vm->cpu[vm->active].sp++;
|
|
vm->cpu[vm->active].data[vm->cpu[vm->active].sp] = NOS;
|
|
}
|
|
|
|
void inst_dr(NgaState *vm) {
|
|
guard(vm, 1, 0, 0);
|
|
vm->cpu[vm->active].data[vm->cpu[vm->active].sp] = 0;
|
|
vm->cpu[vm->active].sp--;
|
|
}
|
|
|
|
void inst_sw(NgaState *vm) {
|
|
guard(vm, 2, 2, 0);
|
|
CELL a;
|
|
a = TOS;
|
|
TOS = NOS;
|
|
NOS = a;
|
|
}
|
|
|
|
void inst_pu(NgaState *vm) {
|
|
guard(vm, 1, 0, 1);
|
|
vm->cpu[vm->active].rp++;
|
|
TORS = TOS;
|
|
inst_dr(vm);
|
|
}
|
|
|
|
void inst_po(NgaState *vm) {
|
|
guard(vm, 0, 1, -1);
|
|
vm->cpu[vm->active].sp++;
|
|
TOS = TORS;
|
|
vm->cpu[vm->active].rp--;
|
|
}
|
|
|
|
void inst_ju(NgaState *vm) {
|
|
guard(vm, 1, 0, 0);
|
|
vm->cpu[vm->active].ip = TOS - 1;
|
|
inst_dr(vm);
|
|
}
|
|
|
|
void inst_ca(NgaState *vm) {
|
|
guard(vm, 1, 0, 1);
|
|
vm->cpu[vm->active].rp++;
|
|
TORS = vm->cpu[vm->active].ip;
|
|
vm->cpu[vm->active].ip = TOS - 1;
|
|
inst_dr(vm);
|
|
}
|
|
|
|
void inst_cc(NgaState *vm) {
|
|
guard(vm, 2, 0, 1);
|
|
CELL a, b;
|
|
a = TOS; inst_dr(vm); /* Target */
|
|
b = TOS; inst_dr(vm); /* Flag */
|
|
if (b != 0) {
|
|
vm->cpu[vm->active].rp++;
|
|
TORS = vm->cpu[vm->active].ip;
|
|
vm->cpu[vm->active].ip = a - 1;
|
|
}
|
|
}
|
|
|
|
void inst_re(NgaState *vm) {
|
|
guard(vm, 0, 0, -1);
|
|
vm->cpu[vm->active].ip = TORS;
|
|
vm->cpu[vm->active].rp--;
|
|
}
|
|
|
|
void inst_eq(NgaState *vm) {
|
|
guard(vm, 2, 1, 0);
|
|
if (vm->cpu[vm->active].u != 0) {
|
|
NOS = ((unsigned)NOS == (unsigned)TOS) ? -1 : 0;
|
|
vm->cpu[vm->active].u = 0;
|
|
} else {
|
|
NOS = (NOS == TOS) ? -1 : 0;
|
|
}
|
|
inst_dr(vm);
|
|
}
|
|
|
|
void inst_ne(NgaState *vm) {
|
|
guard(vm, 2, 1, 0);
|
|
if (vm->cpu[vm->active].u != 0) {
|
|
NOS = ((unsigned)NOS != (unsigned)TOS) ? -1 : 0;
|
|
vm->cpu[vm->active].u = 0;
|
|
} else {
|
|
NOS = (NOS != TOS) ? -1 : 0;
|
|
}
|
|
inst_dr(vm);
|
|
}
|
|
|
|
void inst_lt(NgaState *vm) {
|
|
guard(vm, 2, 1, 0);
|
|
if (vm->cpu[vm->active].u != 0) {
|
|
NOS = ((unsigned)NOS < (unsigned)TOS) ? -1 : 0;
|
|
vm->cpu[vm->active].u = 0;
|
|
} else {
|
|
NOS = (NOS < TOS) ? -1 : 0;
|
|
}
|
|
inst_dr(vm);
|
|
}
|
|
|
|
void inst_gt(NgaState *vm) {
|
|
guard(vm, 2, 1, 0);
|
|
if (vm->cpu[vm->active].u != 0) {
|
|
NOS = ((unsigned)NOS > (unsigned)TOS) ? -1 : 0;
|
|
vm->cpu[vm->active].u = 0;
|
|
} else {
|
|
NOS = (NOS > TOS) ? -1 : 0;
|
|
}
|
|
inst_dr(vm);
|
|
}
|
|
|
|
void inst_fe(NgaState *vm) {
|
|
guard(vm, 1, 1, 0);
|
|
switch (TOS) {
|
|
case -1: TOS = vm->cpu[vm->active].sp - 1; break;
|
|
case -2: TOS = vm->cpu[vm->active].rp; break;
|
|
case -3: TOS = IMAGE_SIZE; break;
|
|
case -4: TOS = CELL_MIN; break;
|
|
case -5: TOS = CELL_MAX; break;
|
|
default: TOS = vm->memory[TOS]; break;
|
|
}
|
|
}
|
|
|
|
void inst_st(NgaState *vm) {
|
|
guard(vm, 2, 0, 0);
|
|
vm->memory[TOS] = NOS;
|
|
inst_dr(vm);
|
|
inst_dr(vm);
|
|
}
|
|
|
|
void inst_ad(NgaState *vm) {
|
|
guard(vm, 2, 1, 0);
|
|
if (vm->cpu[vm->active].u != 0) {
|
|
NOS = (unsigned)NOS + (unsigned)TOS;
|
|
vm->cpu[vm->active].u = 0;
|
|
} else {
|
|
NOS += TOS;
|
|
}
|
|
inst_dr(vm);
|
|
}
|
|
|
|
void inst_su(NgaState *vm) {
|
|
guard(vm, 2, 1, 0);
|
|
if (vm->cpu[vm->active].u != 0) {
|
|
NOS = (unsigned)NOS - (unsigned)TOS;
|
|
vm->cpu[vm->active].u = 0;
|
|
} else {
|
|
NOS -= TOS;
|
|
}
|
|
inst_dr(vm);
|
|
}
|
|
|
|
void inst_mu(NgaState *vm) {
|
|
guard(vm, 2, 1, 0);
|
|
if (vm->cpu[vm->active].u != 0) {
|
|
NOS = (unsigned)NOS * (unsigned)TOS;
|
|
vm->cpu[vm->active].u = 0;
|
|
} else {
|
|
NOS *= TOS;
|
|
}
|
|
inst_dr(vm);
|
|
}
|
|
|
|
void inst_di(NgaState *vm) {
|
|
guard(vm, 2, 2, 0);
|
|
CELL a, b;
|
|
a = TOS;
|
|
b = NOS;
|
|
if (b == 0) {
|
|
#ifdef ENABLE_ERROR
|
|
if (vm->ErrorHandlers[6] != 0) {
|
|
}
|
|
#endif
|
|
}
|
|
if (vm->cpu[vm->active].u != 0) {
|
|
TOS = (unsigned)b / (unsigned)a;
|
|
NOS = (unsigned)b % (unsigned)a;
|
|
vm->cpu[vm->active].u = 0;
|
|
} else {
|
|
TOS = b / a;
|
|
NOS = b % a;
|
|
}
|
|
}
|
|
|
|
void inst_an(NgaState *vm) {
|
|
guard(vm, 2, 1, 0);
|
|
NOS = TOS & NOS;
|
|
inst_dr(vm);
|
|
}
|
|
|
|
void inst_or(NgaState *vm) {
|
|
guard(vm, 2, 1, 0);
|
|
NOS = TOS | NOS;
|
|
inst_dr(vm);
|
|
}
|
|
|
|
void inst_xo(NgaState *vm) {
|
|
guard(vm, 2, 1, 0);
|
|
NOS = TOS ^ NOS;
|
|
inst_dr(vm);
|
|
}
|
|
|
|
void inst_sh(NgaState *vm) {
|
|
guard(vm, 2, 1, 0);
|
|
CELL y = TOS;
|
|
CELL x = NOS;
|
|
if (TOS < 0)
|
|
NOS = NOS << (0 - TOS);
|
|
else {
|
|
if (vm->cpu[vm->active].u != 0) {
|
|
NOS = (unsigned)x >> (unsigned)y;
|
|
vm->cpu[vm->active].u = 0;
|
|
} else {
|
|
if (x < 0 && y > 0)
|
|
NOS = x >> y | ~(~0U >> y);
|
|
else
|
|
NOS = x >> y;
|
|
}
|
|
}
|
|
inst_dr(vm);
|
|
}
|
|
|
|
void inst_zr(NgaState *vm) {
|
|
guard(vm, 1, 0, 0);
|
|
if (TOS == 0) {
|
|
inst_dr(vm);
|
|
vm->cpu[vm->active].ip = TORS;
|
|
vm->cpu[vm->active].rp--;
|
|
}
|
|
}
|
|
|
|
void inst_ha(NgaState *vm) {
|
|
guard(vm, 0, 0, 0);
|
|
vm->cpu[vm->active].ip = IMAGE_SIZE;
|
|
vm->cpu[vm->active].rp = 0;
|
|
}
|
|
|
|
void inst_ie(NgaState *vm) {
|
|
guard(vm, 1, 1, 0);
|
|
stack_push(vm, vm->devices);
|
|
}
|
|
|
|
void inst_iq(NgaState *vm) {
|
|
guard(vm, 1, 1, 0);
|
|
vm->IO_queryHandlers[stack_pop(vm)](vm);
|
|
}
|
|
|
|
void inst_ii(NgaState *vm) {
|
|
guard(vm, 1, 0, 0);
|
|
vm->IO_deviceHandlers[stack_pop(vm)](vm);
|
|
}
|
|
|
|
Handler instructions[] = {
|
|
inst_no, inst_li, inst_du, inst_dr, inst_sw, inst_pu, inst_po,
|
|
inst_ju, inst_ca, inst_cc, inst_re, inst_eq, inst_ne, inst_lt,
|
|
inst_gt, inst_fe, inst_st, inst_ad, inst_su, inst_mu, inst_di,
|
|
inst_an, inst_or, inst_xo, inst_sh, inst_zr, inst_ha, inst_ie,
|
|
inst_iq, inst_ii
|
|
};
|
|
|
|
void process_opcode(NgaState *vm, CELL opcode) {
|
|
#ifdef FAST
|
|
switch (opcode) {
|
|
case 0: break;
|
|
case 1: inst_li(vm); break;
|
|
case 2: inst_du(vm); break;
|
|
case 3: inst_dr(vm); break;
|
|
case 4: inst_sw(vm); break;
|
|
case 5: inst_pu(vm); break;
|
|
case 6: inst_po(vm); break;
|
|
case 7: inst_ju(vm); break;
|
|
case 8: inst_ca(vm); break;
|
|
case 9: inst_cc(vm); break;
|
|
case 10: inst_re(vm); break;
|
|
case 11: inst_eq(vm); break;
|
|
case 12: inst_ne(vm); break;
|
|
case 13: inst_lt(vm); break;
|
|
case 14: inst_gt(vm); break;
|
|
case 15: inst_fe(vm); break;
|
|
case 16: inst_st(vm); break;
|
|
case 17: inst_ad(vm); break;
|
|
case 18: inst_su(vm); break;
|
|
case 19: inst_mu(vm); break;
|
|
case 20: inst_di(vm); break;
|
|
case 21: inst_an(vm); break;
|
|
case 22: inst_or(vm); break;
|
|
case 23: inst_xo(vm); break;
|
|
case 24: inst_sh(vm); break;
|
|
case 25: inst_zr(vm); break;
|
|
case 26: inst_ha(vm); break;
|
|
case 27: inst_ie(vm); break;
|
|
case 28: inst_iq(vm); break;
|
|
case 29: inst_ii(vm); break;
|
|
default: break;
|
|
}
|
|
#else
|
|
if (opcode != 0)
|
|
instructions[opcode](vm);
|
|
#endif
|
|
}
|
|
|
|
int validate_opcode_bundle(CELL opcode) {
|
|
CELL raw = opcode;
|
|
CELL current;
|
|
int valid = -1;
|
|
int i;
|
|
for (i = 0; i < 4; i++) {
|
|
current = raw & 0xFF;
|
|
if (!(current >= 0 && current <= 29))
|
|
valid = 0;
|
|
raw = raw >> 8;
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
void verbose_details(NgaState *vm, CELL opcode) {
|
|
fprintf(stderr, "ip: %lld ", vm->cpu[vm->active].ip);
|
|
fprintf(stderr, "sp: %lld ", vm->cpu[vm->active].sp);
|
|
fprintf(stderr, "rp: %lld ", vm->cpu[vm->active].rp);
|
|
fprintf(stderr, "core: %d ", vm->active);
|
|
fprintf(stderr, "opcode: %lld\n", opcode);
|
|
}
|
|
|
|
#define INST(n) ((opcode >> n) & 0xFF) != 0
|
|
void process_opcode_bundle(NgaState *vm, CELL opcode) {
|
|
if (INST(0)) instructions[opcode & 0xFF](vm);
|
|
if (INST(8)) instructions[(opcode >> 8) & 0xFF](vm);
|
|
if (INST(16)) instructions[(opcode >> 16) & 0xFF](vm);
|
|
if (INST(24)) instructions[(opcode >> 24) & 0xFF](vm);
|
|
}
|
|
|
|
#ifdef NEEDS_STRL
|
|
/*---------------------------------------------------------------------
|
|
Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com>
|
|
|
|
Permission to use, copy, modify, and distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
--------------------------------------------------------------------*/
|
|
|
|
size_t strlcat(char *dst, const char *src, size_t dsize) {
|
|
const char *odst = dst;
|
|
const char *osrc = src;
|
|
size_t n = dsize;
|
|
size_t dlen;
|
|
|
|
/* Find the end of dst and adjust bytes left but don't go past end. */
|
|
while (n-- != 0 && *dst != '\0')
|
|
dst++;
|
|
dlen = dst - odst;
|
|
n = dsize - dlen;
|
|
|
|
if (n-- == 0)
|
|
return(dlen + strlen(src));
|
|
while (*src != '\0') {
|
|
if (n != 0) {
|
|
*dst++ = *src;
|
|
n--;
|
|
}
|
|
src++;
|
|
}
|
|
*dst = '\0';
|
|
return(dlen + (src - osrc)); /* count does not include NUL */
|
|
}
|
|
|
|
size_t strlcpy(char *dst, const char *src, size_t dsize) {
|
|
const char *osrc = src;
|
|
size_t nleft = dsize;
|
|
|
|
/* Copy as many bytes as will fit. */
|
|
if (nleft != 0) {
|
|
while (--nleft != 0) {
|
|
if ((*dst++ = *src++) == '\0')
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Not enough room in dst, add NUL and traverse rest of src. */
|
|
if (nleft == 0) {
|
|
if (dsize != 0)
|
|
*dst = '\0'; /* NUL-terminate dst */
|
|
while (*src++)
|
|
;
|
|
}
|
|
return(src - osrc - 1); /* count does not include NUL */
|
|
}
|
|
#endif
|