equi

A self-descriptive stack-based PC platform
git clone git://git.luxferre.top/equi.git
Log | Files | Refs | README | LICENSE

equi.c (28406B)


      1 /*
      2  * Equi platform reference implementation
      3  *
      4  * Created in 2022 by Luxferre, released into public domain
      5  *
      6  * See README.md file for the specification and manual
      7  *
      8  * @license Unlicense <https://unlicense.org>
      9  * @author Luxferre
     10  */
     11 
     12 /* Standard or non-standard includes depending on the target */
     13 
     14 #include <stdlib.h>
     15 #include <stdio.h>
     16 #include <time.h>
     17 #ifdef __CC65__
     18 #include <conio.h>
     19 #define CRLF "\n"
     20 #else
     21 
     22 #include <termios.h>
     23 #include <unistd.h>
     24 #include <sys/select.h>
     25 
     26 #define cgetc() (getchar())
     27 #define cputc(c) (putchar(c))
     28 #pragma pack(2)
     29 #define CRLF "\r\n"
     30 
     31 /* also, attempt to emulate two other conio methods */
     32 
     33 int kbhit() {
     34   struct timeval tv = { 0L, 0L };
     35   fd_set fds;
     36   FD_ZERO(&fds);
     37   FD_SET(0, &fds);
     38   return select(1, &fds, NULL, NULL, &tv) > 0;
     39 }
     40 
     41 int getch() {
     42   int r;
     43   unsigned char c;
     44   if((r = read(0, &c, 1)) < 0) return r;
     45   else return c;
     46 }
     47 
     48 #endif
     49 
     50 /* Definitions section */
     51 
     52 #define EQUI_VER "0.0.1"
     53 
     54 #define cerr(s) (fputs(s, stderr))
     55 #define ushort unsigned short /* basic 16-bit integer */
     56 #define uchar unsigned char /* basic 8-bit integer */
     57 #define WS sizeof(ushort) /* Equi word size in bytes */
     58 #define BS 8u /* Backspace character code */
     59 #define DEL 0x7fu /* Delete key code */
     60 #define CR 13u /* Character return code */
     61 #define LF 10u /* Line feed code */
     62 
     63 /* Configuration section (constants overridable at compile-time) */
     64 
     65 /* Main and return stack size in bytes */
     66 #ifndef STACK_SIZE
     67 #define STACK_SIZE 256u
     68 #endif
     69 
     70 /* Literal stack size in bytes */
     71 #ifndef LIT_STACK_SIZE
     72 #define LIT_STACK_SIZE 32u
     73 #endif
     74 
     75 /* GPD area size in bytes */
     76 #ifndef GPD_AREA_SIZE
     77 #define GPD_AREA_SIZE 4096u
     78 #endif
     79 
     80 /* Command buffer size in bytes */
     81 #ifndef CMD_BUF_SIZE
     82 #define CMD_BUF_SIZE 13600u
     83 #endif
     84 
     85 /* Maximum amount of CLT entries */
     86 #ifndef CLT_ENTRIES_MAX
     87 #define CLT_ENTRIES_MAX 512u
     88 #endif
     89 
     90 /* Maximum amount of task table entries */
     91 #ifndef EQUI_TASKS_MAX
     92 #define EQUI_TASKS_MAX 1
     93 #endif
     94 
     95 /* Persistent storage sandbox file name */
     96 #ifndef PERSIST_FILE
     97 #define PERSIST_FILE "PERS.DAT"
     98 #endif
     99 
    100 /* Some necessary constants and offsets derived from the above values */
    101 #define STACK_SIZE_WORDS (STACK_SIZE / WS) /* Main and return stack size in words */
    102 
    103 /* Structures that describe Equi machine RAM using the above configuration */
    104 
    105 struct CLTEntry { /* one entry in the compilation lookup table */
    106   ushort nhash; /* compiled word name hash */
    107   ushort loc;   /* compiled word location */
    108 };
    109 
    110 struct EquiCtx { /* one Equi program context */
    111   ushort id; /* task ID */
    112   uchar active; /* 0 - inactive/quit, 1 - active */
    113   uchar privileged; /* whether or not the task is allowed to write to the entire command buffer */
    114   uchar CM; /* compilation mode flag */
    115   uchar lsp; /* literal stack pointer */
    116   ushort msp; /* main stack pointer */
    117   ushort rsp; /* return stack pointer */
    118   ushort cltp; /* compilation lookup table pointer */
    119   ushort cbp; /* compilation buffer pointer */
    120   ushort gpd_start; /* GPD area start for this task */
    121   ushort cmd_start; /* command buffer start for this task */
    122   ushort cmd_size; /* size of loaded code in bytes for this task */
    123   ushort pc; /* program counter */
    124   ushort main_stack[STACK_SIZE_WORDS];
    125   ushort return_stack[STACK_SIZE_WORDS];
    126   uchar literal_stack[LIT_STACK_SIZE];
    127   struct CLTEntry clt[CLT_ENTRIES_MAX]; /* compilation lookup table */
    128   uchar gpd[GPD_AREA_SIZE];
    129 };
    130 
    131 struct EquiRAM {
    132   ushort stack_size; /* main/return stack size in words */
    133   uchar literal_stack_size; /* literal stack size in bytes */
    134   ushort cmd_start; /* (global) command buffer start */
    135   ushort cmd_size; /* (global) command buffer size in bytes */
    136   ushort ibp; /* input buffer pointer */
    137   uchar II; /* instruction ignore mode flag */
    138   uchar MM; /* minification bypass mode flag */
    139   ushort taskid; /* currently running task ID */
    140   struct EquiCtx tasks[EQUI_TASKS_MAX];
    141   uchar cmdbuf[CMD_BUF_SIZE];
    142 };
    143 
    144 /* Before running the main code, instantiate the machine RAM */
    145 static struct EquiRAM ram;
    146 
    147 /* Also create an alternative view of the same RAM area for direct offset-based access */
    148 static uchar* flatram = (uchar *)&ram;
    149 
    150 /* reference to the current task context */
    151 static struct EquiCtx *curtask;
    152 
    153 /* reference to the buffer task context */
    154 static struct EquiCtx *taskptr;
    155 
    156 /* Error reporting codes */
    157 enum EquiErrors {
    158   SUCCESS=0,
    159   STACK_OVERFLOW,
    160   STACK_UNDERFLOW,
    161   DIV_BY_ZERO,
    162   CLT_OVERFLOW,
    163   CMD_OVERFLOW,
    164   INVALID_INSTRUCTION,
    165   INVALID_WORD,
    166   PORT_IO_ERROR,
    167   PERSIST_IO_ERROR,
    168   RESTRICTED_WRITE_ERROR,
    169   OUT_OF_BOUNDS_JUMP,
    170   TASK_SLOTS_FULL
    171 };
    172 
    173 /* Error reporting method */
    174 void trapout(errcode) {
    175   if(errcode > 0) {
    176     if(curtask)
    177       fprintf(stderr, CRLF "Error %d at 0x%x (task 0x%x, instruction %c): ", errcode, curtask->pc, curtask->id, flatram[curtask->pc]);
    178     else
    179       fprintf(stderr, CRLF "System error %d: ", errcode);
    180     switch(errcode) {
    181       case STACK_OVERFLOW:
    182         cerr("Stack overflow" CRLF);
    183         break;
    184       case STACK_UNDERFLOW:
    185         cerr("Stack underflow" CRLF);
    186         break;
    187       case DIV_BY_ZERO:
    188         cerr("Division by zero" CRLF);
    189         break;
    190       case CLT_OVERFLOW:
    191         cerr("Compilation lookup table full" CRLF);
    192         break;
    193       case CMD_OVERFLOW:
    194         cerr("Command buffer full" CRLF);
    195         break;
    196       case INVALID_INSTRUCTION:
    197         cerr("Invalid instruction" CRLF);
    198         break;
    199       case INVALID_WORD:
    200         cerr("Word not found in CLT" CRLF);
    201         break;
    202       case PORT_IO_ERROR:
    203         cerr("Port I/O error" CRLF);
    204         break;
    205       case PERSIST_IO_ERROR:
    206         cerr("Persistent storage I/O error" CRLF);
    207         break;
    208       case RESTRICTED_WRITE_ERROR:
    209         cerr("Attempt to write to a restricted RAM area" CRLF);
    210         break;
    211       case OUT_OF_BOUNDS_JUMP:
    212         cerr("Attempt to jump outside the task context" CRLF);
    213         break;
    214       case TASK_SLOTS_FULL:
    215         cerr("All task slots busy and active" CRLF);
    216         break;
    217     }
    218     if(curtask) /* if we're in a task, only terminate it */
    219       curtask->active=1;
    220     else /* otherwise to a hard trapout */
    221       exit(errcode);
    222   }
    223 }
    224 
    225 /* Equi instructions enum */
    226 enum EquiInstructions {
    227   INS_IISTART='(',
    228   INS_IIEND=')',
    229   INS_CMSTART=':',
    230   INS_CMEND=';',
    231   INS_LITINT='#',
    232   INS_LITSTR='"',
    233   INS_LITCALL='\'',
    234   INS_RET='R',
    235   INS_M2R=']', /* main -> return */
    236   INS_R2M='[', /* return -> main */
    237   INS_LOAD='L',
    238   INS_STORE='S',
    239   INS_STOREBYTE='W',
    240   INS_DROP='!',
    241   INS_DUP='$',
    242   INS_SWAP='%',
    243   INS_ROT='@',
    244   INS_OVER='\\',
    245   INS_JUMP='J',
    246   INS_IF='I',
    247   INS_EXPOINT='X', /* locate execution point */
    248   INS_GPDSTART='G', /* locate GPD area start */
    249   INS_GT='>',
    250   INS_LT='<',
    251   INS_EQ='=',
    252   INS_ADD='+',
    253   INS_SUB='-',
    254   INS_MUL='*',
    255   INS_DIV='/',
    256   INS_NEG='N',
    257   INS_SHIFT='T',
    258   INS_NOT='~',
    259   INS_AND='&',
    260   INS_OR='|',
    261   INS_XOR='^',
    262   INS_COUT='.', /* character output */
    263   INS_NHOUT='H', /* numeric hex output */
    264   INS_NBKIN=',', /* non-blocking key input */
    265   INS_BKIN='?', /* blocking key input */
    266   INS_PORTIO='P',
    267   INS_PERSIST_WRITE='}',
    268   INS_PERSIST_READ='{',
    269   INS_TASKLOAD='Y', /* run and activate a new task */
    270   INS_QUIT='Q'
    271 };
    272 
    273 /* Equi known ports enum */
    274 enum EquiPorts {
    275   PORT_ECHO = 0x0, /* echo port, used for testing: R1=P1, R2=P2 */
    276   PORT_RANDOM, /* RNG port: P1 = valueFrom, P2 = valueTo => R1=rand(from, to), R2 = rand(from, to) */ 
    277   PORT_CHECKSUM, /* checksum port: P1 = memAddr, P2 = length => R1=CRC16(memAddr, length), R2 = 0 */
    278   PORT_TASKCTL /* task control port: P1 = task id, P2 = operation => R1 = operation result, R2 = operation status */
    279 };
    280 
    281 /* push a value onto the main stack */
    282 void pushMain(ushort val) {
    283   curtask->main_stack[curtask->msp++] = val;
    284   if(curtask->msp >= STACK_SIZE_WORDS)
    285     trapout(STACK_OVERFLOW);
    286 }
    287 
    288 /* push a value onto the return stack */
    289 void pushRet(ushort val) {
    290   curtask->return_stack[curtask->rsp++] = val;
    291   if(curtask->rsp >= STACK_SIZE_WORDS)
    292     trapout(STACK_OVERFLOW);
    293 }
    294 
    295 /* pop a value from the main stack */
    296 ushort popMain() {
    297   if(curtask->msp == 0) {
    298     trapout(STACK_UNDERFLOW);
    299     return 0;
    300   }
    301   else
    302     return curtask->main_stack[--curtask->msp];
    303 }
    304 
    305 /* pop a value from the return stack */
    306 ushort popRet() {
    307   if(curtask->rsp == 0) {
    308     trapout(STACK_UNDERFLOW);
    309     return 0;
    310   }
    311   else
    312     return curtask->return_stack[--curtask->rsp];
    313 }
    314 
    315 /* push a character to the literal stack */
    316 void pushLit(uchar c) {
    317   curtask->literal_stack[curtask->lsp++] = c;
    318   if(curtask->lsp >= LIT_STACK_SIZE)
    319     trapout(STACK_OVERFLOW);
    320 }
    321 
    322 /* pop a character from the literal stack */
    323 uchar popLit() {
    324   if(curtask->lsp == 0) {
    325     trapout(STACK_UNDERFLOW);
    326     return 0;
    327   }
    328   else
    329     return curtask->literal_stack[--curtask->lsp];
    330 }
    331 
    332 /* convert ASCII code of a hex digit to the digit value */
    333 uchar a2d(uchar a) {
    334   return (a < 0x3aU) ? (a - 0x30U) : (a - 55U);
    335 }
    336 
    337 /* shape 2-byte value on the main stack from  up to 4 values of the literal stack */
    338 void pushLitVal() {
    339   uchar p[4U] = {0,0,0,0}, i, thr = 4U;
    340   if(curtask->lsp < 4U) thr = curtask->lsp;
    341   for(i=0;i<thr;++i)
    342     p[3-i] = a2d(popLit());
    343   pushMain((p[0]<<12U) | (p[1]<<8U) | (p[2]<<4U) | p[3]);
    344   curtask->lsp = 0; /* clear the literal stack */
    345 }
    346 
    347 /* CCITT CRC16 helper */
    348 ushort crc16(const uchar* data_p, uchar length) {
    349   uchar x;
    350   ushort crc = 0xFFFFu;
    351   while(length--) {
    352     x = crc>>8U ^ *data_p++;
    353     x ^= x>>4U;
    354     crc = (crc << 8U) ^ ((ushort)(x << 12U)) ^ ((ushort)(x << 5U)) ^ ((ushort)x);
    355   }
    356   return crc;
    357 }
    358 
    359 /* Short endianness conversion helper */
    360 ushort tobig(ushort val) {
    361   return (val>>8)|((val&255)<<8);
    362 }
    363 
    364 /* Task-based memory address jump checker */
    365 uchar taskMemJumpAllowed(addr) {
    366   /* a privileged task is allowed to jump anywhere in the command buffer, others only in their own zone */
    367   if(curtask->privileged)
    368     return addr >= ram.cmd_start;
    369   else
    370     return (addr >= curtask->cmd_start) && (addr < curtask->cmd_start + curtask->cmd_size);
    371 }
    372 
    373 /* Task-based memory address write checker */
    374 uchar taskMemWriteAllowed(addr) {
    375   /* a privileged task is allowed to write anywhere in the command buffer, others only in their own zone */
    376   return (addr >= curtask->gpd_start && addr < (curtask->gpd_start + GPD_AREA_SIZE)) || taskMemJumpAllowed(addr);
    377 }
    378 
    379 /* Persistent operation handler */
    380 ushort persistOp(FILE *pfd, ushort maddr, ushort dataLen, ushort blk, uchar isWrite) {
    381   ushort status = 0, proc;
    382   if(pfd) {
    383     fseek(pfd, ((unsigned long) blk << 10), SEEK_SET); /* blocks are 1K-aligned */
    384     if(isWrite) /* writing to the persistent area from the memory */
    385       proc = (ushort) fwrite(&flatram[maddr], 1, dataLen, pfd);
    386     else { /* reading from the persistent area into the memory */
    387       if(!taskMemWriteAllowed(maddr))
    388         trapout(RESTRICTED_WRITE_ERROR);
    389       proc = (ushort) fread(&flatram[maddr], 1, dataLen, pfd);
    390     }
    391     if(proc != dataLen)
    392       status = PERSIST_IO_ERROR;
    393   }
    394   pushMain(status);
    395   return 0;
    396 }
    397 
    398 /* Port I/O handler */
    399 void portIO(ushort port, ushort p2, ushort p1) {
    400   ushort r1 = 0, r2 = 0, status = 0;
    401   switch(port) {
    402     /* Echo port (for testing): r1 = p1, r2 = p2 */
    403     case PORT_ECHO:
    404       r1 = p1;
    405       r2 = p2;
    406       break;
    407     /* Random number generator port: p1 = from, p2 = to, r1 and r2 = rand(from, to) */
    408     case PORT_RANDOM:
    409       r1 = (ushort) (p1 + (rand()%p2));
    410       r2 = (ushort) (p1 + (rand()%p2));
    411       break;
    412     /* Checksum port: p1 = address, p2 = length, r1 = CRC-16-CCITT value */
    413     case PORT_CHECKSUM:
    414       r1 = crc16(&flatram[p1], p2);
    415       break;
    416     /* Task control port: p1 = task ID, p2 = operation code */
    417     /* Operations: 0 - get status, 1 - set active status, 2 - unset active status, 3 - get privilege status */ 
    418     /* Only privileged tasks can change status of other tasks, otherwise fail silently */
    419     case PORT_TASKCTL:
    420       if(p1 >= EQUI_TASKS_MAX) {
    421         trapout(OUT_OF_BOUNDS_JUMP);
    422         return;
    423       }
    424       switch(p2) {
    425         case 0:
    426           r1 = ram.tasks[p1].active;
    427           break;
    428         case 1:
    429           if(curtask->privileged)
    430             ram.tasks[p1].active = 1;
    431           break;
    432         case 2:
    433           if(curtask->privileged)
    434             ram.tasks[p1].active = 0;
    435           break;
    436         case 3:
    437            r1 = ram.tasks[p1].privileged; 
    438            break;
    439       }
    440       break;
    441     default:
    442       fprintf(stderr, "[PORTIO] Unimplemented call to port 0x%X with P1=%X and P2=%X" CRLF, port, p1, p2);
    443   }
    444   pushMain(r1);
    445   pushMain(r2);
    446   pushMain(status);
    447 }
    448 
    449 /* end of internal helper functions, start of main Equi functions */
    450 
    451 /* Find free task slot among remaining inactive tasks */
    452 ushort equi_find_free_task_slot() {
    453   ushort slotid = 0;
    454   /* iterate over task table */
    455   for(;slotid < EQUI_TASKS_MAX;++slotid)
    456     if(!ram.tasks[slotid].active)
    457       return slotid;
    458   /* and error out if all slots are busy and active */
    459   trapout(TASK_SLOTS_FULL);
    460   return TASK_SLOTS_FULL;
    461 }
    462 
    463 /* Find the next active task in the table, round-robin fashion */
    464 struct EquiCtx* equi_find_next_task() {
    465   ushort oldtaskid = ram.taskid; /* save the current one to avoid deadlocks */
    466   while(1) {
    467     ++ram.taskid;
    468     if(ram.taskid == EQUI_TASKS_MAX)
    469       ram.taskid = 0;
    470     if(ram.tasks[ram.taskid].active)
    471       break;
    472     if(ram.taskid == oldtaskid) /* found no active tasks, bailing out */
    473       return NULL;
    474   }
    475   return &ram.tasks[ram.taskid];
    476 }
    477 
    478 /* Task loader method */
    479 struct EquiCtx* equi_load_task(uchar priv, ushort len, ushort progStart) {
    480   ushort tid = equi_find_free_task_slot();
    481   /* don't allow to load privileged tasks from non-privileged ones */
    482   if(curtask && !curtask->privileged)
    483     priv = 0;
    484   taskptr = &ram.tasks[tid]; /* refer to the next available entry */
    485   taskptr->msp = taskptr->rsp = taskptr->lsp = taskptr->cltp = 0; /* init stacks and CLT */
    486   taskptr->pc = progStart - 1U; /* init program counter for preincrement logic */
    487   taskptr->cmd_start = progStart; /* actual command buffer start (from the start of vRAM) */
    488   taskptr->cmd_size = len; /* actual command buffer size */
    489   taskptr->id = tid; /* assign task ID */
    490   taskptr->gpd_start = (ushort) (taskptr->gpd - flatram); /* assign GPD area start */
    491   taskptr->CM = 0; /* unset compilation mode flag */
    492   taskptr->privileged = priv; /* set privileged flag */
    493   return taskptr;
    494 }
    495 
    496 /* Main interpreter loop */
    497 void equi_main_loop() {
    498   uchar instr, bc;
    499   ushort lhash, pbuf, pbuf2;
    500   /* try to open the persistent sandbox file */
    501   FILE *pfd = fopen(PERSIST_FILE, "r+b");
    502   while(1) { /* iterate over the instructions in the command buffer */
    503     curtask = equi_find_next_task(); /* attempt to switch the context on every iteration */
    504     if(curtask == NULL) break; /* exit the main loop when no tasks are left */
    505     instr = flatram[++(curtask->pc)];
    506     /* silently exit on zero or FF */
    507     if(instr == 0 || instr == 0xFFu)
    508       curtask->active = 0;
    509     /* first, check for II mode */
    510     if(ram.II) {
    511       if(instr == INS_IIEND)
    512         ram.II = 0; /* unset instruction ignore mode flag */
    513       continue;
    514     }
    515     /* then, check for compilation mode */
    516     if(curtask->CM) {
    517       if(instr == INS_CMEND) { /* trigger word compilation logic as per the spec */
    518         if(curtask->lsp < 1)
    519           trapout(STACK_UNDERFLOW);
    520         flatram[curtask->pc] = INS_RET; /* in-place patch this instruction to R */
    521         /* hash and save compiled word */
    522         curtask->clt[curtask->cltp].nhash = crc16(&(curtask->literal_stack[0]), curtask->lsp);
    523         curtask->lsp = 0; /* clear the literal stack */
    524         curtask->clt[curtask->cltp].loc = curtask->cbp; /* stored the compiled code location from the most recent CBP value */
    525         ++curtask->cltp; /* increase the word */
    526         if(curtask->cltp == CLT_ENTRIES_MAX)
    527           trapout(CLT_OVERFLOW);
    528         curtask->CM = 0; /* unset compilation mode flag */
    529       }
    530       continue;
    531     }
    532     /* other than that, continue with interpretation mode */
    533     /* first, detect lowercase, underscore, digit and A to F */
    534     if((instr >= '0' && instr <= '9') || (instr >= 'A' && instr <= 'F') || instr == '_' || (instr >= 'a' && instr <= 'z')) {
    535       pushLit(instr);
    536       continue;
    537     }
    538     /* then trigger literal auto-push if applicable */
    539     if(curtask->lsp > 0 && instr != INS_LITSTR && instr != INS_LITCALL && instr != INS_LITINT && instr != INS_CMSTART)
    540       pushLitVal();
    541     switch(instr) { /* then perform all main interpretation logic */
    542       case INS_CMSTART: /* compilation start */
    543         curtask->cbp = curtask->pc + 1U; /* save CBP */
    544         curtask->CM = 1U; /* raise CM flag */
    545         break;
    546       case CR:
    547       case LF:
    548       case '\t':
    549       case ' ': /* all nops in interpretation mode */
    550         break;
    551       case INS_QUIT: /* gracefully quit the task */
    552         curtask->active = 0;
    553         break;
    554       case INS_LITINT: /* literal stack -> main stack as short */
    555         pushLitVal();
    556         break;
    557       case INS_LITSTR: /* literal stack -> each char at main stack as short */
    558         while(curtask->lsp)
    559           pushMain((ushort)popLit());
    560         curtask->lsp = 0;
    561         break;
    562       case INS_LITCALL: /* call the saved word from the literal */
    563         if(curtask->lsp < 1)
    564           trapout(STACK_UNDERFLOW);
    565         lhash = crc16(&(curtask->literal_stack[0]), curtask->lsp);
    566         for(pbuf=0;pbuf<CLT_ENTRIES_MAX;++pbuf)
    567           if(curtask->clt[pbuf].nhash == lhash) {
    568             pushRet(curtask->pc); /* first, save the PC into the return stack */
    569             curtask->pc = curtask->clt[pbuf].loc; /* then jump to the word location */
    570             break;
    571           }
    572         if(pbuf >= CLT_ENTRIES_MAX)
    573           trapout(INVALID_WORD);
    574         curtask->lsp = 0; /* clear the literal stack */
    575         break;
    576       case INS_RET: /* jump to the instruction at ret stack */
    577         curtask->pc = popRet();
    578         break;
    579       case INS_M2R: /* main -> ret stack */
    580         pushRet(popMain());
    581         break;
    582       case INS_R2M: /* ret -> main stack */
    583         pushMain(popRet());
    584         break;
    585       case INS_LOAD: /* mem -> main stack */
    586         pbuf = popMain();
    587         pushMain((flatram[pbuf] << 8)|flatram[pbuf+1U]);
    588         break;
    589       case INS_STORE: /* main stack -> mem (word) */
    590         pbuf = popMain();
    591         pbuf2 = popMain();
    592         if(!taskMemWriteAllowed(pbuf))
    593           trapout(RESTRICTED_WRITE_ERROR);
    594         flatram[pbuf] = pbuf2 >> 8U;
    595         flatram[pbuf + 1U] = pbuf2 & 255U;
    596         break;
    597       case INS_STOREBYTE: /* main stack -> mem (byte) */
    598         pbuf = popMain();
    599         flatram[pbuf] = popMain() & 255U;
    600         break;
    601       case INS_DROP: /* ( a -- ) */
    602         pbuf = popMain();
    603         break;
    604       case INS_DUP: /* ( a -- a a )	*/
    605         if(curtask->msp < 1)
    606           trapout(STACK_UNDERFLOW);
    607         pushMain(curtask->main_stack[curtask->msp-1]);
    608         break;
    609       case INS_SWAP: /* ( a b -- b a ) */
    610         if(curtask->msp < 2)
    611           trapout(STACK_UNDERFLOW);
    612         pbuf = curtask->main_stack[curtask->msp-2];
    613         curtask->main_stack[curtask->msp-2] = curtask->main_stack[curtask->msp-1];
    614         curtask->main_stack[curtask->msp-1] = pbuf;
    615         break;
    616       case INS_ROT: /* ( a b c -- b c a ) */
    617         if(curtask->msp < 3)
    618           trapout(STACK_UNDERFLOW);
    619         pbuf = curtask->main_stack[curtask->msp-3];
    620         pbuf2 = curtask->main_stack[curtask->msp-1];
    621         curtask->main_stack[curtask->msp-3] = curtask->main_stack[curtask->msp-2]; 
    622         curtask->main_stack[curtask->msp-2] = pbuf2;
    623         curtask->main_stack[curtask->msp-1] = pbuf;
    624         break;
    625       case INS_OVER: /* ( a b -- a b a ) */
    626         if(curtask->msp < 2)
    627           trapout(STACK_UNDERFLOW);
    628         pushMain(curtask->main_stack[curtask->msp-2]);
    629         break;
    630       case INS_JUMP: /* unconditional signed jump: ( rel -- ) */
    631         curtask->pc = (ushort) (curtask->pc + (signed short) popMain());
    632         if(!taskMemJumpAllowed(curtask->pc))
    633           trapout(OUT_OF_BOUNDS_JUMP);
    634         break;
    635       case INS_IF: /* conditional signed jump if cond is not zero: ( cond rel -- ) */
    636         pbuf = popMain(); /* reladdr */
    637         pbuf2 = popMain(); /* cond */
    638         if(pbuf2 != 0) {
    639           curtask->pc = (ushort) (curtask->pc + (signed short) pbuf);
    640           if(!taskMemJumpAllowed(curtask->pc))
    641             trapout(OUT_OF_BOUNDS_JUMP);
    642         }
    643         break;
    644       case INS_EXPOINT: /* Locate execution point */
    645         pushMain(curtask->pc + 1);
    646         break;
    647       case INS_GPDSTART: /* Locate GPD area start */
    648         pushMain(curtask->gpd_start);
    649         break;
    650       case INS_GT: /* ( a b -- a>b ) */
    651         pushMain((popMain() < popMain()) ? 1u : 0u);
    652         break;
    653       case INS_LT: /* ( a b -- a<b ) */
    654         pushMain((popMain() > popMain()) ? 1u : 0u);
    655         break;
    656       case INS_EQ: /* ( a b -- a==b ) */
    657         pushMain((popMain() == popMain()) ? 1u : 0u);
    658         break;
    659       case INS_ADD: /* ( a b -- a+b ) */
    660         pushMain((popMain() + popMain())&65535U);
    661         break;
    662       case INS_SUB: /* ( a b -- a-b ) */
    663         pushMain((-popMain() + popMain())&65535U);
    664         break;
    665       case INS_MUL: /* ( a b -- a*b )	*/
    666         pushMain((ushort)(popMain() * popMain())&65535U);
    667         break;
    668       case INS_DIV: /* ( a b -- a/b rem ) */
    669         pbuf2 = popMain();
    670         pbuf = popMain();
    671         pushMain((ushort) pbuf/pbuf2);
    672         pushMain((ushort) pbuf%pbuf2);
    673         break;
    674       case INS_NEG: /* ( a -- -a ) */
    675         pushMain((ushort)-popMain());
    676         break;
    677       case INS_SHIFT: /* ( a XY -- [a >> X] << Y ) */
    678         pbuf = popMain();
    679         pushMain((ushort)(popMain() >> (pbuf&15U)) << (pbuf>>4U));
    680         break;
    681       case INS_NOT: /* ( a -- ~a ) */
    682         pushMain((ushort)(~popMain()&65535U));
    683         break;
    684       case INS_AND: /* ( a b -- a&b ) */
    685         pushMain(popMain() & popMain());
    686         break;
    687       case INS_OR: /* ( a b -- a|b ) */
    688         pushMain(popMain() | popMain());
    689         break;
    690       case INS_XOR: /* ( a b -- a^b ) */
    691         pushMain(popMain() ^ popMain());
    692         break;
    693       case INS_COUT: /* output a character, no Unicode support for now */
    694         cputc((uchar)(popMain()&255U));
    695         break;
    696       case INS_NHOUT: /* output the hex number from the stack top */
    697         printf("%04X", popMain());
    698         break;
    699       case INS_NBKIN: /* non-blocking key input - best attempt to implement it with kbhit or our haphazard emulation of it */
    700         pushMain(kbhit() ? (ushort)cgetc() : 0);
    701         break;
    702       case INS_BKIN: /* blocking key input (again, no Unicode support yet, assuming a single byte incoming to the 16-bit value on the stack) */
    703         bc = 0;
    704         bc = cgetc();
    705         fprintf(stderr, "[in] %c" CRLF, bc);
    706         pushMain((ushort) bc);
    707         break;
    708       case INS_PORTIO: /* ( p1 p2 port -- r1 r2 status ) - simulate/execute port I/O according to the spec */
    709         if(curtask->msp < 3)
    710           trapout(STACK_UNDERFLOW);
    711         portIO(popMain(), popMain(), popMain());
    712         break;
    713       case INS_PERSIST_READ: /* ( blk len maddr -- status) */
    714         pushMain(persistOp(pfd, popMain(), popMain(), popMain(), 0));
    715         break;
    716       case INS_PERSIST_WRITE: /* ( blk len maddr -- status) */
    717         pushMain(persistOp(pfd, popMain(), popMain(), popMain(), 1));
    718         break;
    719       case INS_TASKLOAD: /* ( addr len priv -- taskid ) */
    720         taskptr = equi_load_task(popMain(), popMain(), popMain());
    721         taskptr->active = 1;
    722         pushMain(taskptr->id);
    723         break;
    724       default: /* all characters not processed before are invalid instructions */
    725         trapout(INVALID_INSTRUCTION);
    726     }
    727     if(curtask->msp >= STACK_SIZE_WORDS || curtask->rsp >= STACK_SIZE_WORDS) /* check for stack overflow after any operation */
    728       trapout(STACK_OVERFLOW);
    729   }
    730   /* close the persistent sandbox file if open */
    731   if(pfd) fclose(pfd);
    732   /* reset IBP and exit */
    733   ram.ibp = 65535U;
    734 }
    735 
    736 #if !defined __CC65__ 
    737 struct termios tty_opts_raw, tty_opts_backup;
    738 void restore_term() {
    739   /* restore the terminal settings */
    740   tcsetattr(STDIN_FILENO, TCSANOW, &tty_opts_backup);
    741 }
    742 #endif
    743 
    744 /* Equi VM entry point */
    745 int main(int argc, char* argv[]) {
    746   uchar instr, bc, modec, smode = 0;
    747   FILE *prog = stdin;
    748   /* _attempt_ to disable buffering for char input/output */
    749 #if !defined __CC65__ 
    750   atexit(&restore_term);
    751   cfmakeraw(&tty_opts_raw);
    752   tcsetattr(STDIN_FILENO, TCSANOW, &tty_opts_raw);
    753 #endif
    754   /* initialize the PRNG */
    755   srand((unsigned)time(NULL));
    756   /* initialize the RAM in the most standard way */
    757   ram.stack_size = STACK_SIZE_WORDS;
    758   ram.literal_stack_size = LIT_STACK_SIZE;
    759   ram.cmd_start = (uchar *)&ram.cmdbuf - (uchar *)&ram;
    760   ram.cmd_size = CMD_BUF_SIZE;
    761   ram.II = ram.MM = 0; /* reset all flags */
    762   /* process command line params */
    763   if(argc > 1 && argv[1][0] != '-') { /* we passed the input file, - means "just use stdin" */
    764     prog = fopen(argv[1], "r");
    765   }
    766   if(argc > 2) { /* the first is the file, the second is the mode */
    767     if(argv[2][0] == 'm') { /* enter minification mode, don't run the programs */
    768       ram.MM = 1;
    769       smode = 1; /* also behave as if in the silent mode */
    770     }
    771     else if(argv[2][0] == 's') /* enter silent mode, don't print the banners and prompts */
    772       smode = 1;
    773   }
    774   /* Start input buffering from the start of command buffer (-1 because we use prefix increment) */
    775   ram.ibp = 65535U;
    776   if(!ram.MM && !smode) { /* skip the terminal init and the greeting if in minification or silent mode */
    777     /* CC65-specific terminal init */
    778 #ifdef __CC65__
    779     clrscr();
    780     bc = cursor(1);
    781 #else /* VT100-compatible terminal init */
    782     printf("\033c");
    783 #endif
    784     printf("Welcome to Equi v" EQUI_VER " by Luxferre, 2022" CRLF "System RAM: %d bytes" CRLF "Equi ready" CRLF CRLF "> ", (int) sizeof(ram));
    785   }
    786   while(1) { /* Now, we're in the command mode loop */
    787     instr = fgetc(prog); /* Fetch the next instruction from the keyboard/file */
    788     if(instr == 0xFFU || instr == 0U || instr == 3U || instr == 4U) /* exit on zero byte or ctrl+C or ctrl+D */
    789       break;
    790     else if(instr == BS || instr == DEL || instr == CR || instr == LF || instr == ' ' || instr == '\t') { /* ignore the backspace or whitespaces */
    791       if(!smode && instr != BS && instr != DEL)
    792         cputc(instr); /* echo it if not in silent mode */
    793       if(instr == CR)
    794         cputc(LF);
    795       if(instr == DEL || instr == BS) { /* simulate backspace */
    796 #ifdef __CC65__
    797         if(wherex()>0)
    798           gotox(wherex()-1);
    799 #else
    800         cputc(BS);
    801         cputc(0x20);
    802         cputc(BS);
    803 #endif
    804         if(ram.ibp > 0)
    805           --ram.ibp;
    806       }
    807     } else if(instr == INS_IISTART) { /* process II start */
    808       if(!smode)
    809         cputc(instr); /* echo it if not in silent mode */
    810       ram.II = 1;
    811     } else if(instr == INS_IIEND) { /* process II end */
    812       if(!smode)
    813         cputc(instr); /* echo it if not in silent mode */
    814       ram.II = 0;
    815     } else if(!ram.II && instr == INS_QUIT) { 
    816       if(ram.MM) { /* output command buffer contents to stdout and exit */
    817         ram.cmdbuf[++ram.ibp] = INS_QUIT; /* end program with INS_QUIT */
    818         ram.cmdbuf[++ram.ibp] = 0; /* and zero terminator */
    819         puts((const char *)&ram.cmdbuf[0]); /* output the command buffer */
    820         break; /* and exit the command mode immediately */
    821       } else {
    822         /* if not in II or minification mode, process EOF or Q instruction: trigger interpreter loop */
    823         cputc(INS_QUIT);
    824         if(!smode) {
    825           cputc(CR); /* echo CR */
    826           cputc(LF); /* echo LF */
    827         }
    828         ram.cmdbuf[++ram.ibp] = INS_QUIT; /* end program with INS_QUIT */
    829         curtask = equi_load_task(1, ram.ibp+1, (ushort)ram.cmd_start); /* load the code as task 0 - always privileged */
    830         ram.taskid = curtask->id; /* actualize the current task id */
    831         curtask->active = 1; /*activate the current task */
    832         equi_main_loop(); /* and run the interpreter loop */
    833         if(!smode) {
    834           cputc(CR); /* echo CR */
    835           cputc(LF); /* echo LF */
    836           cputc('>');
    837           cputc(' ');
    838         }
    839       }
    840     } else { /* append the instruction/character to the command buffer if and only if it doesn't match the above criteria and we're not in II mode */
    841       if(!ram.II)
    842         ram.cmdbuf[++ram.ibp] = instr;
    843       if(!smode)
    844         cputc(instr); /* echo it if not in silent mode */
    845     }
    846   } /* command mode loop end */
    847   fclose(prog); /* close the input file */
    848   return 0;
    849 }