#include "Arduino.h" #include "Basic.h" #include "ProgLines.h" #include "Stack.h" Basic basic; Basic::Basic() { rStatus = USER; initVars(); } void Basic::userInput(char *uLine){ // get the target variable location from the statement tokens int idx = statItems[0].intVal; if(statItems[0].sType == STRVAR) { if(stringVars[idx] != NULL) { free(stringVars[idx]); } stringVars[idx] = storeString(uLine); } else { intVars[idx] = atoi(uLine); } progLines.advanceLine(); // as this is end of INPUT statement execution if(rStatus == WAIT) {rStatus = RUN;} // possible for program to have terminated } void Basic::setForRun() { // zero all variables and var pointers set to NULL initVars(); rStatus = RUN; } int Basic::executeLine() { // executes the last parsed line errCode = 0; if(statType == REM) {return errCode;} if(statType == END) { rStatus = USER; Serial.println("Program End"); return errCode; } if(statType == GOTO) { if(progLines.setNextLine(statItems[0].intVal)) { return errCode; // valid line number set } else { return 2; } } if(statType == GOSUB) { short nLine = progLines.getNextLine(); if(progLines.setNextLine(statItems[0].intVal)) { gosubStack.push(nLine); return errCode; } else { return 22; } } if(statType == RETURN) { if(gosubStack.stackCount() < 1) { return 23; } progLines.setNextLine(gosubStack.pop()); return errCode; } if(statType == IF) { int thn = getTokenPos(STATEMENT, 0); statType = statItems[thn].token; if(evalCondition(0, thn -1)) { // we execute the THEN statement thn++; for(int i = 0, j = thn; j < statItemCount; i++, j++) { statItems[i] = statItems[j]; } statItemCount -= thn; errCode = executeLine(); // re-enter the function with new statement } return errCode; } if(statType == FOR) { forVars forvars; forvars.nextLine = progLines.getNextLine(); forvars.forVar = statItems[0].intVal; // values may be set from an expression int tp = getTokenPos(OPERATOR, TO); int sp = getTokenPos(OPERATOR, STEP); intVars[statItems[0].intVal] = evalInt(2, tp - 1); if(sp > 0) { forvars.toVal = evalInt(++tp, sp -1); forvars.stepVal = evalInt(++sp, statItemCount -1); } else { forvars.toVal = evalInt(++tp, statItemCount -1); forvars.stepVal = 1; // default } forStack.push(forvars); // push new FOR loop onto the forStack return errCode; } if(statType == NEXT) { if(forStack.stackCount() < 1) { return 13; // no active FOR loop } forVars forvars = forStack.pop(); int cInt = forvars.forVar; intVars[cInt] += forvars.stepVal; // increment the counter if((intVars[cInt] > forvars.toVal && forvars.stepVal > 0) || (intVars[cInt] < forvars.toVal && forvars.stepVal < 0)) { // we are done progLines.setNextLine(progLines.getNextLine()); // goto the line after the NEXT } else { progLines.setNextLine(forvars.nextLine); // goto the line after the FOR forStack.push(forvars); // push the struct back onto the for stack } return errCode; } char buff[STRINGMAX]; memset(buff, '\0', STRINGMAX); if(statType == INPT) { if(rStatus != RUN) { return 12;} if(statItems[0].sType == LITSTR || (statItems[0].sType == STRVAR && statItemCount == 2)) { // there is a user prompt if(statItems[0].sType == LITSTR) { strcat(buff, (char*)statItems[0].varLoc); } else { strcat(buff, (char*)stringVars[statItems[0].intVal]); } Serial.print(buff); free(statItems[0].varLoc); statItems[0] = statItems[1]; } rStatus = WAIT; // wait for user input return errCode; } if(statType == LET) { // decide if the var is int or string // then evaluate the expression to right of assignment op EQ int idx = statItems[0].intVal; if(statItems[0].sType == INTVAR) { intVars[idx] = evalInt(2, statItemCount -1); } else { evalString(2, statItemCount - 1, buff); if(stringVars[idx] != NULL) { free(stringVars[idx]); } stringVars[idx] = storeString(buff); } return errCode; } if(statType == PRINT) { if(statItemCount > 0) { evalString(0, statItemCount - 1, buff); Serial.println(buff); } else { Serial.println(); // blank line to print } return errCode; } return errCode; } int Basic::parseLine(char *pLine){ // return 0 if valid or error number clearStatement(); // free any memory used to store statement literals statItemCount = 0; pptr = nptr = pLine; statType = getStatement(); bool lastWasOp = true; if(statType == ERRORTOKEN) { return 1; // invalid statement type } // END, REM and NEXT are simple - statement is valid (any subsequent text ignored) switch (statType){ case END: case REM: case NEXT: case RETURN: return 0; } pptr = nptr; if(statType == GOTO || statType == GOSUB) { // all we need is a valid integer (invalid line is a run time error) short ln = atoi(pptr); if(ln < 0) { return 2; } else { // store the goto value and statItems[statItemCount].sType = JUMP; statItems[statItemCount].intVal = ln; statItemCount++; return 0; } } // now we need to get the remaining tokens char buff[STRINGMAX]; memset(buff, '\0', STRINGMAX); byte tkn = getNextToken(lastWasOp); while (tkn != ERRORTOKEN && statItemCount <= TOKENCOUNTLIMIT){ lastWasOp = false; switch (tkn) { case INT: case STRING: statItems[statItemCount].sType = (tkn == INT) ? INTVAR : STRVAR; statItems[statItemCount].intVal = (*pptr - 'A'); break; case NUMCONST: statItems[statItemCount].sType = INTCONST; statItems[statItemCount].intVal = atoi(pptr); break; case LITERAL: strncpy(buff, pptr, (int)(nptr - pptr - 1)); statItems[statItemCount].sType = LITSTR; statItems[statItemCount].varLoc = storeString(buff); memset(buff, '\0', STRINGMAX); break; case THEN: // what follows is another statement statItems[statItemCount].sType = STATEMENT; pptr = nptr; // move past the THEN and then find out what then statItems[statItemCount].token = getStatement(); break; case PLUS: case MINUS: case MULTIPLY: case DIVIDE: statItems[statItemCount].sType = ARITHOP; statItems[statItemCount].token = tkn; lastWasOp = true; break; default: // reasonable to assume these are operators but can include keywords like TO, STEP statItems[statItemCount].sType = OPERATOR; statItems[statItemCount].token = tkn; lastWasOp = true; break; } statItemCount++; pptr = nptr; tkn = getNextToken(lastWasOp); }; //diagPrintStack(); return validateStatement(statType, 0); } int Basic::validateStatement(int statType, int frm) { int itemCount = statItemCount - frm; switch (statType) { case GOTO: // only gets here if IF/THEN *** Should fix inconsistency return (statItems[frm].sType == INTCONST) ? 0 : 2; case GOSUB: return (statItems[frm].sType == INTCONST) ? 0 : 22; case LET: if(itemCount < 3) { return 6; // 3 tokens minimum for LET } if(statItems[frm].sType == INTVAR || statItems[frm].sType == STRVAR) { if(statItems[frm+1].sType == OPERATOR && statItems[frm+1].token == EQ) { return validateExp(frm+2, statItemCount); } else { return 10; } } else { return 9; } case PRINT: if (itemCount > 0) { return validateExp(frm, statItemCount); } else {return 0;} // empty is valid case INPT: switch(itemCount) { case 1: if(statItems[frm].sType == INTVAR || statItems[frm].sType == STRVAR){ return 0; } case 2: if(statItems[frm].sType == LITSTR || statItems[frm].sType == STRVAR){ if(statItems[frm+1].sType == INTVAR || statItems[frm+1].sType == STRVAR){ return 0; } else { return 20; } } default: return 20; } case FOR: if(frm > 0) { return 17; //cant have a FOR as a conditional statement for instance } if(itemCount < 5) { return 6; } if(statItems[0].sType == INTVAR) { if(statItems[1].sType == OPERATOR && statItems[1].token == EQ) { // could have an expression either side of TO and after optional STEP int tp = getTokenPos(OPERATOR, TO); int sp = getTokenPos(OPERATOR, STEP); if(tp == 0) { return 15; } if(validateExp(2, tp) != 0) { return 14;} if(validateExp(++tp, ((sp > 0) ? sp : statItemCount)) != 0) { return 16;} if(sp > 0) { if(validateExp(++sp, statItemCount) != 0) {return 24;} } return 0; // all good } else {return 8;} } else { return 4;} case IF: int th = getTokenPos(STATEMENT, 0); if(th == 0) {return 18;} int ce = validateExp(0, th); if(ce != 0) { return 19;} return validateStatement(statItems[th].token, th+1); // validate the statement after THEN } return 0; } // helper function locates a particular token in the statement list // matches just type or type and token value int Basic::getTokenPos(int type, int tkn) { for(int i = 1; i < statItemCount; i++) { if(statItems[i].sType == type && (tkn == 0 || statItems[i].token == tkn)) { return i; } } return 0; } // validateExp() checks that a token set represent a valid expression // idea is that values and operators should alternate, ( are ignored but should balance ) int Basic::validateExp(int strt, int stp) { int pCount = 0; int tr = 0; bool wasOp = true; for(int i = strt; i < stp; i++) { int st = statItems[i].sType; int tk = statItems[i].token; if(st == OPERATOR || st == ARITHOP){ if(tk == LEFTPAREN) {pCount++; continue;} if(tk == RIGHTPAREN) {pCount--; continue;} if(wasOp) {return 3;} wasOp = true; } if(st == STRVAR || st == INTVAR || st == INTCONST || st == LITSTR) { if(!wasOp) { return 5; } wasOp = false; } } if(pCount != 0) { return 7; } if(wasOp) { return 6; // op without value } return 0; } // getStatement() fetches a token indicating a statement type byte Basic::getStatement() { while(isWhitespace(*pptr)) { pptr++; } for (int k = 0; k < STATEMENTCOUNT ; k++) { if(strncmp(pptr, statements[k].keyword, strlen(statements[k].keyword)) == 0) { nptr = pptr + strlen(statements[k].keyword); return statements[k].token; } } return ERRORTOKEN; } // getNextToken() does what it says - reads forward through a statement to identify the next token byte Basic::getNextToken(bool wasOp){ // we are looking for constants, variables, operators and keywords // ignore whitespace between statement components while(isWhitespace(*pptr)) { pptr++; } if(*pptr == 0) { return ERRORTOKEN; } if(isdigit(*pptr) || (*pptr == '-' && wasOp)) { bool isNum = false; int i = (isdigit(*pptr)) ? 0 : 1; for(int j = strlen(pptr); i < j; i++) { if (isdigit(pptr[i])) { isNum = true; nptr = pptr + i; } else {break;} } if(isNum) { nptr++; return NUMCONST; } } if(*pptr == 34) { pptr++; for(int i = 0, j = strlen(pptr); i < j; i++) { if(pptr[i] == 34) { nptr = pptr + i; break; } } nptr++; return LITERAL; } for(int k = 0; k < OPCOUNT; k++) { if(strncmp(pptr, operators[k].keyword, strlen(operators[k].keyword)) == 0) { nptr = pptr + strlen(operators[k].keyword); return operators[k].token; } } for(int k = 0; k < KEYWORDCOUNT; k++) { if(strncmp(pptr, keywords[k].keyword, strlen(keywords[k].keyword)) == 0) { nptr = pptr + strlen(keywords[k].keyword); return keywords[k].token; } } // nor a keyword so an alpha char should denote a variable if(*pptr >= 'A' && *pptr <= 'Z') { if(pptr[1] == '%') { nptr = pptr + 2; return INT; } else if(pptr[1] == '$') { nptr = pptr + 2; return STRING; } else { nptr = pptr + 1; return ERRORTOKEN; } } return ERRORTOKEN; } // initvars() frees any existing memory used by string variables and sets integers to zero void Basic::initVars() { for(int i = 0; i < 26; i++) { intVars[i] = 0; if(stringVars[i] != NULL) { free(stringVars[i]); stringVars[i] = NULL; } } } // storeString() is a helper function that stores an string im the heap and returns a pointer void* Basic::storeString(char* str) { int strLen = strlen(str) + 1; void* rVal = malloc(strLen); // we try and store a terminating null if(rVal != NULL){ memcpy(rVal, str, strLen); return rVal; } } // clearStatement() frees memory used by program statements void Basic::clearStatement(){ for(int i = 0; i < statItemCount; i++) { if(statItems[i].sType == LITSTR) { free(statItems[i].varLoc); } } } // converts an integer value to a null terminated string void Basic::intToString(int iVal, char buff[]){ bool neg = false; if(iVal < 0) { neg = true; iVal = -iVal; } int i = 0; do { buff[i++] = iVal % 10 + '0'; } while ((iVal /= 10) > 0); if(neg) { buff[i++] = '-'; // add the sign } buff[i--] = '\0'; // terminate the string int h; // now reverse the string for(int j= 0; j < i; j++, i--) { h = buff[j]; buff[j] = buff[i]; buff[i] = h; } } // evaluates a conditional statement bool Basic::evalCondition(int strt, int stp) { Stack valueStack; Stack opStack; bool expInt = true; // expression type defaults to int int exStrt = strt; // start of first expression for(int i = strt; i <= stp; i++) { int st = statItems[i].sType; int tk = statItems[i].token; if((st == OPERATOR && tk == CONCAT) || (st == STRVAR || st == LITSTR)) { // part of string expression expInt = false; } if(st == OPERATOR || i == stp) { if(st == OPERATOR && tk == LEFTPAREN) { opStack.push(tk); exStrt = i + 1; continue; // just push a LEFTPAREN onto the stack } // otherwise evaluate the tokens so far and store the result int exEnd = (i == stp && st != OPERATOR) ? i : i-1; condItem eItem; if(expInt) { eItem.type = INTVAR; eItem.intVal = evalInt(exStrt, exEnd); } else { eItem.type = STRVAR; char bl[STRINGMAX]; memset(bl, '\0', STRINGMAX); evalString(exStrt, exEnd, bl); eItem.strLoc = (char*)storeString(bl); } valueStack.push(eItem); exStrt = i + 1; expInt = true; if (tk == RIGHTPAREN || tk == AND || tk == OR) { // we evaluate some conditional pairs while (opStack.peek() != LEFTPAREN) { int tkn = opStack.pop(); condItem rItem = valueStack.pop(); condItem lItem = valueStack.pop(); bool res = compArgs(tkn, lItem, rItem); rItem.type = 0; rItem.truth = res; valueStack.push(rItem); if(tk == AND || tk == OR) { opStack.push(tk); // we only evaluate last pair before adding this token to stack break; } } if(tk == RIGHTPAREN) { opStack.pop(); // remove the LEFTPAREN } } else { if(st == OPERATOR) { opStack.push(tk); } } } } while(opStack.stackCount() > 0){ int tkn = opStack.pop(); condItem rItem = valueStack.pop(); condItem lItem = valueStack.pop(); bool res = compArgs(tkn, lItem, rItem); rItem.type = 0; rItem.truth = res; valueStack.push(rItem); } condItem rItem = valueStack.pop(); return rItem.truth; } // compArgs() manages the comparison between pairs of stack items // can have any of 3 types bool Basic::compArgs(int op, condItem lc, condItem rc) { int l, r; if(lc.type == STRVAR || rc.type == STRVAR) { char bl[STRINGMAX], br[STRINGMAX]; memset(bl, '\0', STRINGMAX); memset(br, '\0', STRINGMAX); if(lc.type == STRVAR) { strcat(bl, lc.strLoc); free(lc.strLoc); } else { intToString(lc.intVal, bl); } if(rc.type == STRVAR) { strcat(br, rc.strLoc); free(rc.strLoc); } else { intToString(rc.intVal, br); } l = strcmp(bl, br); r = 0; } else if(lc.type == 0) { return (op == OR) ? (lc.truth || rc.truth) : (lc.truth && rc.truth); } else { l = lc.intVal; r = rc.intVal; } switch(op) { case EQ: return (l == r); case NEQ: return (l != r); case LT: return (l < r); case GT: return (l > r); case LTEQ: return (l <= r); case GTEQ: return (l >= r); default: Serial.println("Invalid comparison operator"); return false; } } // evalString() - strings are evaluated left to right with implicit concatenation // integer expressions are evaluated and converted to a numeric string void Basic::evalString(int strt, int stp, char buff[]) { int i = strt; while(i <= stp) { int st = statItems[i].sType; char* str = NULL; if(st == OPERATOR && statItems[i].token == CONCAT) { i++; continue; //the operators should be here but they are the default } if(st == STRVAR) { str = (char*)stringVars[statItems[i++].intVal]; //if(str != NULL) { //strcat(buff, str); //} //strcat(buff, (char*)stringVars[statItems[i++].intVal]); //continue; } if(st == LITSTR) { str = (char*)statItems[i++].varLoc; //strcat(buff, (char*)statItems[i++].varLoc); //continue; } if(partOfIntExp(i)) { int rPos, lPos = i; for(rPos = lPos; rPos <= stp; rPos++) { if(!partOfIntExp(rPos)) { break; } } rPos--; lPos = evalInt(lPos, rPos); char iBuff[30]; intToString(lPos, iBuff); str = iBuff; i = ++rPos; } if(str != NULL) { if(strlen(buff) + strlen(str) <= STRINGMAX - 1) { strcat(buff, str); } else { int lim = (STRINGMAX - 1) - strlen(buff); if (lim > 0) { strncat(buff, str, lim); } } } } } // partOfIntExp() is a helper function to detect components of integer expressions bool Basic::partOfIntExp(int si) { int st = statItems[si].sType; if(st == INTVAR || st == INTCONST) { return true; } if(st == OPERATOR) { if(statItems[si].token == LEFTPAREN || statItems[si].token == RIGHTPAREN) { return true; } } if(st == ARITHOP) { return true; } return false; } // evalInt() uses two stacks to evaluate an integer expression int Basic::evalInt(int strt, int stp) { Stack opStack; Stack valStack; // create a value stack and operator stack int op, r, l; for(int i = strt; i <= stp; i++) { if(statItems[i].sType == INTVAR) { valStack.push(intVars[statItems[i].intVal]); continue; } if(statItems[i].sType == INTCONST) { valStack.push(statItems[i].intVal); continue; } if(statItems[i].sType == STRVAR) { valStack.push(atoi((char*)stringVars[statItems[i].intVal])); continue; } if(statItems[i].sType == LITSTR) { valStack.push(atoi((char*)statItems[i].varLoc)); continue; } if(statItems[i].sType == OPERATOR || statItems[i].sType == ARITHOP) { if(statItems[i].token == LEFTPAREN) { opStack.push(LEFTPAREN); continue; } if(statItems[i].token == RIGHTPAREN) { while(opStack.peek() != LEFTPAREN) { op = opStack.pop(); r = valStack.pop(); l = valStack.pop(); // get the order right before left valStack.push(intArith(op, l, r)); } opStack.pop(); //remove the left paren continue; } // another operator while(opStack.stackCount() > 0) { if(opStack.peek() >= statItems[i].token){ // while ant existing operators have higher precedence op = opStack.pop(); r = valStack.pop(); l = valStack.pop(); // get the order right before left valStack.push(intArith(op, l, r)); } else { break; } } opStack.push(statItems[i].token); // push the new op } } // work back through the stack while(opStack.stackCount() > 0) { op = opStack.pop(); r = valStack.pop(); l = valStack.pop(); valStack.push(intArith(op, l, r)); } return valStack.pop(); } //intArith() - a helper function to apply an operator to two int values int Basic::intArith(int op, int lVal, int rVal) { switch(op) { case MULTIPLY: return lVal * rVal; case DIVIDE: if(rVal == 0) { errCode = 21; // signal run time error code return (sizeof(int) == 4) ? INT32_MAX : INT16_MAX; // or zero maybe ?? } return lVal / rVal; case MINUS: return lVal - rVal; case PLUS: return lVal + rVal; default: return 0; } } // used during program development to display the statement token list content void Basic::diagPrintStack() { for(int i = 0; i < statItemCount; i++) { Serial.print("Type: "); Serial.print(statItems[i].sType); switch(statItems[i].sType) { case LITSTR: Serial.print(", pointer: "); Serial.println((int)statItems[i].varLoc); break; case ARITHOP: Serial.print(", OPCODE: "); Serial.println(statItems[i].token); break; case OPERATOR: Serial.print(", OPCODE: "); Serial.println(statItems[i].token); break; default: Serial.print(", int: "); Serial.println(statItems[i].intVal); break; } } }