elks-enhanced
public
Read
Owner: themaster
Branch: master
Commits: 6893
Updated: 2026-04-19 00:15
Git CLI clone URL
git clone https://www.xt-emporium.com/git/elks-enhanced.git
Fullscreen desktop URL
Code
Commits
History
Branches
Bug Reports
Discussions
Compare
Settings
elks-enhanced
/
elkscmd
/
basic
/
basic.c
File editor
/* --------------------------------------------------------------------------- * Basic Interpreter * Robin Edwards 2014 * --------------------------------------------------------------------------- * This BASIC is modelled on Sinclair BASIC for the ZX81 and ZX Spectrum. It * should be capable of running most of the examples in the manual for both * machines, with the exception of anything machine specific (i.e. graphics, * sound & system variables). * * Notes * - All numbers (except line numbers) are floats internally * - Multiple commands are allowed per line, seperated by : * - LET is optional e.g. LET a = 6: b = 7 * - MOD provides the modulo operator which was missing from Sinclair BASIC. * Both numbers are first rounded to ints e.g. 5 mod 2 = 1 * - CONT can be used to continue from a STOP. It does not continue from any * other error condition. * - Arrays can be any dimension. There is no single char limit to names. * - Like Sinclair BASIC, DIM a(10) and LET a = 5 refer to different 'a's. * One is a simple variable, the other is an array. There is no ambiguity * since the one being referred to is always clear from the context. * - String arrays differ from Sinclair BASIC. DIM a$(5,5) makes an array * of 25 strings, which can be any length. e.g. LET a$(1,1)="long string" * - functions like LEN, require brackets e.g. LEN(a$) * - String manipulation functions are LEFT$, MID$, RIGHT$ * - RND is a nonary operator not a function i.e. RND not RND() * - PRINT AT x,y ... is replaced by POSITION x,y : PRINT ... * - LIST takes an optional start and end e.g. LIST 1,100 or LIST 50 * - INKEY$ reads the last key pressed from the keyboard, or an empty string * if no key pressed. The (single key) buffer is emptied after the call. * e.g. a$ = INKEY$ * - LOAD/SAVE load and save the current program to the EEPROM (1k limit). * SAVE+ will set the auto-run flag, which loads the program automatically * on boot. With a filename e.g. SAVE "test" saves to an external EEPROM. * - DIR/DELETE "filename" - list and remove files from external EEPROM. * - PINMODE <pin>, <mode> - sets the pin mode (0=input, 1=output, 2=pullup) * - PIN <pin>, <state> - sets the pin high (non zero) or low (zero) * - PINREAD(pin) returns pin value, ANALOGRD(pin) for analog pins * --------------------------------------------------------------------------- * * From Arduino BASIC https://github.com/robinhedwards/ArduinoBASIC * Apr 2022 ported to Linux, macOS & ELKS Greg Haerr */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <float.h> #include <limits.h> #include <math.h> #include "host.h" #include "basic.h" int sysPROGEND; int sysSTACKSTART, sysSTACKEND; int sysVARSTART, sysVAREND; int sysGOSUBSTART, sysGOSUBEND; const char string_0[] PROGMEM = "OK"; const char string_1[] PROGMEM = "Bad number"; const char string_2[] PROGMEM = "Line too long"; const char string_3[] PROGMEM = "Unexpected input"; const char string_4[] PROGMEM = "Unterminated string"; const char string_5[] PROGMEM = "Missing bracket"; const char string_6[] PROGMEM = "Error in expr"; const char string_7[] PROGMEM = "Numeric expr expected"; const char string_8[] PROGMEM = "String expr expected"; const char string_9[] PROGMEM = "Line number too big"; const char string_10[] PROGMEM = "Out of memory"; const char string_11[] PROGMEM = "Div by zero"; const char string_12[] PROGMEM = "Variable not found"; const char string_13[] PROGMEM = "Bad command"; const char string_14[] PROGMEM = "Bad line number"; const char string_15[] PROGMEM = "Break pressed"; const char string_16[] PROGMEM = "NEXT without FOR"; const char string_17[] PROGMEM = "STOP statement"; const char string_18[] PROGMEM = "Missing THEN in IF"; const char string_19[] PROGMEM = "RETURN without GOSUB"; const char string_20[] PROGMEM = "Wrong array dims"; const char string_21[] PROGMEM = "Bad array index"; const char string_22[] PROGMEM = "Bad string index"; const char string_23[] PROGMEM = "Error in VAL input"; const char string_24[] PROGMEM = "Bad parameter"; const char string_25[] PROGMEM = "End of file"; const char string_26[] PROGMEM = "File error"; const char string_27[] PROGMEM = "Function not builtin"; const char string_28[] PROGMEM = "Wrong number of function arguments"; const char string_29[] PROGMEM = "No DATA for READ"; const char* const errorTable[] PROGMEM = { string_0, string_1, string_2, string_3, string_4, string_5, string_6, string_7, string_8, string_9, string_10, string_11, string_12, string_13, string_14, string_15, string_16, string_17, string_18, string_19, string_20, string_21, string_22, string_23, string_24, string_25, string_26, string_27, string_28, string_29 }; // Token flags // bits 1+2 number of arguments #define TKN_ARGS_NUM_MASK 0x03 // bit 3 return type (set if string) #define TKN_RET_TYPE_STR 0x04 // bits 4-6 argument type (set if string) #define TKN_ARG1_TYPE_STR 0x08 #define TKN_ARG2_TYPE_STR 0x10 #define TKN_ARG3_TYPE_STR 0x20 #define TKN_ARG_MASK 0x38 #define TKN_ARG_SHIFT 3 // bits 7,8 formatting #define TKN_FMT_POST 0x40 #define TKN_FMT_PRE 0x80 PROGMEM const TokenTableEntry tokenTable[] = { {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {"(", 0}, {")",0}, {"+",0}, {"-",0}, {"*",0}, {"/",0}, {"=",0}, {">",0}, {"<",0}, {"<>",0}, {">=",0}, {"<=",0}, {":",TKN_FMT_POST}, {";",0}, {",",0}, {"AND",TKN_FMT_PRE|TKN_FMT_POST}, {"OR",TKN_FMT_PRE|TKN_FMT_POST}, {"NOT",TKN_FMT_POST}, {"PRINT",TKN_FMT_POST}, {"LET",TKN_FMT_POST}, {"LIST",TKN_FMT_POST}, {"RUN",TKN_FMT_POST}, {"GOTO",TKN_FMT_POST}, {"REM",TKN_FMT_POST}, {"STOP",TKN_FMT_POST}, {"INPUT",TKN_FMT_POST}, {"CONT",TKN_FMT_POST}, {"IF",TKN_FMT_POST}, {"THEN",TKN_FMT_PRE|TKN_FMT_POST}, {"LEN",1|TKN_ARG1_TYPE_STR}, {"VAL",1|TKN_ARG1_TYPE_STR}, {"RND",0}, {"INT",1}, {"STR$",1|TKN_RET_TYPE_STR}, {"FOR",TKN_FMT_POST}, {"TO",TKN_FMT_PRE|TKN_FMT_POST}, {"STEP",TKN_FMT_PRE|TKN_FMT_POST}, {"NEXT", TKN_FMT_POST}, {"MOD",TKN_FMT_PRE|TKN_FMT_POST}, {"NEW",TKN_FMT_POST}, {"GOSUB",TKN_FMT_POST}, {"RETURN",TKN_FMT_POST}, {"DIM",TKN_FMT_POST}, {"LEFT$",2|TKN_ARG1_TYPE_STR|TKN_RET_TYPE_STR}, {"RIGHT$",2|TKN_ARG1_TYPE_STR|TKN_RET_TYPE_STR}, {"MID$",3|TKN_ARG1_TYPE_STR|TKN_RET_TYPE_STR}, {"CLS",TKN_FMT_POST}, {"PAUSE",TKN_FMT_POST}, {"POSITION",TKN_FMT_POST}, {"PIN",TKN_FMT_POST}, {"PINMODE",TKN_FMT_POST}, {"INKEY$",0}, {"SAVE",TKN_FMT_POST}, {"LOAD",TKN_FMT_POST}, {"PINREAD",1}, {"ANALOGRD",1}, {"DIR",TKN_FMT_POST}, {"DELETE",TKN_FMT_POST}, {"PI",0}, {"ABS",1}, {"COS",1}, {"SIN",1}, {"TAN",1}, {"ACS",1}, {"ASN",1}, {"ATN",1}, {"EXP",1}, {"LN",1}, {"POW",2}, {"CHR$",1|TKN_RET_TYPE_STR}, {"CODE",1|TKN_ARG1_TYPE_STR}, {"DATA",TKN_FMT_POST}, {"READ",TKN_FMT_POST}, {"RESTORE",TKN_FMT_POST}, {"MODE",TKN_FMT_POST}, {"COLOR",TKN_FMT_POST}, {"PLOT",TKN_FMT_POST}, {"DRAW",TKN_FMT_POST}, {"CIRCLE",TKN_FMT_POST}, {"INPB", 1}, {"INPW", 1}, {"OUTB", TKN_FMT_POST}, {"OUTW", TKN_FMT_POST}, {"HEX$",1|TKN_RET_TYPE_STR}, {"PEEK", 2}, {"POKE", TKN_FMT_POST}, }; /* ************************************************************************** * PROGRAM FUNCTIONS * **************************************************************************/ void printTokens(unsigned char *p) { int modeREM = 0; while (*p != TOKEN_EOL) { if (*p == TOKEN_IDENT) { p++; while (*p < 0x80) host_outputChar(*p++); host_outputChar((*p++)-0x80); } else if (*p == TOKEN_NUMBER) { p++; host_outputFloat(*(float*)p); p+=sizeof(float); } else if (*p == TOKEN_INTEGER) { p++; host_outputLong(*(long*)p); p+=sizeof(long); } else if (*p == TOKEN_STRING) { p++; if (modeREM) { host_outputString((char*)p); p+=1 + strlen((char*)p); } else { host_outputChar('\"'); while (*p) { if (*p == '\"') host_outputChar('\"'); host_outputChar(*p++); } host_outputChar('\"'); p++; } } else { uint8_t fmt = pgm_read_byte_near(tokenTable[*p].format); if (fmt & TKN_FMT_PRE) host_outputChar(' '); host_outputString(pgm_read_word(tokenTable[*p].token)); if (fmt & TKN_FMT_POST) host_outputChar(' '); if (*p==TOKEN_REM) modeREM = 1; p++; } } } void listProg(uint16_t first, uint16_t last) { unsigned char *p = &mem[0]; while (p < &mem[sysPROGEND]) { uint16_t lineNum = *(uint16_t*)(p+2); if ((!first || lineNum >= first) && (!last || lineNum <= last)) { host_outputLong(lineNum); host_outputChar(' '); printTokens(p+4); host_newLine(); } p+= *(uint16_t *)p; } } unsigned char *findProgLine(uint16_t targetLineNumber) { unsigned char *p = &mem[0]; while (p < &mem[sysPROGEND]) { uint16_t lineNum = *(uint16_t*)(p+2); if (lineNum >= targetLineNumber) break; p+= *(uint16_t *)p; } return p; } void deleteProgLine(unsigned char *p) { uint16_t lineLen = *(uint16_t*)p; sysPROGEND -= lineLen; memmove(p, p+lineLen, &mem[sysPROGEND] - p); } int doProgLine(uint16_t lineNumber, unsigned char* tokenPtr, int tokensLength) { // find line of the at or immediately after the number unsigned char *p = findProgLine(lineNumber); uint16_t foundLine = 0; if (p < &mem[sysPROGEND]) foundLine = *(uint16_t*)(p+2); // if there's a line matching this one - delete it if (foundLine == lineNumber) deleteProgLine(p); // now check to see if this is an empty line, if so don't insert it if (*tokenPtr == TOKEN_EOL) return 1; // we now need to insert the new line at p int bytesNeeded = 4 + tokensLength; // length, linenum + tokens if (sysPROGEND + bytesNeeded > sysVARSTART) return 0; // make room if this isn't the last line if (foundLine) memmove(p + bytesNeeded, p, &mem[sysPROGEND] - p); *(uint16_t *)p = bytesNeeded; p += 2; *(uint16_t *)p = lineNumber; p += 2; memcpy(p, tokenPtr, tokensLength); sysPROGEND += bytesNeeded; return 1; } /* ************************************************************************** * CALCULATOR STACK FUNCTIONS * **************************************************************************/ // Calculator stack starts at the start of memory after the program // and grows towards the end // contains either floats or null-terminated strings with the length on the end int stackPushNum(float val) { if (sysSTACKEND + sizeof(float) > sysVARSTART) return 0; // out of memory unsigned char *p = &mem[sysSTACKEND]; *(float *)p = val; sysSTACKEND += sizeof(float); return 1; } float stackPopNum() { sysSTACKEND -= sizeof(float); unsigned char *p = &mem[sysSTACKEND]; return *(float *)p; } int stackPushStr(char *str) { int len = 1 + strlen(str); if (sysSTACKEND + len + 2 > sysVARSTART) return 0; // out of memory unsigned char *p = &mem[sysSTACKEND]; strcpy((char*)p, str); p += len; *(uint16_t *)p = len; sysSTACKEND += len + 2; return 1; } char *stackGetStr() { // returns string without popping it unsigned char *p = &mem[sysSTACKEND]; int len = *(uint16_t *)(p-2); return (char *)(p-len-2); } char *stackPopStr() { unsigned char *p = &mem[sysSTACKEND]; int len = *(uint16_t *)(p-2); sysSTACKEND -= (len+2); return (char *)&mem[sysSTACKEND]; } void stackAdd2Strs() { // equivalent to popping 2 strings, concatenating them and pushing the result unsigned char *p = &mem[sysSTACKEND]; int str2len = *(uint16_t *)(p-2); sysSTACKEND -= (str2len+2); char *str2 = (char*)&mem[sysSTACKEND]; p = &mem[sysSTACKEND]; int str1len = *(uint16_t *)(p-2); sysSTACKEND -= (str1len+2); char *str1 = (char*)&mem[sysSTACKEND]; p = &mem[sysSTACKEND]; // shift the second string up (overwriting the null terminator of the first string) memmove(str1 + str1len - 1, str2, str2len); // write the length and update stackend int newLen = str1len + str2len - 1; p += newLen; *(uint16_t *)p = newLen; sysSTACKEND += newLen + 2; } // mode 0 = LEFT$, 1 = RIGHT$ void stackLeftOrRightStr(int len, int mode) { // equivalent to popping the current string, doing the operation then pushing it again unsigned char *p = &mem[sysSTACKEND]; int strlen = *(uint16_t *)(p-2); len++; // include trailing null if (len > strlen) len = strlen; if (len == strlen) return; // nothing to do sysSTACKEND -= (strlen+2); p = &mem[sysSTACKEND]; if (mode == 0) { // truncate the string on the stack *(p+len-1) = 0; } else { // copy the rightmost characters memmove(p, p + strlen - len, len); } // write the length and update stackend p += len; *(uint16_t *)p = len; sysSTACKEND += len + 2; } void stackMidStr(int start, int len) { // equivalent to popping the current string, doing the operation then pushing it again unsigned char *p = &mem[sysSTACKEND]; int strlen = *(uint16_t *)(p-2); len++; // include trailing null if (start > strlen) start = strlen; start--; // basic strings start at 1 if (start + len > strlen) len = strlen - start; if (len == strlen) return; // nothing to do sysSTACKEND -= (strlen+2); p = &mem[sysSTACKEND]; // copy the characters memmove(p, p + start, len-1); *(p+len-1) = 0; // write the length and update stackend p += len; *(uint16_t *)p = len; sysSTACKEND += len + 2; } /* ************************************************************************** * VARIABLE TABLE FUNCTIONS * **************************************************************************/ // Variable table starts at the end of memory and grows towards the start // Simple variable // table +--------+-------+-----------------+-----------------+ . . . // <--- | len | type | name | value | // grows | 2bytes | 1byte | null terminated | float/string | // +--------+-------+-----------------+-----------------+ . . . // // Array // +--------+-------+-----------------+----------+-------+ . . .+-------+-------------+. . // | len | type | name | num dims | dim1 | | dimN | elem(1,..1) | // | 2bytes | 1byte | null terminated | 2bytes | 2bytes| | 2bytes| float | // +--------+-------+-----------------+----------+-------+ . . .+-------+-------------+. . // variable type byte #define VAR_TYPE_NUM 0x1 #define VAR_TYPE_FORNEXT 0x2 #define VAR_TYPE_NUM_ARRAY 0x4 #define VAR_TYPE_STRING 0x8 #define VAR_TYPE_STR_ARRAY 0x10 unsigned char *findVariable(char *searchName, int searchMask) { unsigned char *p = &mem[sysVARSTART]; while (p < &mem[sysVAREND]) { int type = *(p+2); if (type & searchMask) { unsigned char *name = p+3; if (strcmp((char*)name, searchName) == 0) return p; } p+= *(uint16_t *)p; } return NULL; } void deleteVariableAt(unsigned char *pos) { int len = *(uint16_t *)pos; if (pos == &mem[sysVARSTART]) { sysVARSTART += len; return; } memmove(&mem[sysVARSTART] + len, &mem[sysVARSTART], pos - &mem[sysVARSTART]); sysVARSTART += len; } // todo - consistently return errors rather than 1 or 0? int storeNumVariable(char *name, float val) { // these can be modified in place int nameLen = strlen(name); unsigned char *p = findVariable(name, VAR_TYPE_NUM|VAR_TYPE_FORNEXT); if (p != NULL) { // replace the old value // (could either be VAR_TYPE_NUM or VAR_TYPE_FORNEXT) p += 3; // len + type; p += nameLen + 1; *(float *)p = val; } else { // allocate a new variable int bytesNeeded = 3; // len + flags bytesNeeded += nameLen + 1; // name bytesNeeded += sizeof(float); // val if (sysVARSTART - bytesNeeded < sysSTACKEND) return 0; // out of memory sysVARSTART -= bytesNeeded; unsigned char *p = &mem[sysVARSTART]; *(uint16_t *)p = bytesNeeded; p += 2; *p++ = VAR_TYPE_NUM; strcpy((char*)p, name); p += nameLen + 1; *(float *)p = val; } return 1; } int storeForNextVariable(char *name, float start, float step, float end, uint16_t lineNum, uint16_t stmtNum) { int nameLen = strlen(name); int bytesNeeded = 3; // len + flags bytesNeeded += nameLen + 1; // name bytesNeeded += 3 * sizeof(float); // vals bytesNeeded += 2 * sizeof(uint16_t); // unlike simple numeric variables, these are reallocated if they already exist // since the existing value might be a simple variable or a for/next variable unsigned char *p = findVariable(name, VAR_TYPE_NUM|VAR_TYPE_FORNEXT); if (p != NULL) { // check there will actually be room for the new value uint16_t oldVarLen = *(uint16_t*)p; if (sysVARSTART - (bytesNeeded - oldVarLen) < sysSTACKEND) return 0; // not enough memory deleteVariableAt(p); } if (sysVARSTART - bytesNeeded < sysSTACKEND) return 0; // out of memory sysVARSTART -= bytesNeeded; p = &mem[sysVARSTART]; *(uint16_t *)p = bytesNeeded; p += 2; *p++ = VAR_TYPE_FORNEXT; strcpy((char*)p, name); p += nameLen + 1; *(float *)p = start; p += sizeof(float); *(float *)p = step; p += sizeof(float); *(float *)p = end; p += sizeof(float); *(uint16_t *)p = lineNum; p += sizeof(uint16_t); *(uint16_t *)p = stmtNum; return 1; } int storeStrVariable(char *name, char *val) { int nameLen = strlen(name); int valLen = strlen(val); int bytesNeeded = 3; // len + type bytesNeeded += nameLen + 1; // name bytesNeeded += valLen + 1; // val // strings and arrays are re-allocated if they already exist unsigned char *p = findVariable(name, VAR_TYPE_STRING); if (p != NULL) { // check there will actually be room for the new value uint16_t oldVarLen = *(uint16_t*)p; if (sysVARSTART - (bytesNeeded - oldVarLen) < sysSTACKEND) return 0; // not enough memory deleteVariableAt(p); } if (sysVARSTART - bytesNeeded < sysSTACKEND) return 0; // out of memory sysVARSTART -= bytesNeeded; p = &mem[sysVARSTART]; *(uint16_t *)p = bytesNeeded; p += 2; *p++ = VAR_TYPE_STRING; strcpy((char*)p, name); p += nameLen + 1; strcpy((char*)p, val); return 1; } int createArray(char *name, int isString) { // dimensions and number of dimensions on the calculator stack int nameLen = strlen(name); int bytesNeeded = 3; // len + flags bytesNeeded += nameLen + 1; // name bytesNeeded += 2; // num dims int numElements = 1; int numDims = (int)stackPopNum(); // keep the current stack position, since we'll need to pop these values again int oldSTACKEND = sysSTACKEND; for (int i=0; i<numDims; i++) { int dim = (int)stackPopNum(); numElements *= dim; } bytesNeeded += 2 * numDims + (isString ? 1 : sizeof(float)) * numElements; // strings and arrays are re-allocated if they already exist unsigned char *p = findVariable(name, (isString ? VAR_TYPE_STR_ARRAY : VAR_TYPE_NUM_ARRAY)); if (p != NULL) { // check there will actually be room for the new value uint16_t oldVarLen = *(uint16_t*)p; if (sysVARSTART - (bytesNeeded - oldVarLen) < sysSTACKEND) return 0; // not enough memory deleteVariableAt(p); } if (sysVARSTART - bytesNeeded < sysSTACKEND) return 0; // out of memory sysVARSTART -= bytesNeeded; p = &mem[sysVARSTART]; *(uint16_t *)p = bytesNeeded; p += 2; *p++ = (isString ? VAR_TYPE_STR_ARRAY : VAR_TYPE_NUM_ARRAY); strcpy((char*)p, name); p += nameLen + 1; *(uint16_t *)p = numDims; p += 2; sysSTACKEND = oldSTACKEND; for (int i=0; i<numDims; i++) { int dim = (int)stackPopNum(); *(uint16_t *)p = dim; p += 2; } memset(p, 0, numElements * (isString ? 1 : sizeof(float))); return 1; } int _getArrayElemOffset(unsigned char **p, int *pOffset) { // check for correct dimensionality int numArrayDims = *(uint16_t*)*p; *p+=2; int numDimsGiven = (int)stackPopNum(); if (numArrayDims != numDimsGiven) return ERROR_WRONG_ARRAY_DIMENSIONS; // now lookup the element int offset = 0; int base = 1; for (int i=0; i<numArrayDims; i++) { int index = (int)stackPopNum(); int arrayDim = *(uint16_t*)*p; *p+=2; if (index < 1 || index > arrayDim) return ERROR_ARRAY_SUBSCRIPT_OUT_RANGE; offset += base * (index-1); base *= arrayDim; } *pOffset = offset; return 0; } int setNumArrayElem(char *name, float val) { // each index and number of dimensions on the calculator stack unsigned char *p = findVariable(name, VAR_TYPE_NUM_ARRAY); if (p == NULL) return ERROR_VARIABLE_NOT_FOUND; p += 3 + strlen(name) + 1; int offset; int ret = _getArrayElemOffset(&p, &offset); if (ret) return ret; p += sizeof(float)*offset; *(float *)p = val; return ERROR_NONE; } int setStrArrayElem(char *name) { // string is top of the stack // each index and number of dimensions on the calculator stack // keep the current stack position, since we can't overwrite the value string int oldSTACKEND = sysSTACKEND; // how long is the new value? char *newValPtr = stackPopStr(); int newValLen = strlen(newValPtr); unsigned char *p = findVariable(name, VAR_TYPE_STR_ARRAY); unsigned char *p1 = p; // so we can correct the length when done if (p == NULL) return ERROR_VARIABLE_NOT_FOUND; p += 3 + strlen(name) + 1; int offset; int ret = _getArrayElemOffset(&p, &offset); if (ret) return ret; // find the correct element by skipping over null terminators int i = 0; while (i < offset) { if (*p == 0) i++; p++; } int oldValLen = strlen((char*)p); int bytesNeeded = newValLen - oldValLen; // check if we've got enough room for the new value if (sysVARSTART - bytesNeeded < oldSTACKEND) return 0; // out of memory // correct the length of the variable *(uint16_t*)p1 += bytesNeeded; memmove(&mem[sysVARSTART - bytesNeeded], &mem[sysVARSTART], p - &mem[sysVARSTART]); // copy in the new value strcpy((char*)(p - bytesNeeded), newValPtr); sysVARSTART -= bytesNeeded; return ERROR_NONE; } float lookupNumArrayElem(char *name, int *error) { // each index and number of dimensions on the calculator stack unsigned char *p = findVariable(name, VAR_TYPE_NUM_ARRAY); if (p == NULL) { *error = ERROR_VARIABLE_NOT_FOUND; return 0.0f; } p += 3 + strlen(name) + 1; int offset; int ret = _getArrayElemOffset(&p, &offset); if (ret) { *error = ret; return 0.0f; } p += sizeof(float)*offset; return *(float *)p; } char *lookupStrArrayElem(char *name, int *error) { // each index and number of dimensions on the calculator stack unsigned char *p = findVariable(name, VAR_TYPE_STR_ARRAY); if (p == NULL) { *error = ERROR_VARIABLE_NOT_FOUND; return NULL; } p += 3 + strlen(name) + 1; int offset; int ret = _getArrayElemOffset(&p, &offset); if (ret) { *error = ret; return NULL; } // find the correct element by skipping over null terminators int i = 0; while (i < offset) { if (*p == 0) i++; p++; } return (char *)p; } float lookupNumVariable(char *name) { unsigned char *p = findVariable(name, VAR_TYPE_NUM|VAR_TYPE_FORNEXT); if (p == NULL) { return FLT_MAX; } p += 3 + strlen(name) + 1; return *(float *)p; } char *lookupStrVariable(char *name) { unsigned char *p = findVariable(name, VAR_TYPE_STRING); if (p == NULL) { return NULL; } p += 3 + strlen(name) + 1; return (char *)p; } ForNextData lookupForNextVariable(char *name) { ForNextData ret; unsigned char *p = findVariable(name, VAR_TYPE_NUM|VAR_TYPE_FORNEXT); if (p == NULL) ret.val = FLT_MAX; else if (*(p+2) != VAR_TYPE_FORNEXT) ret.step = FLT_MAX; else { p += 3 + strlen(name) + 1; ret.val = *(float *)p; p += sizeof(float); ret.step = *(float *)p; p += sizeof(float); ret.end = *(float *)p; p += sizeof(float); ret.lineNumber = *(uint16_t *)p; p += sizeof(uint16_t); ret.stmtNumber = *(uint16_t *)p; } return ret; } /* ************************************************************************** * GOSUB STACK * **************************************************************************/ // gosub stack (if used) is after the variables int gosubStackPush(int lineNumber,int stmtNumber) { int bytesNeeded = 2 * sizeof(uint16_t); if (sysVARSTART - bytesNeeded < sysSTACKEND) return 0; // out of memory // shift the variable table memmove(&mem[sysVARSTART]-bytesNeeded, &mem[sysVARSTART], sysVAREND-sysVARSTART); sysVARSTART -= bytesNeeded; sysVAREND -= bytesNeeded; // push the return address sysGOSUBSTART = sysVAREND; uint16_t *p = (uint16_t*)&mem[sysGOSUBSTART]; *p++ = (uint16_t)lineNumber; *p = (uint16_t)stmtNumber; return 1; } int gosubStackPop(int *lineNumber, int *stmtNumber) { if (sysGOSUBSTART == sysGOSUBEND) return 0; uint16_t *p = (uint16_t*)&mem[sysGOSUBSTART]; *lineNumber = (int)*p++; *stmtNumber = (int)*p; int bytesFreed = 2 * sizeof(uint16_t); // shift the variable table memmove(&mem[sysVARSTART]+bytesFreed, &mem[sysVARSTART], sysVAREND-sysVARSTART); sysVARSTART += bytesFreed; sysVAREND += bytesFreed; sysGOSUBSTART = sysVAREND; return 1; } /* ************************************************************************** * LEXER * **************************************************************************/ static unsigned char *tokenIn, *tokenOut; static int tokenOutLeft; // nextToken returns -1 for end of input, 0 for success, +ve number = error code int nextToken() { // Skip any whitespace. while (isspace(*tokenIn)) tokenIn++; // check for end of line if (*tokenIn == 0) { *tokenOut++ = TOKEN_EOL; tokenOutLeft--; return -1; } // Number: [0-9.]+ // TODO - handle 1e4 etc if (isdigit(*tokenIn) || *tokenIn == '.') { // Number: [0-9.]+ int gotDecimal = 0; char numStr[MAX_NUMBER_LEN+1]; int numLen = 0; do { if (numLen == MAX_NUMBER_LEN) return ERROR_LEXER_BAD_NUM; if (*tokenIn == '.') { if (gotDecimal) return ERROR_LEXER_BAD_NUM; else gotDecimal = 1; } numStr[numLen++] = *tokenIn++; } while (isdigit(*tokenIn) || *tokenIn == '.'); numStr[numLen] = 0; if (tokenOutLeft <= 5) return ERROR_LEXER_TOO_LONG; tokenOutLeft -= 5; if (!gotDecimal) { long val = strtol(numStr, 0, 10); if (val == LONG_MAX || val == LONG_MIN) gotDecimal = true; else { *tokenOut++ = TOKEN_INTEGER; *(long*)tokenOut = val; tokenOut += sizeof(long); } } if (gotDecimal) { *tokenOut++ = TOKEN_NUMBER; *(float*)tokenOut = (float)strtod(numStr, 0); tokenOut += sizeof(float); } return 0; } // identifier: [a-zA-Z][a-zA-Z0-9]*[$] if (isalpha(*tokenIn)) { char identStr[MAX_IDENT_LEN+1]; int identLen = 0; identStr[identLen++] = toupper(*tokenIn++); // copy first char while (isalnum(*tokenIn) || *tokenIn=='$') { if (identLen < MAX_IDENT_LEN) identStr[identLen++] = toupper(*tokenIn); tokenIn++; } identStr[identLen] = 0; // check to see if this is a keyword for (int i = FIRST_IDENT_TOKEN; i <= LAST_IDENT_TOKEN; i++) { if (strcmp(identStr, pgm_read_word(tokenTable[i].token)) == 0) { if (tokenOutLeft <= 1) return ERROR_LEXER_TOO_LONG; tokenOutLeft--; *tokenOut++ = i; // special case for REM if (i == TOKEN_REM) { *tokenOut++ = TOKEN_STRING; // skip whitespace while (isspace(*tokenIn)) tokenIn++; // copy the comment while (*tokenIn) { *tokenOut++ = *tokenIn++; } *tokenOut++ = 0; } return 0; } } // no matching keyword - this must be an identifier // $ is only allowed at the end char *dollarPos = strchr(identStr, '$'); if (dollarPos && dollarPos!= &identStr[0] + identLen - 1) return ERROR_LEXER_UNEXPECTED_INPUT; if (tokenOutLeft <= 1+identLen) return ERROR_LEXER_TOO_LONG; tokenOutLeft -= 1+identLen; *tokenOut++ = TOKEN_IDENT; strcpy((char*)tokenOut, identStr); tokenOut[identLen-1] |= 0x80; tokenOut += identLen; return 0; } // string if (*tokenIn=='\"') { *tokenOut++ = TOKEN_STRING; tokenOutLeft--; if (tokenOutLeft <= 1) return ERROR_LEXER_TOO_LONG; tokenIn++; while (*tokenIn) { if (*tokenIn == '\"' && *(tokenIn+1) != '\"') break; else if (*tokenIn == '\"') tokenIn++; *tokenOut++ = *tokenIn++; tokenOutLeft--; if (tokenOutLeft <= 1) return ERROR_LEXER_TOO_LONG; } if (!*tokenIn) return ERROR_LEXER_UNTERMINATED_STRING; tokenIn++; *tokenOut++ = 0; tokenOutLeft--; return 0; } // handle non-alpha tokens e.g. = for (int i=LAST_NON_ALPHA_TOKEN; i>=FIRST_NON_ALPHA_TOKEN; i--) { // do this "backwards" so we match >= correctly, not as > then = int len = strlen(pgm_read_word(tokenTable[i].token)); if (strncmp(pgm_read_word(tokenTable[i].token), (char*)tokenIn, len) == 0) { if (tokenOutLeft <= 1) return ERROR_LEXER_TOO_LONG; *tokenOut++ = i; tokenOutLeft--; tokenIn += len; return 0; } } return ERROR_LEXER_UNEXPECTED_INPUT; } int tokenize(unsigned char *input, unsigned char *output, int outputSize) { tokenIn = input; tokenOut = output; tokenOutLeft = outputSize; int ret; while (1) { ret = nextToken(); if (ret) break; } return (ret > 0) ? ret : 0; } /* ************************************************************************** * PARSER / INTERPRETER * **************************************************************************/ static char executeMode; // 0 = syntax check only, 1 = execute uint16_t lineNumber, stmtNumber; // stmt number is 0 for the first statement, then increases after each command seperator (:) // Note that IF a=1 THEN PRINT "x": print "y" is considered to be only 2 statements static uint16_t jumpLineNumber, jumpStmtNumber; static uint16_t stopLineNumber, stopStmtNumber; static uint16_t dataLineNumber; static char breakCurrentLine; static unsigned char *tokenBuffer, *prevToken; static int curToken; static char identVal[MAX_IDENT_LEN+1]; static char isStrIdent; static float numVal; static char *strVal; int getNextToken() { prevToken = tokenBuffer; curToken = *tokenBuffer++; if (curToken == TOKEN_IDENT) { int i=0; while (*tokenBuffer < 0x80) identVal[i++] = *tokenBuffer++; identVal[i] = (*tokenBuffer++)-0x80; isStrIdent = (identVal[i++] == '$'); identVal[i++] = '\0'; } else if (curToken == TOKEN_NUMBER) { numVal = *(float*)tokenBuffer; tokenBuffer += sizeof(float); } else if (curToken == TOKEN_INTEGER) { // these are really just for line numbers numVal = (float)(*(long*)tokenBuffer); tokenBuffer += sizeof(long); } else if (curToken == TOKEN_STRING) { strVal = (char*)tokenBuffer; tokenBuffer += 1 + strlen(strVal); } return curToken; } // value (int) returned from parseXXXXX #define ERROR_MASK 0x0FFF #define TYPE_MASK 0xF000 #define TYPE_NUMBER 0x0000 #define TYPE_STRING 0x1000 #define IS_TYPE_NUM(x) ((x & TYPE_MASK) == TYPE_NUMBER) #define IS_TYPE_STR(x) ((x & TYPE_MASK) == TYPE_STRING) // forward declarations int parseExpression(); int parsePrimary(); int expectNumber(); // parse a number int parseNumberExpr() { if (executeMode && !stackPushNum(numVal)) return ERROR_OUT_OF_MEMORY; getNextToken(); // consume the number return TYPE_NUMBER; } // parse (x1,....xn) e.g. DIM a(10,20) int parseSubscriptExpr() { // stacks x1, .. xn followed by n int numDims = 0; if (curToken != TOKEN_LBRACKET) return ERROR_EXPR_MISSING_BRACKET; getNextToken(); while(1) { numDims++; int val = expectNumber(); if (val) return val; // error if (curToken == TOKEN_RBRACKET) break; else if (curToken == TOKEN_COMMA) getNextToken(); else return ERROR_EXPR_MISSING_BRACKET; } getNextToken(); // eat ) if (executeMode && !stackPushNum(numDims)) return ERROR_OUT_OF_MEMORY; return 0; } // parse a function call e.g. LEN(a$) int parseFnCallExpr() { int op = curToken; int fnSpec = pgm_read_byte_near(tokenTable[curToken].format); getNextToken(); // get the required arguments and types from the token table if (curToken != TOKEN_LBRACKET) return ERROR_EXPR_MISSING_BRACKET; getNextToken(); int reqdArgs = fnSpec & TKN_ARGS_NUM_MASK; int argTypes = (fnSpec & TKN_ARG_MASK) >> TKN_ARG_SHIFT; int ret = (fnSpec & TKN_RET_TYPE_STR) ? TYPE_STRING : TYPE_NUMBER; for (int i=0; i<reqdArgs; i++) { int val = parseExpression(); if (val & ERROR_MASK) return val; // check we've got the right type if (!(argTypes & 1) && !IS_TYPE_NUM(val)) return ERROR_EXPR_EXPECTED_NUM; if ((argTypes & 1) && !IS_TYPE_STR(val)) return ERROR_EXPR_EXPECTED_STR; argTypes >>= 1; // if this isn't the last argument, eat the , if (i+1<reqdArgs) { if (curToken != TOKEN_COMMA) return ERROR_WRONG_NUM_FUNCTION_ARGS; getNextToken(); } } // now all the arguments will be on the stack (last first) if (executeMode) { int tmp; switch (op) { case TOKEN_INT: stackPushNum((float)host_floor(stackPopNum())); break; case TOKEN_ABS: { float f = stackPopNum(); if (f < (float)0) f = -f; stackPushNum(f); } break; case TOKEN_STR: { char buf[MAX_NUMBER_LEN+1]; if (!stackPushStr(host_floatToStr(stackPopNum(), buf))) return ERROR_OUT_OF_MEMORY; } break; case TOKEN_HEX: { char buf[MAX_NUMBER_LEN+1]; tmp = (int)host_floor(stackPopNum()); snprintf(buf, MAX_NUMBER_LEN, "%X", tmp); if (!stackPushStr(buf)) return ERROR_OUT_OF_MEMORY; } break; case TOKEN_CHR: { char buf[2]; buf[0] = (int)stackPopNum(); buf[1] = '\0'; if (!stackPushStr(buf)) return ERROR_OUT_OF_MEMORY; } break; case TOKEN_LEN: tmp = strlen(stackPopStr()); if (!stackPushNum(tmp)) return ERROR_OUT_OF_MEMORY; break; case TOKEN_CODE: if (!stackPushNum((float)*(unsigned char *)stackPopStr())) return ERROR_OUT_OF_MEMORY; break; case TOKEN_VAL: { // tokenise str onto the stack int oldStackEnd = sysSTACKEND; unsigned char *oldTokenBuffer = prevToken; int val = tokenize((unsigned char*)stackGetStr(), &mem[sysSTACKEND], sysVARSTART - sysSTACKEND); if (val) { if (val == ERROR_LEXER_TOO_LONG) return ERROR_OUT_OF_MEMORY; else return ERROR_IN_VAL_INPUT; } // set tokenBuffer to point to the new set of tokens on the stack tokenBuffer = &mem[sysSTACKEND]; // move stack end to the end of the new tokens sysSTACKEND = tokenOut - &mem[0]; getNextToken(); // then parseExpression val = parseExpression(); if (val & ERROR_MASK) { if (val == ERROR_OUT_OF_MEMORY) return val; else return ERROR_IN_VAL_INPUT; } if (!IS_TYPE_NUM(val)) return ERROR_EXPR_EXPECTED_NUM; // read the result from the stack float f = stackPopNum(); // pop the tokens from the stack sysSTACKEND = oldStackEnd; // and pop the original string stackPopStr(); // finally, push the result and set the token buffer back stackPushNum(f); tokenBuffer = oldTokenBuffer; getNextToken(); } break; case TOKEN_LEFT: tmp = (int)stackPopNum(); if (tmp < 0) return ERROR_STR_SUBSCRIPT_OUT_RANGE; stackLeftOrRightStr(tmp, 0); break; case TOKEN_RIGHT: tmp = (int)stackPopNum(); if (tmp < 0) return ERROR_STR_SUBSCRIPT_OUT_RANGE; stackLeftOrRightStr(tmp, 1); break; case TOKEN_MID: { tmp = (int)stackPopNum(); int start = stackPopNum(); if (tmp < 0 || start < 1) return ERROR_STR_SUBSCRIPT_OUT_RANGE; stackMidStr(start, tmp); } break; case TOKEN_PINREAD: tmp = (int)stackPopNum(); if (!stackPushNum(host_digitalRead(tmp))) return ERROR_OUT_OF_MEMORY; break; case TOKEN_ANALOGRD: tmp = (int)stackPopNum(); if (!stackPushNum(host_analogRead(tmp))) return ERROR_OUT_OF_MEMORY; break; case TOKEN_POW: #if MATH_FUNCTIONS && MATH_INCLUDE_POW { float y = stackPopNum(); stackPushNum(POW(stackPopNum(), y)); break; } #else return ERROR_FUNCTION_NOT_BUILTIN; #endif case TOKEN_INPB: { tmp = (int)stackPopNum(); if (!stackPushNum(host_inpb(tmp))) return ERROR_OUT_OF_MEMORY; break; } case TOKEN_INPW: { tmp = (int)stackPopNum(); if (!stackPushNum(host_inpw(tmp))) return ERROR_OUT_OF_MEMORY; break; } case TOKEN_PEEK: { int segment = stackPopNum(); stackPushNum(host_peekb(stackPopNum(), segment)); break; } default: return ERROR_UNEXPECTED_TOKEN; } } if (curToken != TOKEN_RBRACKET) return ERROR_EXPR_MISSING_BRACKET; getNextToken(); // eat ) return ret; } // parse a single argument math function call e.g. COS(a) int parseMathFn() { int op = curToken; getNextToken(); if (curToken != TOKEN_LBRACKET) return ERROR_EXPR_MISSING_BRACKET; getNextToken(); int val = parseExpression(); if (!IS_TYPE_NUM(val)) return ERROR_EXPR_EXPECTED_NUM; if (executeMode) { #if MATH_FUNCTIONS switch (op) { case TOKEN_COS: stackPushNum(COS(stackPopNum())); break; case TOKEN_SIN: stackPushNum(SIN(stackPopNum())); break; case TOKEN_TAN: stackPushNum(TAN(stackPopNum())); break; case TOKEN_ACS: stackPushNum(ACOS(stackPopNum())); break; case TOKEN_ASN: stackPushNum(ASIN(stackPopNum())); break; case TOKEN_ATN: stackPushNum(ATAN(stackPopNum())); break; case TOKEN_EXP: stackPushNum(EXP(stackPopNum())); break; case TOKEN_LN: stackPushNum(LOG(stackPopNum())); break; default: return ERROR_UNEXPECTED_TOKEN; } #else return ERROR_FUNCTION_NOT_BUILTIN; #endif } if (curToken != TOKEN_RBRACKET) return ERROR_EXPR_MISSING_BRACKET; getNextToken(); // eat ) return TYPE_NUMBER; } // parse an identifer e.g. a$ or a(5,3) int parseIdentifierExpr() { char ident[MAX_IDENT_LEN+1]; if (executeMode) strcpy(ident, identVal); int isStringIdentifier = isStrIdent; getNextToken(); // eat ident if (curToken == TOKEN_LBRACKET) { // array access int val = parseSubscriptExpr(); if (val) return val; if (executeMode) { if (isStringIdentifier) { int error = 0; char *str = lookupStrArrayElem(ident, &error); if (error) return error; else if (!stackPushStr(str)) return ERROR_OUT_OF_MEMORY; } else { int error = 0; float f = lookupNumArrayElem(ident, &error); if (error) return error; else if (!stackPushNum(f)) return ERROR_OUT_OF_MEMORY; } } } else { // simple variable if (executeMode) { if (isStringIdentifier) { char *str = lookupStrVariable(ident); if (!str) return ERROR_VARIABLE_NOT_FOUND; else if (!stackPushStr(str)) return ERROR_OUT_OF_MEMORY; } else { float f = lookupNumVariable(ident); if (f == FLT_MAX) return ERROR_VARIABLE_NOT_FOUND; else if (!stackPushNum(f)) return ERROR_OUT_OF_MEMORY; } } } return isStringIdentifier ? TYPE_STRING : TYPE_NUMBER; } // parse a string e.g. "hello" int parseStringExpr() { if (executeMode && !stackPushStr(strVal)) return ERROR_OUT_OF_MEMORY; getNextToken(); // consume the string return TYPE_STRING; } // parse a bracketed expressed e.g. (5+3) int parseParenExpr() { getNextToken(); // eat ( int val = parseExpression(); if (val & ERROR_MASK) return val; if (curToken != TOKEN_RBRACKET) return ERROR_EXPR_MISSING_BRACKET; getNextToken(); // eat ) return val; } int parse_PseudoID() { int op = curToken; getNextToken(); if (executeMode) { switch (op) { case TOKEN_RND: if (!stackPushNum((float)rand()/(float)RAND_MAX)) return ERROR_OUT_OF_MEMORY; break; case TOKEN_PI: if (!stackPushNum((float)M_PI)) return ERROR_OUT_OF_MEMORY; break; } } return TYPE_NUMBER; } int parse_INKEY() { getNextToken(); if (executeMode) { char str[2]; str[0] = host_getKey(); str[1] = 0; if (!stackPushStr(str)) return ERROR_OUT_OF_MEMORY; } return TYPE_STRING; } int parseUnaryNumExp() { int op = curToken; getNextToken(); int val = parsePrimary(); if (val & ERROR_MASK) return val; if (!IS_TYPE_NUM(val)) return ERROR_EXPR_EXPECTED_NUM; switch (op) { case TOKEN_MINUS: if (executeMode) stackPushNum(stackPopNum() * -1.0f); return TYPE_NUMBER; case TOKEN_NOT: if (executeMode) stackPushNum(stackPopNum() ? 0.0f : 1.0f); return TYPE_NUMBER; default: return ERROR_UNEXPECTED_TOKEN; } } /// primary int parsePrimary() { switch (curToken) { case TOKEN_IDENT: return parseIdentifierExpr(); case TOKEN_NUMBER: case TOKEN_INTEGER: return parseNumberExpr(); case TOKEN_STRING: return parseStringExpr(); case TOKEN_LBRACKET: return parseParenExpr(); // "psuedo-identifiers" case TOKEN_RND: case TOKEN_PI: return parse_PseudoID(); case TOKEN_INKEY: return parse_INKEY(); // unary ops case TOKEN_MINUS: case TOKEN_NOT: return parseUnaryNumExp(); // functions case TOKEN_INT: case TOKEN_ABS: case TOKEN_STR: case TOKEN_CHR: case TOKEN_LEN: case TOKEN_CODE: case TOKEN_VAL: case TOKEN_LEFT: case TOKEN_RIGHT: case TOKEN_MID: case TOKEN_PINREAD: case TOKEN_ANALOGRD: case TOKEN_POW: case TOKEN_INPB: case TOKEN_INPW: case TOKEN_HEX: case TOKEN_PEEK: return parseFnCallExpr(); // single argument math functions case TOKEN_COS: case TOKEN_SIN: case TOKEN_TAN: case TOKEN_ACS: case TOKEN_ASN: case TOKEN_ATN: case TOKEN_EXP: case TOKEN_LN: return parseMathFn(); default: return ERROR_UNEXPECTED_TOKEN; } } int getTokPrecedence() { if (curToken == TOKEN_AND || curToken == TOKEN_OR) return 5; if (curToken == TOKEN_EQUALS || curToken == TOKEN_NOT_EQ) return 10; if (curToken == TOKEN_LT || curToken == TOKEN_GT || curToken == TOKEN_LT_EQ || curToken == TOKEN_GT_EQ) return 20; if (curToken == TOKEN_MINUS || curToken == TOKEN_PLUS) return 30; else if (curToken == TOKEN_MULT || curToken == TOKEN_DIV || curToken == TOKEN_MOD) return 40; else return -1; } // Operator-Precedence Parsing int parseBinOpRHS(int ExprPrec, int lhsVal) { // If this is a binop, find its precedence. while (1) { int TokPrec = getTokPrecedence(); // If this is a binop that binds at least as tightly as the current binop, // consume it, otherwise we are done. if (TokPrec < ExprPrec) return lhsVal; // Okay, we know this is a binop. int BinOp = curToken; getNextToken(); // eat binop // Parse the primary expression after the binary operator. int rhsVal = parsePrimary(); if (rhsVal & ERROR_MASK) return rhsVal; // If BinOp binds less tightly with RHS than the operator after RHS, let // the pending operator take RHS as its LHS. int NextPrec = getTokPrecedence(); if (TokPrec < NextPrec) { rhsVal = parseBinOpRHS(TokPrec+1, rhsVal); if (rhsVal & ERROR_MASK) return rhsVal; } if (IS_TYPE_NUM(lhsVal) && IS_TYPE_NUM(rhsVal)) { // Number operations float r, l; if (executeMode) { r = stackPopNum(); l = stackPopNum(); } if (BinOp == TOKEN_PLUS) { if (executeMode) stackPushNum(l+r); } else if (BinOp == TOKEN_MINUS) { if (executeMode) stackPushNum(l-r); } else if (BinOp == TOKEN_MULT) { if (executeMode) stackPushNum(l*r); } else if (BinOp == TOKEN_DIV) { if (executeMode) { if (r) stackPushNum(l/r); else return ERROR_EXPR_DIV_ZERO; } } else if (BinOp == TOKEN_MOD) { if (executeMode) { if ((int)r) stackPushNum((float)((int)l % (int)r)); else return ERROR_EXPR_DIV_ZERO; } } else if (BinOp == TOKEN_LT) { if (executeMode) stackPushNum(l < r ? 1.0f : 0.0f); } else if (BinOp == TOKEN_GT) { if (executeMode) stackPushNum(l > r ? 1.0f : 0.0f); } else if (BinOp == TOKEN_EQUALS) { if (executeMode) stackPushNum(l == r ? 1.0f : 0.0f); } else if (BinOp == TOKEN_NOT_EQ) { if (executeMode) stackPushNum(l != r ? 1.0f : 0.0f); } else if (BinOp == TOKEN_LT_EQ) { if (executeMode) stackPushNum(l <= r ? 1.0f : 0.0f); } else if (BinOp == TOKEN_GT_EQ) { if (executeMode) stackPushNum(l >= r ? 1.0f : 0.0f); } else if (BinOp == TOKEN_AND) { if (executeMode) stackPushNum(r != 0.0f ? l : 0.0f); } else if (BinOp == TOKEN_OR) { if (executeMode) stackPushNum(r != 0.0f ? 1 : l); } else return ERROR_UNEXPECTED_TOKEN; } else if (IS_TYPE_STR(lhsVal) && IS_TYPE_STR(rhsVal)) { // String operations if (BinOp == TOKEN_PLUS) { if (executeMode) stackAdd2Strs(); } else if (BinOp >= TOKEN_EQUALS && BinOp <=TOKEN_LT_EQ) { if (executeMode) { char *r = stackPopStr(); char *l = stackPopStr(); int ret = strcmp(l,r); if (BinOp == TOKEN_EQUALS && ret == 0) stackPushNum(1.0f); else if (BinOp == TOKEN_NOT_EQ && ret != 0) stackPushNum(1.0f); else if (BinOp == TOKEN_GT && ret > 0) stackPushNum(1.0f); else if (BinOp == TOKEN_LT && ret < 0) stackPushNum(1.0f); else if (BinOp == TOKEN_GT_EQ && ret >= 0) stackPushNum(1.0f); else if (BinOp == TOKEN_LT_EQ && ret <= 0) stackPushNum(1.0f); else stackPushNum(0.0f); } lhsVal = TYPE_NUMBER; } else return ERROR_UNEXPECTED_TOKEN; } else return ERROR_UNEXPECTED_TOKEN; } } int parseExpression() { int val = parsePrimary(); if (val & ERROR_MASK) return val; return parseBinOpRHS(0, val); } int expectNumber() { int val = parseExpression(); if (val & ERROR_MASK) return val; if (!IS_TYPE_NUM(val)) return ERROR_EXPR_EXPECTED_NUM; return 0; } // parse both RUN and RESTORE int parse_RUN(int isRestore) { getNextToken(); uint16_t startLine = 1; if (curToken != TOKEN_EOL) { int val = expectNumber(); if (val) return val; // error if (executeMode) { startLine = (uint16_t)stackPopNum(); /*if (startLine <= 0) return ERROR_BAD_LINE_NUM;*/ } } if (executeMode) { if (isRestore) { dataLineNumber = startLine; } else { // clear variables sysVARSTART = sysVAREND = sysGOSUBSTART = sysGOSUBEND = MEMORY_SIZE; jumpLineNumber = startLine; stopLineNumber = stopStmtNumber = 0; dataLineNumber = 1; } } return 0; } int parse_GOTO() { getNextToken(); int val = expectNumber(); if (val) return val; // error if (executeMode) { uint16_t startLine = (uint16_t)stackPopNum(); /*if (startLine <= 0) return ERROR_BAD_LINE_NUM;*/ jumpLineNumber = startLine; } return 0; } int parse_PAUSE() { getNextToken(); int val = expectNumber(); if (val) return val; // error if (executeMode) { long ms = (long)stackPopNum(); if (ms < 0) return ERROR_BAD_PARAMETER; host_sleep(ms); } return 0; } int parse_MODE() { getNextToken(); int val = expectNumber(); if (val) return val; // error if (executeMode) { int mode = (int)stackPopNum(); host_mode(mode); } return 0; } int parse_LIST() { getNextToken(); uint16_t first = 0, last = 0; if (curToken != TOKEN_EOL && curToken != TOKEN_CMD_SEP) { int val = expectNumber(); if (val) return val; // error if (executeMode) first = (uint16_t)stackPopNum(); } if (curToken == TOKEN_COMMA) { getNextToken(); int val = expectNumber(); if (val) return val; // error if (executeMode) last = (uint16_t)stackPopNum(); } if (executeMode) { listProg(first,last); host_showBuffer(); } return 0; } int parse_PRINT() { getNextToken(); // zero + expressions seperated by semicolons or commas int newLine = 1; while (curToken != TOKEN_EOL && curToken != TOKEN_CMD_SEP) { int val = parseExpression(); if (val & ERROR_MASK) return val; if (executeMode) { if (IS_TYPE_NUM(val)) host_outputFloat(stackPopNum()); else host_outputString(stackPopStr()); newLine = 1; } if (curToken == TOKEN_SEMICOLON) { newLine = 0; getNextToken(); } if (curToken == TOKEN_COMMA) { newLine = 0; getNextToken(); if (executeMode) host_outputString("\t"); } } if (executeMode) { if (newLine) host_newLine(); host_showBuffer(); } return 0; } // parse a stmt that takes N int parameters (max 3) // e.g. POSITION 3,2 int parseNIntCmd(int n) { int i; int op = curToken; int para[3]; if (n > 3) return ERROR_LEXER_TOO_LONG; getNextToken(); int val = expectNumber(); if (val) return val; // error for (i=1; i<n; i++) { if (curToken != TOKEN_COMMA) return ERROR_UNEXPECTED_TOKEN; getNextToken(); val = expectNumber(); if (val) return val; // error } if (executeMode) { for (i=n-1; i>=0; i--) para[i] = (int)stackPopNum(); switch(op) { case TOKEN_POSITION: host_moveCursor(para[0],para[1]); break; case TOKEN_PIN: host_digitalWrite(para[0],para[1]); break; case TOKEN_PINMODE: host_pinMode(para[0],para[1]); break; case TOKEN_COLOR: host_color(para[0],para[1]); break; case TOKEN_PLOT: host_plot(para[0],para[1]); break; case TOKEN_DRAW: host_draw(para[0],para[1]); break; case TOKEN_CIRCLE: host_circle(para[0],para[1],para[2]); break; case TOKEN_OUTB: host_outb(para[0],para[1]); break; case TOKEN_OUTW: host_outw(para[0],para[1]); break; case TOKEN_POKE: host_pokeb(para[0],para[1],para[2]); break; } } return 0; } // get number or string from DATA statement int readDataStmt() { int token, ret; int type = TYPE_NUMBER; static unsigned char *p; if (dataLineNumber) { p = findProgLine(dataLineNumber); nextdata: for (;;) { if (p == &mem[sysPROGEND]) return ERROR_NO_DATA_FOR_READ;; if (*(p + 2 + sizeof(uint16_t)) != TOKEN_DATA) { p+= *(uint16_t *)p; // next line continue; } p+= 2 + sizeof(uint16_t) + 1; // line size + line number + DATA token dataLineNumber = 0; break; } } token = *p++; switch (token) { case TOKEN_NUMBER: ret = stackPushNum(*(float *)p); p += sizeof(float); break; case TOKEN_INTEGER: ret = stackPushNum(*(long *)p); p += sizeof(long); break; case TOKEN_STRING: ret = stackPushStr((char *)p); p += strlen((char *)p) + 1; type = TYPE_STRING; break; case TOKEN_EOL: goto nextdata; } if (*p != TOKEN_EOL) p+= 1; // skip COMMA if (!ret) return ERROR_OUT_OF_MEMORY; return type; } // this handles LET a$="hello", INPUT a$ and READ a$ type assignments int parseAssignment(int token) { char ident[MAX_IDENT_LEN+1]; int val; if (token == TOKEN_INPUT && curToken == TOKEN_STRING) { int val = parseExpression(); if (val & ERROR_MASK) return val; if (curToken != TOKEN_COMMA) return ERROR_UNEXPECTED_TOKEN; if (executeMode) host_outputString(stackPopStr()); getNextToken(); } if (curToken != TOKEN_IDENT) return ERROR_UNEXPECTED_TOKEN; if (executeMode) strcpy(ident, identVal); int isStringIdentifier = isStrIdent; int isArray = 0; getNextToken(); // eat ident if (curToken == TOKEN_LBRACKET) { // array element being set val = parseSubscriptExpr(); if (val) return val; isArray = 1; } if (token == TOKEN_INPUT) { // from INPUT statement if (executeMode) { char *inputStr = host_readLine(); if (!inputStr) return ERROR_BREAK_PRESSED; if (isStringIdentifier) { if (!stackPushStr(inputStr)) return ERROR_OUT_OF_MEMORY; } else { float f = (float)strtod(inputStr, 0); if (!stackPushNum(f)) return ERROR_OUT_OF_MEMORY; } host_newLine(); host_showBuffer(); } val = isStringIdentifier ? TYPE_STRING : TYPE_NUMBER; } else if (token == TOKEN_READ) { val = isStringIdentifier ? TYPE_STRING : TYPE_NUMBER; if (executeMode) { val = readDataStmt(); if (val & ERROR_MASK) return val; } } else { // from LET statement if (curToken != TOKEN_EQUALS) return ERROR_UNEXPECTED_TOKEN; getNextToken(); // eat = val = parseExpression(); if (val & ERROR_MASK) return val; } // type checking and actual assignment if (!isStringIdentifier) { // numeric variable if (!IS_TYPE_NUM(val)) return ERROR_EXPR_EXPECTED_NUM; if (executeMode) { if (isArray) { val = setNumArrayElem(ident, stackPopNum()); if (val) return val; } else { if (!storeNumVariable(ident, stackPopNum())) return ERROR_OUT_OF_MEMORY; } } } else { // string variable if (!IS_TYPE_STR(val)) return ERROR_EXPR_EXPECTED_STR; if (executeMode) { if (isArray) { // annoyingly, we've got the string at the top of the stack // (from parseExpression) and the array index stuff (from // parseSubscriptExpr) underneath. val = setStrArrayElem(ident); if (val) return val; } else { if (!storeStrVariable(ident, stackGetStr())) return ERROR_OUT_OF_MEMORY; stackPopStr(); } } } return 0; } int parse_IF() { getNextToken(); // eat if int val = expectNumber(); if (val) return val; // error if (curToken != TOKEN_THEN) return ERROR_MISSING_THEN; getNextToken(); if (executeMode && stackPopNum() == 0.0f) { // condition not met breakCurrentLine = 1; return 0; } else return 0; } int parse_FOR() { char ident[MAX_IDENT_LEN+1]; float start, end, step = 1.0f; getNextToken(); // eat for if (curToken != TOKEN_IDENT || isStrIdent) return ERROR_UNEXPECTED_TOKEN; if (executeMode) strcpy(ident, identVal); getNextToken(); // eat ident if (curToken != TOKEN_EQUALS) return ERROR_UNEXPECTED_TOKEN; getNextToken(); // eat = // parse START int val = expectNumber(); if (val) return val; // error if (executeMode) start = stackPopNum(); // parse TO if (curToken != TOKEN_TO) return ERROR_UNEXPECTED_TOKEN; getNextToken(); // eat TO // parse END val = expectNumber(); if (val) return val; // error if (executeMode) end = stackPopNum(); // parse optional STEP if (curToken == TOKEN_STEP) { getNextToken(); // eat STEP val = expectNumber(); if (val) return val; // error if (executeMode) step = stackPopNum(); } if (executeMode) { if (!storeForNextVariable(ident, start, step, end, lineNumber, stmtNumber)) return ERROR_OUT_OF_MEMORY; } return 0; } int parse_NEXT() { getNextToken(); // eat next if (curToken != TOKEN_IDENT || isStrIdent) return ERROR_UNEXPECTED_TOKEN; if (executeMode) { ForNextData data = lookupForNextVariable(identVal); if (data.val == FLT_MAX) return ERROR_VARIABLE_NOT_FOUND; else if (data.step == FLT_MAX) return ERROR_NEXT_WITHOUT_FOR; // update and store the count variable data.val += data.step; storeNumVariable(identVal, data.val); // loop? if ((data.step >= 0 && data.val <= data.end) || (data.step < 0 && data.val >= data.end)) { jumpLineNumber = data.lineNumber; jumpStmtNumber = data.stmtNumber+1; } } getNextToken(); // eat ident return 0; } int parse_GOSUB() { getNextToken(); // eat gosub int val = expectNumber(); if (val) return val; // error if (executeMode) { uint16_t startLine = (uint16_t)stackPopNum(); /*if (startLine <= 0) return ERROR_BAD_LINE_NUM;*/ jumpLineNumber = startLine; if (!gosubStackPush(lineNumber,stmtNumber)) return ERROR_OUT_OF_MEMORY; } return 0; } // LOAD or LOAD "x" // SAVE, SAVE+ or SAVE "x" // DELETE "x" int parseLoadSaveCmd() { int op = curToken; int autoexec = 0, gotFileName = 0; getNextToken(); if (op == TOKEN_SAVE && curToken == TOKEN_PLUS) { getNextToken(); autoexec = 1; } if (curToken != TOKEN_EOL && curToken != TOKEN_CMD_SEP) { int val = parseExpression(); if (val & ERROR_MASK) return val; if (!IS_TYPE_STR(val)) return ERROR_EXPR_EXPECTED_STR; gotFileName = 1; } if (executeMode) { if (gotFileName) { char fileName[MAX_PATH_LEN]; if (strlen(stackGetStr()) > MAX_PATH_LEN-1) return ERROR_BAD_PARAMETER; strcpy(fileName, stackPopStr()); #if DISK_FUNCTIONS if (op == TOKEN_SAVE) return host_saveProgramToFile(fileName, autoexec); else if (op == TOKEN_LOAD) { reset(); return host_loadProgramFromFile(fileName); } else if (op == TOKEN_DELETE) return host_removeFile(fileName); #endif } else return ERROR_EXPR_EXPECTED_STR; return ERROR_UNEXPECTED_CMD; } return 0; } int parseSimpleCmd() { int op = curToken; getNextToken(); // eat op if (executeMode) { switch (op) { case TOKEN_NEW: reset(); breakCurrentLine = 1; break; case TOKEN_STOP: stopLineNumber = lineNumber; stopStmtNumber = stmtNumber; return ERROR_STOP_STATEMENT; case TOKEN_CONT: if (stopLineNumber) { jumpLineNumber = stopLineNumber; jumpStmtNumber = stopStmtNumber+1; } break; case TOKEN_RETURN: { int returnLineNumber, returnStmtNumber; if (!gosubStackPop(&returnLineNumber, &returnStmtNumber)) return ERROR_RETURN_WITHOUT_GOSUB; jumpLineNumber = returnLineNumber; jumpStmtNumber = returnStmtNumber+1; break; } case TOKEN_CLS: host_cls(); host_showBuffer(); break; case TOKEN_DIR: #if DISK_FUNCTIONS return host_directoryListing(); #else return ERROR_FILE_ERROR; #endif break; } } return 0; } int parse_DIM() { char ident[MAX_IDENT_LEN+1]; getNextToken(); // eat DIM if (curToken != TOKEN_IDENT) return ERROR_UNEXPECTED_TOKEN; if (executeMode) strcpy(ident, identVal); int isStringIdentifier = isStrIdent; getNextToken(); // eat ident int val = parseSubscriptExpr(); if (val) return val; if (executeMode && !createArray(ident, isStringIdentifier ? 1 : 0)) return ERROR_OUT_OF_MEMORY; return 0; } int parse_DATA() { getNextToken(); // eat DATA for (;;) { switch (curToken) { case TOKEN_NUMBER: case TOKEN_INTEGER: case TOKEN_STRING: getNextToken(); break; default: return ERROR_UNEXPECTED_TOKEN; } if (curToken == TOKEN_EOL) break; if (curToken != TOKEN_COMMA) return ERROR_UNEXPECTED_TOKEN; getNextToken(); } return 0; } static int targetStmtNumber; int parseStmts() { int ret = 0, lastToken; breakCurrentLine = 0; jumpLineNumber = 0; jumpStmtNumber = 0; while (ret == 0) { if (curToken == TOKEN_EOL) break; if (executeMode) sysSTACKEND = sysSTACKSTART = sysPROGEND; // clear calculator stack int needCmdSep = 1; switch (curToken) { case TOKEN_PRINT: ret = parse_PRINT(); break; case TOKEN_IDENT: ret = parseAssignment(TOKEN_IDENT); break; case TOKEN_LIST: ret = parse_LIST(); break; case TOKEN_RUN: ret = parse_RUN(false); break; case TOKEN_RESTORE: ret = parse_RUN(true); break; case TOKEN_GOTO: ret = parse_GOTO(); break; case TOKEN_REM: getNextToken(); getNextToken(); break; case TOKEN_IF: ret = parse_IF(); needCmdSep = 0; break; case TOKEN_FOR: ret = parse_FOR(); break; case TOKEN_NEXT: ret = parse_NEXT(); break; case TOKEN_GOSUB: ret = parse_GOSUB(); break; case TOKEN_DIM: ret = parse_DIM(); break; case TOKEN_PAUSE: ret = parse_PAUSE(); break; case TOKEN_MODE: ret = parse_MODE(); break; case TOKEN_DATA: ret = parse_DATA(); break; case TOKEN_LET: case TOKEN_INPUT: case TOKEN_READ: case TOKEN_INPB: case TOKEN_INPW: lastToken = curToken; getNextToken(); ret = parseAssignment(lastToken); break; case TOKEN_LOAD: case TOKEN_SAVE: case TOKEN_DELETE: ret = parseLoadSaveCmd(); break; case TOKEN_POSITION: case TOKEN_PIN: case TOKEN_PINMODE: case TOKEN_COLOR: case TOKEN_PLOT: case TOKEN_DRAW: case TOKEN_OUTB: case TOKEN_OUTW: ret = parseNIntCmd(2); break; case TOKEN_CIRCLE: case TOKEN_POKE: ret = parseNIntCmd(3); break; case TOKEN_NEW: case TOKEN_STOP: case TOKEN_CONT: case TOKEN_RETURN: case TOKEN_CLS: case TOKEN_DIR: ret = parseSimpleCmd(); break; default: ret = ERROR_UNEXPECTED_CMD; } // if error, or the execution line has been changed, exit here if (ret || breakCurrentLine || jumpLineNumber || jumpStmtNumber) break; // it should either be the end of the line now, and (generally) a command seperator // before the next command if (curToken != TOKEN_EOL) { if (needCmdSep) { if (curToken != TOKEN_CMD_SEP) ret = ERROR_UNEXPECTED_CMD; else { getNextToken(); // don't allow a trailing : if (curToken == TOKEN_EOL) ret = ERROR_UNEXPECTED_CMD; } } } // increase the statement number stmtNumber++; // if we're just scanning to find a target statement, check if (stmtNumber == targetStmtNumber) break; } return ret; } int processInput(unsigned char *tokenBuf) { // first token can be TOKEN_INTEGER for line number - stored as a float in numVal // store as WORD line number (max 65535) tokenBuffer = tokenBuf; getNextToken(); // check for a line number at the start uint16_t gotLineNumber = 0; unsigned char *lineStartPtr = 0; if (curToken == TOKEN_INTEGER) { long val = (long)numVal; if (val > 65535) return ERROR_LINE_NUM_TOO_BIG; if (val == 0) return ERROR_LEXER_BAD_NUM; else { gotLineNumber = (uint16_t)val; lineStartPtr = tokenBuffer; getNextToken(); } } executeMode = 0; targetStmtNumber = 0; int ret = parseStmts(); // syntax check if (ret != ERROR_NONE) return ret; if (gotLineNumber) { if (!doProgLine(gotLineNumber, lineStartPtr, tokenBuffer - lineStartPtr)) ret = ERROR_OUT_OF_MEMORY; } else { // we start off executing from the input buffer tokenBuffer = tokenBuf; executeMode = 1; lineNumber = 0; // buffer unsigned char *p; while (1) { getNextToken(); stmtNumber = 0; // skip any statements? (e.g. for/next) if (targetStmtNumber) { executeMode = 0; parseStmts(); executeMode = 1; targetStmtNumber = 0; } // now execute ret = parseStmts(); if (ret != ERROR_NONE) break; // are we processing the input buffer? if (!lineNumber && !jumpLineNumber && !jumpStmtNumber) break; // if no control flow jumps, then just exit // are we RETURNing to the input buffer? if (lineNumber && !jumpLineNumber && jumpStmtNumber) lineNumber = 0; if (!lineNumber && !jumpLineNumber && jumpStmtNumber) { // we're executing the buffer, and need to jump stmt (e.g. for/next) tokenBuffer = tokenBuf; } else { // we're executing the program if (jumpLineNumber || jumpStmtNumber) { // line/statement number was changed e.g. goto p = findProgLine(jumpLineNumber); } else { // line number didn't change, so just move one to the next one p+= *(uint16_t *)p; } // end of program? if (p == &mem[sysPROGEND]) break; // end of program lineNumber = *(uint16_t*)(p+2); tokenBuffer = p+4; // if the target for a jump is missing (e.g. line deleted) and we're on the next line // reset the stmt number to 0 if (jumpLineNumber && jumpStmtNumber && lineNumber > jumpLineNumber) jumpStmtNumber = 0; } if (jumpStmtNumber) targetStmtNumber = jumpStmtNumber; if (host_breakPressed()) { ret = ERROR_BREAK_PRESSED; break; } } } return ret; } void reset() { // program at the start of memory sysPROGEND = 0; // stack is at the end of the program area sysSTACKSTART = sysSTACKEND = sysPROGEND; // variables/gosub stack at the end of memory sysVARSTART = sysVAREND = sysGOSUBSTART = sysGOSUBEND = MEMORY_SIZE; memset(&mem[0], 0, MEMORY_SIZE); stopLineNumber = 0; stopStmtNumber = 0; lineNumber = 0; }
Commit message
This repository is read-only for this account.
Repository snapshot
Current branch
master
Visibility
public
Your access
Read
Remote
Configured
File activity
View file history