/* * @cond * The following section will be excluded from the documentation. */ /*********************************************************************************************************************** PicoMite MMBasic MMBasic.c Geoff Graham, Peter Mather Copyright (c) 2021, All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name MMBasic be used when referring to the interpreter in any documentation and promotional material and the original copyright message be displayed on the console at startup (additional copyright messages may be added). 4. All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed by the . 5. Neither the name of the nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ************************************************************************************************************************/ #include #include #include #include "MMBasic.h" #include "pico/stdlib.h" #include "Functions.h" #include "Commands.h" #include "Operators.h" #include "Custom.h" #include "Hardware_Includes.h" #include "hardware/flash.h" #ifndef PICOMITEWEB #include "pico/multicore.h" #endif // this is the command table that defines the various tokens for commands in the source code // most of them are listed in the .h files so you should not add your own here but instead add // them to the appropiate .h file #define INCLUDE_COMMAND_TABLE const struct s_tokentbl commandtbl[] = { #include "Functions.h" #include "Commands.h" #include "Operators.h" #include "Custom.h" #include "Hardware_Includes.h" }; #undef INCLUDE_COMMAND_TABLE // this is the token table that defines the other tokens in the source code // most of them are listed in the .h files so you should not add your own here // but instead add them to the appropiate .h file #define INCLUDE_TOKEN_TABLE const struct s_tokentbl tokentbl[] = { #include "Functions.h" #include "Commands.h" #include "Operators.h" #include "Custom.h" #include "Hardware_Includes.h" }; #undef INCLUDE_TOKEN_TABLE static inline CommandToken commandtbl_decode(const unsigned char *p){ return ((CommandToken)(p[0] & 0x7f)) | ((CommandToken)(p[1] & 0x7f)<<7); } // these are initialised at startup int CommandTableSize, TokenTableSize; #ifdef rp2350 struct s_funtbl funtbl[MAXSUBFUN]; //void hashlabels(int errabort); void hashlabels(unsigned char *p,int ErrAbort); #endif struct s_vartbl __attribute__ ((aligned (64))) g_vartbl[MAXVARS]={0}; // this table stores all variables int g_varcnt=0; // number of variables int g_VarIndex; // Global set by findvar after a variable has been created or found int g_Localvarcnt; // number of LOCAL variables int g_Globalvarcnt; // number of GLOBAL variables int g_LocalIndex; // used to track the level of local variables unsigned char OptionExplicit, OptionEscape, OptionConsole; // used to force the declaration of variables before their use bool OptionNoCheck=false; unsigned char DefaultType; // the default type if a variable is not specifically typed int emptyarray=0; int TempStringClearStart; // used to prevent clearing of space in an expression that called a FUNCTION unsigned char *subfun[MAXSUBFUN]; // table used to locate all subroutines and functions char CurrentSubFunName[MAXVARLEN + 1]; // the name of the current sub or fun char CurrentInterruptName[MAXVARLEN + 1]; // the name of the current interrupt function jmp_buf jmprun; jmp_buf mark; // longjump to recover from an error and abort jmp_buf ErrNext; // longjump to recover from an error and continue unsigned char inpbuf[STRINGSIZE]; // used to store user keystrokes until we have a line unsigned char tknbuf[STRINGSIZE]; // used to store the tokenised representation of the users input line //unsigned char lastcmd[STRINGSIZE]; // used to store the last command in case it is needed by the EDIT command unsigned char PromptString[MAXPROMPTLEN]; // the prompt for input, an empty string means use the default int ProgramChanged; // true if the program in memory has been changed and not saved struct s_hash g_hashlist[MAXVARS/2]={0}; int g_hashlistpointer=0; unsigned char *LibMemory; //This is where the library is stored. At the last flash slot (4) int multi=false; unsigned char *ProgMemory; // program memory, this is where the program is stored int PSize; // the size of the program stored in ProgMemory[] int NextData; // used to track the next item to read in DATA & READ stmts unsigned char *NextDataLine; // used to track the next line to read in DATA & READ stmts int g_OptionBase; // track the state of OPTION BASE int PrepareProgramExt(unsigned char *, int, unsigned char **, int); extern uint32_t core1stack[];; #if defined(MMFAMILY) unsigned char FunKey[NBRPROGKEYS][MAXKEYLEN + 1]; // data storage for the programmable function keys #endif char digit[256]={ 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,0,0,0,0,0, //0x10 0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,0, //0x20 1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, //0x30 0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0, //0x40 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x50 0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0, //0x60 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x70 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x80 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x90 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0xA0 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0xB0 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0xC0 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0xD0 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0xE0 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //0xF0 }; /////////////////////////////////////////////////////////////////////////////////////////////// // Global information used by operators and functions // int targ; // the type of the returned value MMFLOAT farg1, farg2, fret; // the two float arguments and returned value long long int iarg1, iarg2, iret; // the two integer arguments and returned value unsigned char *sarg1, *sarg2, *sret; // the two string arguments and returned value //////////////////////////////////////////////////////////////////////////////////////////////// // Global information used by functions // functions use targ, fret and sret as defined for operators (above) unsigned char *ep; // pointer to the argument to the function terminated with a zero byte. // it is NOT trimmed of spaces //////////////////////////////////////////////////////////////////////////////////////////////// // Global information used by commands // int cmdtoken; // Token number of the command unsigned char *cmdline; // Command line terminated with a zero unsigned char and trimmed of spaces unsigned char *nextstmt; // Pointer to the next statement to be executed. unsigned char *CurrentLinePtr, *SaveCurrentLinePtr; // Pointer to the current line (used in error reporting) unsigned char *ContinuePoint; // Where to continue from if using the continue statement extern int TraceOn; extern unsigned char *TraceBuff[TRACE_BUFF_SIZE]; extern int TraceBuffIndex; // used for listing the contents of the trace buffer extern long long int CallCFunction(unsigned char *CmdPtr, unsigned char *ArgList, unsigned char *DefP, unsigned char *CallersLinePtr); ///////////////////////////////////////////////////////////////////////////////////////////////// // Functions only used within MMBasic.c // //void getexpr(unsigned char *); //void checktype(int *, int); unsigned char __not_in_flash_func(*getvalue)(unsigned char *p, MMFLOAT *fa, long long int *ia, unsigned char **sa, int *oo, int *ta); unsigned char tokenTHEN, tokenELSE, tokenGOTO, tokenEQUAL, tokenTO, tokenSTEP, tokenWHILE, tokenUNTIL, tokenGOSUB, tokenAS, tokenFOR; unsigned short cmdIF, cmdENDIF, cmdEND_IF, cmdELSEIF, cmdELSE_IF, cmdELSE, cmdSELECT_CASE, cmdFOR, cmdNEXT, cmdWHILE, cmdENDSUB, cmdENDFUNCTION, cmdLOCAL, cmdSTATIC, cmdCASE, cmdDO, cmdLOOP, cmdCASE_ELSE, cmdEND_SELECT; unsigned short cmdSUB, cmdFUN, cmdCFUN, cmdCSUB, cmdIRET, cmdComment, cmdEndComment; /******************************************************************************************************************************************** Program management Includes the routines to initialise MMBasic, start running the interpreter, and to run a program in memory *********************************************************************************************************************************************/ // Initialise MMBasic void MIPS16 InitBasic(void) { DefaultType = T_NBR; CommandTableSize = (sizeof(commandtbl)/sizeof(struct s_tokentbl)); TokenTableSize = (sizeof(tokentbl)/sizeof(struct s_tokentbl)); ClearProgram(true); // load the commonly used tokens // by looking them up once here performance is improved considerably tokenTHEN = GetTokenValue( (unsigned char *)"Then"); tokenELSE = GetTokenValue( (unsigned char *)"Else"); tokenGOTO = GetTokenValue( (unsigned char *)"GoTo"); tokenEQUAL = GetTokenValue( (unsigned char *)"="); tokenTO = GetTokenValue( (unsigned char *)"To"); tokenSTEP = GetTokenValue( (unsigned char *)"Step"); tokenWHILE = GetTokenValue( (unsigned char *)"While"); tokenUNTIL = GetTokenValue( (unsigned char *)"Until"); tokenGOSUB = GetTokenValue( (unsigned char *)"GoSub"); tokenAS = GetTokenValue( (unsigned char *)"As"); tokenFOR = GetTokenValue( (unsigned char *)"For"); cmdLOOP = GetCommandValue( (unsigned char *)"Loop"); cmdIF = GetCommandValue( (unsigned char *)"If"); cmdENDIF = GetCommandValue( (unsigned char *)"EndIf"); cmdEND_IF = GetCommandValue( (unsigned char *)"End If"); cmdELSEIF = GetCommandValue( (unsigned char *)"ElseIf"); cmdELSE_IF = GetCommandValue( (unsigned char *)"Else If"); cmdELSE = GetCommandValue( (unsigned char *)"Else"); cmdSELECT_CASE = GetCommandValue( (unsigned char *)"Select Case"); cmdCASE = GetCommandValue( (unsigned char *)"Case"); cmdCASE_ELSE = GetCommandValue( (unsigned char *)"Case Else"); cmdEND_SELECT = GetCommandValue( (unsigned char *)"End Select"); cmdSUB = GetCommandValue( (unsigned char *)"Sub"); cmdFUN = GetCommandValue( (unsigned char *)"Function"); cmdLOCAL = GetCommandValue( (unsigned char *)"Local"); cmdSTATIC = GetCommandValue( (unsigned char *)"Static"); cmdENDSUB= GetCommandValue( (unsigned char *)"End Sub"); cmdENDFUNCTION = GetCommandValue( (unsigned char *)"End Function"); cmdDO= GetCommandValue( (unsigned char *)"Do"); cmdFOR= GetCommandValue( (unsigned char *)"For"); cmdNEXT= GetCommandValue( (unsigned char *)"Next"); cmdIRET = GetCommandValue( (unsigned char *)"IReturn"); cmdCSUB = GetCommandValue( (unsigned char *)"CSub"); cmdComment = GetCommandValue( (unsigned char *)"/*"); cmdEndComment = GetCommandValue( (unsigned char *)"*/"); // SInt(CommandTableSize); // SIntComma(TokenTableSize); // SSPrintString("\r\n"); } int CheckEmpty(char *p){ int emptyarray=0; char *pp = strchr((char *)p, '('); if(pp){ pp++; skipspace(pp); if(*pp == ')')emptyarray=1; } while(*(++pp)){ if(*pp=='(')return 1; // can't be a function call with an implied opening if(*pp==')')return 0; // closing bracket without open so much be implied in a function call e.g. PEEK( } return emptyarray; } // run a program // this will continuously execute a program until the end (marked by TWO zero chars) // the argument p must point to the first line to be executed void MIPS16 __not_in_flash_func(ExecuteProgram)(unsigned char *p) { int i, SaveLocalIndex = 0; jmp_buf SaveErrNext; memcpy(SaveErrNext, ErrNext, sizeof(jmp_buf)); // we call ExecuteProgram() recursively so we need to store/restore old jump buffer between calls skipspace(p); // just in case, skip any whitespace while(1) { if(*p == 0) p++; // step over the zero byte marking the beginning of a new element if(*p == T_NEWLINE) { CurrentLinePtr = p; // and pointer to the line for error reporting TraceBuff[TraceBuffIndex] = p; // used by TRACE LIST if(++TraceBuffIndex >= TRACE_BUFF_SIZE) TraceBuffIndex = 0; if(TraceOn && p > ProgMemory && p < ProgMemory + MAX_PROG_SIZE) { inpbuf[0] = '['; IntToStr((char *)inpbuf + 1, CountLines(p), 10); strcat((char *)inpbuf, "]"); MMPrintString((char *)inpbuf); uSec(1000); } p++; // and step over the token } if(*p == T_LINENBR) p += 3; // and step over the number skipspace(p); // and skip any trailing white space if(p[0] == T_LABEL) { // got a label p += p[1] + 2; // skip over the label skipspace(p); // and any following spaces } if(*p) { // if p is pointing to a command if(*p=='\'')nextstmt = cmdline = p + 1; else nextstmt = cmdline = p + sizeof(CommandToken); skipspace(cmdline); skipelement(nextstmt); if(*p && *p != '\'') { // ignore a comment line SaveLocalIndex = g_LocalIndex; // save this if we need to cleanup after an error if(setjmp(ErrNext) == 0) { // return to the else leg of this if error and OPTION ERROR SKIP/IGNORE is in effect if(p[0]>= C_BASETOKEN && p[1]>=C_BASETOKEN){ cmdtoken=commandtbl_decode(p); targ = T_CMD; commandtbl[cmdtoken].fptr(); // execute the command } else { if(!isnamestart(*p) && *p=='~') error("Unknown command"); else if(!isnamestart(*p)) error("Invalid character: @", (int)(*p)); i = FindSubFun(p, false); // it could be a defined command if(i >= 0) { // >= 0 means it is a user defined command DefinedSubFun(false, p, i, NULL, NULL, NULL, NULL); } else error("Unknown command"); } } else { g_LocalIndex = SaveLocalIndex; // restore so that we can clean up any memory leaks ClearTempMemory(); } if(OptionErrorSkip > 0) OptionErrorSkip--; // if OPTION ERROR SKIP decrement the count - we do not error if it is greater than zero if(g_TempMemoryIsChanged) ClearTempMemory(); // at the end of each command we need to clear any temporary string vars #ifndef PICOMITEWEB if(core1stack[0]!=0x12345678)error("CPU2 Stack overflow"); #endif if(!OptionNoCheck){ CheckAbort(); check_interrupt(); // check for an MMBasic interrupt or touch event and handle it } } p = nextstmt; } if((p[0] == 0 && p[1] == 0) || (p[0] == 0xff && p[1] == 0xff)) break; // the end of the program is marked by TWO zero chars, empty flash by two 0xff } memcpy(ErrNext, SaveErrNext, sizeof(jmp_buf)); // restore old jump buffer } /******************************************************************************************************************************************** Code associated with processing user defined subroutines and functions ********************************************************************************************************************************************/ // Scan through the program loaded in flash and build a table pointing to the definition of all user defined subroutines and functions. // This pre processing speeds up the program when using defined subroutines and functions // this routine also looks for embedded fonts and adds them to the font table void MIPS16 PrepareProgram(int ErrAbort) { int i, j, NbrFuncts; #ifdef rp2350 int u, namelen; uint32_t hash=FNV_offset_basis; char printvar[MAXVARLEN+1]; #endif unsigned char *p1, *p2; for(i = FONT_BUILTIN_NBR; i < FONT_TABLE_SIZE-1; i++) FontTable[i] = NULL; // clear the font table NbrFuncts = 0; CFunctionFlash = CFunctionLibrary = NULL; if(Option.LIBRARY_FLASH_SIZE == MAX_PROG_SIZE) NbrFuncts = PrepareProgramExt(LibMemory , 0, &CFunctionLibrary, ErrAbort); PrepareProgramExt(ProgMemory, NbrFuncts,&CFunctionFlash, ErrAbort); // check the sub/fun table for duplicates #ifdef rp2350 memset(funtbl,0,sizeof(struct s_funtbl)*MAXSUBFUN); for(i = 0; i < MAXSUBFUN && subfun[i] != NULL; i++) { // First we will hash the function name and add it to the function table // This allows for a fast check of a variable name being the same as a function name // It also allows a hash look up for function name matching p1 = subfun[i]; p1+=sizeof(CommandToken); skipspace(p1); p2 = (unsigned char *)printvar; namelen = 0; hash=FNV_offset_basis; do { u=mytoupper(*p1); hash ^= u; hash*=FNV_prime; *p2++ = u; p1++; if(++namelen > MAXVARLEN){ if(ErrAbort) error("Function name too long"); } } while(isnamechar(*p1)); if(namelen!=MAXVARLEN)*p2=0; hash %= MAXSUBHASH; //scale to size of table while(funtbl[hash].name[0]!=0){ hash++; if(hash==MAXSUBFUN)hash=0; } funtbl[hash].index=i; memcpy(funtbl[hash].name,printvar,(namelen == MAXVARLEN ? namelen :namelen+1)); } if(Option.LIBRARY_FLASH_SIZE == MAX_PROG_SIZE){ hashlabels(LibMemory,ErrAbort); // if(!ErrAbort) return; } hashlabels(ProgMemory,ErrAbort); //if(!ErrAbort) return; #endif if(!ErrAbort) return; for(i = 0; i < MAXSUBFUN && subfun[i] != NULL; i++) { for(j = i + 1; j < MAXSUBFUN && subfun[j] != NULL; j++) { CurrentLinePtr = p1 = subfun[i]; p1+=sizeof(CommandToken); skipspace(p1); p2 = subfun[j]; p2+=sizeof(CommandToken); skipspace(p2); while(1) { if(!isnamechar(*p1) && !isnamechar(*p2)) { if(ErrAbort) error("Duplicate name"); return; } if(mytoupper(*p1) != mytoupper(*p2)) break; p1++; p2++; } } } // for(i=0;i= MAXSUBFUN) { FlashWriteInit(PROGRAM_FLASH); flash_range_erase(realflashpointer, MAX_PROG_SIZE); int j=MAX_PROG_SIZE/4; int *pp=(int *)(flash_progmemory); while(j--)if(*pp++ != 0xFFFFFFFF){ enable_interrupts_pico(); error("Flash erase problem"); } enable_interrupts_pico(); MMPrintString("Error: Too many subroutines and functions - erasing program\r\n"); uSec(100000); ClearProgram(true); cmdline=NULL; do_end(false); longjmp(mark, 1); // jump back to the input prompt } subfun[i++] = p++; // save the address and step over the token p++; //step past rest of command token skipspace(p); if(!isnamestart(*p)) { if(ErrAbort) error("Invalid identifier"); i--; continue; } } while(*p) p++; // look for the zero marking the start of the next element } while(*p == 0) p++; // the end of the program can have multiple zeros p++; // step over the terminating 0xff *CFunPtr = (unsigned char *)(((unsigned int)p + 0b11) & ~0b11); // CFunction flash (if it exists) starts on the next word address after the program in flash if(i < MAXSUBFUN) subfun[i] = NULL; CurrentLinePtr = NULL; // now, step through the CFunction area looking for fonts to add to the font table //Bit 7 on the last address byte is used to identify a font. cfp = *(unsigned int **)CFunPtr; while(*cfp != 0xffffffff) { if(*cfp & 0x80000000) FontTable[*cfp & (FONT_TABLE_SIZE-1)] = (unsigned char *)(cfp + 2); cfp++; cfp += (*cfp + 4) / sizeof(unsigned int); } return i; } // searches the subfun[] table to locate a defined sub or fun // returns with the index of the sub/function in the table or -1 if not found // if type = 0 then look for a sub otherwise a function #ifdef rp2350 int __not_in_flash_func(FindSubFun)(unsigned char *p, int type) { unsigned char *s; unsigned char name[MAXVARLEN + 1]; int j, u, namelen; unsigned int hash=FNV_offset_basis; unsigned char *tp, *ip; // copy the variable name into name s = name; namelen = 0; do { u=mytoupper(*p); hash ^= u; // PIntComma(u); hash*=FNV_prime; *s++ = u; p++; if(++namelen > MAXVARLEN) error("Variable name too long"); } while(isnamechar(*p)); // PRet(); *s=0; hash %= MAXSUBHASH; //scale 0-512 // MMPrintString("Searching for function: ");MMPrintString((char *)name);PIntComma(hash);PRet(); while(funtbl[hash].name[0]!=0){ ip=name; tp=(unsigned char *)funtbl[hash].name; // MMPrintString("Testing : ");MMPrintString((char *)tp);PRet(); if(*ip++ == *tp++) { // preliminary quick check j = namelen-1; while(j > 0 && *ip == *tp) { // compare each letter j--; ip++; tp++; } if(j == 0 && (*(char *)tp == 0 || namelen == MAXVARLEN) && funtbl[hash].index and save it CurrentLinePtr = SubLinePtr; // report errors at the definition FunType = T_NOTYPE; if(isfun) { ttp = skipvar(ttp, false); // point to after the function name and bracketed arguments skipspace(ttp); if(*ttp == tokenAS) { // are we using Microsoft syntax (eg, AS INTEGER)? ttp++; // step over the AS token ttp = CheckIfTypeSpecified(ttp, &FunType, true); // get the type if(!(FunType & T_IMPLIED)) error("Variable type"); } FunType |= (V_FIND | V_DIM_VAR | V_LOCAL | V_EMPTY_OK); } // from now on // tp = the caller's argument list // p = the argument list for the definition skipspace(tp); skipspace(p); // similar if this is a CSUB CommandToken tkn=commandtbl_decode(SubLinePtr); if(tkn == cmdCSUB) { CallCFunction(SubLinePtr, tp, p, CallersLinePtr); // run the CSUB g_TempMemoryIsChanged = true; // signal that temporary memory should be checked return; } // from now on we have a user defined sub or function (not a C routine) if(gosubindex >= MAXGOSUB) error("Too many nested SUB/FUN"); errorstack[gosubindex] = CallersLinePtr; gosubstack[gosubindex++] = isfun ? NULL : nextstmt; // NULL signifies that this is returned to by ending ExecuteProgram() #define buffneeded MAX_ARG_COUNT*(sizeof(union u_argval)+ 2*sizeof(int)+3*sizeof(unsigned char *)+sizeof(unsigned char))+ 2*STRINGSIZE // allocate memory for processing the arguments argval=GetSystemMemory(buffneeded); argtype=(void *)argval+MAX_ARG_COUNT * sizeof(union u_argval); argVarIndex = (void *)argtype+MAX_ARG_COUNT * sizeof(int); argbuf1 = (void *)argVarIndex+MAX_ARG_COUNT * sizeof(int); argv1 = (void *)argbuf1+STRINGSIZE; argbuf2 = (void *)argv1+MAX_ARG_COUNT * sizeof(unsigned char *); argv2 = (void *)argbuf2+STRINGSIZE; argbyref=(void *)argv2+MAX_ARG_COUNT * sizeof(unsigned char *); // now split up the arguments in the caller CurrentLinePtr = CallersLinePtr; // report errors at the caller argc1 = 0; if(*tp) makeargs(&tp, MAX_ARG_COUNT, argbuf1, argv1, &argc1, (*tp == '(') ? (unsigned char *)"(," : (unsigned char *)","); // split up the arguments in the definition CurrentLinePtr = SubLinePtr; // any errors must be at the definition argc2 = 0; if(*p) makeargs(&p, MAX_ARG_COUNT, argbuf2, argv2, &argc2, (*p == '(') ? (unsigned char *)"(," : (unsigned char *)","); // error checking if(argc2 && (argc2 & 1) == 0) error("Argument list"); CurrentLinePtr = CallersLinePtr; // report errors at the caller if(argc1 > argc2 || (argc1 && (argc1 & 1) == 0)) error("Argument list"); // step through the arguments supplied by the caller and get the value supplied // these can be: // - missing (ie, caller did not supply that parameter) // - a variable, in which case we need to get a pointer to that variable's data and save its index so later we can get its type // - an expression, in which case we evaluate the expression and get its value and type for(i = 0; i < argc2; i += 2) { // count through the arguments in the definition of the sub/fun if(i < argc1 && *argv1[i]) { // check if the argument is a valid variable if(i < argc1 && isnamestart(*argv1[i]) && *skipvar(argv1[i], false) == 0) { // yes, it is a variable (or perhaps a user defined function which looks the same)? if(!(FindSubFun(argv1[i], 1) >= 0 && strchr((char *)argv1[i], '(') != NULL)) { // yes, this is a valid variable. set argvalue to point to the variable's data and argtype to its type argval[i].s = findvar(argv1[i], V_FIND | V_EMPTY_OK); // get a pointer to the variable's data argtype[i] = g_vartbl[g_VarIndex].type; // and the variable's type argVarIndex[i] = g_VarIndex; if(argtype[i] & T_CONST) { argtype[i] = 0; // we don't want to point to a constant } else { argtype[i] |= T_PTR; // flag this as a pointer } } } // check for BYVAL or BYREF in sub/fun definition argbyref[i]=0; skipspace(argv2[i]); if(toupper(*argv2[i]) == 'B' && toupper(*(argv2[i]+1)) == 'Y') { if((checkstring(argv2[i] + 2, (unsigned char *)"VAL")) != NULL) { // if BYVAL argtype[i] = 0; // remove any pointer flag in the caller argv2[i] += 5; // skip to the variable start } else { if((checkstring(argv2[i] + 2, (unsigned char *)"REF")) != NULL) { // if BYREF if((argtype[i] & T_PTR) == 0) error("Variable required for BYREF"); argv2[i] += 5; // skip to the variable start } argbyref[i]=1; } skipspace(argv2[i]); } // if argument is present and is not a pointer to a variable then evaluate it as an expression if(argtype[i] == 0) { long long int ia; evaluate(argv1[i], &argval[i].f, &ia, &s, &argtype[i], false); // get the value and type of the argument if(argtype[i] & T_INT) argval[i].i = ia; else if(argtype[i] & T_STR) { argval[i].s = GetMemory(STRINGSIZE); Mstrcpy(argval[i].s, s); } } } } // now we step through the parameters in the definition of the sub/fun // for each one we create the local variable and compare its type to that supplied in the callers list CurrentLinePtr = SubLinePtr; // any errors must be at the definition g_LocalIndex++; for(i = 0; i < argc2; i += 2) { // count through the arguments in the definition of the sub/fun ArgType = T_NOTYPE; tp = skipvar(argv2[i], false); // point to after the variable skipspace(tp); if(*tp == tokenAS) { // are we using Microsoft syntax (eg, AS INTEGER)? *tp++ = 0; // terminate the string and step over the AS token tp = CheckIfTypeSpecified(tp, &ArgType, true); // and get the type if(!(ArgType & T_IMPLIED)) error("Variable type"); } ArgType |= (V_FIND | V_DIM_VAR | V_LOCAL | V_EMPTY_OK); tp = findvar(argv2[i], ArgType); // declare the local variable if(g_vartbl[g_VarIndex].dims[0] > 0) error("Argument list"); // if it is an array it must be an empty array CurrentLinePtr = CallersLinePtr; // report errors at the caller // if the definition called for an array, special processing and checking will be required if(g_vartbl[g_VarIndex].dims[0] == -1) { int j; if(g_vartbl[argVarIndex[i]].dims[0] == 0) error("Expected an array"); if(TypeMask(g_vartbl[g_VarIndex].type) != TypeMask(argtype[i])) error("Incompatible type: $", argv1[i]); g_vartbl[g_VarIndex].val.s = NULL; for(j = 0; j < MAXDIM; j++) // copy the dimensions of the supplied variable into our local variable g_vartbl[g_VarIndex].dims[j] = g_vartbl[argVarIndex[i]].dims[j]; } // if this is a pointer check and the type is NOT the same as that requested in the sub/fun definition if((argtype[i] & T_PTR) && TypeMask(g_vartbl[g_VarIndex].type) != TypeMask(argtype[i])) { if(argbyref[i]){ error("BYREF requires same types: $", argv1[i]);} if((TypeMask(g_vartbl[g_VarIndex].type) & T_STR) || (TypeMask(argtype[i]) & T_STR)) error("Incompatible type: $", argv1[i]); // make this into an ordinary argument if(g_vartbl[argVarIndex[i]].type & T_PTR) { argval[i].i = *g_vartbl[argVarIndex[i]].val.ia; // get the value if the supplied argument is a pointer } else { argval[i].i = *(long long int *)argval[i].s; // get the value if the supplied argument is an ordinary variable } argtype[i] &= ~T_PTR; // and remove the pointer flag } // if this is a pointer (note: at this point the caller type and the required type must be the same) if(argtype[i] & T_PTR) { // the argument supplied was a variable so we must setup the local variable as a pointer if((g_vartbl[g_VarIndex].type & T_STR) && g_vartbl[g_VarIndex].val.s != NULL) { FreeMemorySafe((void **)&g_vartbl[g_VarIndex].val.s); // free up the local variable's memory if it is a pointer to a string } g_vartbl[g_VarIndex].val.s = argval[i].s; // point to the data of the variable supplied as an argument g_vartbl[g_VarIndex].type |= T_PTR; // set the type to a pointer g_vartbl[g_VarIndex].size = g_vartbl[argVarIndex[i]].size; // just in case it is a string copy the size // this is not a pointer } else if(argtype[i] != 0) { // in getting the memory argtype[] is initialised to zero // the parameter was an expression or a just straight variables with different types (therefore not a pointer)) if((g_vartbl[g_VarIndex].type & T_STR) && (argtype[i] & T_STR)) { // both are a string Mstrcpy(g_vartbl[g_VarIndex].val.s, argval[i].s); FreeMemorySafe((void **)&argval[i].s); } else if((g_vartbl[g_VarIndex].type & T_NBR) && (argtype[i] & T_NBR)) // both are a float g_vartbl[g_VarIndex].val.f = argval[i].f; else if((g_vartbl[g_VarIndex].type & T_NBR) && (argtype[i] & T_INT)) // need a float but supplied an integer g_vartbl[g_VarIndex].val.f = argval[i].i; else if((g_vartbl[g_VarIndex].type & T_INT) && (argtype[i] & T_INT)) // both are integers g_vartbl[g_VarIndex].val.i = argval[i].i; else if((g_vartbl[g_VarIndex].type & T_INT) && (argtype[i] & T_NBR)) // need an integer but was supplied with a float g_vartbl[g_VarIndex].val.i = FloatToInt64(argval[i].f); else error("Incompatible type: $", argv1[i]); } } // temp memory used in setting up the arguments can be deleted now /* FreeMemory((unsigned char *)argval); FreeMemory((unsigned char *)argtype); FreeMemory((unsigned char *)argVarIndex); FreeMemory(argbuf1); FreeMemory((unsigned char *)argv1); FreeMemory(argbuf2); FreeMemory((unsigned char *)argv2); FreeMemory((unsigned char *)argbyref);*/ FreeMemory((void*)argval); strcpy((char *)CurrentSubFunName, (char *)fun_name); // if it is a defined command we simply point to the first statement in our command and allow ExecuteProgram() to carry on as before // exit from the sub is via cmd_return which will decrement g_LocalIndex if(!isfun) { skipelement(p); nextstmt = p; // point to the body of the subroutine return; } // if it is a defined function we have a lot more work to do. We must: // - Create a local variable for the function's name // - Save the globals being used by the current command that caused the function to be called // - Invoke another instance of ExecuteProgram() to execute the body of the function // - When that returns we need to restore the global variables // - Get the variable's value and save that in the return value globals (fret or sret) // - Return to the expression parser tp = findvar(fun_name, FunType | V_FUNCT); // declare the local variable FunType = g_vartbl[g_VarIndex].type; if(FunType & T_STR) { FreeMemorySafe((void **)&g_vartbl[g_VarIndex].val.s); // free the memory if it is a string g_vartbl[g_VarIndex].type |= T_PTR; g_LocalIndex--; // allocate the memory at the previous level g_vartbl[g_VarIndex].val.s = tp = GetTempMemory(STRINGSIZE); // and use our own memory g_LocalIndex++; } skipelement(p); // point to the body of the function ttp = nextstmt; // save the globals used by commands tcmdtoken = cmdtoken; s = cmdline; ExecuteProgram(p); // execute the function's code CurrentLinePtr = CallersLinePtr; // report errors at the caller cmdline = s; // restore the globals cmdtoken = tcmdtoken; nextstmt = ttp; // return the value of the function's variable to the caller if(FunType & T_NBR) *fa = *(MMFLOAT *)tp; else if(FunType & T_INT) *i64a = *(long long int *)tp; else *sa = tp; // for a string we just need to return the local memory *typ = FunType; // save the function type for the caller ClearVars(g_LocalIndex--, true); // delete any local variables g_TempMemoryIsChanged = true; // signal that temporary memory should be checked gosubindex--; } char MIPS16 *strcasechr(const char *p, int ch) { char c; c = mytoupper(ch); for (;; ++p) { if (mytoupper(*p) == c) return ((char *)p); if (*p == '\0') return (NULL); } /* NOTREACHED */ } char MIPS16 *fstrstr (const char *s1, const char *s2) { const char *p = s1; const size_t len = strlen (s2); for (; (p = strcasechr (p, *s2)) != 0; p++) { if (strncasecmp (p, s2, len) == 0) return (char *)p; } return (0); } void MIPS16 str_replace(char *target, const char *needle, const char *replacement, uint8_t ignoresurround) { char buffer[288] = { 0 }; char *insert_point = &buffer[0]; const char *tmp = target; size_t needle_len = strlen(needle); size_t repl_len = strlen(replacement); while (1) { const char *p = fstrstr(tmp, needle); // walked past last occurrence of needle; copy remaining part if (p == NULL) { strcpy(insert_point, tmp); break; } char *q; if(p==target){ ignoresurround|=1; q=(char *)p; } else q=(char *)p-1; if( (isnamechar(*q) && !(ignoresurround & 1)) || (isnameend(p[strlen(needle)]) && !(ignoresurround & 2))){ // copy part before needle memcpy(insert_point, tmp, p - tmp); insert_point += p - tmp; // copy replacement string memcpy(insert_point, needle, needle_len); insert_point += needle_len; // adjust pointers, move on tmp = p + needle_len; } else { // copy part before needle memcpy(insert_point, tmp, p - tmp); insert_point += p - tmp; // copy replacement string memcpy(insert_point, replacement, repl_len); insert_point += repl_len; // adjust pointers, move on tmp = p + needle_len; } } // write altered string back to target strcpy(target, buffer); } void MIPS16 STR_REPLACE(char *target, const char *needle, const char *replacement, uint8_t ignoresurround){ char *ip=target; int toggle=0; char comment[STRINGSIZE]={0}; skipspace(ip); if(!(toupper(*ip)=='R' && toupper(ip[1])=='E' && toupper(ip[2])=='M' )){ while(*ip){ if(*ip==34){ if(toggle==0)toggle=1; else toggle=0; } if(toggle && *ip==' '){ *ip=0xFF; } if(toggle && *ip=='.'){ *ip=0xFE; } if(toggle && *ip=='='){ *ip=0xFD; } if(toggle && *ip=='\\'){ *ip=0xFC; } if(toggle==0 && *ip=='\''){ strcpy(comment,ip); *ip=0; break; } ip++; } str_replace(target, needle, replacement, ignoresurround); ip=target; if(comment[0]=='\''){ strcat(target,comment); } while(*ip){ if(*ip==0xFF)*ip=' '; if(*ip==0xFE)*ip='.'; if(*ip==0xFD)*ip='='; if(*ip==0xFC)*ip='\\'; ip++; } } } /******************************************************************************************************************************************** take an input line and turn it into a line with tokens suitable saving into memory ********************************************************************************************************************************************/ //take an input string in inpbuf[] and copy it to tknbuf[] and: // - convert the line number to a binary number // - convert a label to the token format // - convert keywords to tokens // - convert the colon to a zero char //the result in tknbuf[] is terminated with MMFLOAT zero chars // if the arg console is true then do not add a line number void MIPS16 tokenise(int console) { unsigned char *p, *op, *tp; int i=0; int firstnonwhite; int labelvalid; // first, make sure that only printable characters are in the line p = inpbuf; while(*p) { *p = *p & 0x7f; if(*p < ' ' || *p == 0x7f) *p = ' '; p++; } tp = inpbuf; skipspace(tp); if(toupper(tp[0])=='H' && toupper(tp[1])=='E' && toupper(tp[2])=='L' && toupper(tp[3])=='P' && tp[4]==' '){ unsigned char *q=&tp[5]; skipspace(q); if(*q!='"'){ int end=strlen((char *)q); memmove(&q[1],q,strlen((char *)q)); *q='"'; q[end+1]=0; } } if(toupper(tp[0])=='R' && toupper(tp[1])=='E' && toupper(tp[2])=='M' && tp[3]==' ')i=1; if(multi==false && i==false){ int i=0; while(i",">=", 3); STR_REPLACE((char *)inpbuf,"=<","<=", 3); STR_REPLACE((char *)inpbuf,"SPRITE MEMORY","BLIT MEMORY",0); STR_REPLACE((char *)inpbuf,"PEEK(BYTE","PEEK(INT8",0); } // setup the input and output buffers p = inpbuf; op = tknbuf; if(!console) *op++ = T_NEWLINE; // get the line number if it exists tp = p; skipspace(tp); for(i = 0; i < 8; i++) if(!isxdigit(tp[i])) break; // test if this is eight hex digits if(IsDigitinline(*tp) && i < 8) { // if it a digit and not an 8 digit hex number (ie, it is CFUNCTION data) then try for a line number i = strtol((char *)tp, (char **)&tp, 10); if(!console && i > 0 && i <= MAXLINENBR) { *op++ = T_LINENBR; *op++ = (i>>8); *op++ = (i & 0xff); } p = tp; } // process the rest of the line firstnonwhite = true; labelvalid = true; tp=p; skipspace(tp); if(*tp=='.'){ if(!strncasecmp((char *)tp,".SIDE SET ",10) || !strncasecmp((char *)tp,".END PROGRAM",12) || !strncasecmp((char *)tp,".WRAP",4) || !strncasecmp((char *)tp,".LINE ",5) || !strncasecmp((char *)tp,".PROGRAM ",9) || !strncasecmp((char *)tp,".LABEL ",6) ) *tp='_'; } while(*p) { if(*p=='*' && p[1]=='/'){ multi=false; } // just copy a space if(*p == ' ') { *op++ = *p++; continue; } // first look for quoted text and copy it across // this will also accept a string without the closing quote and it will add the quote in if(*p == '"') { do { *op++ = *p++; } while(*p != '"' && *p); *op++ = '"'; if(*p == '"') p++; continue; } // copy anything after a comment (') if(*p == '\'' || multi==true) { do { *op++=*p++; } while(*p); continue; } // check for multiline separator (colon) and replace with a zero char if(*p == ':') { *op++ = 0; p++; while(*p == ':') { // insert a space between consecutive colons *op++ = ' '; *op++ = 0; p++; } firstnonwhite = true; continue; } // not whitespace or string or comment - try a number if(IsDigitinline(*p) || *p == '.') { // valid chars at the start of a number while(IsDigitinline(*p) || *p == '.' || *p == 'E' || *p == 'e') if (*p == 'E' || *p == 'e') { // check for '+' or '-' as part of the exponent *op++ = *p++; // copy the number if (*p == '+' || *p == '-') { // BUGFIX by Gerard Sexton *op++ = *p++; // copy the '+' or '-' } } else { *op++ = *p++; // copy the number } firstnonwhite = false; continue; } // not whitespace or string or comment or number - see if we can find a label or a token identifier if(firstnonwhite) { // first entry on the line must be a command // these variables are only used in the search for a command code unsigned char *tp2, *match_p = NULL; int match_i = -1, match_l = 0; // first test if it is a print shortcut char (?) - this needs special treatment if(*p == '?') { match_i = GetCommandValue((unsigned char *)"Print"); if(*++p == ' ') p++; // eat a trailing space match_p = p; } else if((tp2 = checkstring(p, (unsigned char *)"BITBANG")) != NULL) { match_i = GetCommandValue((unsigned char *)"Device"); match_p = p = tp2; } else { // now try for a command in the command table // this works by scanning the entire table looking for the match with the longest command name // this is needed because we need to differentiate between END and END SUB for example. // without looking for the longest match we might think that we have a match when we found just END. for(i = 0 ; i < CommandTableSize - 1; i++) { tp2 = p; tp = commandtbl[i].name; while(mytoupper(*tp2) == mytoupper(*tp) && *tp != 0) { if(*tp == ' ') skipspace(tp2); // eat up any extra spaces between keywords else tp2++; tp++; if(*tp == '(') skipspace(tp2); // eat up space between a keyword and bracket } // we have a match if(*tp == 0 && (!isnamechar(*tp2) || (commandtbl[i].type & T_FUN))) { if(*(tp - 1) != '(' && isnamechar(*tp2)) continue; // skip if not the function // save the details if it is the longest command found so far if(strlen((char *)commandtbl[i].name) > match_l) { match_p = tp2; match_l = strlen((char *)commandtbl[i].name); match_i = i; } } } } if(match_i > -1) { // we have found a command // *op++ = match_i + C_BASETOKEN; // insert the token found *op++ = (match_i & 0x7f ) + C_BASETOKEN; *op++ = (match_i >> 7) + C_BASETOKEN; //tokens can be 14-bit p = match_p; // step over the command in the source if(isalpha(*(p-1)) && *p == ' ') p++; // if the command is followed by a space skip over it if(match_i == GetCommandValue((unsigned char *)"Rem")) // check if it is a REM command while(*p) *op++ = *p++; // and in that case just copy everything firstnonwhite = false; labelvalid = false; // we do not want any labels after this if(match_i == GetCommandValue((unsigned char *)"/*")){ multi= true; } if(match_i == GetCommandValue((unsigned char *)"*/"))multi= false; if(match_i == GetCommandValue((unsigned char *)"OPTION") || match_i == GetCommandValue((unsigned char *)"CONFIGURE" )){ STR_REPLACE((char *)inpbuf,"GAME*MITE","GAMEMITE", false); STR_REPLACE((char *)inpbuf,"PICO-RESTOUCH-LCD-3.5","PICORESTOUCHLCD3.5",false); STR_REPLACE((char *)inpbuf,"PICO-RESTOUCH-LCD-2.8","PICORESTOUCHLCD2.8",false); STR_REPLACE((char *)inpbuf,"RP2040-LCD-1.28","RP2040LCD1.28",false); STR_REPLACE((char *)inpbuf,"RP2040-LCD-0.96","RP2040LCD0.96",false); STR_REPLACE((char *)inpbuf,"RP2040-GEEK","RP2040GEEK",false); STR_REPLACE((char *)inpbuf,"PICOGAME 4-PWM","PICOGAME 4PWM",false); STR_REPLACE((char *)inpbuf,"OLIMEX USB","OLIMEXUSB",false); } continue; } // next test if it is a label if(labelvalid && isnamestart(*p)) { for(i = 0, tp = p + 1; i < MAXVARLEN - 1; i++, tp++) if(!isnamechar(*tp)) break; // search for the first invalid char if(*tp == ':') { // Yes !! It is a label labelvalid = false; // we do not want any more labels *op++ = T_LABEL; // insert the token *op++ = tp - p; // insert the length of the label for(i = tp - p; i > 0; i--) *op++ = *p++; // copy the label p++; // step over the terminating colon continue; } } } else { // check to see if it is a function or keyword unsigned char *tp2 = NULL; for(i = 0 ; i < TokenTableSize - 1; i++) { tp2 = p; tp = tokentbl[i].name; // check this entry while(mytoupper(*tp2) == mytoupper(*tp) && *tp != 0) { tp++; tp2++; if(*tp == '(') skipspace(tp2); } if(*tp == 0 && (!isnameend(*(tp - 1)) || !isnamechar(*tp2))) break; } if(i != TokenTableSize - 1) { // we have a match i += C_BASETOKEN; *op++ = i; // insert the token found p = tp2; // and step over it in the source text if(i == tokenTHEN || i == tokenELSE) firstnonwhite = true; // a command is valid after a THEN or ELSE else firstnonwhite = false; continue; } } // not whitespace or string or comment or token identifier or number // try for a variable name which could be a user defined subroutine or an implied let if(isnamestart(*p)) { // valid chars at the start of a variable name if(firstnonwhite) { // first entry on the line? tp = skipvar(p, true); // find the char after the variable skipspace(tp); if(*tp == '=') { unsigned short tkn = GetCommandValue((unsigned char *)"Let"); // is it an implied let? *op++ = (tkn & 0x7f ) + C_BASETOKEN; *op++ = (tkn >> 7) + C_BASETOKEN; //tokens can be 14-bit } } while(isnamechar(*p)) *op++ = *p++; // copy the variable name firstnonwhite = false; labelvalid = false; // we do not want any labels after this continue; } // special case where the character to copy is an opening parenthesis // we search back to see if the previous non space char was the end of an identifier and, if it is, we remove any spaces following the identifier // this enables the programmer to put spaces after a function name or array identifier without causing a confusing error if(*p == '(') { tp = op - 1; if(*tp == ' ') { while(*tp == ' ') tp--; if(isnameend(*tp)) op = tp + 1; } } // something else, so just copy the one character *op++ = *p++; labelvalid = false; // we do not want any labels after this firstnonwhite = false; } // end of loop, trim any trailing blanks (but not part of a line number) while(*(op - 1) == ' ' && op > tknbuf + 3) *--op = 0; // make sure that it is terminated properly *op++ = 0; *op++ = 0; *op++ = 0; // terminate with zero chars } /******************************************************************************************************************************************** routines for evaluating expressions the main functions are getnumber(), getinteger() and getstring() ********************************************************************************************************************************************/ // A convenient way of evaluating an expression // it takes two arguments: // p = pointer to the expression in memory (leading spaces will be skipped) // t = pointer to the type // if *t = T_STR or T_NBR or T_INT will throw an error if the result is not the correct type // if *t = T_NOTYPE it will not throw an error and will return the type found in *t // it returns with a void pointer to a float, integer or string depending on the value returned in *t // this will check that the expression is terminated correctly and throw an error if not void __not_in_flash_func(*DoExpression)(unsigned char *p, int *t) { static MMFLOAT f; static long long int i64; static unsigned char *s; evaluate(p, &f, &i64, &s, t, false); if(*t & T_INT) return &i64; if(*t & T_NBR) return &f; if(*t & T_STR) return s; error("Internal fault 1(sorry)"); return NULL; // to keep the compiler happy } // evaluate an expression. p points to the start of the expression in memory // returns either the float or string in the pointer arguments // *t points to an integer which holds the type of variable we are looking for // if *t = T_STR or T_NBR or T_INT will throw an error if the result is not the correct type // if *t = T_NOTYPE it will not throw an error and will return the type found in *t // this will check that the expression is terminated correctly and throw an error if not. flags & E_NOERROR will suppress that check unsigned char MIPS16 __not_in_flash_func(*evaluate)(unsigned char *p, MMFLOAT *fa, long long int *ia, unsigned char **sa, int *ta, int flags) { int o; int t = *ta; unsigned char *s; p = getvalue(p, fa, ia, &s, &o, &t); // get the left hand side of the expression, the operator is returned in o while(o != E_END) p = doexpr(p, fa, ia, &s, &o, &t); // get the right hand side of the expression and evaluate the operator in o // check that the types match and convert them if we can if((*ta & (T_NBR |T_INT)) && t & T_STR) error("Expected a number"); if(*ta & T_STR && (t & (T_NBR | T_INT))) error("Expected a string"); if(o != E_END) error("Argument count"); if((*ta & T_NBR) && (t & T_INT)) *fa = *ia; if((*ta & T_INT) && (t & T_NBR)) *ia = FloatToInt64(*fa); *ta = t; *sa = s; // check that the expression is terminated correctly if(!(flags & E_NOERROR)) { skipspace(p); if(!(*p == 0 || *p == ',' || *p == ')' || *p == '\'')) error("Expression syntax"); } return p; } // evaluate an expression to get a number MMFLOAT __not_in_flash_func(getnumber)(unsigned char *p) { int t = T_NBR; MMFLOAT f; long long int i64; unsigned char *s; evaluate(p, &f, &i64, &s, &t, false); if(t & T_INT) return (MMFLOAT)i64; return f; } // evaluate an expression and return a 64 bit integer long long int __not_in_flash_func(getinteger)(unsigned char *p) { int t = T_INT; MMFLOAT f; long long int i64; unsigned char *s; evaluate(p, &f, &i64, &s, &t, false); if(t & T_NBR) return FloatToInt64(f); return i64; } // evaluate an expression and return an integer // this will throw an error is the integer is outside a specified range // this will correctly round the number if it is a fraction of an integer long long int __not_in_flash_func(getint)(unsigned char *p, long long int min, long long int max) { long long int i; int t = T_INT; MMFLOAT f; long long int i64; unsigned char *s; evaluate(p, &f, &i64, &s, &t, false); if(t & T_NBR) i= FloatToInt64(f); else i=i64; if(i < min || i > max) error("~ is invalid (valid is ~ to ~)", i, min, max); return i; } // evaluate an expression to get a string unsigned char __not_in_flash_func(*getstring)(unsigned char *p) { int t = T_STR; MMFLOAT f; long long int i64; unsigned char *s; evaluate(p, &f, &i64, &s, &t, false); return s; } // evaluate an expression to get a string using the C style for a string // as against the MMBasic style returned by getstring() unsigned char __not_in_flash_func(*getCstring)(unsigned char *p) { unsigned char *tp; tp = GetTempMemory(STRINGSIZE); // this will last for the life of the command Mstrcpy(tp, getstring(p)); // get the string and save in a temp place MtoC(tp); // convert to a C style string return tp; } unsigned char *getFstring(unsigned char *p) { unsigned char *tp; tp = GetTempMemory(STRINGSIZE); // this will last for the life of the command Mstrcpy(tp, getstring(p)); // get the string and save in a temp place for(int i=1;i<=*tp;i++)if(tp[i]=='\\')tp[i]='/'; if((toupper(tp[1])=='A' || toupper(tp[1])=='B') && tp[2]==':' && !(tp[3]=='/')){ memmove(&tp[4],&tp[3],tp[0]-2); tp[3]='/'; tp[0]++; } MtoC(tp); return tp; } // recursively evaluate an expression observing the rules of operator precedence unsigned char MIPS16 __not_in_flash_func(*doexpr)(unsigned char *p, MMFLOAT *fa, long long int *ia, unsigned char **sa, int *oo, int *ta) { MMFLOAT fa1, fa2; long long int ia1, ia2; int o1, o2; int t1, t2; unsigned char *sa1, *sa2; TestStackOverflow(); // throw an error if we have overflowed the PIC32's stack fa1 = *fa; ia1 = *ia; sa1 = *sa; t1 = TypeMask(*ta); o1 = *oo; p = getvalue(p, &fa2, &ia2, &sa2, &o2, &t2); while(1) { if(o2 == E_END || tokentbl[o1].precedence <= tokentbl[o2].precedence) { if((t1 & T_STR) != (t2 & T_STR)) error("Incompatible types in expression"); targ = tokentbl[o1].type & (T_NBR | T_INT); if(targ == T_NBR) { // if the operator does not work with ints convert the args to floats if(t1 & T_INT) { fa1 = ia1; t1 = T_NBR; } // at this time the only example of this is op_div (/) if(t2 & T_INT) { fa2 = ia2; t2 = T_NBR; } } if(targ == T_INT) { // if the operator does not work with floats convert the args to ints if(t1 & T_NBR) { ia1 = FloatToInt64(fa1); t1 = T_INT; } if(t2 & T_NBR) { ia2 = FloatToInt64(fa2); t2 = T_INT; } } if(targ == (T_NBR | T_INT)) { // if the operator will work with both floats and ints if(t1 & T_NBR && t2 & T_INT) { fa2 = ia2; t2 = T_NBR; } // if one arg is float convert the other to a float if(t1 & T_INT && t2 & T_NBR) { fa1 = ia1; t1 = T_NBR; } } if(!(tokentbl[o1].type & T_OPER) || !(tokentbl[o1].type & t1)) { error("Invalid operator"); } farg1 = fa1; farg2 = fa2; // setup the float args (incase it is a float) sarg1 = sa1; sarg2 = sa2; // ditto string args iarg1 = ia1; iarg2 = ia2; // ditto integer args targ = t1; // this is what both args are tokentbl[o1].fptr(); // call the operator function *fa = fret; *ia = iret; *sa = sret; *oo = o2; *ta = targ; return p; } // the next operator has a higher precedence, recursive call to evaluate it else p = doexpr(p, &fa2, &ia2, &sa2, &o2, &t2); } } // get a value, either from a constant, function or variable // also returns the next operator to the right of the value or E_END if no operator unsigned char MIPS16 __not_in_flash_func(*getvalue)(unsigned char *p, MMFLOAT *fa, long long int *ia, unsigned char **sa, int *oo, int *ta) { MMFLOAT f = 0; long long int i64 = 0; unsigned char *s = NULL; int t = T_NOTYPE; unsigned char *tp, *p1, *p2; int i; TestStackOverflow(); // throw an error if we have overflowed the PIC32's stack skipspace(p); if(*p>=C_BASETOKEN){ //don't waste time if not a built-in function // special processing for the NOT operator // just get the next value and invert its logical value if(*p<=131){ // special processing for the unary operators if(tokenfunction(*p) == op_not) { int ro; p++; t = T_NOTYPE; p = getvalue(p, &f, &i64, &s, &ro, &t); // get the next value if(t &T_NBR) f = (MMFLOAT)((f != 0)?0:1); // invert the value returned else if(t & T_INT) i64 = ((i64 != 0)?0:1); else error("Expected a number"); skipspace(p); *fa = f; // save what we have *ia = i64; *sa = s; *ta = t; *oo = ro; return p; // return straight away as we already have the next operator } else if(tokenfunction(*p) == op_inv) { int ro; p++; t = T_NOTYPE; p = getvalue(p, &f, &i64, &s, &ro, &t); // get the next value if(t & T_NBR) i64 = FloatToInt64(f); else if(!(t & T_INT)) error("Expected a number"); i64 = ~i64; t = T_INT; skipspace(p); *fa = f; // save what we have *ia = i64; *sa = s; *ta = t; *oo = ro; return p; // return straight away as we already have the next operator } else if(tokenfunction(*p) == op_subtract) { int ro; p++; t = T_NOTYPE; p = getvalue(p, &f, &i64, &s, &ro, &t); // get the next value if(t & T_NBR) f = -f; // negate the MMFLOAT returned else if(t & T_INT) i64 = -i64; // negate the integer returned else error("Expected a number"); skipspace(p); *fa = f; // save what we have *ia = i64; *sa = s; *ta = t; *oo = ro; return p; // return straight away as we already have the next operator } else if(tokenfunction(*p) == op_add) { int ro; p++; t = T_NOTYPE; p = getvalue(p, &f, &i64, &s, &ro, &t); // get the next value skipspace(p); *fa = f; // save what we have *ia = i64; *sa = s; *ta = t; *oo = ro; return p; // return straight away as we already have the next operator } } if(tokentype(*p) & (T_FUN | T_FNA)) { // if a function execute it and save the result int tmp; tp = p; // if it is a function with arguments we need to locate the closing bracket and copy the argument to // a temporary variable so that functions like getarg() will work. if(tokentype(*p) & T_FUN) { p1 = p + 1; p = getclosebracket(p); // find the closing bracket p2 = ep = GetTempMemory(STRINGSIZE); // this will last for the life of the command while(p1 != p) *p2++ = *p1++; } p++; // point to after the function (without argument) or after the closing bracket tmp = targ = TypeMask(tokentype(*tp)); // set the type of the function (which might need to know this) tokenfunction(*tp)(); // execute the function if((tmp & targ) == 0) error("Internal fault 2(sorry)"); // as a safety check the function must return a type the same as set in the header t = targ; // save the type of the function f = fret; i64 = iret; s = sret; // save the result } } else { // if it is a variable or a defined function, find it and get its value if(isnamestart(*p)) { // first check if it is terminated with a bracket tp = p + 1; while(isnamechar(*tp)) tp++; // search for the end of the identifier if(*tp == '$' || *tp == '%' || *tp == '!') tp++; i = -1; if(*tp == '(') i = FindSubFun(p, 1); // if terminated with a bracket it could be a function if(i >= 0) { // >= 0 means it is a user defined function unsigned char *SaveCurrentLinePtr = CurrentLinePtr; // in case the code in DefinedSubFun messes with this DefinedSubFun(true, p, i, &f, &i64, &s, &t); CurrentLinePtr = SaveCurrentLinePtr; } else { s = (unsigned char *)findvar(p, V_FIND); // if it is a string then the string pointer is automatically set t = TypeMask(g_vartbl[g_VarIndex].type); if(t & T_NBR) f = (*(MMFLOAT *)s); if(t & T_INT) i64 = (*(long long int *)s); } p = skipvar(p, false); } // is it an ordinary numeric constant? get its value if yes // a leading + or - might have been converted to a token so we need to check for them also else if(IsDigitinline(*p) || *p == '.') { char ts[31], *tsp; int isi64 = true; tsp = ts; int isf=true; long long int scale=0; // copy the first digit of the string to a temporary place if(*p == '.') { isi64 = false; scale=1; } else if(IsDigitinline(*p)){ i64=(*p - '0'); } *tsp++ = *p++; // now concatenate the remaining digits while((digit[(uint8_t)*p]) && (tsp - ts) < 30) { if(*p >= '0' && *p <= '9'){ i64 = i64 * 10 + (*p - '0'); if(scale)scale*=10; } else { if((*p) == '.'){ isi64 = false; scale =1; } else { if(mytoupper(*p) == 'E' || *p == '-' || *p == '+' ){ isi64 = false; isf=false; } } } *tsp++ = *p++; // copy the string to a temporary place } *tsp = 0; // terminate it if(isi64) { t = T_INT; } else if(isf && (tsp - ts) < 18) { f=(MMFLOAT)i64/(MMFLOAT)scale; t = T_NBR; } else { f = (MMFLOAT)strtod(ts, &tsp); // and convert to a MMFLOAT t = T_NBR; } } // if it is a numeric constant starting with the & character then get its base and convert to an integer else if(*p == '&') { p++; i64 = 0; switch(mytoupper(*p++)) { case 'H': while(isxdigit(*p)) { i64 = (i64 << 4) | ((mytoupper(*p) >= 'A') ? mytoupper(*p) - 'A' + 10 : *p - '0'); p++; } break; case 'O': while(*p >= '0' && *p <= '7') { i64 = (i64 << 3) | (*p++ - '0'); } break; case 'B': while(*p == '0' || *p == '1') { i64 = (i64 << 1) | (*p++ - '0'); } break; default: error("Type prefix"); } t = T_INT; } // if opening bracket then first evaluate the contents of the bracket else if(*p == '(') { p++; // step over the bracket p = evaluate(p, &f, &i64, &s, &t, true); // recursively get the contents if(*p != ')') error("No closing bracket"); ++p; // step over the closing bracket } // if it is a string constant, return a pointer to that. Note: tokenise() guarantees that strings end with a quote else if(*p == '"') { p++; // step over the quote p1 = s = GetTempMemory(STRINGSIZE); // this will last for the life of the command tp = (unsigned char *)strchr((char *)p, '"'); int toggle=0; while(p != tp){ if(*p=='\\' && tp>p+1 && OptionEscape)toggle^=1; if(toggle){ if(*p=='\\' && isdigit(p[1]) && isdigit(p[2]) && isdigit(p[3])){ p++; i=(*p++)-48; i*=10; i+=(*p++)-48; i*=10; i+=(*p++)-48; if(i==0)error("Null character \\000 in escape sequence - use CHR$(0)","$"); *p1++=i; } else { p++; switch(*p){ case '\\': *p1++='\\'; p++; break; case 'a': *p1++='\a'; p++; break; case 'b': *p1++='\b'; p++; break; case 'e': *p1++='\e'; p++; break; case 'f': *p1++='\f'; p++; break; case 'n': *p1++='\n'; p++; break; case 'q': *p1++='\"'; p++; break; case 'r': *p1++='\r'; p++; break; case 't': *p1++='\t'; p++; break; case 'v': *p1++='\v'; p++; break; case '&': p++; if(isxdigit(*p) && isxdigit(p[1])){ i=0; i = (i << 4) | ((mytoupper(*p) >= 'A') ? mytoupper(*p) - 'A' + 10 : *p - '0'); p++; i = (i << 4) | ((mytoupper(*p) >= 'A') ? mytoupper(*p) - 'A' + 10 : *p - '0'); p++; if(i==0)error("Null character \\&00 in escape sequence - use CHR$(0)","$"); *p1++=i; } else *p1++='x'; break; default: *p1++=*p++; } } toggle=0; } else *p1++ = *p++; } p++; CtoM(s); // convert to a MMBasic string t = T_STR; } else error("Syntax"); } skipspace(p); *fa = f; // save what we have *ia = i64; *sa = s; *ta = t; // get the next operator, if there is not an operator set the operator to end of expression (E_END) if(tokentype(*p) & T_OPER) *oo = *p++ - C_BASETOKEN; else *oo = E_END; return p; } // search through program memory looking for a line number. Stops when it has a matching or larger number // returns a pointer to the T_NEWLINE token or a pointer to the two zero characters representing the end of the program unsigned char MIPS16 *findline(int nbr, int mustfind) { unsigned char *p; unsigned char *next; int i,j=0; p = ProgMemory; next=LibMemory; if (Option.LIBRARY_FLASH_SIZE==MAX_PROG_SIZE){ if (CurrentLinePtr >= LibMemory && CurrentLinePtr <= LibMemory + MAX_PROG_SIZE){ p=LibMemory; next=ProgMemory; } } while(1) { if(p[0] == 0 && p[1] == 0) { if (Option.LIBRARY_FLASH_SIZE==MAX_PROG_SIZE){ if(j==0){ j=1; p = next; }else{ i = MAXLINENBR; break; } } else{ i = MAXLINENBR; break; } } if(p[0] == T_NEWLINE) { p++; continue; } if(p[0] == T_LINENBR) { i = (p[1] << 8) | p[2]; if(mustfind) { if(i == nbr) break; } else { if(i >= nbr) break; } p += 3; continue; } if(p[0] == T_LABEL) { p += p[1] + 2; continue; } p++; } if(mustfind && i != nbr) error("Line number"); return p; } #ifdef rp2350 void hashlabels(unsigned char *p,int ErrAbort){ //unsigned char *p = (unsigned char *)ProgMemory; int j, u, namelen; uint32_t originalhash,hash=FNV_offset_basis; // char *lastp = (char *)ProgMemory + 1; char *lastp = (char *)p + 1; // now do the search while(1) { if(p[0] == 0 && p[1] == 0) // end of the program break; if(p[0] == T_NEWLINE) { lastp = (char *)p; // save in case this is the right line p++; // and step over the line number continue; } if(p[0] == T_LINENBR) { p += 3; // and step over the line number continue; } if(p[0] == T_LABEL) { p++; // point to the length of the label hash=FNV_offset_basis; namelen=0; for(j=1;j<=p[0];j++) { u=mytoupper(p[j]); hash ^= u; hash*=FNV_prime; namelen++; } hash %= MAXSUBHASH; //scale to size of table originalhash=hash-1; if(originalhash<0)originalhash+=MAXSUBFUN; while(funtbl[hash].name[0]!=0 && hash!=originalhash){ hash++; hash %= MAXSUBFUN; } if(hash==originalhash){ MMPrintString("Error: Too many labels - erasing program\r\n"); unsigned char dummy=0; cmdline=&dummy; cmd_new(); // jump back to the input prompt } funtbl[hash].index=(uint32_t)lastp; for(j=0;j MAXVARLEN ) error("Label too long"); // too long, not a correctly formed label label[i]=mytoupper(*labelptr++); hash ^= label[i]; hash*=FNV_prime; } label[0] = i - 1; // the length byte hash %= MAXSUBHASH; //scale to size of table if(funtbl[hash].name[0]==0)error("Cannot find label"); while(funtbl[hash].name[0]!=0){ //if(funtbl[hash].index>=(uint32_t)ProgMemory){ //Is there a need to test this? Without this we can find labels in the Library tp=(unsigned char *)funtbl[hash].name; ip=(unsigned char *)&label[1]; if(*ip++ == *tp++) { // preliminary quick check i = label[0]-1; while(i > 0 && *ip == *tp) { // compare each letter i--; ip++; tp++; } if(i == 0 && (*(char *)tp == 0)) { // found a matching name return (unsigned char *)funtbl[hash].index; } } //} hash++; hash %= MAXSUBFUN; } if(funtbl[hash].name[0]==0)error("Cannot find label"); return 0; } #else unsigned char MIPS16 *findlabel(unsigned char *labelptr) { char *p, *lastp = (char *)ProgMemory + 1; char *next; int i,j=0; char label[MAXVARLEN + 1]; // first, just exit we have a NULL argument if(labelptr == NULL) return NULL; // convert the label to the token format and load into label[] // this assumes that the first character has already been verified as a valid label character label[1] = *labelptr++; for(i = 2; ; i++) { if(!isnamechar(*labelptr)) break; // the end of the label if(i > MAXVARLEN ) error("Label too long"); // too long, not a correctly formed label label[i] = *labelptr++; } label[0] = i - 1; // the length byte p = (char *)ProgMemory; next=(char *)LibMemory; if (Option.LIBRARY_FLASH_SIZE==MAX_PROG_SIZE){ if (CurrentLinePtr >= LibMemory && CurrentLinePtr <= LibMemory + MAX_PROG_SIZE){ p=(char *)LibMemory; next=(char *)ProgMemory; } } // now do the search while(1) { if(p[0] == 0 && p[1] == 0) { // end of the program if (Option.LIBRARY_FLASH_SIZE==MAX_PROG_SIZE){ if(j==0){ j=1; p = next; }else{ error("Cannot find label"); } } else{ error("Cannot find label"); } } if(p[0] == T_NEWLINE) { lastp = p; // save in case this is the right line p++; // and step over the line number continue; } if(p[0] == T_LINENBR) { p += 3; // and step over the line number continue; } if(p[0] == T_LABEL) { p++; // point to the length of the label if(mem_equal((unsigned char *)p, (unsigned char *)label, label[0] + 1)) // compare the strings including the length byte return (unsigned char *)lastp; // and if successful return pointing to the beginning of the line p += p[0] + 1; // still looking! skip over the label continue; } p++; } } #endif // returns true if 'line' is a valid line in the program int IsValidLine(int nbr) { unsigned char *p; p = findline(nbr, false); if(*p == T_NEWLINE) p++; if(*p == T_LINENBR) { if(((p[1] << 8) | p[2]) == nbr) return true; } return false; } // count the number of lines up to and including the line pointed to by the argument // used for error reporting in programs that do not use line numbers int MIPS16 CountLines(unsigned char *target) { unsigned char *p; int cnt; p = ProgMemory; if(ProgMemory[0]==1 && ProgMemory[1]==39 && ProgMemory[2]==35)cnt=-1; else cnt = 0; while(1) { if(*p == 0xff || (p[0] == 0 && p[1] == 0)) // end of the program return cnt; if(*p == T_NEWLINE) { p++; // and step over the line number cnt++; if(p >= target) return cnt; continue; } if(*p == T_LINENBR) { p += 3; // and step over the line number continue; } if(*p == T_LABEL) { p += p[0] + 2; // still looking! skip over the label continue; } if(p++ > target) return cnt; } } /******************************************************************************************************************************************** routines for storing and manipulating variables ********************************************************************************************************************************************/ // find or create a variable // the action parameter can be the following (these can be ORed together) // - V_FIND a straight forward find, if the variable is not found it is created and set to zero // - V_NOFIND_ERR throw an error if not found // - V_NOFIND_NULL return a null pointer if not found // - V_DIM_VAR dimension an array // - V_LOCAL create a local variable // // there are four types of variable: // - T_NOTYPE a free slot that was used but is now free for reuse // - T_STR string variable // - T_NBR holds a float // - T_INT integer variable // // A variable can have a number of characteristics // - T_PTR the variable points to another variable's data // - T_IMPLIED the variables type does not have to be specified with a suffix // - T_CONST the contents of this variable cannot be changed // - T_FUNCT this variable represents the return value from a function // // storage of the variable's data: // if it is type T_NBR or T_INT the value is held in the variable slot // for T_STR a block of memory of MAXSTRLEN size (or size determined by the LENGTH keyword) will be malloc'ed and the pointer stored in the variable slot. #ifdef PICOMITEWEB #ifdef rp2350 void MIPS16 __not_in_flash_func(*findvar)(unsigned char *p, int action) { #else void MIPS16 *findvar(unsigned char *p, int action) { #endif #else void MIPS16 __not_in_flash_func(*findvar)(unsigned char *p, int action) { #endif unsigned char name[MAXVARLEN + 1]; int i=0, j, size, ifree, globalifree, localifree, nbr, vtype, vindex, namelen, tmp; unsigned char *s, *x, u, suffix=0; void *mptr; // int hashIndex=0; int GlobalhashIndex, OriginalGlobalHash; int LocalhashIndex, OriginalLocalHash; uint32_t hash=FNV_offset_basis; #ifdef rp2350 uint32_t funhash; #endif char *tp, *ip; int dim[MAXDIM]={0}, dnbr; // if(__get_MSP() < (uint32_t)&stackcheck-0x5000){ // error("Expression is too complex at depth %",g_LocalIndex); // } vtype = dnbr = emptyarray = 0; // first zero the array used for holding the dimension values // for(i = 0; i < MAXDIM; i++) dim[i] = 0; ifree = -1; // check the first char for a legal variable name skipspace(p); if(!isnamestart(*p)) error("Variable name"); // copy the variable name into name s = name; namelen = 0; do { u=mytoupper(*p++); hash ^= u; hash*=FNV_prime; *s++ = u; if(++namelen > MAXVARLEN) error("Variable name too long"); } while(isnamechar(*p)); #ifdef rp2350 funhash=hash % MAXSUBHASH; #endif hash %= MAXVARHASH; //scale 0-255 if(namelen!=MAXVARLEN)*s=0; // check the terminating char and set the type if(*p == '$') { if((action & T_IMPLIED) && !(action & T_STR)) error("Conflicting variable type"); vtype = T_STR; suffix=1; p++; } else if(*p == '%') { if((action & T_IMPLIED) && !(action & T_INT)) error("Conflicting variable type"); vtype = T_INT; suffix=1; p++; } else if(*p == '!') { if((action & T_IMPLIED) && !(action & T_NBR)) error("Conflicting variable type"); vtype = T_NBR; suffix=1; p++; } else if((action & V_DIM_VAR) && DefaultType == T_NOTYPE && !(action & T_IMPLIED)) error("Variable type not specified"); else vtype = 0; // check if this is an array if(*p == '(') { char *pp = (char *)p + 1; skipspace(pp); if(action & V_EMPTY_OK && *pp == ')') { // if this is an empty array. eg () emptyarray=1; dnbr = -1; // flag this } else { // else, get the dimensions // start a new block - getargs macro must be the first executable stmt in a block // split the argument into individual elements // find the value of each dimension and store in dims[] // the bracket in "(," is a signal to getargs that the list is in brackets getargs(&p, MAXDIM * 2, (unsigned char *)"(,"); if((argc & 0x01) == 0) error("Dimensions"); dnbr = argc/2 + 1; if(dnbr > MAXDIM) error("Dimensions"); for(i = 0; i < argc; i += 2) { MMFLOAT f; long long int in; char *s; int targ = T_NOTYPE; evaluate(argv[i], &f, &in, (unsigned char **)&s, &targ, false); // get the value and type of the argument if(targ == T_STR) dnbr = MAXDIM; // force an error to be thrown later (with the correct message) if(targ == T_NBR) in = FloatToInt32(f); dim[i/2] = in; if(dim[i/2] < g_OptionBase) error("Dimensions"); } } } // we now have the variable name and, if it is an array, the parameters // search the table looking for a match LocalhashIndex=hash; OriginalLocalHash=LocalhashIndex-1; if(OriginalLocalHash<0)OriginalLocalHash+=MAXVARS/2; localifree=-1; GlobalhashIndex=hash+MAXVARS/2; OriginalGlobalHash=GlobalhashIndex-1; if(OriginalGlobalHash 0 && *ip == *tp) { // compare each letter j--; ip++; tp++; } if(j == 0 && (*(char *)tp == 0 || namelen == MAXVARLEN)) { // found a matching name if(g_vartbl[LocalhashIndex].level == g_LocalIndex) break; //matching global while not in a subroutine } } LocalhashIndex++; LocalhashIndex %= MAXVARS/2; if(LocalhashIndex==OriginalLocalHash)error("Too many local variables"); } if(g_vartbl[LocalhashIndex].name[0]==0){ // not found localifree=LocalhashIndex; if(tmp!=-1){ localifree=tmp; g_vartbl[LocalhashIndex].type=T_NOTYPE; g_vartbl[LocalhashIndex].name[0]=0; } } } if(g_vartbl[LocalhashIndex].name[0]==0){ // not found in the local table so try the global tmp=-1; globalifree=-1; if(g_vartbl[GlobalhashIndex].type == T_NOTYPE){ globalifree = GlobalhashIndex; } else { while(g_vartbl[GlobalhashIndex].name[0]!=0){ ip=(char *)name; tp=(char *)g_vartbl[GlobalhashIndex].name; if(g_vartbl[GlobalhashIndex].type==T_BLOCKED)tmp=GlobalhashIndex; if(*ip++ == *tp++) { // preliminary quick check j = namelen-1; while(j > 0 && *ip == *tp) { // compare each letter j--; ip++; tp++; } if(j == 0 && (*(char *)tp == 0 || namelen == MAXVARLEN)) { // found a matching name break; //matching global while not in a subroutine } } GlobalhashIndex++; if(GlobalhashIndex==MAXVARS)GlobalhashIndex=MAXVARS/2; if(GlobalhashIndex==OriginalGlobalHash)error("Too many global variables"); } if(g_vartbl[GlobalhashIndex].name[0]==0){ // not found globalifree=GlobalhashIndex; if(tmp!=-1){ globalifree=tmp; g_vartbl[GlobalhashIndex].type=T_NOTYPE; g_vartbl[GlobalhashIndex].name[0]=0; } } } } } else { localifree=9999; //set a marker that a local variable is irrelevant if(g_vartbl[GlobalhashIndex].type == T_NOTYPE){ globalifree = GlobalhashIndex; } else { while(g_vartbl[GlobalhashIndex].name[0]!=0){ ip=(char *)name; tp=(char *)g_vartbl[GlobalhashIndex].name; if(g_vartbl[GlobalhashIndex].type==T_BLOCKED)tmp=GlobalhashIndex; if(*ip++ == *tp++) { // preliminary quick check j = namelen-1; while(j > 0 && *ip == *tp) { // compare each letter j--; ip++; tp++; } if(j == 0 && (*(char *)tp == 0 || namelen == MAXVARLEN)) { // found a matching name break; //matching global while not in a subroutine } } GlobalhashIndex++; if(GlobalhashIndex==MAXVARS)GlobalhashIndex=MAXVARS/2; } if(g_vartbl[GlobalhashIndex].name[0]==0){ // not found globalifree=GlobalhashIndex; if(tmp!=-1){ globalifree=tmp; g_vartbl[GlobalhashIndex].type=T_NOTYPE; g_vartbl[GlobalhashIndex].name[0]=0; } } } } // MMPrintString("search status : ");PInt(g_LocalIndex);PIntComma(localifree);PIntComma(LocalhashIndex);PIntComma(globalifree);PIntComma(GlobalhashIndex); // MMPrintString((action & V_LOCAL ? " LOCAL" : " "));MMPrintString((action & V_LOCAL ? " DIM" : " "));PRet(); // At this point we know if a local variable has been found or if a global variable has been found if(action & V_LOCAL) { // if we declared the variable as LOCAL within a sub/fun and an existing local was found if(localifree==-1) error("$ Local variable already declared", name); } else if(action & V_DIM_VAR) { // if are using DIM to declare a global variable and an existing global variable was found if(globalifree==-1 ) error("$ Global variable already declared", name); } // we are not declaring the variable but it may need to be created if(action & V_LOCAL) { ifree = i = localifree; } else if(localifree==-1){ // can only happen when a local variable has been found so we can ignore everything global ifree= -1; i = LocalhashIndex; } else if(globalifree==-1){ //A global variable has been found ifree= -1; i = GlobalhashIndex; } else { //nothing has been found so we are going to create a global unless EXPLICIT is set ifree = i = globalifree; } // MMPrintString(name);PIntComma(i);MMPrintString((ifree==-1 ? " - found" : " - not there"));PRet(); // if we found an existing and matching variable // set the global g_VarIndex indicating the index in the table if(ifree==-1 && g_vartbl[i].name[0] != 0) { g_VarIndex = vindex = i; // check that the dimensions match for(i = 0; i < MAXDIM && g_vartbl[vindex].dims[i] != 0; i++); if(dnbr == -1) { if(i == 0) error("Array dimensions"); } else { if(i != dnbr) error("Array dimensions"); } if(vtype == 0) { if(!(g_vartbl[vindex].type & (DefaultType | T_IMPLIED))) error("$ Different type already declared", name); } else { if(!(g_vartbl[vindex].type & vtype)) error("$ Different type already declared", name); } // if it is a non arrayed variable or an empty array it is easy, just calculate and return a pointer to the value if(dnbr == -1 || g_vartbl[vindex].dims[0] == 0) { if(dnbr == -1 || g_vartbl[vindex].type & (T_PTR | T_STR)) return g_vartbl[vindex].val.s; // if it is a string or pointer just return the pointer to the data else if(g_vartbl[vindex].type & (T_INT)) return &(g_vartbl[vindex].val.i); // must be an integer, point to its value else return &(g_vartbl[vindex].val.f); // must be a straight number (float), point to its value } // if we reached this point it must be a reference to an existing array // check that we are not using DIM and that all parameters are within the dimensions if(action & V_DIM_VAR) error("Cannot re dimension array"); for(i = 0; i < dnbr; i++) { if(dim[i] > g_vartbl[vindex].dims[i] || dim[i] < g_OptionBase) error("Index out of bounds"); } // then calculate the index into the array. Bug fix by Gerard Sexton. nbr = dim[0] - g_OptionBase; j = 1; for(i = 1; i < dnbr; i++) { j *= (g_vartbl[vindex].dims[i - 1] + 1 - g_OptionBase); nbr += (dim[i] - g_OptionBase) * j; } // finally return a pointer to the value if(g_vartbl[vindex].type & T_NBR) return g_vartbl[vindex].val.s + (nbr * sizeof(MMFLOAT)); else if(g_vartbl[vindex].type & T_INT) return g_vartbl[vindex].val.s + (nbr * sizeof(long long int)); else return g_vartbl[vindex].val.s + (nbr * (g_vartbl[vindex].size + 1)); } // we reached this point if no existing variable has been found if(action & V_NOFIND_ERR) error("Cannot find $", name); if(action & V_NOFIND_NULL) return NULL; if((OptionExplicit || dnbr != 0) && !(action & V_DIM_VAR)) error("$ is not declared", name); if(vtype == 0) { if(action & T_IMPLIED) vtype = (action & (T_NBR | T_INT | T_STR)); else vtype = DefaultType; } // now scan the sub/fun table to make sure that there is not a sub/fun with the same name #ifdef rp2350 if(!(action & V_FUNCT) && (funtbl[funhash].name[0])) { // don't do this if we are defining the local variable for a function name while(funtbl[funhash].name[0]!=0){ ip=(char *)name; tp=funtbl[funhash].name; if(*ip++ == *tp++) { // preliminary quick check j = namelen-1; while(j > 0 && *ip == *tp) { // compare each letter j--; ip++; tp++; } if(j == 0 && (*(char *)tp == 0 || namelen == MAXVARLEN)) { // found a matching name if(funtbl[funhash].index= MAXVARLEN) break; s++; x++; } } } #endif // set a default string size size = MAXSTRLEN; // if it is an array we must be dimensioning it // if it is a string array we skip over the dimension values and look for the LENGTH keyword // and if found find the string size and change the g_vartbl entry if(action & V_DIM_VAR) { if(vtype & T_STR) { i = 0; if(*p == '(') { do { if(*p == '(') i++; if(tokentype(*p) & T_FUN) i++; if(*p == ')') i--; p++; } while(i); } skipspace(p); if((s = checkstring(p, (unsigned char *)"LENGTH")) != NULL) size = getint(s, 1, MAXSTRLEN) ; else if(!(*p == ',' || *p == 0 || tokenfunction(*p) == op_equal || tokenfunction(*p) == op_invalid)) error("Unexpected text: $", p); } } // at this point we need to create the variable // as a result of the previous search ifree is the index to the entry that we should use // if we are adding to the top, increment the number of vars if(ifree>=MAXVARS/2){ g_Globalvarcnt++; if(g_Globalvarcnt>=MAXVARS/2)error("Not enough Global variable memory"); } else { g_Localvarcnt++; if(g_Localvarcnt>=MAXVARS/2)error("Not enough Local variable memory"); } g_varcnt=g_Globalvarcnt+g_Localvarcnt; g_VarIndex = vindex = ifree; // initialise it: save the name, set the initial value to zero and set the type s = name; x = g_vartbl[ifree].name; j = namelen; while(j--) *x++ = *s++; if(namelen < MAXVARLEN)*x++ = 0; g_vartbl[ifree].type = vtype | (action & (T_IMPLIED | T_CONST)); if(suffix)g_vartbl[ifree].type|=T_EXPLICIT; if(ifree argbuf && *(op - 1) == ' ') op--; // trim trailing spaces *op++ = 0; // terminate it } else if(*argc) { // otherwise we have two delimiters in a row (except for the first argument) argv[(*argc)++] = op; // create a null argument to go between the two delimiters *op++ = 0; // and terminate it } inarg = false; if(*argc >= maxargs) error("Syntax"); argv[(*argc)++] = op; // save the pointer for this delimiter *op++ = *tp++; // copy the token or char (always one) *op++ = 0; // terminate it continue; } // check if we have a THEN or ELSE token and if so flag that a command should be next if(*tp == then_tkn || *tp == else_tkn) expect_cmd = true; // remove all spaces (outside of quoted text and bracketed text) if(!inarg && *tp == ' ') { tp++; continue; } // not a special char so we must start a new argument if(!inarg) { if(*argc >= maxargs) error("Syntax"); argv[(*argc)++] = op; // save the pointer for this arg inarg = true; } // if an opening bracket '(' copy everything until we hit the matching closing bracket // this includes special characters such as , and ; and keeps track of any nested brackets if(*tp == '(' || ((tokentype(*tp) & T_FUN) && !expect_cmd)) { int x; x = (getclosebracket(tp) - tp) + 1; memcpy(op, tp, x); op += x; tp += x; continue; } // if quote mark (") copy everything until the closing quote // this includes special characters such as , and ; // the tokenise() function will have ensured that the closing quote is always there if(*tp == '"') { do { *op++ = *tp++; if(*tp == 0) error("Syntax"); } while(*tp != '"'); *op++ = *tp++; continue; } // anything else is just copied into the argument *op++ = *tp++; if(expect_cmd)*op++ = *tp++; //copy rest of command token expect_cmd = false; } if(expect_bracket && *tp != ')') error("Syntax"); while(op - 1 > argbuf && *(op-1) == ' ') --op; // trim any trailing spaces on the last argument *op = 0; // terminate the last argument } static void MIPS16 display_string(const char *s, bool fill) { // Indent each line by one space. if (CurrentX == 0) DisplayPutC(' '); // Display characters one at a time, for (const char *p = s; *p; ++p) { if (CurrentX + gui_font_width >= HRes) { DisplayPutC(' '); // Leave one space at the end of each line and wrap to the next. DisplayPutC(' '); // Indent each new line by one space. if (*p == ' ') continue; // Skip first space on each new line. } DisplayPutC(*p); } // Fill to the end of the line with spaces. if (fill) { while (CurrentX + gui_font_width <= HRes) DisplayPutC(' '); CurrentX = 0; CurrentY += gui_font_height; } } /** * Displays an error message with context on the display. * * @param line_num The error line, * -1 for the LIBRARY, * -2 when error occurs at the prompt. * @param line_txt The text of the line that caused the error. * @param error_msg The error message. */ void MIPS16 LCD_error(int line_num, const char *line_txt, const char* error_msg) { if (HRes == 0) return; // No display configured. // Always write error to the actual display. restorepanel(); // Store current property display values. const unsigned char old_console = Option.DISPLAY_CONSOLE; const int old_font = gui_font; const int old_fcolour = gui_fcolour; const int old_bcolour = gui_bcolour; // Override properties required by DisplayPutC. const int font = 1; Option.DISPLAY_CONSOLE = 1; SetFont(font); gui_fcolour = 0xEE4B2B; // Bright Red. gui_bcolour = 0x0; // Display the error message halfway down the display (approx.) const int chars_per_line = (HRes / gui_font_width) - 2; int num_lines = 2; num_lines += strlen(error_msg) / chars_per_line; if (strlen(error_msg) % chars_per_line > 0) num_lines++; num_lines += strlen(line_txt) / chars_per_line; if (strlen(line_txt) % chars_per_line > 0) num_lines++; CurrentX = 0; CurrentY = (VRes / 2) - (num_lines * gui_font_height / 2); display_string("", true); display_string("ERROR: ", false); display_string(error_msg, true); if (*line_txt) { char buf[32]; if (line_num == -1) { sprintf(buf, "[LIBRARY] "); } else { sprintf(buf, "[%d] ", line_num); } display_string(buf, false); display_string(line_txt, true); } display_string("", true); // Restore display property values. SetFont(old_font); PromptFont=old_font; Option.DISPLAY_CONSOLE = old_console; gui_fcolour = old_fcolour; gui_bcolour = old_bcolour; } // throw an error // displays the error message and aborts the program // the message can contain variable text which is indicated by a special character in the message string // $ = insert a string at this place // @ = insert a character // % = insert a number // the optional data to be inserted is the second argument to this function // this uses longjump to skip back to the command input and cleanup the stack void MIPS16 error(char *msg, ...) { char *p, *tp, tstr[STRINGSIZE * 2]; va_list ap; ScrewUpTimer = 0; // first build the error message in the global string MMErrMsg if(MMerrno == 0) MMerrno = 16; // indicate an error memset(tstr, 0, STRINGSIZE * 2); // clear any previous string if(*msg) { va_start(ap, msg); while(*msg) { tp = &tstr[strlen(tstr)]; // point to the end of the string if(*msg == '$') // insert a string strcpy(tp, va_arg(ap, char *)); else if(*msg == '@') // insert a character *tp = (va_arg(ap, int)); else if(*msg == '%') // insert an integer IntToStr(tp, va_arg(ap, int), 10); else if(*msg == '~') // insert a long long integer IntToStr(tp, va_arg(ap, int64_t), 10); else if(*msg == '|') // insert an integer strcpy(tp,PinDef[va_arg(ap, int)].pinname); else *tp = *msg; msg++; } } // copy the error message into the global MMErrMsg truncating at any tokens or if the string is too long for(p = MMErrMsg, tp = tstr; *tp < 127 && (tp - tstr) < MAXERRMSG - 1; ) *p++ = *tp++; *p = 0; if(optionlogging){ lfs_file_t lfs_file; char crlf[]="\r\n"; lfs_file_open(&lfs, &lfs_file, "log.txt", LFS_O_APPEND | LFS_O_CREAT); lfs_file_write(&lfs, &lfs_file, MMErrMsg, sizeof(MMErrMsg)); lfs_file_write(&lfs, &lfs_file, crlf, sizeof(crlf)); lfs_file_close(&lfs, &lfs_file); } if(OptionErrorSkip) longjmp(ErrNext, 1); // if OPTION ERROR SKIP/IGNORE is in force #ifdef PICOMITE multicore_fifo_push_blocking(0xFF); busy_wait_ms(mergetimer+200); if(mergerunning){ _excep_code = RESET_COMMAND; SoftReset(); } #endif LoadOptions(); // make sure that the option struct is in a clean state OptionConsole=1; if(Option.DISPLAY_CONSOLE) { OptionConsole=3; #ifdef PICOMITEVGA WriteBuf=(unsigned char *)FRAMEBUFFER; DisplayBuf=(unsigned char *)FRAMEBUFFER; #else restorepanel(); #endif // we now have CurrentLinePtr pointing to the start of the line SetFont(PromptFont); gui_fcolour = PromptFC; gui_bcolour = PromptBC; if((DISPLAY_TYPE==SCREENMODE2 || DISPLAY_TYPE==SCREENMODE4 || DISPLAY_TYPE==SCREENMODE5) && gui_font_width>6){ SetFont((6<<4) | 1) ; PromptFont=(6<<4) | 1; } else { #ifdef HDMI if(((FullColour) || DISPLAY_TYPE==SCREENMODE3) && gui_font_width>8){ SetFont(1) ; PromptFont = 1; } else if(gui_font_width>16){ SetFont((2<<4) | 1) ; PromptFont=(2<<4) | 1; } #else if(gui_font_width>8){ SetFont(1) ; PromptFont = 1; } #endif } if(CurrentX != 0) MMPrintString("\r\n"); // error message should be on a new line } if(MMCharPos > 1) MMPrintString("\r\n"); int line_num = -2; if(CurrentLinePtr) { tp = p = (char *)ProgMemory; if (Option.LIBRARY_FLASH_SIZE==MAX_PROG_SIZE && CurrentLinePtr < LibMemory+MAX_PROG_SIZE) tp = p = (char *)LibMemory; //if(*CurrentLinePtr != T_NEWLINE && CurrentLinePtr < ProgMemory + MAX_PROG_SIZE) { if(*CurrentLinePtr != T_NEWLINE && ((CurrentLinePtr < ProgMemory + MAX_PROG_SIZE) || (Option.LIBRARY_FLASH_SIZE==MAX_PROG_SIZE && CurrentLinePtr < LibMemory+MAX_PROG_SIZE))) { // normally CurrentLinePtr points to a T_NEWLINE token but in this case it does not // so we have to search for the start of the line and set CurrentLinePtr to that while(*p != 0xff) { while(*p) p++; // look for the zero marking the start of an element if(p >= (char *)CurrentLinePtr || p[1] == 0) { // the previous line was the one that we wanted CurrentLinePtr = (unsigned char *)tp; break; } if(p[1] == T_NEWLINE) { tp = ++p; // save because it might be the line we want } p++; // step over the zero marking the start of the element skipspace(p); if(p[0] == T_LABEL) p += p[1] + 2; // skip over the label } } // we now have CurrentLinePtr pointing to the start of the line // dump(CurrentLinePtr, 80); llist(tknbuf, CurrentLinePtr); p = (char *) tknbuf; skipspace(p); if(CurrentLinePtr >= ProgMemory && CurrentLinePtr < ProgMemory + MAX_PROG_SIZE){ line_num = CountLines(CurrentLinePtr); StartEditPoint = CurrentLinePtr; StartEditChar = 0; } else { line_num = -1; } } // Print the line. if (line_num != -2) { if (line_num == -1) { sprintf(tstr, "[LIBRARY] %s\r\n", p); } else { sprintf(tstr, "[%d] %s\r\n", line_num, p); } MMPrintString(tstr); } // Print the error message. if (*MMErrMsg) { sprintf(tstr, "Error : %s\r\n", MMErrMsg); } else { sprintf(tstr, "Error"); } MMPrintString(tstr); #ifndef PICOMITEVGA if (!Option.DISPLAY_CONSOLE && Option.DISPLAY_TYPE>I2C_PANEL) { int width=Option.Width; int height=Option.Height; LCD_error(line_num, p, MMErrMsg); Option.Width=width; Option.Height=height; } #endif cmdline=NULL; do_end(false); longjmp(mark, 1); // jump back to the input prompt } /********************************************************************************************** Routines to convert floats and integers to formatted strings These replace the sprintf() libraries with much less flash usage **********************************************************************************************/ #define IntToStrBufSize 65 // convert a integer to a string. // sstr is a buffer where the chars are to be written to // sum is the number to be converted // base is the numbers base radix (10 = decimal, 16 = hex, etc) // if base 10 the number will be signed otherwise it will be unsigned void MIPS16 IntToStr(char *strr, long long int nbr, unsigned int base) { int i, negative; unsigned char digit; unsigned long long int sum; extern long long int llabs (long long int n); unsigned char str[IntToStrBufSize]; if(nbr < 0 && base == 10) { // we can have negative numbers in base 10 only nbr = llabs(nbr); negative = true; } else negative = false; // this generates the digits in reverse order sum = (unsigned long long int) nbr; i = 0; do { digit = sum % base; if (digit < 0xA) str[i++] = '0' + digit; else str[i++] = 'A' + digit - 0xA; sum /= base; } while (sum && i < IntToStrBufSize); if(negative) *strr++ = '-'; // we now need to reverse the digits into their correct order for(i--; i >= 0; i--) *strr++ = str[i]; *strr = 0; } // convert an integer to a string padded with a leading character // p is a pointer to the destination // nbr is the number to convert (can be signed in which case the number is preceeded by '-') // padch is the leading padding char (usually a space) // maxch is the desired width of the resultant string (incl padding chars) // radix is the base of the number. Base 10 is signed, all others are unsigned // Special case (used by FloatToStr() only): // if padch is negative and nbr is zero prefix the number with the - sign void MIPS16 IntToStrPad(char *p, long long int nbr, signed char padch, int maxch, int radix) { int j; char sign, buf[IntToStrBufSize]; sign = 0; if ((nbr < 0 && radix == 10 && nbr!=0x8000000000000000) || padch < 0) { // if the number is negative or we are forced to use a - symbol sign = '-'; // set the sign nbr *= -1; // convert to a positive nbr padch = abs(padch); } else { if(nbr >= 0 && maxch < 0 && radix == 10) // should we display the + sign? sign = '+'; } IntToStr(buf, nbr, radix); j = abs(maxch) - strlen(buf); // calc padding required if(j <= 0)j = 0; else memset(p, padch, abs(maxch)); // fill the buffer with the padding char if(sign != 0) { // if we need a sign if(j == 0) j = 1; // make space if necessary if(padch == '0') p[0] = sign; // for 0 padding the sign is before the padding else p[j - 1] = sign; // for anything else the padding is before the sign } strcpy(&p[j], buf) ; } // convert a float to a string including scientific notation if necessary // p is the buffer to store the string // f is the number // m is the nbr of chars before the decimal point (if negative print the + sign) // n is the nbr chars after the point // if n == STR_AUTO_PRECISION we should automatically determine the precision // if n is negative always use exponential format // ch is the leading pad char void MIPS16 FloatToStr(char *p, MMFLOAT f, int m, int n, unsigned char ch) { int exp, trim = false, digit; MMFLOAT rounding; char *pp; if(f==INFINITY){ strcpy(p,"INF"); return; } ch &= 0x7f; // make sure that ch is an ASCII char if(f == 0) exp = 0; else exp = floor(log10(fabs(f))); // get the exponent part if(((fabs(f) < 0.0001 || fabs(f) >= 1000000) && f != 0 && (n == STR_AUTO_PRECISION || n==STR_FLOAT_PRECISION)) || n < 0) { // we must use scientific notation f /= pow(10, exp); // scale the number to 1.2345 if(f >= 10) { f /= 10; exp++; } if(n < 0) n = -n; // negative indicates always use exponantial format FloatToStr(p, f, m, n, ch); // recursively call ourself to convert that to a string p = p + strlen(p); *p++ = 'e'; // add the exponent if(exp >= 0) { *p++ = '+'; IntToStrPad(p, exp, '0', 2, 10); // add a positive exponent } else { *p++ = '-'; IntToStrPad(p, exp * -1, '0', 2, 10); // add a negative exponent } } else { // we can treat it as a normal number // first figure out how many decimal places we want. // n == STR_AUTO_PRECISION means that we should automatically determine the precision if(n == STR_AUTO_PRECISION) { trim = true; n = STR_SIG_DIGITS - exp; if(n < 0) n = 0; } if(n == STR_FLOAT_PRECISION) { trim = true; n = STR_FLOAT_DIGITS - exp; if(n < 0) n = 0; } // calculate rounding to hide the vagaries of floating point if(n > 0) rounding = 0.5/pow(10, n); else rounding = 0.5; if(f > 0) f += rounding; // add rounding for positive numbers if(f < 0) f -= rounding; // add rounding for negative numbers // convert the digits before the decimal point if((int)f == 0 && f < 0) IntToStrPad(p, 0, -ch, m, 10); // convert -0 incl padding if necessary else IntToStrPad(p, f, ch, m, 10); // convert the integer incl padding if necessary p += strlen(p); // point to the end of the integer pp = p; // convert the digits after the decimal point if(f < 0) f = -f; // make the number positive if(n > 0) { // if we need to have a decimal point and following digits *pp++ = '.'; // add the decimal point f -= floor(f); // get just the fractional part while(n--) { f *= 10; digit = floor(f); // get the next digit for the string f -= digit; *pp++ = digit + '0'; } // if we do not have a fixed number of decimal places step backwards removing trailing zeros and the decimal point if necessary while(trim && pp > p) { pp--; if(*pp == '.') break; if(*pp != '0') { pp++; break; } } } *pp = 0; } } /********************************************************************************************** Various routines to clear memory or the interpreter's state **********************************************************************************************/ // clear (or delete) variables // if level is not zero it will only delete local variables at that level or greater // if level is zero to will delete all variables and reset global settings void MIPS16 __not_in_flash_func(ClearVars)(int level, bool all) { int i, newhashpointer,hashcurrent,hashnext; // first step through the variable table and delete local variables at that level or greater if(level){ newhashpointer=g_hashlistpointer; //save the current number of stored values for(i=g_hashlistpointer-1;i>=0;i--){ //delete in reverse order of creation if(g_hashlist[i].level>= level){ hashnext = hashcurrent = g_hashlist[i].hash; hashnext++; hashnext %= MAXVARS/2; if(((g_vartbl[hashcurrent].type & T_STR) || g_vartbl[hashcurrent].dims[0] != 0) && !(g_vartbl[hashcurrent].type & T_PTR) && ((uint32_t)g_vartbl[hashcurrent].val.s<(uint32_t)MMHeap + heap_memory_size)&& ((uint32_t)g_vartbl[hashcurrent].val.s>(uint32_t)MMHeap)) { FreeMemorySafe((void **)&g_vartbl[hashcurrent].val.s); // free any memory (if allocated) } // MMPrintString("Deleting ");MMPrintString(g_vartbl[g_hashlist[i].hash].name);PIntComma(g_hashlist[i].level);PIntComma(g_hashlist[i].hash);PRet(); g_hashlist[i].level=-1; newhashpointer=i; //set the new highest index memset(&g_vartbl[hashcurrent],0,sizeof(struct s_vartbl)); if(g_vartbl[hashnext].type){ g_vartbl[hashcurrent].type = T_BLOCKED ; // block slot g_vartbl[hashcurrent].name[0] = '~'; // safety precaution } g_Localvarcnt--; } } g_hashlistpointer=newhashpointer; } else { for(i = 0; i < MAXVARS; i++) { if(((g_vartbl[i].type & T_STR) || g_vartbl[i].dims[0] != 0) && !(g_vartbl[i].type & T_PTR)) { if((uint32_t)g_vartbl[i].val.s>(uint32_t)MMHeap && (uint32_t)g_vartbl[i].val.s<(uint32_t)MMHeap + heap_memory_size){ FreeMemorySafe((void **)&g_vartbl[i].val.s); // free any memory (if allocated) } } #ifdef rp2350 #ifndef PICOMITEWEB if(all){ if(((g_vartbl[i].type & T_STR) || g_vartbl[i].dims[0] != 0) && !(g_vartbl[i].type & T_PTR)) { if((uint32_t)g_vartbl[i].val.s>(uint32_t)PSRAMbase && (uint32_t)g_vartbl[i].val.s<(uint32_t)PSRAMbase + PSRAMsize){ FreeMemorySafe((void **)&g_vartbl[i].val.s); // free any memory (if allocated) } } } #endif #endif memset(&g_vartbl[i],0,sizeof(struct s_vartbl)); } } // then step through the for...next table and remove any loops at the level or greater for(i = 0; i < g_forindex; i++) { if(g_forstack[i].level >= level) { g_forindex = i; break; } } // also step through the do...loop table and remove any loops at the level or greater for(i = 0; i < g_doindex; i++) { if(g_dostack[i].level >= level) { g_doindex = i; break; } } if(level != 0) return; g_forindex = g_doindex = 0; g_LocalIndex = 0; // signal that all space is to be cleared ClearTempMemory(); // clear temp string space // we can now delete all variables by zeroing the counters g_Localvarcnt = 0; g_Globalvarcnt = 0; g_OptionBase = 0; g_DimUsed = false; g_hashlistpointer=0; } // clear all stack pointers (eg, FOR/NEXT stack, DO/LOOP stack, GOSUB stack, etc) // this is done at the command prompt or at any break void MIPS16 ClearStack(void) { NextData = 0; NextDataLine = ProgMemory; g_forindex = 0; g_doindex = 0; gosubindex = 0; g_LocalIndex = 0; g_TempMemoryIsChanged = true; // signal that temporary memory should be checked InterruptReturn = NULL; } // clear the runtime (eg, variables, external I/O, etc) includes ClearStack() and ClearVars() // this is done before running a program void MIPS16 ClearRuntime(bool all) { int i; #ifdef PICOMITEWEB if(TCPstate){ TCP_SERVER_T *state = (TCP_SERVER_T*)TCPstate; for(int i=0 ; iclient_pcb[i] && state->telnet_pcb_no!=i)tcp_server_close(state, i); if(state->buffer_recv[i])FreeMemorySafe((void **)&state->buffer_recv[i]); state->inttrig[i]=0; state->sent_len[i]=0; state->recv_len[i]=0; state->to_send[i]=0; } } optionsuppressstatus=0; #endif CloseAllFiles(); ClearExternalIO(); // this MUST come before InitHeap(true) ClearStack(); #ifdef USBKEYBOARD clearrepeat(); #endif OptionExplicit = false; OptionEscape = false; OptionConsole=3; DefaultType = T_NBR; ds18b20Timers = NULL; // InitHeap(true) will recover the memory allocated to this array findlabel(NULL); // clear the label cache OptionErrorSkip = 0; optionangle=1.0; useoptionangle=false; optionfulltime=false; optionfastaudio=0; optionlogging=false; /*frame frame=NULL; outframe=NULL; */ #ifndef PICOMITEVGA if(ScrollLCD==ScrollLCDSPISCR){ ScrollStart=0; spi_write_command(CMD_SET_SCROLL_START); spi_write_data(0); spi_write_data(0); } if(ScrollLCD==ScrollLCDSPISCR){ ScrollStart=0; WriteComand(CMD_SET_SCROLL_START); WriteData(0); WriteData(0); } if(SSD16TYPE || Option.DISPLAY_TYPE==IPS_4_16 || SPI480)clear320(); #endif MMerrno = 0; // clear the error flags *MMErrMsg = 0; InitHeap(true); m_alloc(all? M_VAR : M_LIMITED); ClearVars(0,true); memset(cmdlinebuff,0,sizeof(cmdlinebuff)); memset(datastore, 0, sizeof(struct sa_data) * MAXRESTORE); restorepointer = 0; g_flag=0; g_varcnt = 0; CurrentLinePtr = ContinuePoint = NULL; for(i = 0; i < MAXSUBFUN; i++) subfun[i] = NULL; #ifdef GUICONTROLS for(i = 1; i < Option.MaxCtrls; i++) { memset(&Ctrl[i],0,sizeof(struct s_ctrl)); Ctrl[i].state = Ctrl[i].type = 0; Ctrl[i].s = NULL; } #endif } // clear everything including program memory (includes ClearStack() and ClearRuntime(true)) // this is used before loading a program void MIPS16 ClearProgram(bool psram) { // InitHeap(true); initFonts(); m_alloc(psram ? M_PROG : M_LIMITED); // init the variables for program memory if(Option.DISPLAY_TYPE>=VIRTUAL && WriteBuf)FreeMemorySafe((void **)&WriteBuf); ClearRuntime(true); // ProgMemory[0] = ProgMemory[1] = ProgMemory[3] = ProgMemory[4] = 0; PSize = 0; StartEditPoint = NULL; StartEditChar= 0; ProgramChanged = false; TraceOn = false; } // round a float to an integer #ifdef rp2350 int __not_in_flash_func(FloatToInt32)(MMFLOAT x) { #else #ifdef PICOMITEVGA int FloatToInt32(MMFLOAT x) { #else int __not_in_flash_func(FloatToInt32)(MMFLOAT x) { #endif #endif if(x < LONG_MIN - 0.5 || x > LONG_MAX + 0.5) error("Number too large"); return (x >= 0 ? (int)(x + 0.5) : (int)(x - 0.5)) ; } #ifdef rp2350 long long int __not_in_flash_func(FloatToInt64)(MMFLOAT x) { #else #ifdef PICOMITEVGA long long int FloatToInt64(MMFLOAT x) { #else long long int __not_in_flash_func(FloatToInt64)(MMFLOAT x) { #endif #endif if(x < (-(0x7fffffffffffffffLL) -1) - 0.5 || x > 0x7fffffffffffffffLL + 0.5) error("Number too large"); if ((x < -0xfffffffffffff) || (x > 0xfffffffffffff)) return (long long int)(x); else return (x >= 0 ? (long long int )(x + 0.5) : (long long int )(x - 0.5)) ; } // make a string uppercase void __not_in_flash_func(makeupper)(unsigned char *p) { while(*p) { *p = mytoupper(*p); p++; } } // find the value of a command token given its name int GetCommandValue( unsigned char *n) { int i; for(i = 0; i < CommandTableSize - 1; i++) if(str_equal(n, commandtbl[i].name)) return i; error("Invalid statement in Type definition"); return 0; } // find the value of a token given its name int GetTokenValue (unsigned char *n) { int i; for(i = 0; i < TokenTableSize - 1; i++) if(str_equal(n, tokentbl[i].name)) return i + C_BASETOKEN; error("Internal fault 4(sorry)"); return 0; } // skip to the end of a variable unsigned char MIPS16 __not_in_flash_func(*skipvar)(unsigned char *p, int noerror) { unsigned char *pp, *tp; int i; int inquote = false; tp = p; // check the first char for a legal variable name skipspace(p); if(!isnamestart(*p)) return tp; do { p++; } while(isnamechar(*p)); // check the terminating char. if(*p == '$' || *p == '%' || *p == '!') p++; if(p - tp > MAXVARLEN) { if(noerror) return p; error("Variable name too long"); } pp = p; skipspace(pp); if(*pp == (unsigned char)'(') p = pp; if(*p == '(') { // this is an array p++; if(p - tp > MAXVARLEN) { if(noerror) return p; error("Variable name too long"); } // step over the parameters keeping track of nested brackets i = 1; while(1) { if(*p == '\"') inquote = !inquote; if(*p == 0) { if(noerror) return p; error("Expected closing bracket"); } if(!inquote) { if(*p == ')') if(--i == 0) break; if(*p == '(' || (tokentype(*p) & T_FUN)) i++; } p++; } p++; // step over the closing bracket } return p; } // skip to the end of an expression (terminates on null, comma, comment or unpaired ')' unsigned char __not_in_flash_func(*skipexpression)(unsigned char *p) { int i, inquote; for(i = inquote = 0; *p; p++) { if(*p == '\"') inquote = !inquote; if(!inquote) { if(*p == ')') i--; if(*p == '(' || (tokentype(*p) & T_FUN)) i++; } if(i < 0 || (i == 0 && (*p == ',' || *p == '\''))) break; } return p; } // find the next command in the program // this contains the logic for stepping over a line number and label (if present) // p is the current place in the program to start the search from // CLine is a pointer to a char pointer which in turn points to the start of the current line for error reporting (if NULL it will be ignored) // EOFMsg is the error message to use if the end of the program is reached // returns a pointer to the next command unsigned char __not_in_flash_func(*GetNextCommand)(unsigned char *p, unsigned char **CLine, unsigned char *EOFMsg) { do { if(*p != T_NEWLINE) { // if we are not already at the start of a line while(*p) p++; // look for the zero marking the start of an element p++; // step over the zero } if(*p == 0) { if(EOFMsg == NULL) return p; error((char *)EOFMsg); } if(*p == T_NEWLINE) { if(CLine) *CLine = p; // and a pointer to the line also for error reporting p++; } if(*p == T_LINENBR) p += 3; skipspace(p); if(p[0] == T_LABEL) { // got a label p += p[1] + 2; // skip over the label skipspace(p); // and any following spaces } } while(*p < C_BASETOKEN); return p; } // scans text looking for the matching closing bracket // it will handle nested strings, brackets and functions // it expects to be called pointing at the opening bracket or a function token unsigned char __not_in_flash_func(*getclosebracket)(unsigned char *p) { int i = 0; int inquote = false; do { if(*p == 0) error("Expected closing bracket"); if(*p == '\"') inquote = !inquote; if(!inquote) { if(*p == ')') i--; if(*p == '(' || (tokentype(*p) & T_FUN)) i++; } p++; } while(i); return p - 1; } // check that there is no excess text following an element // will skip spaces and abort if a zero char is not found void __not_in_flash_func(checkend)(unsigned char *p) { skipspace(p); if(*p == '\'') return; if(*p) error("Unexpected text: $", p); } // check if the next text in an element (a basic statement) corresponds to an alpha string // leading whitespace is skipped and the string must be terminated with a valid terminating // character. Returns a pointer to the end of the string if found or NULL is not unsigned char __not_in_flash_func(*checkstring)(unsigned char *p, unsigned char *tkn) { skipspace(p); // skip leading spaces while(*tkn && (mytoupper(*tkn) == mytoupper(*p))) { tkn++; p++; } // compare the strings // if(*tkn == 0 && (*p == (unsigned char)' ' || *p == (unsigned char)',' || *p == (unsigned char)'\'' || *p == 0 || *p == (unsigned char)'(' || *p == (unsigned char)'=')) { if(*tkn == 0 && !isnamechar(*p)){ skipspace(p); return p; // if successful return a pointer to the next non space character after the matched string } return NULL; // or NULL if not } /******************************************************************************************************************************************** A couple of I/O routines that do not belong anywhere else *********************************************************************************************************************************************/ /******************************************************************************************************************************************** string routines these routines form a library of functions for manipulating MMBasic strings. These strings differ from ordinary C strings in that the length of the string is stored in the first byte and the string is NOT terminated with a zero valued byte. This type of string can store the full range of binary values (0x00 to 0xff) in each character. *********************************************************************************************************************************************/ // convert a MMBasic string to a C style string // if the MMstr contains a null byte that byte is skipped and not copied unsigned char __not_in_flash_func(*MtoC)(unsigned char *p) { int i; unsigned char *p1, *p2; i = *p; p1 = p + 1; p2 = p; while(i) { if(p1) *p2++ = *p1; p1++; i--; } *p2 = 0; return p; } // convert a c style string to a MMBasic string unsigned char __not_in_flash_func(*CtoM)(unsigned char *p) { int len, i; unsigned char *p1, *p2; len = i = strlen((char *)p); if(len > MAXSTRLEN) error("String too long"); p1 = p + len; p2 = p + len - 1; while(i--) *p1-- = *p2--; *p = len; return p; } // copy a MMBasic string to a new location #ifdef rp2350 void __not_in_flash_func(Mstrcpy)(unsigned char *dest, unsigned char *src) { #else #ifdef PICOMITEVGA void Mstrcpy(unsigned char *dest, unsigned char *src) { #else void __not_in_flash_func(Mstrcpy)(unsigned char *dest, unsigned char *src) { #endif #endif int i; i = *src + 1; while(i--) *dest++ = *src++; } // concatenate two MMBasic strings void Mstrcat(unsigned char *dest, unsigned char *src) { int i; i = *src; *dest += i; dest += *dest + 1 - i; src++; while(i--) *dest++ = *src++; } // compare two MMBasic style strings // returns 1 if s1 > s2 or 0 if s1 = s2 or -1 if s1 < s2 int Mstrcmp(unsigned char *s1, unsigned char *s2) { register int i; register unsigned char *p1, *p2; // get the smaller length i = *s1 < *s2 ? *s1 : *s2; // skip the length byte and point to the unsigned char array p1 = s1 + 1; p2 = s2 + 1; // compare each char while(i--) { if(*p1 > *p2) return 1; if(*p1 < *p2) return -1; p1++; p2++; } // up to this point the strings matched - make the decision based on which one is shorter if(*s1 > *s2) return 1; if(*s1 < *s2) return -1; return 0; } //////////////////////////////////////////////////////////////////////////////////////////////////// // these library functions went missing in the PIC32 C compiler ver 1.12 and later //////////////////////////////////////////////////////////////////////////////////////////////////// /* * mystrncasecmp.c -- * * Source code for the "mystrncasecmp" library routine. * * Copyright (c) 1988-1993 The Regents of the University of California. * Copyright (c) 1995-1996 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * * RCS: @(#) $Id: mystrncasecmp.c,v 1.3 2007/04/16 13:36:34 dkf Exp $ */ /* * This array is designed for mapping upper and lower case letter together for * a case independent comparison. The mappings are based upon ASCII character * sequences. */ static unsigned char charmap[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xe1, 0xe2, 0xe3, 0xe4, 0xc5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, }; /* *---------------------------------------------------------------------- * * mystrncasecmp -- * * Compares two strings, ignoring case differences. * * Results: * Compares up to length chars of s1 and s2, returning -1, 0, or 1 if s1 * is lexicographically less than, equal to, or greater than s2 over * those characters. * * Side effects: * None. * *---------------------------------------------------------------------- */ int mystrncasecmp( const unsigned char *s1, /* First string. */ const unsigned char *s2, /* Second string. */ size_t length) /* Maximum number of characters to compare * (stop earlier if the end of either string * is reached). */ { register unsigned char u1, u2; for (; length != 0; length--, s1++, s2++) { u1 = (unsigned char) *s1; u2 = (unsigned char) *s2; if (charmap[u1] != charmap[u2]) { return charmap[u1] - charmap[u2]; } if (u1 == '\0') { return 0; } } return 0; } // Compare two strings, ignoring case differences. // Returns true if the strings are equal (ignoring case) otherwise returns false. #if defined(__PIC32MX__) inline #endif int __not_in_flash_func(str_equal)(const unsigned char *s1, const unsigned char *s2) { if(charmap[*(unsigned char *)s1] != charmap[*(unsigned char *)s2]) return 0; for ( ; ; ) { if(*s2 == '\0') return 1; s1++; s2++; if(charmap[*(unsigned char *)s1] != charmap[*(unsigned char *)s2]) return 0; } return 0; } // Compare two areas of memory, ignoring case differences. // Returns true if they are equal (ignoring case) otherwise returns false. int __not_in_flash_func(mem_equal)(unsigned char *s1, unsigned char *s2, int i) { if(charmap[*(unsigned char *)s1] != charmap[*(unsigned char *)s2]) return 0; while (--i) { if(charmap[*(unsigned char *)++s1] != charmap[*(unsigned char *)++s2]) return 0; } return 1; } /* @endcond */