/* * Equi platform reference implementation * * Created in 2022 by Luxferre, released into public domain * * See equi.md file for the specification and manual * * @license Unlicense * @author Luxferre */ /* Standard includes with size optimizations for TCC */ #ifdef __TINYC__ #include #define stderr 2 #else #include #include #endif /* * Non-standard includes * To save space, we emulate necessary conio functions on non-6502 systems, not vice versa */ #ifdef __CC65__ #include #else #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 BS 8u /* Backspace character code */ #define CR 13u /* Character return code */ #define LF 10u /* Line feed code */ /* Configuration section (constants overridable at compile-time) */ /* Main and return stack size in bytes */ #ifndef STACK_SIZE #define STACK_SIZE 256u #endif /* Literal stack size in bytes */ #ifndef LIT_STACK_SIZE #define LIT_STACK_SIZE 32u #endif /* Command buffer size in bytes */ #ifndef GPD_AREA_SIZE #define GPD_AREA_SIZE 6400u #endif /* Command buffer size in bytes */ #ifndef CMD_BUF_SIZE #define CMD_BUF_SIZE 15600u #endif /* Maximum amount of CLT entries */ #ifndef CLT_ENTRIES_MAX #define CLT_ENTRIES_MAX 512u #endif /* Some necessary constants and offsets derived from the above values */ #define STACK_SIZE_WORDS (STACK_SIZE / WS) /* Main and return stack size in words */ /* * Structures that describe Equi machine RAM using the above configuration */ struct CLTEntry { ushort nhash; /* compiled word name hash */ ushort loc; /* compiled word location */ }; struct EquiRAM { ushort main_stack[STACK_SIZE_WORDS]; ushort return_stack[STACK_SIZE_WORDS]; uchar literal_stack[LIT_STACK_SIZE]; ushort pc; /* program counter */ ushort msp; /* main stack pointer */ ushort rsp; /* return stack pointer */ uchar lsp; /* literal stack pointer */ ushort cbp; /* compilation buffer pointer */ ushort cltp; /* compilation lookup table pointer */ unsigned int II:1; /* instruction ignore mode */ unsigned int CM:1; /* compilation mode */ unsigned int IM:1; /* interpretation mode */ ushort gpd_start; ushort cmd_start; ushort cmd_size; ushort ibp; /* input buffer pointer */ struct CLTEntry clt[CLT_ENTRIES_MAX]; /* compilation lookup table */ uchar gpd[GPD_AREA_SIZE]; uchar cmdbuf[CMD_BUF_SIZE]; }; /* Before running the main code, instantiate the machine RAM */ 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, INVALID_WORD, PORT_IO_ERROR }; /* Error reporting method */ void trapout(errcode) { if(errcode > 0) { fprintf(stderr, "\nError %d at 0x%x (instruction %c): ", errcode, ram.pc, ram.cmdbuf[ram.pc]); switch(errcode) { case STACK_OVERFLOW: cerr("Stack overflow\n"); break; case STACK_UNDERFLOW: cerr("Stack underflow\n"); break; case DIV_BY_ZERO: cerr("Division by zero\n"); break; case CLT_OVERFLOW: cerr("Compilation lookup table full\n"); break; case CMD_OVERFLOW: cerr("Command buffer full\n"); break; case INVALID_INSTRUCTION: cerr("Invalid instruction\n"); break; case INVALID_WORD: cerr("Word not found in CLT\n"); break; case PORT_IO_ERROR: cerr("Port I/O error\n"); 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_GPDSTART='G', /* locate GPD area start */ INS_GT='>', INS_LT='<', INS_EQ='=', INS_ADD='+', INS_SUB='-', INS_MUL='*', INS_DIV='/', INS_NEG='N', INS_SHIFT='T', INS_NOT='~', INS_AND='&', INS_OR='|', INS_XOR='^', INS_COUT='.', /* character output */ INS_NHOUT='H', /* numeric hex output */ 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); return 0; } else return ram.main_stack[--ram.msp]; } /* pop a value from the return stack */ ushort popRet() { if(ram.rsp == 0) { trapout(STACK_UNDERFLOW); return 0; } else return ram.return_stack[--ram.rsp]; } /* push a character to the literal stack */ void pushLit(uchar c) { ram.literal_stack[ram.lsp++] = c; if(ram.lsp >= LIT_STACK_SIZE) trapout(STACK_OVERFLOW); } /* pop a character from the literal stack */ uchar popLit() { if(ram.lsp == 0) { trapout(STACK_UNDERFLOW); return 0; } else return ram.literal_stack[--ram.lsp]; } /* convert ASCII code of a hex digit to the digit value */ uchar a2d(uchar a) { return (a < 0x3aU) ? (a - 0x30U) : (a - 55U); } /* shape 2-byte vlaue on the main stack from up to 4 values of the literal stack */ void pushLitVal() { uchar p[4U] = {0,0,0,0}, i, thr = 4U; if(ram.lsp < 4U) thr = ram.lsp; for(i=0;i>8U ^ *data_p++; x ^= x>>4U; crc = (crc << 8U) ^ ((ushort)(x << 12U)) ^ ((ushort)(x << 5U)) ^ ((ushort)x); } return crc; } /* persistent operation handler */ ushort persistOp(maddr, dataLen, adl, adh, isWrite) { return 0; } /* Main interpreter loop */ void equi_main_loop() { uchar instr, i; ushort lhash, pbuf, pbuf2; /* reset all stacks before running and reinit CLT */ ram.msp = ram.rsp = ram.lsp = ram.cltp = 0; /* reset pc */ ram.pc = 65535U; while(1) { /* iterate over the instructions in the command buffer */ instr = ram.cmdbuf[++ram.pc]; /* silently exit on zero or FF */ if(instr == 0 || instr == 0xFFu) break; /* first, check for II mode */ if(ram.II) { if(instr == INS_IIEND) ram.II = 0; /* unset instruction ignore mode flag */ continue; } /* then, check for compilation mode */ if(ram.CM) { if(instr == INS_CMEND) { /* trigger word compilation logic as per the spec */ if(ram.lsp < 1) trapout(STACK_UNDERFLOW); ram.cmdbuf[ram.pc] = INS_RET; /* in-place patch this instruction to R */ /* hash and save compiled word */ ram.clt[ram.cltp].nhash = crc16(&ram.literal_stack[0], ram.lsp); ram.lsp = 0; /* clear the literal stack */ 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); 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(ram.lsp > 0 && instr != INS_LITSTR && instr != INS_LITCALL && instr != INS_LITINT && instr != INS_CMSTART) pushLitVal(); switch(instr) { /* then perform all main interpretation logic */ case INS_IISTART: /* instruction ignore start */ ram.II = 1; /* raise II flag */ break; case INS_CMSTART: /* compilation start */ ram.cbp = ram.pc + 1U; /* save CBP */ ram.CM = 1U; /* raise CM flag */ break; case CR: case LF: case '\t': case ' ': /* all nops in interpretation mode */ break; case INS_QUIT: /* gracefully quit the interpretation mode */ goto brx; case INS_LITINT: /* literal stack -> main stack as short */ pushLitVal(); break; case INS_LITSTR: /* literal stack -> each char at main stack as short */ while(ram.lsp) pushMain((ushort)popLit()); ram.lsp = 0; break; case INS_LITCALL: /* call the saved word from the literal */ if(ram.lsp < 1) trapout(STACK_UNDERFLOW); lhash = crc16(&ram.literal_stack[0], ram.lsp); for(pbuf=0;pbuf= CLT_ENTRIES_MAX) trapout(INVALID_WORD); ram.lsp = 0; /* clear the literal stack */ break; case INS_RET: /* jump to the instruction at ret stack */ ram.pc = popRet(); break; case INS_M2R: /* main -> ret stack */ pushRet(popMain()); break; case INS_R2M: /* ret -> main stack */ pushMain(popRet()); break; case INS_LOAD: /* mem -> main stack */ pbuf = popMain(); pushMain((flatram[pbuf] << 8)|flatram[pbuf+1U]); break; case INS_STORE: /* main stack -> mem (word) */ pbuf = popMain(); pbuf2 = popMain(); flatram[pbuf] = pbuf2 >> 8U; flatram[pbuf + 1U] = pbuf2 & 255U; break; case INS_STOREBYTE: /* main stack -> mem (byte) */ pbuf = popMain(); flatram[pbuf] = popMain() & 255U; break; case INS_DROP: /* ( a -- ) */ pbuf = popMain(); break; case INS_DUP: /* ( a -- a a ) */ if(ram.msp < 1) trapout(STACK_UNDERFLOW); pushMain(ram.main_stack[ram.msp-1]); break; case INS_SWAP: /* ( a b -- b a ) */ if(ram.msp < 2) trapout(STACK_UNDERFLOW); pbuf = ram.main_stack[ram.msp-2]; ram.main_stack[ram.msp-2] = ram.main_stack[ram.msp-1]; ram.main_stack[ram.msp-1] = pbuf; break; case INS_ROT: /* ( a b c -- b c a ) */ if(ram.msp < 3) trapout(STACK_UNDERFLOW); pbuf = ram.main_stack[ram.msp-3]; pbuf2 = ram.main_stack[ram.msp-1]; ram.main_stack[ram.msp-3] = ram.main_stack[ram.msp-2]; ram.main_stack[ram.msp-2] = pbuf2; ram.main_stack[ram.msp-1] = pbuf; break; case INS_OVER: /* ( a b -- a b a ) */ if(ram.msp < 2) trapout(STACK_UNDERFLOW); pushMain(ram.main_stack[ram.msp-2]); break; case INS_JUMP: /* unconditional signed jump: ( rel -- ) */ ram.pc = (ushort) (ram.pc + (signed short) popMain()); break; case INS_IF: /* conditional signed jump if cond is not zero: ( cond rel -- ) */ pbuf = popMain(); /* reladdr */ pbuf2 = popMain(); /* cond */ if(pbuf2 != 0) ram.pc = (ushort) (ram.pc + (signed short) pbuf); break; case INS_EXPOINT: /* Locate execution point */ pushMain(ram.pc + 1); break; case INS_GPDSTART: /* Locate GPD area start */ pushMain(ram.gpd_start); break; case INS_GT: /* ( a b -- a>b ) */ pushMain((popMain() < popMain()) ? 1u : 0u); break; case INS_LT: /* ( a b -- a popMain()) ? 1u : 0u); break; case INS_EQ: /* ( a b -- a==b ) */ pushMain((popMain() == popMain()) ? 1u : 0u); break; case INS_ADD: /* ( a b -- a+b ) */ pushMain((popMain() + popMain())&65535U); break; case INS_SUB: /* ( a b -- a-b ) */ pushMain((-popMain() + popMain())&65535U); break; case INS_MUL: /* ( a b -- a*b ) */ pushMain((ushort)(popMain() * popMain())&65535U); break; case INS_DIV: /* ( a b -- a/b rem ) */ pbuf2 = popMain(); pbuf = popMain(); pushMain((ushort) pbuf/pbuf2); pushMain((ushort) pbuf%pbuf2); break; case INS_NEG: /* ( a -- -a ) */ pushMain((ushort)-popMain()); break; case INS_SHIFT: /* ( a XY -- [a >> X] << Y ) */ pbuf = popMain(); pushMain((ushort)(popMain() >> (pbuf&15U)) << (pbuf>>4U)); break; case INS_NOT: /* ( a -- ~a ) */ pushMain((ushort)(~popMain()&65535U)); break; case INS_AND: /* ( a b -- a&b ) */ pushMain(popMain() & popMain()); break; case INS_OR: /* ( a b -- a|b ) */ pushMain(popMain() | popMain()); break; case INS_XOR: /* ( a b -- a^b ) */ pushMain(popMain() ^ popMain()); break; case INS_COUT: /* output a character, no Unicode support for now */ cputc((uchar)(popMain()&255U)); break; case INS_NHOUT: /* output the hex number from the stack top */ printf("%04X", popMain()); break; case INS_NBKIN: /* non-blocking key input - in this implementation it's equal to: */ case INS_BKIN: /* blocking key input (again, no Unicode support yet, assuming a single byte incoming to the 16-bit value on the stack) */ pushMain((ushort)cgetc()); break; case INS_PORTIO: /* ( p1 p2 port -- r1 r2 status ) - simulate port I/O according to the spec */ pbuf = popMain(); /* port */ pbuf2 = popMain(); /* p2 */ fprintf(stderr, "[PORTIO ] Call to port 0x%X with P1=%X and P2=%X, returning status=0, R1=0, R2=0\n", pbuf, pbuf2, popMain()); pushMain(0u); pushMain(0u); pushMain(0u); break; case INS_PERSIST_READ: /* ( adh adl len maddr -- status) */ pushMain(persistOp(popMain(), popMain(), popMain(), popMain(), 0)); break; case INS_PERSIST_WRITE: /* ( adh adl len maddr -- status) */ pushMain(persistOp(popMain(), popMain(), popMain(), popMain(), 1)); break; default: /* all characters not processed before are invalid instructions */ trapout(INVALID_INSTRUCTION); goto brx; } if(ram.msp >= STACK_SIZE_WORDS || ram.rsp >= STACK_SIZE_WORDS) /* check for stack overflow after any operation */ trapout(STACK_OVERFLOW); continue; brx: break; } /* unset interpretation mode flag and exit */ ram.IM = 0; ram.pc = ram.ibp = 65535U; /* clear all stacks and CLT */ ram.msp = ram.rsp = ram.lsp = ram.cltp = 0; } /* Equi VM entry point */ int main(int argc, char* argv[]) { /* _attempt_ to disable buffering for char input/output */ #if !defined __CC65__ && !defined __TINYC__ 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.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 = 65535U; printf("Welcome to Equi v" EQUI_VER " by Luxferre, 2022\n\nCLT: 0x%04X (%d bytes)\nGPD: 0x%04X (%d bytes)\nCommand buffer: 0x%04X (%d bytes)\nEqui ready\n\n", (unsigned int) ((uchar *)&ram.clt - (uchar *)&ram.main_stack), (unsigned int) ((uchar *)&ram.gpd - (uchar *)&ram.clt), ram.gpd_start, ram.cmd_start - ram.gpd_start, ram.cmd_start, ram.cmd_size); cputc('>'); 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 == 0xFFU || instr == 0U) /* exit */ break; else if(instr == BS && ram.ibp > ram.cmd_start) { /* process the backspace */ #ifdef __CC65__ cputc(instr); /* echo it */ #endif --ram.ibp; } else if(instr == INS_IISTART) { /* process II just to avoid quitting in command mode */ #ifdef __CC65__ cputc(instr); /* echo it */ #endif ram.II = 1; } else if(instr == INS_IIEND) { /* process II just to avoid quitting in command mode */ #ifdef __CC65__ cputc(instr); /* echo it */ #endif ram.II = 0; } else if(instr == CR || instr == LF) { /* process carriage return or linefeed */ cputc(CR); /* echo it */ cputc(LF); /* echo it */ ram.cmdbuf[ram.ibp] = 0; /* replace itself with 0 */ ram.IM = 1; /* set the mandatory interpretation mode flag */ equi_main_loop(); /* and run the interpreter loop */ cputc(CR); /* echo it */ cputc(LF); /* echo it */ cputc('>'); cputc(' '); } #ifdef __CC65__ else cputc(instr); /* echo it */ #endif } /* command mode loop end */ return 0; }