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 }