2022-08-06 00:08:17 +03:00
|
|
|
/*
|
|
|
|
|
* Equi platform reference implementation
|
|
|
|
|
*
|
|
|
|
|
* Created in 2022 by Luxferre, released into public domain
|
|
|
|
|
*
|
2022-08-12 17:34:46 +03:00
|
|
|
* See README.md file for the specification and manual
|
2022-08-06 00:08:17 +03:00
|
|
|
*
|
|
|
|
|
* @license Unlicense <https://unlicense.org>
|
|
|
|
|
* @author Luxferre
|
|
|
|
|
*/
|
|
|
|
|
|
2022-08-06 09:34:32 +03:00
|
|
|
/* Standard includes with size optimizations for TCC */
|
2022-08-06 00:08:17 +03:00
|
|
|
|
2022-08-06 09:34:32 +03:00
|
|
|
#ifdef __TINYC__
|
|
|
|
|
#include <tcclib.h>
|
2022-08-08 06:48:46 +03:00
|
|
|
#define stderr 2
|
2022-08-12 12:16:06 +03:00
|
|
|
#define SEEK_SET 0
|
2022-08-06 09:34:32 +03:00
|
|
|
#else
|
2022-08-06 00:08:17 +03:00
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <stdio.h>
|
2022-08-11 15:36:39 +03:00
|
|
|
#include <time.h>
|
2022-08-06 09:34:32 +03:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Non-standard includes
|
|
|
|
|
* To save space, we emulate necessary conio functions on non-6502 systems, not vice versa
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#ifdef __CC65__
|
|
|
|
|
#include <conio.h>
|
|
|
|
|
#else
|
2022-08-07 22:20:48 +03:00
|
|
|
#define cgetc() (getchar())
|
|
|
|
|
#define cputc(c) (putchar(c))
|
2022-08-06 09:34:32 +03:00
|
|
|
#endif
|
2022-08-06 00:08:17 +03:00
|
|
|
|
2022-08-13 08:46:25 +03:00
|
|
|
/* also, do our best to ensure short-packed value storage in our virtual RAM */
|
|
|
|
|
#pragma pack(2)
|
|
|
|
|
|
2022-08-06 00:08:17 +03:00
|
|
|
/* Definitions section */
|
|
|
|
|
|
2022-08-06 21:09:10 +03:00
|
|
|
#define EQUI_VER "0.0.1"
|
|
|
|
|
|
2022-08-07 22:20:48 +03:00
|
|
|
#define cerr(s) (fputs(s, stderr))
|
2022-08-06 00:08:17 +03:00
|
|
|
#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 */
|
2022-08-06 21:09:10 +03:00
|
|
|
#define BS 8u /* Backspace character code */
|
2022-08-07 22:20:48 +03:00
|
|
|
#define CR 13u /* Character return code */
|
2022-08-09 20:59:08 +03:00
|
|
|
#define LF 10u /* Line feed code */
|
2022-08-06 00:08:17 +03:00
|
|
|
|
|
|
|
|
/* Configuration section (constants overridable at compile-time) */
|
|
|
|
|
|
|
|
|
|
/* Main and return stack size in bytes */
|
|
|
|
|
#ifndef STACK_SIZE
|
2022-08-06 13:35:10 +03:00
|
|
|
#define STACK_SIZE 256u
|
2022-08-06 00:08:17 +03:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* Literal stack size in bytes */
|
|
|
|
|
#ifndef LIT_STACK_SIZE
|
2022-08-06 13:35:10 +03:00
|
|
|
#define LIT_STACK_SIZE 32u
|
2022-08-06 00:08:17 +03:00
|
|
|
#endif
|
|
|
|
|
|
2022-08-13 18:48:31 +03:00
|
|
|
/* GPD area size in bytes */
|
2022-08-06 21:09:10 +03:00
|
|
|
#ifndef GPD_AREA_SIZE
|
2022-08-13 18:48:31 +03:00
|
|
|
#define GPD_AREA_SIZE 4096u
|
2022-08-06 00:08:17 +03:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* Command buffer size in bytes */
|
|
|
|
|
#ifndef CMD_BUF_SIZE
|
2022-08-13 10:24:55 +03:00
|
|
|
#define CMD_BUF_SIZE 13600u
|
2022-08-06 00:08:17 +03:00
|
|
|
#endif
|
|
|
|
|
|
2022-08-06 21:09:10 +03:00
|
|
|
/* Maximum amount of CLT entries */
|
|
|
|
|
#ifndef CLT_ENTRIES_MAX
|
|
|
|
|
#define CLT_ENTRIES_MAX 512u
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-08-13 18:48:31 +03:00
|
|
|
/* Maximum amount of task table entries */
|
|
|
|
|
#ifndef EQUI_TASKS_MAX
|
|
|
|
|
#define EQUI_TASKS_MAX 1
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-08-12 11:26:34 +03:00
|
|
|
/* Persistent storage sandbox file name */
|
|
|
|
|
#ifndef PERSIST_FILE
|
|
|
|
|
#define PERSIST_FILE "PERS.DAT"
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-08-06 00:08:17 +03:00
|
|
|
/* Some necessary constants and offsets derived from the above values */
|
|
|
|
|
#define STACK_SIZE_WORDS (STACK_SIZE / WS) /* Main and return stack size in words */
|
|
|
|
|
|
2022-08-13 18:48:31 +03:00
|
|
|
/* Structures that describe Equi machine RAM using the above configuration */
|
2022-08-06 00:08:17 +03:00
|
|
|
|
2022-08-13 18:48:31 +03:00
|
|
|
struct CLTEntry { /* one entry in the compilation lookup table */
|
2022-08-09 07:47:50 +03:00
|
|
|
ushort nhash; /* compiled word name hash */
|
|
|
|
|
ushort loc; /* compiled word location */
|
2022-08-06 00:08:17 +03:00
|
|
|
};
|
|
|
|
|
|
2022-08-13 18:48:31 +03:00
|
|
|
struct EquiCtx { /* one Equi program context */
|
|
|
|
|
ushort id; /* task ID */
|
2022-08-13 23:19:55 +03:00
|
|
|
uchar active; /* 0 - inactive/quit, 1 - active */
|
2022-08-13 19:05:25 +03:00
|
|
|
uchar CM; /* compilation mode flag */
|
|
|
|
|
uchar IM; /* interpretation mode flag */
|
2022-08-13 23:19:55 +03:00
|
|
|
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 */
|
2022-08-13 18:48:31 +03:00
|
|
|
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 */
|
2022-08-06 00:08:17 +03:00
|
|
|
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];
|
2022-08-13 18:48:31 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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 */
|
2022-08-13 23:19:55 +03:00
|
|
|
ushort taskid; /* currently running task ID */
|
2022-08-13 18:48:31 +03:00
|
|
|
struct EquiCtx tasks[EQUI_TASKS_MAX];
|
2022-08-06 00:08:17 +03:00
|
|
|
uchar cmdbuf[CMD_BUF_SIZE];
|
|
|
|
|
};
|
|
|
|
|
|
2022-08-06 09:34:32 +03:00
|
|
|
/* Before running the main code, instantiate the machine RAM */
|
|
|
|
|
static struct EquiRAM ram;
|
2022-08-06 00:08:17 +03:00
|
|
|
|
|
|
|
|
/* Also create an alternative view of the same RAM area for direct offset-based access */
|
|
|
|
|
static uchar* flatram = (uchar *)&ram;
|
|
|
|
|
|
2022-08-13 18:48:31 +03:00
|
|
|
/* reference to the current task context */
|
|
|
|
|
static struct EquiCtx *curtask;
|
2022-08-13 11:49:03 +03:00
|
|
|
|
2022-08-07 22:20:48 +03:00
|
|
|
/* Error reporting codes */
|
|
|
|
|
enum EquiErrors {
|
|
|
|
|
SUCCESS=0,
|
|
|
|
|
STACK_OVERFLOW,
|
|
|
|
|
STACK_UNDERFLOW,
|
|
|
|
|
DIV_BY_ZERO,
|
|
|
|
|
CLT_OVERFLOW,
|
|
|
|
|
CMD_OVERFLOW,
|
|
|
|
|
INVALID_INSTRUCTION,
|
2022-08-09 07:47:50 +03:00
|
|
|
INVALID_WORD,
|
2022-08-12 11:26:34 +03:00
|
|
|
PORT_IO_ERROR,
|
2022-08-13 07:40:48 +03:00
|
|
|
PERSIST_IO_ERROR,
|
2022-08-13 18:48:31 +03:00
|
|
|
RESTRICTED_WRITE_ERROR,
|
2022-08-13 23:19:55 +03:00
|
|
|
OUT_OF_BOUNDS_JUMP,
|
|
|
|
|
TASK_SLOTS_FULL
|
2022-08-07 22:20:48 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* Error reporting method */
|
|
|
|
|
void trapout(errcode) {
|
|
|
|
|
if(errcode > 0) {
|
2022-08-13 23:19:55 +03:00
|
|
|
if(curtask)
|
|
|
|
|
fprintf(stderr, "\nError %d at 0x%x (task 0x%x, instruction %c): ", errcode, curtask->pc, curtask->id, flatram[curtask->pc]);
|
|
|
|
|
else
|
|
|
|
|
fprintf(stderr, "\nSystem error %d: ", errcode);
|
2022-08-07 22:20:48 +03:00
|
|
|
switch(errcode) {
|
|
|
|
|
case STACK_OVERFLOW:
|
2022-08-09 20:59:08 +03:00
|
|
|
cerr("Stack overflow\n");
|
2022-08-07 22:20:48 +03:00
|
|
|
break;
|
|
|
|
|
case STACK_UNDERFLOW:
|
2022-08-09 20:59:08 +03:00
|
|
|
cerr("Stack underflow\n");
|
2022-08-07 22:20:48 +03:00
|
|
|
break;
|
|
|
|
|
case DIV_BY_ZERO:
|
2022-08-09 20:59:08 +03:00
|
|
|
cerr("Division by zero\n");
|
2022-08-07 22:20:48 +03:00
|
|
|
break;
|
|
|
|
|
case CLT_OVERFLOW:
|
2022-08-09 20:59:08 +03:00
|
|
|
cerr("Compilation lookup table full\n");
|
2022-08-07 22:20:48 +03:00
|
|
|
break;
|
|
|
|
|
case CMD_OVERFLOW:
|
2022-08-09 20:59:08 +03:00
|
|
|
cerr("Command buffer full\n");
|
2022-08-07 22:20:48 +03:00
|
|
|
break;
|
|
|
|
|
case INVALID_INSTRUCTION:
|
2022-08-09 20:59:08 +03:00
|
|
|
cerr("Invalid instruction\n");
|
2022-08-07 22:20:48 +03:00
|
|
|
break;
|
2022-08-09 07:47:50 +03:00
|
|
|
case INVALID_WORD:
|
2022-08-09 20:59:08 +03:00
|
|
|
cerr("Word not found in CLT\n");
|
2022-08-09 07:47:50 +03:00
|
|
|
break;
|
2022-08-07 22:20:48 +03:00
|
|
|
case PORT_IO_ERROR:
|
2022-08-09 20:59:08 +03:00
|
|
|
cerr("Port I/O error\n");
|
2022-08-07 22:20:48 +03:00
|
|
|
break;
|
2022-08-12 11:26:34 +03:00
|
|
|
case PERSIST_IO_ERROR:
|
|
|
|
|
cerr("Persistent storage I/O error\n");
|
|
|
|
|
break;
|
2022-08-13 07:40:48 +03:00
|
|
|
case RESTRICTED_WRITE_ERROR:
|
|
|
|
|
cerr("Attempt to write to a restricted RAM area\n");
|
|
|
|
|
break;
|
2022-08-13 18:48:31 +03:00
|
|
|
case OUT_OF_BOUNDS_JUMP:
|
|
|
|
|
cerr("Attempt to jump outside the task context\n");
|
|
|
|
|
break;
|
2022-08-13 23:19:55 +03:00
|
|
|
case TASK_SLOTS_FULL:
|
|
|
|
|
cerr("All task slots busy and active\n");
|
|
|
|
|
break;
|
2022-08-07 22:20:48 +03:00
|
|
|
}
|
|
|
|
|
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 */
|
2022-08-11 09:29:05 +03:00
|
|
|
INS_GPDSTART='G', /* locate GPD area start */
|
2022-08-07 22:20:48 +03:00
|
|
|
INS_GT='>',
|
|
|
|
|
INS_LT='<',
|
|
|
|
|
INS_EQ='=',
|
|
|
|
|
INS_ADD='+',
|
|
|
|
|
INS_SUB='-',
|
|
|
|
|
INS_MUL='*',
|
|
|
|
|
INS_DIV='/',
|
|
|
|
|
INS_NEG='N',
|
2022-08-09 20:59:08 +03:00
|
|
|
INS_SHIFT='T',
|
2022-08-07 22:20:48 +03:00
|
|
|
INS_NOT='~',
|
|
|
|
|
INS_AND='&',
|
|
|
|
|
INS_OR='|',
|
|
|
|
|
INS_XOR='^',
|
2022-08-09 20:59:08 +03:00
|
|
|
INS_COUT='.', /* character output */
|
|
|
|
|
INS_NHOUT='H', /* numeric hex output */
|
2022-08-07 22:20:48 +03:00
|
|
|
INS_NBKIN=',', /* non-blocking key input */
|
|
|
|
|
INS_BKIN='?', /* blocking key input */
|
|
|
|
|
INS_PORTIO='P',
|
|
|
|
|
INS_PERSIST_WRITE='}',
|
|
|
|
|
INS_PERSIST_READ='{',
|
|
|
|
|
INS_QUIT='Q'
|
|
|
|
|
};
|
|
|
|
|
|
2022-08-11 15:36:39 +03:00
|
|
|
/* 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 */
|
|
|
|
|
};
|
|
|
|
|
|
2022-08-07 22:20:48 +03:00
|
|
|
/* push a value onto the main stack */
|
|
|
|
|
void pushMain(ushort val) {
|
2022-08-13 18:48:31 +03:00
|
|
|
curtask->main_stack[curtask->msp++] = val;
|
|
|
|
|
if(curtask->msp >= STACK_SIZE_WORDS)
|
2022-08-07 22:20:48 +03:00
|
|
|
trapout(STACK_OVERFLOW);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* push a value onto the return stack */
|
|
|
|
|
void pushRet(ushort val) {
|
2022-08-13 18:48:31 +03:00
|
|
|
curtask->return_stack[curtask->rsp++] = val;
|
|
|
|
|
if(curtask->rsp >= STACK_SIZE_WORDS)
|
2022-08-07 22:20:48 +03:00
|
|
|
trapout(STACK_OVERFLOW);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* pop a value from the main stack */
|
|
|
|
|
ushort popMain() {
|
2022-08-13 18:48:31 +03:00
|
|
|
if(curtask->msp == 0) {
|
2022-08-07 22:20:48 +03:00
|
|
|
trapout(STACK_UNDERFLOW);
|
2022-08-09 07:47:50 +03:00
|
|
|
return 0;
|
|
|
|
|
}
|
2022-08-07 22:20:48 +03:00
|
|
|
else
|
2022-08-13 18:48:31 +03:00
|
|
|
return curtask->main_stack[--curtask->msp];
|
2022-08-07 22:20:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* pop a value from the return stack */
|
|
|
|
|
ushort popRet() {
|
2022-08-13 18:48:31 +03:00
|
|
|
if(curtask->rsp == 0) {
|
2022-08-07 22:20:48 +03:00
|
|
|
trapout(STACK_UNDERFLOW);
|
2022-08-09 07:47:50 +03:00
|
|
|
return 0;
|
|
|
|
|
}
|
2022-08-07 22:20:48 +03:00
|
|
|
else
|
2022-08-13 18:48:31 +03:00
|
|
|
return curtask->return_stack[--curtask->rsp];
|
2022-08-07 22:20:48 +03:00
|
|
|
}
|
|
|
|
|
|
2022-08-09 20:59:08 +03:00
|
|
|
/* push a character to the literal stack */
|
2022-08-07 22:20:48 +03:00
|
|
|
void pushLit(uchar c) {
|
2022-08-13 18:48:31 +03:00
|
|
|
curtask->literal_stack[curtask->lsp++] = c;
|
|
|
|
|
if(curtask->lsp >= LIT_STACK_SIZE)
|
2022-08-09 20:59:08 +03:00
|
|
|
trapout(STACK_OVERFLOW);
|
2022-08-07 22:20:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* pop a character from the literal stack */
|
|
|
|
|
uchar popLit() {
|
2022-08-13 18:48:31 +03:00
|
|
|
if(curtask->lsp == 0) {
|
2022-08-07 22:20:48 +03:00
|
|
|
trapout(STACK_UNDERFLOW);
|
2022-08-09 07:47:50 +03:00
|
|
|
return 0;
|
|
|
|
|
}
|
2022-08-07 22:20:48 +03:00
|
|
|
else
|
2022-08-13 18:48:31 +03:00
|
|
|
return curtask->literal_stack[--curtask->lsp];
|
2022-08-07 22:20:48 +03:00
|
|
|
}
|
|
|
|
|
|
2022-08-09 20:59:08 +03:00
|
|
|
/* convert ASCII code of a hex digit to the digit value */
|
|
|
|
|
uchar a2d(uchar a) {
|
|
|
|
|
return (a < 0x3aU) ? (a - 0x30U) : (a - 55U);
|
2022-08-08 11:07:24 +03:00
|
|
|
}
|
2022-08-07 22:20:48 +03:00
|
|
|
|
2022-08-10 16:49:46 +03:00
|
|
|
/* 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;
|
2022-08-13 18:48:31 +03:00
|
|
|
if(curtask->lsp < 4U) thr = curtask->lsp;
|
2022-08-10 16:49:46 +03:00
|
|
|
for(i=0;i<thr;++i)
|
|
|
|
|
p[3-i] = a2d(popLit());
|
|
|
|
|
pushMain((p[0]<<12U) | (p[1]<<8U) | (p[2]<<4U) | p[3]);
|
2022-08-13 18:48:31 +03:00
|
|
|
curtask->lsp = 0; /* clear the literal stack */
|
2022-08-07 22:20:48 +03:00
|
|
|
}
|
|
|
|
|
|
2022-08-09 07:47:50 +03:00
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-13 08:46:25 +03:00
|
|
|
/* Short endianness conversion helper */
|
|
|
|
|
ushort tobig(ushort val) {
|
|
|
|
|
return (val>>8)|((val&255)<<8);
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-13 18:48:31 +03:00
|
|
|
/* Task-based memory address jump checker */
|
|
|
|
|
uchar taskMemJumpAllowed(addr) {
|
|
|
|
|
/* task 0 is allowed to jump anywhere in the command buffer, others only in their own zone */
|
|
|
|
|
if(curtask->id)
|
|
|
|
|
return (addr >= curtask->cmd_start) && (addr < curtask->cmd_start + curtask->cmd_size);
|
|
|
|
|
else
|
|
|
|
|
return addr >= ram.cmd_start;
|
|
|
|
|
}
|
2022-08-09 20:59:08 +03:00
|
|
|
|
2022-08-13 18:48:31 +03:00
|
|
|
/* Task-based memory address write checker */
|
|
|
|
|
uchar taskMemWriteAllowed(addr) {
|
|
|
|
|
/* task 0 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 */
|
2022-08-13 10:58:02 +03:00
|
|
|
ushort persistOp(FILE *pfd, ushort maddr, ushort dataLen, ushort blk, uchar isWrite) {
|
2022-08-12 11:26:34 +03:00
|
|
|
ushort status = 0, proc;
|
|
|
|
|
if(pfd) {
|
2022-08-13 10:58:02 +03:00
|
|
|
fseek(pfd, ((unsigned long) blk << 10), SEEK_SET); /* blocks are 1K-aligned */
|
2022-08-12 11:26:34 +03:00
|
|
|
if(isWrite) /* writing to the persistent area from the memory */
|
|
|
|
|
proc = (ushort) fwrite(&flatram[maddr], 1, dataLen, pfd);
|
2022-08-13 11:49:03 +03:00
|
|
|
else { /* reading from the persistent area into the memory */
|
2022-08-13 18:48:31 +03:00
|
|
|
if(!taskMemWriteAllowed(maddr))
|
2022-08-13 11:49:03 +03:00
|
|
|
trapout(RESTRICTED_WRITE_ERROR);
|
2022-08-12 11:26:34 +03:00
|
|
|
proc = (ushort) fread(&flatram[maddr], 1, dataLen, pfd);
|
2022-08-13 11:49:03 +03:00
|
|
|
}
|
2022-08-12 11:26:34 +03:00
|
|
|
if(proc != dataLen)
|
|
|
|
|
status = PERSIST_IO_ERROR;
|
|
|
|
|
}
|
|
|
|
|
pushMain(status);
|
2022-08-09 21:34:33 +03:00
|
|
|
return 0;
|
2022-08-09 20:59:08 +03:00
|
|
|
}
|
|
|
|
|
|
2022-08-11 15:36:39 +03:00
|
|
|
/* Port I/O handler */
|
2022-08-12 11:26:34 +03:00
|
|
|
void portIO(ushort port, ushort p2, ushort p1) {
|
2022-08-11 15:36:39 +03:00
|
|
|
ushort r1 = 0, r2 = 0, status = 0;
|
|
|
|
|
switch(port) {
|
|
|
|
|
case PORT_ECHO:
|
|
|
|
|
r1 = p1;
|
|
|
|
|
r2 = p2;
|
|
|
|
|
break;
|
|
|
|
|
case PORT_RANDOM:
|
|
|
|
|
r1 = (ushort) (p1 + (rand()%p2));
|
|
|
|
|
r2 = (ushort) (p1 + (rand()%p2));
|
|
|
|
|
break;
|
|
|
|
|
case PORT_CHECKSUM:
|
|
|
|
|
r1 = crc16(&flatram[p1], p2);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
fprintf(stderr, "[PORTIO] Unimplemented call to port 0x%X with P1=%X and P2=%X, returning status=0, R1=0, R2=0\n", port, p1, p2);
|
|
|
|
|
}
|
|
|
|
|
pushMain(r1);
|
|
|
|
|
pushMain(r2);
|
|
|
|
|
pushMain(status);
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-13 23:19:55 +03:00
|
|
|
/* 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(!ram.tasks[ram.taskid].active) {
|
|
|
|
|
++ram.taskid;
|
|
|
|
|
if(ram.taskid == EQUI_TASKS_MAX) ram.taskid = 0;
|
|
|
|
|
if(ram.taskid == oldtaskid) /* found no active tasks, bailing out */
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
return &ram.tasks[ram.taskid];
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-13 18:48:31 +03:00
|
|
|
/* Task loader method */
|
|
|
|
|
struct EquiCtx* equi_load_task(ushort progStart, ushort len) {
|
2022-08-13 23:19:55 +03:00
|
|
|
ushort tid = equi_find_free_task_slot();
|
|
|
|
|
struct EquiCtx *taskptr = &ram.tasks[tid]; /* refer to the next available entry */
|
2022-08-13 18:48:31 +03:00
|
|
|
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 */
|
2022-08-13 23:19:55 +03:00
|
|
|
taskptr->id = tid; /* assign task ID */
|
2022-08-13 18:48:31 +03:00
|
|
|
taskptr->gpd_start = (ushort) (taskptr->gpd - flatram); /* assign GPD area start */
|
2022-08-13 19:05:25 +03:00
|
|
|
taskptr->CM = 0; /* unset compilation mode flag */
|
2022-08-13 18:48:31 +03:00
|
|
|
return taskptr;
|
|
|
|
|
}
|
2022-08-07 22:20:48 +03:00
|
|
|
|
2022-08-13 18:48:31 +03:00
|
|
|
/* Main interpreter loop */
|
2022-08-07 22:20:48 +03:00
|
|
|
void equi_main_loop() {
|
2022-08-11 21:05:38 +03:00
|
|
|
uchar instr;
|
2022-08-09 20:59:08 +03:00
|
|
|
ushort lhash, pbuf, pbuf2;
|
2022-08-12 11:26:34 +03:00
|
|
|
/* try to open the persistent sandbox file */
|
|
|
|
|
FILE *pfd = fopen(PERSIST_FILE, "r+b");
|
2022-08-07 22:20:48 +03:00
|
|
|
while(1) { /* iterate over the instructions in the command buffer */
|
2022-08-13 23:19:55 +03:00
|
|
|
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 */
|
2022-08-13 18:48:31 +03:00
|
|
|
instr = flatram[++curtask->pc];
|
2022-08-10 17:53:10 +03:00
|
|
|
/* silently exit on zero or FF */
|
2022-08-13 23:19:55 +03:00
|
|
|
if(instr == 0 || instr == 0xFFu)
|
|
|
|
|
curtask->active = 0;
|
2022-08-07 22:20:48 +03:00
|
|
|
/* first, check for II mode */
|
|
|
|
|
if(ram.II) {
|
2022-08-08 11:07:24 +03:00
|
|
|
if(instr == INS_IIEND)
|
2022-08-07 22:20:48 +03:00
|
|
|
ram.II = 0; /* unset instruction ignore mode flag */
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* then, check for compilation mode */
|
2022-08-13 19:05:25 +03:00
|
|
|
if(curtask->CM) {
|
2022-08-08 11:07:24 +03:00
|
|
|
if(instr == INS_CMEND) { /* trigger word compilation logic as per the spec */
|
2022-08-13 18:48:31 +03:00
|
|
|
if(curtask->lsp < 1)
|
2022-08-09 07:47:50 +03:00
|
|
|
trapout(STACK_UNDERFLOW);
|
2022-08-13 18:48:31 +03:00
|
|
|
flatram[curtask->pc] = INS_RET; /* in-place patch this instruction to R */
|
2022-08-09 07:47:50 +03:00
|
|
|
/* hash and save compiled word */
|
2022-08-13 18:48:31 +03:00
|
|
|
curtask->clt[curtask->cltp].nhash = crc16(&(curtask->literal_stack[0]), curtask->lsp);
|
|
|
|
|
curtask->lsp = 0; /* clear the literal stack */
|
2022-08-13 19:05:25 +03:00
|
|
|
curtask->clt[curtask->cltp].loc = curtask->cbp; /* stored the compiled code location from the most recent CBP value */
|
2022-08-13 18:48:31 +03:00
|
|
|
++curtask->cltp; /* increase the word */
|
|
|
|
|
if(curtask->cltp == CLT_ENTRIES_MAX)
|
2022-08-07 22:20:48 +03:00
|
|
|
trapout(CLT_OVERFLOW);
|
2022-08-13 19:05:25 +03:00
|
|
|
curtask->CM = 0; /* unset compilation mode flag */
|
2022-08-07 22:20:48 +03:00
|
|
|
}
|
|
|
|
|
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 */
|
2022-08-13 18:48:31 +03:00
|
|
|
if(curtask->lsp > 0 && instr != INS_LITSTR && instr != INS_LITCALL && instr != INS_LITINT && instr != INS_CMSTART)
|
2022-08-10 16:49:46 +03:00
|
|
|
pushLitVal();
|
2022-08-08 11:07:24 +03:00
|
|
|
switch(instr) { /* then perform all main interpretation logic */
|
|
|
|
|
case INS_CMSTART: /* compilation start */
|
2022-08-13 19:05:25 +03:00
|
|
|
curtask->cbp = curtask->pc + 1U; /* save CBP */
|
|
|
|
|
curtask->CM = 1U; /* raise CM flag */
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case CR:
|
|
|
|
|
case LF:
|
2022-08-09 21:34:33 +03:00
|
|
|
case '\t':
|
2022-08-09 20:59:08 +03:00
|
|
|
case ' ': /* all nops in interpretation mode */
|
|
|
|
|
break;
|
2022-08-13 23:19:55 +03:00
|
|
|
case INS_QUIT: /* gracefully quit the task */
|
|
|
|
|
curtask->active = 0;
|
|
|
|
|
break;
|
2022-08-08 11:07:24 +03:00
|
|
|
case INS_LITINT: /* literal stack -> main stack as short */
|
2022-08-10 16:49:46 +03:00
|
|
|
pushLitVal();
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_LITSTR: /* literal stack -> each char at main stack as short */
|
2022-08-13 18:48:31 +03:00
|
|
|
while(curtask->lsp)
|
2022-08-09 20:59:08 +03:00
|
|
|
pushMain((ushort)popLit());
|
2022-08-13 18:48:31 +03:00
|
|
|
curtask->lsp = 0;
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 07:47:50 +03:00
|
|
|
case INS_LITCALL: /* call the saved word from the literal */
|
2022-08-13 18:48:31 +03:00
|
|
|
if(curtask->lsp < 1)
|
2022-08-09 07:47:50 +03:00
|
|
|
trapout(STACK_UNDERFLOW);
|
2022-08-13 18:48:31 +03:00
|
|
|
lhash = crc16(&(curtask->literal_stack[0]), curtask->lsp);
|
2022-08-09 07:47:50 +03:00
|
|
|
for(pbuf=0;pbuf<CLT_ENTRIES_MAX;++pbuf)
|
2022-08-13 18:48:31 +03:00
|
|
|
if(curtask->clt[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 */
|
2022-08-09 07:47:50 +03:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if(pbuf >= CLT_ENTRIES_MAX)
|
|
|
|
|
trapout(INVALID_WORD);
|
2022-08-13 18:48:31 +03:00
|
|
|
curtask->lsp = 0; /* clear the literal stack */
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_RET: /* jump to the instruction at ret stack */
|
2022-08-13 18:48:31 +03:00
|
|
|
curtask->pc = popRet();
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_M2R: /* main -> ret stack */
|
|
|
|
|
pushRet(popMain());
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_R2M: /* ret -> main stack */
|
|
|
|
|
pushMain(popRet());
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_LOAD: /* mem -> main stack */
|
|
|
|
|
pbuf = popMain();
|
|
|
|
|
pushMain((flatram[pbuf] << 8)|flatram[pbuf+1U]);
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_STORE: /* main stack -> mem (word) */
|
|
|
|
|
pbuf = popMain();
|
|
|
|
|
pbuf2 = popMain();
|
2022-08-13 18:48:31 +03:00
|
|
|
if(!taskMemWriteAllowed(pbuf))
|
2022-08-13 11:49:03 +03:00
|
|
|
trapout(RESTRICTED_WRITE_ERROR);
|
2022-08-09 20:59:08 +03:00
|
|
|
flatram[pbuf] = pbuf2 >> 8U;
|
|
|
|
|
flatram[pbuf + 1U] = pbuf2 & 255U;
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_STOREBYTE: /* main stack -> mem (byte) */
|
|
|
|
|
pbuf = popMain();
|
|
|
|
|
flatram[pbuf] = popMain() & 255U;
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_DROP: /* ( a -- ) */
|
|
|
|
|
pbuf = popMain();
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_DUP: /* ( a -- a a ) */
|
2022-08-13 18:48:31 +03:00
|
|
|
if(curtask->msp < 1)
|
2022-08-09 20:59:08 +03:00
|
|
|
trapout(STACK_UNDERFLOW);
|
2022-08-13 18:48:31 +03:00
|
|
|
pushMain(curtask->main_stack[curtask->msp-1]);
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_SWAP: /* ( a b -- b a ) */
|
2022-08-13 18:48:31 +03:00
|
|
|
if(curtask->msp < 2)
|
2022-08-09 20:59:08 +03:00
|
|
|
trapout(STACK_UNDERFLOW);
|
2022-08-13 18:48:31 +03:00
|
|
|
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;
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_ROT: /* ( a b c -- b c a ) */
|
2022-08-13 18:48:31 +03:00
|
|
|
if(curtask->msp < 3)
|
2022-08-09 20:59:08 +03:00
|
|
|
trapout(STACK_UNDERFLOW);
|
2022-08-13 18:48:31 +03:00
|
|
|
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;
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_OVER: /* ( a b -- a b a ) */
|
2022-08-13 18:48:31 +03:00
|
|
|
if(curtask->msp < 2)
|
2022-08-09 20:59:08 +03:00
|
|
|
trapout(STACK_UNDERFLOW);
|
2022-08-13 18:48:31 +03:00
|
|
|
pushMain(curtask->main_stack[curtask->msp-2]);
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_JUMP: /* unconditional signed jump: ( rel -- ) */
|
2022-08-13 18:48:31 +03:00
|
|
|
curtask->pc = (ushort) (curtask->pc + (signed short) popMain());
|
|
|
|
|
if(!taskMemJumpAllowed(curtask->pc))
|
|
|
|
|
trapout(OUT_OF_BOUNDS_JUMP);
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_IF: /* conditional signed jump if cond is not zero: ( cond rel -- ) */
|
|
|
|
|
pbuf = popMain(); /* reladdr */
|
|
|
|
|
pbuf2 = popMain(); /* cond */
|
2022-08-13 18:48:31 +03:00
|
|
|
if(pbuf2 != 0) {
|
|
|
|
|
curtask->pc = (ushort) (curtask->pc + (signed short) pbuf);
|
|
|
|
|
if(!taskMemJumpAllowed(curtask->pc))
|
|
|
|
|
trapout(OUT_OF_BOUNDS_JUMP);
|
|
|
|
|
}
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
|
|
|
|
case INS_EXPOINT: /* Locate execution point */
|
2022-08-13 18:48:31 +03:00
|
|
|
pushMain(curtask->pc + 1);
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-11 09:29:05 +03:00
|
|
|
case INS_GPDSTART: /* Locate GPD area start */
|
2022-08-13 18:48:31 +03:00
|
|
|
pushMain(curtask->gpd_start);
|
2022-08-11 09:29:05 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_GT: /* ( a b -- a>b ) */
|
|
|
|
|
pushMain((popMain() < popMain()) ? 1u : 0u);
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_LT: /* ( a b -- a<b ) */
|
|
|
|
|
pushMain((popMain() > popMain()) ? 1u : 0u);
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_EQ: /* ( a b -- a==b ) */
|
|
|
|
|
pushMain((popMain() == popMain()) ? 1u : 0u);
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_ADD: /* ( a b -- a+b ) */
|
|
|
|
|
pushMain((popMain() + popMain())&65535U);
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_SUB: /* ( a b -- a-b ) */
|
|
|
|
|
pushMain((-popMain() + popMain())&65535U);
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_MUL: /* ( a b -- a*b ) */
|
|
|
|
|
pushMain((ushort)(popMain() * popMain())&65535U);
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_DIV: /* ( a b -- a/b rem ) */
|
|
|
|
|
pbuf2 = popMain();
|
|
|
|
|
pbuf = popMain();
|
|
|
|
|
pushMain((ushort) pbuf/pbuf2);
|
|
|
|
|
pushMain((ushort) pbuf%pbuf2);
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_NEG: /* ( a -- -a ) */
|
|
|
|
|
pushMain((ushort)-popMain());
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_SHIFT: /* ( a XY -- [a >> X] << Y ) */
|
|
|
|
|
pbuf = popMain();
|
|
|
|
|
pushMain((ushort)(popMain() >> (pbuf&15U)) << (pbuf>>4U));
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_NOT: /* ( a -- ~a ) */
|
|
|
|
|
pushMain((ushort)(~popMain()&65535U));
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_AND: /* ( a b -- a&b ) */
|
|
|
|
|
pushMain(popMain() & popMain());
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_OR: /* ( a b -- a|b ) */
|
|
|
|
|
pushMain(popMain() | popMain());
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_XOR: /* ( a b -- a^b ) */
|
|
|
|
|
pushMain(popMain() ^ popMain());
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_COUT: /* output a character, no Unicode support for now */
|
|
|
|
|
cputc((uchar)(popMain()&255U));
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
case INS_NHOUT: /* output the hex number from the stack top */
|
|
|
|
|
printf("%04X", popMain());
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-09 20:59:08 +03:00
|
|
|
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());
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
2022-08-11 15:36:39 +03:00
|
|
|
case INS_PORTIO: /* ( p1 p2 port -- r1 r2 status ) - simulate/execute port I/O according to the spec */
|
2022-08-13 18:48:31 +03:00
|
|
|
if(curtask->msp < 3)
|
2022-08-11 15:36:39 +03:00
|
|
|
trapout(STACK_UNDERFLOW);
|
|
|
|
|
portIO(popMain(), popMain(), popMain());
|
2022-08-09 20:59:08 +03:00
|
|
|
break;
|
2022-08-13 10:58:02 +03:00
|
|
|
case INS_PERSIST_READ: /* ( blk len maddr -- status) */
|
|
|
|
|
pushMain(persistOp(pfd, popMain(), popMain(), popMain(), 0));
|
2022-08-09 20:59:08 +03:00
|
|
|
break;
|
2022-08-13 10:58:02 +03:00
|
|
|
case INS_PERSIST_WRITE: /* ( blk len maddr -- status) */
|
|
|
|
|
pushMain(persistOp(pfd, popMain(), popMain(), popMain(), 1));
|
2022-08-08 11:07:24 +03:00
|
|
|
break;
|
|
|
|
|
default: /* all characters not processed before are invalid instructions */
|
|
|
|
|
trapout(INVALID_INSTRUCTION);
|
2022-08-07 22:20:48 +03:00
|
|
|
}
|
2022-08-13 18:48:31 +03:00
|
|
|
if(curtask->msp >= STACK_SIZE_WORDS || curtask->rsp >= STACK_SIZE_WORDS) /* check for stack overflow after any operation */
|
2022-08-09 20:59:08 +03:00
|
|
|
trapout(STACK_OVERFLOW);
|
2022-08-07 22:20:48 +03:00
|
|
|
}
|
2022-08-12 11:26:34 +03:00
|
|
|
/* close the persistent sandbox file if open */
|
|
|
|
|
if(pfd) fclose(pfd);
|
2022-08-13 23:19:55 +03:00
|
|
|
/* reset IBP and exit */
|
|
|
|
|
ram.ibp = 65535U;
|
2022-08-07 22:20:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Equi VM entry point */
|
2022-08-06 00:08:17 +03:00
|
|
|
int main(int argc, char* argv[]) {
|
2022-08-13 07:40:48 +03:00
|
|
|
uchar instr, bc, smode = 0;
|
2022-08-13 08:46:25 +03:00
|
|
|
/* detect host endianness */
|
|
|
|
|
int host_islittle = 1;
|
|
|
|
|
host_islittle = (*((char *)&host_islittle) == 1) ? 1 : 0;
|
2022-08-07 22:20:48 +03:00
|
|
|
/* _attempt_ to disable buffering for char input/output */
|
2022-08-08 06:48:46 +03:00
|
|
|
#if !defined __CC65__ && !defined __TINYC__
|
2022-08-07 22:20:48 +03:00
|
|
|
setvbuf(stdin, NULL, _IONBF, 0);
|
|
|
|
|
setvbuf(stdout, NULL, _IONBF, 0);
|
|
|
|
|
#endif
|
2022-08-11 15:36:39 +03:00
|
|
|
/* initialize the PRNG */
|
|
|
|
|
srand((unsigned)time(NULL));
|
2022-08-06 09:34:32 +03:00
|
|
|
/* initialize the RAM in the most standard way */
|
2022-08-13 07:40:48 +03:00
|
|
|
ram.stack_size = STACK_SIZE_WORDS;
|
|
|
|
|
ram.literal_stack_size = LIT_STACK_SIZE;
|
2022-08-13 18:48:31 +03:00
|
|
|
ram.cmd_start = (uchar *)&ram.cmdbuf - (uchar *)&ram;
|
2022-08-06 09:34:32 +03:00
|
|
|
ram.cmd_size = CMD_BUF_SIZE;
|
2022-08-13 19:05:25 +03:00
|
|
|
ram.II = ram.MM = 0; /* reset all flags */
|
2022-08-13 07:40:48 +03:00
|
|
|
/* process command line params */
|
|
|
|
|
if(argc > 1 && argv[1][0] == 'm') /* enter minification mode, don't run the programs */
|
|
|
|
|
ram.MM = 1;
|
|
|
|
|
else if(argc > 1 && argv[1][0] == 's') /* enter silent mode, don't print the banners and prompts */
|
|
|
|
|
smode = 1;
|
2022-08-13 18:48:31 +03:00
|
|
|
/* Start input buffering from the start of command buffer (-1 because we use prefix increment) */
|
|
|
|
|
ram.ibp = 65535U;
|
2022-08-13 07:40:48 +03:00
|
|
|
if(!ram.MM && !smode) { /* skip the terminal init and the greeting if in minification or silent mode */
|
|
|
|
|
/* CC65-specific terminal init */
|
2022-08-11 23:23:02 +03:00
|
|
|
#ifdef __CC65__
|
2022-08-13 07:40:48 +03:00
|
|
|
clrscr();
|
|
|
|
|
bc = cursor(1);
|
2022-08-11 23:23:02 +03:00
|
|
|
#else /* VT100-compatible terminal init */
|
2022-08-13 07:40:48 +03:00
|
|
|
printf("\033c");
|
2022-08-11 23:23:02 +03:00
|
|
|
#endif
|
2022-08-13 18:48:31 +03:00
|
|
|
printf("Welcome to Equi v" EQUI_VER " by Luxferre, 2022\nStack size: %d bytes\nLiteral stack size: %d bytes\nCommand buffer: 0x%04X (%d bytes)\nEqui ready\n\n> ",
|
|
|
|
|
STACK_SIZE, LIT_STACK_SIZE, ram.cmd_start, ram.cmd_size);
|
2022-08-13 08:46:25 +03:00
|
|
|
}
|
|
|
|
|
|
2022-08-07 22:20:48 +03:00
|
|
|
while(1) { /* Now, we're in the command mode loop */
|
2022-08-11 11:43:05 +03:00
|
|
|
instr = cgetc(); /* Fetch the next instruction from the keyboard/stdin */
|
|
|
|
|
if(instr == 0xFFU || instr == 0U) /* exit on zero byte */
|
2022-08-10 17:53:10 +03:00
|
|
|
break;
|
2022-08-11 11:43:05 +03:00
|
|
|
else if(instr == BS || instr == CR || instr == LF || instr == ' ' || instr == '\t') { /* ignore the backspace or whitespaces */
|
2022-08-07 22:20:48 +03:00
|
|
|
#ifdef __CC65__
|
|
|
|
|
cputc(instr); /* echo it */
|
|
|
|
|
#endif
|
2022-08-11 11:43:05 +03:00
|
|
|
} else if(instr == INS_IISTART) { /* process II start */
|
2022-08-07 22:20:48 +03:00
|
|
|
#ifdef __CC65__
|
|
|
|
|
cputc(instr); /* echo it */
|
|
|
|
|
#endif
|
|
|
|
|
ram.II = 1;
|
2022-08-11 11:43:05 +03:00
|
|
|
} else if(instr == INS_IIEND) { /* process II end */
|
2022-08-07 22:20:48 +03:00
|
|
|
#ifdef __CC65__
|
|
|
|
|
cputc(instr); /* echo it */
|
|
|
|
|
#endif
|
|
|
|
|
ram.II = 0;
|
2022-08-11 19:11:18 +03:00
|
|
|
} else if(!ram.II && (instr == 0xFFU || instr == INS_QUIT)) {
|
2022-08-13 07:40:48 +03:00
|
|
|
if(ram.MM) { /* output command buffer contents to stdout and exit */
|
2022-08-11 19:11:18 +03:00
|
|
|
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 */
|
2022-08-12 12:52:45 +03:00
|
|
|
if(!smode) {
|
|
|
|
|
cputc(CR); /* echo CR */
|
|
|
|
|
cputc(LF); /* echo LF */
|
|
|
|
|
}
|
2022-08-11 19:11:18 +03:00
|
|
|
ram.cmdbuf[++ram.ibp] = INS_QUIT; /* end program with INS_QUIT */
|
2022-08-13 18:48:31 +03:00
|
|
|
curtask = equi_load_task((ushort)ram.cmd_start, ram.ibp + 1); /* load the code as task 0 */
|
2022-08-13 23:19:55 +03:00
|
|
|
ram.taskid = curtask->id; /* actualize the current task id */
|
|
|
|
|
curtask->active = 1; /*activate the current task */
|
2022-08-11 19:11:18 +03:00
|
|
|
equi_main_loop(); /* and run the interpreter loop */
|
2022-08-12 12:52:45 +03:00
|
|
|
if(!smode) {
|
|
|
|
|
cputc(CR); /* echo CR */
|
|
|
|
|
cputc(LF); /* echo LF */
|
|
|
|
|
cputc('>');
|
|
|
|
|
cputc(' ');
|
|
|
|
|
}
|
2022-08-11 19:11:18 +03:00
|
|
|
}
|
2022-08-11 11:43:05 +03:00
|
|
|
} 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;
|
2022-08-07 22:20:48 +03:00
|
|
|
#ifdef __CC65__
|
2022-08-11 11:43:05 +03:00
|
|
|
cputc(instr); /* echo it */
|
2022-08-07 22:20:48 +03:00
|
|
|
#endif
|
2022-08-11 11:43:05 +03:00
|
|
|
}
|
2022-08-07 22:20:48 +03:00
|
|
|
} /* command mode loop end */
|
2022-08-06 00:08:17 +03:00
|
|
|
return 0;
|
|
|
|
|
}
|