some skeleton for further codebase
This commit is contained in:
@@ -127,9 +127,10 @@ Please also note that Equi doesn't specify any graphical or sound output capabil
|
|||||||
|
|
||||||
## Reference implementation
|
## 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 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.
|
- 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.
|
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.
|
||||||
|
|||||||
@@ -27,21 +27,22 @@
|
|||||||
#include <conio.h>
|
#include <conio.h>
|
||||||
#else
|
#else
|
||||||
|
|
||||||
#define cgetc() (fgetc(stdin))
|
#define cgetc() (getchar())
|
||||||
#define cputc(c) (fputc(c, stdout))
|
#define cputc(c) (putchar(c))
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Definitions section */
|
/* Definitions section */
|
||||||
|
|
||||||
#define EQUI_VER "0.0.1"
|
#define EQUI_VER "0.0.1"
|
||||||
|
|
||||||
|
#define cerr(s) (fputs(s, stderr))
|
||||||
#define ushort unsigned short /* basic 16-bit integer */
|
#define ushort unsigned short /* basic 16-bit integer */
|
||||||
#define uchar unsigned char /* basic 8-bit integer */
|
#define uchar unsigned char /* basic 8-bit integer */
|
||||||
#define WS sizeof(ushort) /* Equi word size in bytes */
|
#define WS sizeof(ushort) /* Equi word size in bytes */
|
||||||
#define CLT_ENTRY_LEN 6u /* Amount of significant compiled word characters */
|
#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 CLT_ENTRY_SIZE (CLT_ENTRY_LEN + WS) /* Full size in bytes taken by one CLT entry */
|
||||||
#define BS 8u /* Backspace character code */
|
#define BS 8u /* Backspace character code */
|
||||||
|
#define CR 13u /* Character return code */
|
||||||
|
|
||||||
/* Configuration section (constants overridable at compile-time) */
|
/* 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 */
|
/* Also create an alternative view of the same RAM area for direct offset-based access */
|
||||||
static uchar* flatram = (uchar *)&ram;
|
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[]) {
|
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 */
|
/* initialize the RAM in the most standard way */
|
||||||
ram.gpd_start = (uchar *)&ram.gpd - (uchar *)&ram.main_stack;
|
ram.gpd_start = (uchar *)&ram.gpd - (uchar *)&ram.main_stack;
|
||||||
ram.cmd_start = (uchar *)&ram.cmdbuf - (uchar *)&ram.main_stack;
|
ram.cmd_start = (uchar *)&ram.cmdbuf - (uchar *)&ram.main_stack;
|
||||||
ram.cmd_size = CMD_BUF_SIZE;
|
ram.cmd_size = CMD_BUF_SIZE;
|
||||||
ram.pc = ram.cmd_start; /* Start execution from the start of command buffer */
|
ram.II = ram.CM = ram.IM = 0; /* reset all flags */
|
||||||
ram.ibp = ram.cmd_start; /* Start input buffering from the start of command buffer */
|
/* 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",
|
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,
|
(uchar *)&ram.clt - (uchar *)&ram.main_stack,
|
||||||
@@ -126,8 +323,42 @@ int main(int argc, char* argv[]) {
|
|||||||
ram.cmd_start, ram.cmd_size);
|
ram.cmd_start, ram.cmd_size);
|
||||||
|
|
||||||
cputc('>');
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user