commit 27d12a1131b1d23d7e16dd48d24bea4c73632669
parent 11a73d1c6e2fbe014f2d2e3c3318753fd5ffefe3
Author: Luxferre <lux@ferre>
Date: Sun, 7 Aug 2022 22:20:48 +0300
some skeleton for further codebase
Diffstat:
M | README.md | | | 3 | ++- |
M | equi.c | | | 245 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
2 files changed, 240 insertions(+), 8 deletions(-)
diff --git a/README.md b/README.md
@@ -127,9 +127,10 @@ Please also note that Equi doesn't specify any graphical or sound output capabil
## Reference implementation
-Being a purely PC-oriented low-level runtime/programming environment, Equi has the reference implementation emulator/VM written in C (ANSI C89 standard), `equi.c`, compilable and runnable on all the systems supporting standard I/O. Note that this emulator:
+Being a purely PC-oriented low-level runtime/programming environment, Equi has the reference implementation emulator/VM written in C (ANSI C89 standard), `equi.c`, compilable and runnable on all the systems supporting standard I/O. Note that, for portability reasons, this emulator:
- doesn't fully implement `P` instruction but instead outputs its parameters to the standard error stream and puts three 0x0000 values back onto the stack,
+- doesn't implement non-blocking key input (the ',' instruction is identical to blocking key input instruction `?`),
- sandboxes the `{` and `}` operations using the file you supply in the command-line parameter. If you don't supply the file, these operations will effectively do nothing except putting 0x0000 (success status) onto the stack.
The source code file should compile using any mainstream C compiler with C89 support, like GCC/DJGPP, Clang, TCC etc. However, it is also being developed to be compilable with CC65 compiler for targets like Apple II or Atari 800. All the machine/target specific configuration is done at compile time, using compiler command-line switches. Here are the instructions to build Equi using different known C compilers.
diff --git a/equi.c b/equi.c
@@ -27,21 +27,22 @@
#include <conio.h>
#else
-#define cgetc() (fgetc(stdin))
-#define cputc(c) (fputc(c, stdout))
-
+#define cgetc() (getchar())
+#define cputc(c) (putchar(c))
#endif
/* Definitions section */
#define EQUI_VER "0.0.1"
+#define cerr(s) (fputs(s, stderr))
#define ushort unsigned short /* basic 16-bit integer */
#define uchar unsigned char /* basic 8-bit integer */
#define WS sizeof(ushort) /* Equi word size in bytes */
#define CLT_ENTRY_LEN 6u /* Amount of significant compiled word characters */
#define CLT_ENTRY_SIZE (CLT_ENTRY_LEN + WS) /* Full size in bytes taken by one CLT entry */
#define BS 8u /* Backspace character code */
+#define CR 13u /* Character return code */
/* Configuration section (constants overridable at compile-time) */
@@ -110,13 +111,209 @@ static struct EquiRAM ram;
/* Also create an alternative view of the same RAM area for direct offset-based access */
static uchar* flatram = (uchar *)&ram;
+/* Error reporting codes */
+enum EquiErrors {
+ SUCCESS=0,
+ STACK_OVERFLOW,
+ STACK_UNDERFLOW,
+ DIV_BY_ZERO,
+ CLT_OVERFLOW,
+ CMD_OVERFLOW,
+ INVALID_INSTRUCTION,
+ PORT_IO_ERROR
+};
+
+/* Error reporting method */
+void trapout(errcode) {
+ if(errcode > 0) {
+ fprintf(stderr, "Error %d at 0x%x (instruction %c): ", errcode, ram.pc, ram.cmdbuf[ram.pc]);
+ switch(errcode) {
+ case STACK_OVERFLOW:
+ cerr("Stack overflow");
+ break;
+ case STACK_UNDERFLOW:
+ cerr("Stack underflow");
+ break;
+ case DIV_BY_ZERO:
+ cerr("Division by zero");
+ break;
+ case CLT_OVERFLOW:
+ cerr("Compilation lookup table full");
+ break;
+ case CMD_OVERFLOW:
+ cerr("Command buffer full");
+ break;
+ case INVALID_INSTRUCTION:
+ cerr("Invalid instruction");
+ break;
+ case PORT_IO_ERROR:
+ cerr("Port I/O error");
+ break;
+ }
+ exit(errcode);
+ }
+}
+
+/* Equi instructions enum */
+
+enum EquiInstructions {
+ INS_IISTART='(',
+ INS_IIEND=')',
+ INS_CMSTART=':',
+ INS_CMEND=';',
+ INS_LITINT='#',
+ INS_LITSTR='"',
+ INS_LITCALL='\'',
+ INS_RET='R',
+ INS_M2R=']', /* main -> return */
+ INS_R2M='[', /* return -> main */
+ INS_LOAD='L',
+ INS_STORE='S',
+ INS_STOREBYTE='W',
+ INS_DROP='!',
+ INS_DUP='$',
+ INS_SWAP='%',
+ INS_ROT='@',
+ INS_OVER='\\',
+ INS_JUMP='J',
+ INS_IF='I',
+ INS_EXPOINT='X', /* locate execution point */
+ INS_GT='>',
+ INS_LT='<',
+ INS_EQ='=',
+ INS_ADD='+',
+ INS_SUB='-',
+ INS_MUL='*',
+ INS_DIV='/',
+ INS_NEG='N',
+ INS_NOT='~',
+ INS_AND='&',
+ INS_OR='|',
+ INS_XOR='^',
+ INS_COUT='.',
+ INS_NBKIN=',', /* non-blocking key input */
+ INS_BKIN='?', /* blocking key input */
+ INS_PORTIO='P',
+ INS_PERSIST_WRITE='}',
+ INS_PERSIST_READ='{',
+ INS_QUIT='Q'
+};
+
+/* push a value onto the main stack */
+void pushMain(ushort val) {
+ ram.main_stack[ram.msp++] = val;
+ if(ram.msp >= STACK_SIZE_WORDS)
+ trapout(STACK_OVERFLOW);
+}
+
+/* push a value onto the return stack */
+void pushRet(ushort val) {
+ ram.return_stack[ram.rsp++] = val;
+ if(ram.rsp >= STACK_SIZE_WORDS)
+ trapout(STACK_OVERFLOW);
+}
+
+/* pop a value from the main stack */
+ushort popMain() {
+ if(ram.msp == 0)
+ trapout(STACK_UNDERFLOW);
+ else
+ return ram.main_stack[--ram.msp];
+}
+
+/* pop a value from the return stack */
+ushort popRet() {
+ if(ram.rsp == 0)
+ trapout(STACK_UNDERFLOW);
+ else
+ return ram.return_stack[--ram.rsp];
+}
+
+/* push a character to the literal stack, auto-discarding overflown items */
+void pushLit(uchar c) {
+ ram.literal_stack[ram.lsp++] = c;
+ if(ram.lsp >= LIT_STACK_SIZE)
+ ram.lsp = 0;
+}
+
+/* pop a character from the literal stack */
+uchar popLit() {
+ if(ram.lsp == 0)
+ trapout(STACK_UNDERFLOW);
+ else
+ return ram.literal_stack[--ram.lsp];
+}
+
+/* check literal stack and push 4 top values as hex short literal if not empty and discard the rest of the stack */
+void attemptPushNumericLiteral() {
+
+}
+
+/* Main interpreter loop */
+
+void equi_main_loop() {
+ uchar instr;
+ while(1) { /* iterate over the instructions in the command buffer */
+ instr = ram.cmdbuf[++ram.pc];
+ /* first, check for II mode */
+ if(ram.II) {
+ if(instr == ')')
+ ram.II = 0; /* unset instruction ignore mode flag */
+ continue;
+ }
+ /* then, check for compilation mode */
+ if(ram.CM) {
+ if(instr == ';') { /* trigger word compilation logic as per the spec */
+ ram.cmdbuf[ram.pc] = 'R'; /* in-place patch this instruction to R */
+
+ ram.clt[ram.cltp].loc = ram.cbp; /* stored the compiled code location from the most recent CBP value */
+ ++ram.cltp; /* increase the word */
+ if(ram.cltp == CLT_ENTRIES_MAX) {
+ trapout(CLT_OVERFLOW);
+ break;
+ }
+
+ ram.CM = 0; /* unset compilation mode flag */
+ }
+ continue;
+ }
+ /* other than that, continue with interpretation mode */
+ /* first, detect lowercase, underscore, digit and A to F */
+ if((instr >= '0' && instr <= '9') || (instr >= 'A' && instr <= 'F') || instr == '_' || (instr >= 'a' && instr <= 'z')) {
+ pushLit(instr);
+ continue;
+ }
+ /* then trigger literal auto-push if applicable */
+ if(instr != '"' && instr != '\'' && instr != '#' && instr != ':')
+ attemptPushNumericLiteral();
+ switch(instr) {
+
+ default:
+ goto brx;
+ }
+ continue;
+ brx: break;
+ }
+ /* unset interpretation mode flag and exit */
+ ram.IM = 0;
+}
+
+/* Equi VM entry point */
+
int main(int argc, char* argv[]) {
+ /* _attempt_ to disable buffering for char input/output */
+#ifndef __CC65__
+ setvbuf(stdin, NULL, _IONBF, 0);
+ setvbuf(stdout, NULL, _IONBF, 0);
+#endif
+ uchar instr;
/* initialize the RAM in the most standard way */
ram.gpd_start = (uchar *)&ram.gpd - (uchar *)&ram.main_stack;
ram.cmd_start = (uchar *)&ram.cmdbuf - (uchar *)&ram.main_stack;
ram.cmd_size = CMD_BUF_SIZE;
- ram.pc = ram.cmd_start; /* Start execution from the start of command buffer */
- ram.ibp = ram.cmd_start; /* Start input buffering from the start of command buffer */
+ ram.II = ram.CM = ram.IM = 0; /* reset all flags */
+ /* Start both execution and input buffering from the start of command buffer (-1 because we use prefix increment) */
+ ram.pc = ram.ibp = ram.cmd_start-1;
printf("Welcome to Equi v" EQUI_VER " by Luxferre, 2022\n\nCLT: 0x%X (%d bytes)\nGPD: 0x%X (%d bytes)\nCommand buffer: 0x%X (%d bytes)\nEqui ready\n\n",
(uchar *)&ram.clt - (uchar *)&ram.main_stack,
@@ -126,8 +323,42 @@ int main(int argc, char* argv[]) {
ram.cmd_start, ram.cmd_size);
cputc('>');
- cgetc();
-
+ cputc(' ');
+ while(1) { /* Now, we're in the command mode loop */
+ instr = ram.cmdbuf[++ram.ibp] = cgetc(); /* Fetch the next instruction from the keyboard */
+ if(instr == BS && ram.ibp > ram.cmd_start) { /* process the backspace */
+#ifdef __CC65__
+ cputc(instr); /* echo it */
+#endif
+ --ram.ibp;
+ } else if(instr == '(') { /* process II just to avoid quitting in command mode */
+#ifdef __CC65__
+ cputc(instr); /* echo it */
+#endif
+ ram.II = 1;
+ } else if(instr == ')') { /* process II just to avoid quitting in command mode */
+#ifdef __CC65__
+ cputc(instr); /* echo it */
+#endif
+ ram.II = 0;
+ } else if(instr == CR) { /* process carriage return */
+ cputc(CR); /* echo it */
+#ifdef __CC65__
+ cputc(10); /* echo it */
+#endif
+ ram.IM = 1; /* set the mandatory interpretation mode flag */
+ equi_main_loop(); /* and run the interpreter loop */
+ cputc(CR); /* echo it */
+#ifdef __CC65__
+ cputc(10); /* echo it */
+#endif
+ cputc('>');
+ cputc(' ');
+ }
+#ifdef __CC65__
+ else cputc(instr); /* echo it */
+#endif
+ } /* command mode loop end */
return 0;
}