/* * Equi platform reference implementation * * Created in 2022 by Luxferre, released into public domain * * See README.md file for the specification and manual * * @license Unlicense * @author Luxferre */ /* Standard or non-standard includes depending on the target */ #include #include #include #ifdef __CC65__ #include #define CRLF "\n" #else #include #include #include #define cgetc() (getchar()) #define cputc(c) (putchar(c)) #pragma pack(2) #define CRLF "\r\n" /* also, attempt to emulate two other conio methods */ int kbhit() { struct timeval tv = { 0L, 0L }; fd_set fds; FD_ZERO(&fds); FD_SET(0, &fds); return select(1, &fds, NULL, NULL, &tv) > 0; } int getch() { int r; unsigned char c; if((r = read(0, &c, 1)) < 0) return r; else return 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 DEL 0x7fu /* Delete key 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 /* GPD area size in bytes */ #ifndef GPD_AREA_SIZE #define GPD_AREA_SIZE 4096u #endif /* Command buffer size in bytes */ #ifndef CMD_BUF_SIZE #define CMD_BUF_SIZE 13600u #endif /* Maximum amount of CLT entries */ #ifndef CLT_ENTRIES_MAX #define CLT_ENTRIES_MAX 512u #endif /* Maximum amount of task table entries */ #ifndef EQUI_TASKS_MAX #define EQUI_TASKS_MAX 1 #endif /* Persistent storage sandbox file name */ #ifndef PERSIST_FILE #define PERSIST_FILE "PERS.DAT" #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 { /* one entry in the compilation lookup table */ ushort nhash; /* compiled word name hash */ ushort loc; /* compiled word location */ }; struct EquiCtx { /* one Equi program context */ ushort id; /* task ID */ uchar active; /* 0 - inactive/quit, 1 - active */ uchar privileged; /* whether or not the task is allowed to write to the entire command buffer */ uchar CM; /* compilation mode flag */ uchar lsp; /* literal stack pointer */ ushort msp; /* main stack pointer */ ushort rsp; /* return stack pointer */ ushort cltp; /* compilation lookup table pointer */ ushort cbp; /* compilation buffer pointer */ ushort gpd_start; /* GPD area start for this task */ ushort cmd_start; /* command buffer start for this task */ ushort cmd_size; /* size of loaded code in bytes for this task */ ushort pc; /* program counter */ ushort main_stack[STACK_SIZE_WORDS]; ushort return_stack[STACK_SIZE_WORDS]; uchar literal_stack[LIT_STACK_SIZE]; struct CLTEntry clt[CLT_ENTRIES_MAX]; /* compilation lookup table */ uchar gpd[GPD_AREA_SIZE]; }; struct EquiRAM { ushort stack_size; /* main/return stack size in words */ uchar literal_stack_size; /* literal stack size in bytes */ ushort cmd_start; /* (global) command buffer start */ ushort cmd_size; /* (global) command buffer size in bytes */ ushort ibp; /* input buffer pointer */ uchar II; /* instruction ignore mode flag */ uchar MM; /* minification bypass mode flag */ ushort taskid; /* currently running task ID */ struct EquiCtx tasks[EQUI_TASKS_MAX]; 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; /* reference to the current task context */ static struct EquiCtx *curtask; /* reference to the buffer task context */ static struct EquiCtx *taskptr; /* 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, PERSIST_IO_ERROR, RESTRICTED_WRITE_ERROR, OUT_OF_BOUNDS_JUMP, TASK_SLOTS_FULL }; /* Error reporting method */ void trapout(errcode) { if(errcode > 0) { if(curtask) fprintf(stderr, CRLF "Error %d at 0x%x (task 0x%x, instruction %c): ", errcode, curtask->pc, curtask->id, flatram[curtask->pc]); else fprintf(stderr, CRLF "System error %d: ", errcode); switch(errcode) { case STACK_OVERFLOW: cerr("Stack overflow" CRLF); break; case STACK_UNDERFLOW: cerr("Stack underflow" CRLF); break; case DIV_BY_ZERO: cerr("Division by zero" CRLF); break; case CLT_OVERFLOW: cerr("Compilation lookup table full" CRLF); break; case CMD_OVERFLOW: cerr("Command buffer full" CRLF); break; case INVALID_INSTRUCTION: cerr("Invalid instruction" CRLF); break; case INVALID_WORD: cerr("Word not found in CLT" CRLF); break; case PORT_IO_ERROR: cerr("Port I/O error" CRLF); break; case PERSIST_IO_ERROR: cerr("Persistent storage I/O error" CRLF); break; case RESTRICTED_WRITE_ERROR: cerr("Attempt to write to a restricted RAM area" CRLF); break; case OUT_OF_BOUNDS_JUMP: cerr("Attempt to jump outside the task context" CRLF); break; case TASK_SLOTS_FULL: cerr("All task slots busy and active" CRLF); break; } if(curtask) /* if we're in a task, only terminate it */ curtask->active=1; else /* otherwise to a hard trapout */ 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_TASKLOAD='Y', /* run and activate a new task */ INS_QUIT='Q' }; /* Equi known ports enum */ enum EquiPorts { PORT_ECHO = 0x0, /* echo port, used for testing: R1=P1, R2=P2 */ PORT_RANDOM, /* RNG port: P1 = valueFrom, P2 = valueTo => R1=rand(from, to), R2 = rand(from, to) */ PORT_CHECKSUM, /* checksum port: P1 = memAddr, P2 = length => R1=CRC16(memAddr, length), R2 = 0 */ PORT_TASKCTL /* task control port: P1 = task id, P2 = operation => R1 = operation result, R2 = operation status */ }; /* push a value onto the main stack */ void pushMain(ushort val) { curtask->main_stack[curtask->msp++] = val; if(curtask->msp >= STACK_SIZE_WORDS) trapout(STACK_OVERFLOW); } /* push a value onto the return stack */ void pushRet(ushort val) { curtask->return_stack[curtask->rsp++] = val; if(curtask->rsp >= STACK_SIZE_WORDS) trapout(STACK_OVERFLOW); } /* pop a value from the main stack */ ushort popMain() { if(curtask->msp == 0) { trapout(STACK_UNDERFLOW); return 0; } else return curtask->main_stack[--curtask->msp]; } /* pop a value from the return stack */ ushort popRet() { if(curtask->rsp == 0) { trapout(STACK_UNDERFLOW); return 0; } else return curtask->return_stack[--curtask->rsp]; } /* push a character to the literal stack */ void pushLit(uchar c) { curtask->literal_stack[curtask->lsp++] = c; if(curtask->lsp >= LIT_STACK_SIZE) trapout(STACK_OVERFLOW); } /* pop a character from the literal stack */ uchar popLit() { if(curtask->lsp == 0) { trapout(STACK_UNDERFLOW); return 0; } else return curtask->literal_stack[--curtask->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(curtask->lsp < 4U) thr = curtask->lsp; for(i=0;ilsp = 0; /* clear the literal stack */ } /* CCITT CRC16 helper */ ushort crc16(const uchar* data_p, uchar length) { uchar x; ushort crc = 0xFFFFu; while(length--) { x = crc>>8U ^ *data_p++; x ^= x>>4U; crc = (crc << 8U) ^ ((ushort)(x << 12U)) ^ ((ushort)(x << 5U)) ^ ((ushort)x); } return crc; } /* Short endianness conversion helper */ ushort tobig(ushort val) { return (val>>8)|((val&255)<<8); } /* Task-based memory address jump checker */ uchar taskMemJumpAllowed(addr) { /* a privileged task is allowed to jump anywhere in the command buffer, others only in their own zone */ if(curtask->privileged) return addr >= ram.cmd_start; else return (addr >= curtask->cmd_start) && (addr < curtask->cmd_start + curtask->cmd_size); } /* Task-based memory address write checker */ uchar taskMemWriteAllowed(addr) { /* a privileged task is allowed to write anywhere in the command buffer, others only in their own zone */ return (addr >= curtask->gpd_start && addr < (curtask->gpd_start + GPD_AREA_SIZE)) || taskMemJumpAllowed(addr); } /* Persistent operation handler */ ushort persistOp(FILE *pfd, ushort maddr, ushort dataLen, ushort blk, uchar isWrite) { ushort status = 0, proc; if(pfd) { fseek(pfd, ((unsigned long) blk << 10), SEEK_SET); /* blocks are 1K-aligned */ if(isWrite) /* writing to the persistent area from the memory */ proc = (ushort) fwrite(&flatram[maddr], 1, dataLen, pfd); else { /* reading from the persistent area into the memory */ if(!taskMemWriteAllowed(maddr)) trapout(RESTRICTED_WRITE_ERROR); proc = (ushort) fread(&flatram[maddr], 1, dataLen, pfd); } if(proc != dataLen) status = PERSIST_IO_ERROR; } pushMain(status); return 0; } /* Port I/O handler */ void portIO(ushort port, ushort p2, ushort p1) { ushort r1 = 0, r2 = 0, status = 0; switch(port) { /* Echo port (for testing): r1 = p1, r2 = p2 */ case PORT_ECHO: r1 = p1; r2 = p2; break; /* Random number generator port: p1 = from, p2 = to, r1 and r2 = rand(from, to) */ case PORT_RANDOM: r1 = (ushort) (p1 + (rand()%p2)); r2 = (ushort) (p1 + (rand()%p2)); break; /* Checksum port: p1 = address, p2 = length, r1 = CRC-16-CCITT value */ case PORT_CHECKSUM: r1 = crc16(&flatram[p1], p2); break; /* Task control port: p1 = task ID, p2 = operation code */ /* Operations: 0 - get status, 1 - set active status, 2 - unset active status, 3 - get privilege status */ /* Only privileged tasks can change status of other tasks, otherwise fail silently */ case PORT_TASKCTL: if(p1 >= EQUI_TASKS_MAX) { trapout(OUT_OF_BOUNDS_JUMP); return; } switch(p2) { case 0: r1 = ram.tasks[p1].active; break; case 1: if(curtask->privileged) ram.tasks[p1].active = 1; break; case 2: if(curtask->privileged) ram.tasks[p1].active = 0; break; case 3: r1 = ram.tasks[p1].privileged; break; } break; default: fprintf(stderr, "[PORTIO] Unimplemented call to port 0x%X with P1=%X and P2=%X" CRLF, port, p1, p2); } pushMain(r1); pushMain(r2); pushMain(status); } /* end of internal helper functions, start of main Equi functions */ /* Find free task slot among remaining inactive tasks */ ushort equi_find_free_task_slot() { ushort slotid = 0; /* iterate over task table */ for(;slotid < EQUI_TASKS_MAX;++slotid) if(!ram.tasks[slotid].active) return slotid; /* and error out if all slots are busy and active */ trapout(TASK_SLOTS_FULL); return TASK_SLOTS_FULL; } /* Find the next active task in the table, round-robin fashion */ struct EquiCtx* equi_find_next_task() { ushort oldtaskid = ram.taskid; /* save the current one to avoid deadlocks */ while(1) { ++ram.taskid; if(ram.taskid == EQUI_TASKS_MAX) ram.taskid = 0; if(ram.tasks[ram.taskid].active) break; if(ram.taskid == oldtaskid) /* found no active tasks, bailing out */ return NULL; } return &ram.tasks[ram.taskid]; } /* Task loader method */ struct EquiCtx* equi_load_task(uchar priv, ushort len, ushort progStart) { ushort tid = equi_find_free_task_slot(); /* don't allow to load privileged tasks from non-privileged ones */ if(curtask && !curtask->privileged) priv = 0; taskptr = &ram.tasks[tid]; /* refer to the next available entry */ taskptr->msp = taskptr->rsp = taskptr->lsp = taskptr->cltp = 0; /* init stacks and CLT */ taskptr->pc = progStart - 1U; /* init program counter for preincrement logic */ taskptr->cmd_start = progStart; /* actual command buffer start (from the start of vRAM) */ taskptr->cmd_size = len; /* actual command buffer size */ taskptr->id = tid; /* assign task ID */ taskptr->gpd_start = (ushort) (taskptr->gpd - flatram); /* assign GPD area start */ taskptr->CM = 0; /* unset compilation mode flag */ taskptr->privileged = priv; /* set privileged flag */ return taskptr; } /* Main interpreter loop */ void equi_main_loop() { uchar instr, bc; ushort lhash, pbuf, pbuf2; /* try to open the persistent sandbox file */ FILE *pfd = fopen(PERSIST_FILE, "r+b"); while(1) { /* iterate over the instructions in the command buffer */ curtask = equi_find_next_task(); /* attempt to switch the context on every iteration */ if(curtask == NULL) break; /* exit the main loop when no tasks are left */ instr = flatram[++(curtask->pc)]; /* silently exit on zero or FF */ if(instr == 0 || instr == 0xFFu) curtask->active = 0; /* 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(curtask->CM) { if(instr == INS_CMEND) { /* trigger word compilation logic as per the spec */ if(curtask->lsp < 1) trapout(STACK_UNDERFLOW); flatram[curtask->pc] = INS_RET; /* in-place patch this instruction to R */ /* hash and save compiled word */ curtask->clt[curtask->cltp].nhash = crc16(&(curtask->literal_stack[0]), curtask->lsp); curtask->lsp = 0; /* clear the literal stack */ curtask->clt[curtask->cltp].loc = curtask->cbp; /* stored the compiled code location from the most recent CBP value */ ++curtask->cltp; /* increase the word */ if(curtask->cltp == CLT_ENTRIES_MAX) trapout(CLT_OVERFLOW); curtask->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(curtask->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_CMSTART: /* compilation start */ curtask->cbp = curtask->pc + 1U; /* save CBP */ curtask->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 task */ curtask->active = 0; break; case INS_LITINT: /* literal stack -> main stack as short */ pushLitVal(); break; case INS_LITSTR: /* literal stack -> each char at main stack as short */ while(curtask->lsp) pushMain((ushort)popLit()); curtask->lsp = 0; break; case INS_LITCALL: /* call the saved word from the literal */ if(curtask->lsp < 1) trapout(STACK_UNDERFLOW); lhash = crc16(&(curtask->literal_stack[0]), curtask->lsp); for(pbuf=0;pbufclt[pbuf].nhash == lhash) { pushRet(curtask->pc); /* first, save the PC into the return stack */ curtask->pc = curtask->clt[pbuf].loc; /* then jump to the word location */ break; } if(pbuf >= CLT_ENTRIES_MAX) trapout(INVALID_WORD); curtask->lsp = 0; /* clear the literal stack */ break; case INS_RET: /* jump to the instruction at ret stack */ curtask->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(); if(!taskMemWriteAllowed(pbuf)) trapout(RESTRICTED_WRITE_ERROR); 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(curtask->msp < 1) trapout(STACK_UNDERFLOW); pushMain(curtask->main_stack[curtask->msp-1]); break; case INS_SWAP: /* ( a b -- b a ) */ if(curtask->msp < 2) trapout(STACK_UNDERFLOW); pbuf = curtask->main_stack[curtask->msp-2]; curtask->main_stack[curtask->msp-2] = curtask->main_stack[curtask->msp-1]; curtask->main_stack[curtask->msp-1] = pbuf; break; case INS_ROT: /* ( a b c -- b c a ) */ if(curtask->msp < 3) trapout(STACK_UNDERFLOW); pbuf = curtask->main_stack[curtask->msp-3]; pbuf2 = curtask->main_stack[curtask->msp-1]; curtask->main_stack[curtask->msp-3] = curtask->main_stack[curtask->msp-2]; curtask->main_stack[curtask->msp-2] = pbuf2; curtask->main_stack[curtask->msp-1] = pbuf; break; case INS_OVER: /* ( a b -- a b a ) */ if(curtask->msp < 2) trapout(STACK_UNDERFLOW); pushMain(curtask->main_stack[curtask->msp-2]); break; case INS_JUMP: /* unconditional signed jump: ( rel -- ) */ curtask->pc = (ushort) (curtask->pc + (signed short) popMain()); if(!taskMemJumpAllowed(curtask->pc)) trapout(OUT_OF_BOUNDS_JUMP); break; case INS_IF: /* conditional signed jump if cond is not zero: ( cond rel -- ) */ pbuf = popMain(); /* reladdr */ pbuf2 = popMain(); /* cond */ if(pbuf2 != 0) { curtask->pc = (ushort) (curtask->pc + (signed short) pbuf); if(!taskMemJumpAllowed(curtask->pc)) trapout(OUT_OF_BOUNDS_JUMP); } break; case INS_EXPOINT: /* Locate execution point */ pushMain(curtask->pc + 1); break; case INS_GPDSTART: /* Locate GPD area start */ pushMain(curtask->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 - best attempt to implement it with kbhit or our haphazard emulation of it */ pushMain(kbhit() ? (ushort)cgetc() : 0); break; case INS_BKIN: /* blocking key input (again, no Unicode support yet, assuming a single byte incoming to the 16-bit value on the stack) */ bc = 0; bc = cgetc(); fprintf(stderr, "[in] %c" CRLF, bc); pushMain((ushort) bc); break; case INS_PORTIO: /* ( p1 p2 port -- r1 r2 status ) - simulate/execute port I/O according to the spec */ if(curtask->msp < 3) trapout(STACK_UNDERFLOW); portIO(popMain(), popMain(), popMain()); break; case INS_PERSIST_READ: /* ( blk len maddr -- status) */ pushMain(persistOp(pfd, popMain(), popMain(), popMain(), 0)); break; case INS_PERSIST_WRITE: /* ( blk len maddr -- status) */ pushMain(persistOp(pfd, popMain(), popMain(), popMain(), 1)); break; case INS_TASKLOAD: /* ( addr len priv -- taskid ) */ taskptr = equi_load_task(popMain(), popMain(), popMain()); taskptr->active = 1; pushMain(taskptr->id); break; default: /* all characters not processed before are invalid instructions */ trapout(INVALID_INSTRUCTION); } if(curtask->msp >= STACK_SIZE_WORDS || curtask->rsp >= STACK_SIZE_WORDS) /* check for stack overflow after any operation */ trapout(STACK_OVERFLOW); } /* close the persistent sandbox file if open */ if(pfd) fclose(pfd); /* reset IBP and exit */ ram.ibp = 65535U; } /* Equi VM entry point */ int main(int argc, char* argv[]) { uchar instr, bc, modec, smode = 0; FILE *prog = stdin; /* _attempt_ to disable buffering for char input/output */ #if !defined __CC65__ struct termios tty_opts_raw, tty_opts_backup; tcgetattr(STDIN_FILENO, &tty_opts_backup); cfmakeraw(&tty_opts_raw); tcsetattr(STDIN_FILENO, TCSANOW, &tty_opts_raw); #endif /* initialize the PRNG */ srand((unsigned)time(NULL)); /* initialize the RAM in the most standard way */ ram.stack_size = STACK_SIZE_WORDS; ram.literal_stack_size = LIT_STACK_SIZE; ram.cmd_start = (uchar *)&ram.cmdbuf - (uchar *)&ram; ram.cmd_size = CMD_BUF_SIZE; ram.II = ram.MM = 0; /* reset all flags */ /* process command line params */ if(argc > 1 && argv[1][0] != '-') { /* we passed the input file, - means "just use stdin" */ prog = fopen(argv[1], "r"); } if(argc > 2) { /* the first is the file, the second is the mode */ if(argv[2][0] == 'm') { /* enter minification mode, don't run the programs */ ram.MM = 1; smode = 1; /* also behave as if in the silent mode */ } else if(argv[2][0] == 's') /* enter silent mode, don't print the banners and prompts */ smode = 1; } /* Start input buffering from the start of command buffer (-1 because we use prefix increment) */ ram.ibp = 65535U; if(!ram.MM && !smode) { /* skip the terminal init and the greeting if in minification or silent mode */ /* CC65-specific terminal init */ #ifdef __CC65__ clrscr(); bc = cursor(1); #else /* VT100-compatible terminal init */ printf("\033c"); #endif printf("Welcome to Equi v" EQUI_VER " by Luxferre, 2022" CRLF "System RAM: %d bytes" CRLF "Equi ready" CRLF CRLF "> ", (int) sizeof(ram)); } while(1) { /* Now, we're in the command mode loop */ instr = fgetc(prog); /* Fetch the next instruction from the keyboard/file */ if(instr == 0xFFU || instr == 0U || instr == 3U || instr == 4U) /* exit on zero byte or ctrl+C or ctrl+D */ break; else if(instr == BS || instr == DEL || instr == CR || instr == LF || instr == ' ' || instr == '\t') { /* ignore the backspace or whitespaces */ if(!smode && instr != BS && instr != DEL) cputc(instr); /* echo it if not in silent mode */ if(instr == CR) cputc(LF); if(instr == DEL || instr == BS) { /* simulate backspace */ #ifdef __CC65__ if(wherex()>0) gotox(wherex()-1); #else cputc(BS); cputc(0x20); cputc(BS); #endif if(ram.ibp > 0) --ram.ibp; } } else if(instr == INS_IISTART) { /* process II start */ if(!smode) cputc(instr); /* echo it if not in silent mode */ ram.II = 1; } else if(instr == INS_IIEND) { /* process II end */ if(!smode) cputc(instr); /* echo it if not in silent mode */ ram.II = 0; } else if(!ram.II && instr == INS_QUIT) { if(ram.MM) { /* output command buffer contents to stdout and exit */ ram.cmdbuf[++ram.ibp] = INS_QUIT; /* end program with INS_QUIT */ ram.cmdbuf[++ram.ibp] = 0; /* and zero terminator */ puts((const char *)&ram.cmdbuf[0]); /* output the command buffer */ break; /* and exit the command mode immediately */ } else { /* if not in II or minification mode, process EOF or Q instruction: trigger interpreter loop */ cputc(INS_QUIT); if(!smode) { cputc(CR); /* echo CR */ cputc(LF); /* echo LF */ } ram.cmdbuf[++ram.ibp] = INS_QUIT; /* end program with INS_QUIT */ curtask = equi_load_task(1, ram.ibp+1, (ushort)ram.cmd_start); /* load the code as task 0 - always privileged */ ram.taskid = curtask->id; /* actualize the current task id */ curtask->active = 1; /*activate the current task */ equi_main_loop(); /* and run the interpreter loop */ if(!smode) { cputc(CR); /* echo CR */ cputc(LF); /* echo LF */ cputc('>'); cputc(' '); } } } 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 */ if(!ram.II) ram.cmdbuf[++ram.ibp] = instr; if(!smode) cputc(instr); /* echo it if not in silent mode */ } } /* command mode loop end */ #ifndef __CC65__ /* restore the terminal settings */ tcsetattr(STDIN_FILENO, TCSANOW, &tty_opts_backup); #endif fclose(prog); /* close the input file */ return 0; }