2025-06-15 21:31:45 +08:00

3738 lines
160 KiB
C

/*
* @cond
* The following section will be excluded from the documentation.
*/
/***********************************************************************************************************************
PicoMite MMBasic
MMBasic.c
<COPYRIGHT HOLDERS> Geoff Graham, Peter Mather
Copyright (c) 2021, <COPYRIGHT HOLDERS> 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 <copyright holder>.
5. Neither the name of the <copyright holder> 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 <COPYRIGHT HOLDERS> 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 <COPYRIGHT HOLDERS> 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 <stdio.h>
#include <limits.h>
#include <stdarg.h>
#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;i++){
// if(funtbl[i].name[0]!=0){
// MMPrintString(funtbl[i].name);PIntHC(funtbl[i].index);PIntComma(i);PRet();
// }
// }
}
// This scans one area (main program or the library area) for user defined subroutines and functions.
// It is only used by PrepareProgram() above.
int MIPS16 PrepareProgramExt(unsigned char *p, int i, unsigned char **CFunPtr, int ErrAbort) {
unsigned int *cfp;
while(*p != 0xff) {
p = GetNextCommand(p, &CurrentLinePtr, NULL);
if(*p == 0) break; // end of the program or module
CommandToken tkn=commandtbl_decode(p);
if(tkn == cmdSUB || tkn == cmdFUN /*|| tkn == cmdCFUN*/ || tkn == cmdCSUB) { // found a SUB, FUN, CFUNCTION or CSUB token
if(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<MAXSUBFUN) { // found a matching name
// MMPrintString("Found : ");MMPrintString((char *)name);MMPrintString(", hash key : ");PInt(hash);PRet();
return funtbl[hash].index;
break;
}
}
hash++;
if(hash==MAXSUBFUN)hash=0;
}
return -1;
}
#else
int __not_in_flash_func(FindSubFun)(unsigned char *p, int type) {
unsigned char *p1, *p2;
int i;
for(i = 0; i < MAXSUBFUN && subfun[i] != NULL; i++) {
p2 = subfun[i]; // point to the command token
CommandToken tkn=commandtbl_decode(p2);
if(type == 0) { // if it is a sub and we want a fun or vice versa skip this one
if(!(tkn == cmdSUB || tkn == cmdCSUB)) continue;
} else {
if(!(tkn == cmdFUN /*|| tkn == cmdCFUN*/)) continue;
}
p2+=sizeof(CommandToken); skipspace(p2); // point to the identifier
if(toupper(*p) != toupper(*p2)) continue; // quick first test
p1 = p + 1; p2++;
while(isnamechar(*p1) && toupper(*p1) == toupper(*p2)) { p1++; p2++; };
if((*p1 == '$' && *p2 == '$') || (*p1 == '%' && *p2 == '%') || (*p1 == '!' && *p2 == '!') || (!isnamechar(*p1) && !isnamechar(*p2))) return i; // found it !
}
return -1;
}
#endif
// This function is responsible for executing a defined subroutine or function.
// As these two are similar they are processed in the one lump of code.
//
// The arguments when called are:
// isfun = true if we are executing a function
// cmd = pointer to the command name used by the caller (in program memory)
// index = index into subfun[i] which points to the definition of the sub or funct
// fa, i64a, sa and typ are pointers to where the return value is to be stored (used by functions only)
#if defined(PICOMITEWEB) || defined(PICOMITEVGA)
#ifdef rp2350
void MIPS16 __not_in_flash_func(DefinedSubFun)(int isfun, unsigned char *cmd, int index, MMFLOAT *fa, long long int *i64a, unsigned char **sa, int *typ) {
#else
void MIPS16 DefinedSubFun(int isfun, unsigned char *cmd, int index, MMFLOAT *fa, long long int *i64a, unsigned char **sa, int *typ) {
#endif
#else
void MIPS16 __not_in_flash_func(DefinedSubFun)(int isfun, unsigned char *cmd, int index, MMFLOAT *fa, long long int *i64a, unsigned char **sa, int *typ) {
#endif
unsigned char *p, *s, *tp, *ttp, tcmdtoken;
unsigned char *CallersLinePtr, *SubLinePtr = NULL;
unsigned char *argbuf1; unsigned char **argv1; int argc1;
unsigned char *argbuf2; unsigned char **argv2; int argc2;
unsigned char fun_name[MAXVARLEN + 1];
unsigned char *argbyref;
int i;
int ArgType, FunType;
int *argtype;
union u_argval {
MMFLOAT f; // the value if it is a float
long long int i; // the value if it is an integer
MMFLOAT *fa; // pointer to the allocated memory if it is an array of floats
long long int *ia; // pointer to the allocated memory if it is an array of integers
unsigned char *s; // pointer to the allocated memory if it is a string
} *argval;
int *argVarIndex;
CallersLinePtr = CurrentLinePtr;
SubLinePtr = subfun[index]; // used for error reporting
p = SubLinePtr + sizeof(CommandToken); // point to the sub or function definition
skipspace(p);
ttp = p;
// copy the sub/fun name from the definition into temp storage and terminate
// p is left pointing to the end of the name (ie, start of the argument list in the definition)
CurrentLinePtr = SubLinePtr; // report errors at the definition
tp = fun_name;
*tp++ = *p++; while(isnamechar(*p)) *tp++ = *p++;
if(*p == '$' || *p == '%' || *p == '!') {
if(!isfun) {
error("Type specification is invalid: @", (int)(*p));
}
*tp++ = *p++;
}
*tp = 0;
if(isfun && *p != '(' /*&& (*SubLinePtr != cmdCFUN)*/) error("Function definition");
// find the end of the caller's identifier, tp is left pointing to the start of the caller's argument list
CurrentLinePtr = CallersLinePtr; // report errors at the caller
tp = cmd + 1;
while(isnamechar(*tp)) tp++;
if(*tp == '$' || *tp == '%' || *tp == '!') {
if(!isfun) error("Type specification");
tp++;
}
if(mytoupper(*(p-1)) != mytoupper(*(tp-1))) error("Inconsistent type suffix");
// if this is a function we check to find if the function's type has been specified with AS <type> 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
//Only if not an array remove any pointer flag in the caller
if(g_vartbl[argVarIndex[i]].dims[0] == 0){
argtype[i] = 0;
}else{
error("Array as BYVAL not allowed $",argv1[i]);
}
argv2[i] += 5; // skip to the variable start
argbyref[i]=2;
} else {
if((checkstring(argv2[i] + 2, (unsigned char *)"REF")) != NULL) { // if BYREF
//if((argtype[i] & T_PTR) == 0) error("Variable required for BYREF $", argv1[i]);
argv2[i] += 5; // skip to the variable start
argbyref[i]=1;
if((argtype[i] & T_PTR) == 0)error("Variable required for BYREF $", argv1[i]);
}
}
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;
//skip BYVAL/BYREF keywords
if(toupper(*argv2[i]) == 'B' && toupper(*(argv2[i]+1)) == 'Y') {
if((checkstring(argv2[i] + 2,(unsigned char *) "VAL")) != NULL) {
argv2[i] += 5;
}else if((checkstring(argv2[i] + 2, (unsigned char *)"REF")) != NULL) { // if BYREF
argv2[i] += 5; // skip to the variable start
}
}
// if (argbyref[i] )argv2[i] += 5;
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) {
//if( (argbyref[i]==2)) error("Array must be passed as BYREF $",argv1[i]);
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]==1){ 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<MMEND){
char buff[]="~( )";
buff[2]=i+'A';
STR_REPLACE((char *)inpbuf,overlaid_functions[i],buff, false);
i++;
}
STR_REPLACE((char *)inpbuf,"MM.INFO$","MM.INFO",0);
STR_REPLACE((char *)inpbuf,"=>",">=", 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<p[0];j++)funtbl[hash].name[j]=mytoupper(p[j+1]);
p += p[0] + 1; // still looking! skip over the label
continue;
}
p++;
}
}
// search through program memory looking for a label.
// returns a pointer to the T_NEWLINE token or throws an error if not found
// non cached version
unsigned char *findlabel(unsigned char *labelptr) {
// char *p, *lastp = (char *)ProgMemory + 1;
unsigned char *tp, *ip;
int i;
uint32_t hash=FNV_offset_basis;
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] = mytoupper(*labelptr++);
hash ^= label[1];
hash*=FNV_prime;
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]=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<MAXVARS/2)OriginalGlobalHash+=MAXVARS/2;
globalifree=-1;
tmp=-1;
if(g_LocalIndex){ //search
if(g_vartbl[LocalhashIndex].type == T_NOTYPE){
localifree = LocalhashIndex;
} else {
while(g_vartbl[LocalhashIndex].name[0]!=0){
ip=(char *)name;
tp=(char *)g_vartbl[LocalhashIndex].name;
if(g_vartbl[LocalhashIndex].type==T_BLOCKED)tmp=LocalhashIndex;
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(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<MAXSUBFUN)error("A sub/fun has the same name: $", name);
}
}
funhash++;
if(funhash==MAXSUBFUN)funhash=0;
}
}
#else
if(!(action & V_FUNCT)) { // don't do this if we are defining the local variable for a function name
for(i = 0; i < MAXSUBFUN && subfun[i] != NULL; i++) {
x = subfun[i]; // point to the command token
x++; skipspace(x); // point to the identifier
s = name; // point to the new variable
if(*s != toupper(*x)) continue; // quick first test
while(1) {
if(!isnamechar(*s) && !isnamechar(*x)) error("A sub/fun has the same name: $", name);
if(*s != toupper(*x) || *s == 0 || !isnamechar(*x) || s - name >= 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<MAXVARS/2){
g_hashlist[g_hashlistpointer].level=g_LocalIndex;
g_hashlist[g_hashlistpointer++].hash=ifree;
g_vartbl[ifree].level = g_LocalIndex;
} else g_vartbl[ifree].level = 0;
// cleardims(&g_vartbl[ifree].dims[0]);
for(j = 0; j < MAXDIM; j++) g_vartbl[ifree].dims[j] = 0;
// MMPrintString("Creating variable : ");MMPrintString(g_vartbl[ifree].name);MMPrintString(", at depth : ");PInt(g_vartbl[ifree].level);MMPrintString(", Type : ");PInt(vtype);MMPrintString(", hash key : ");PInt(ifree);PRet();
// the easy request is for is a non array numeric variable, so just initialise to
// zero and return the pointer
if(dnbr == 0) {
if(vtype & T_NBR) {
g_vartbl[ifree].val.f = 0;
return &(g_vartbl[ifree].val.f);
} else if(vtype & T_INT) {
g_vartbl[ifree].val.i = 0;
return &(g_vartbl[ifree].val.i);
}
}
// if this is a definition of an empty array (only used in the parameter list for a sub/function)
if(dnbr == -1) {
g_vartbl[vindex].dims[0] = -1; // let the caller know that this is an empty array and needs more work
return g_vartbl[vindex].val.s; // just return a pointer to the data element as it will be replaced in the sub/fun with a pointer
}
// if this is an array copy the array dimensions and calculate the overall size
// for a non array string this will leave nbr = 1 which is just what we want
for(nbr = 1, i = 0; i < dnbr; i++) {
if(dim[i] <= g_OptionBase) error("Dimensions");
g_vartbl[vindex].dims[i] = dim[i];
nbr *= (dim[i] + 1 - g_OptionBase);
}
// we now have a string, an array of strings or an array of numbers
// all need some memory to be allocated (note: GetMemory() zeros the memory)
// First, set the important characteristics of the variable to indicate that the
// variable is not allocated. Thus, if GetMemory() fails with "not enough memory",
// the variable will remain not allocated
g_vartbl[ifree].val.s = NULL;
g_vartbl[ifree].type = T_BLOCKED;
i = *g_vartbl[ifree].name; *g_vartbl[ifree].name = 0;
j = g_vartbl[ifree].dims[0]; g_vartbl[ifree].dims[0] = 0;
// Now, grab the memory
if(vtype & (T_NBR | T_INT)) {
tmp=(nbr * sizeof(MMFLOAT));
if(tmp<=256)mptr = GetMemory(STRINGSIZE);
else mptr = GetMemory(tmp);
} else {
tmp=(nbr * (size + 1));
if(tmp<=(MAXDIM-1)*sizeof(g_vartbl[ifree].dims[1]) && j==0)mptr = (void *)&g_vartbl[ifree].dims[1];
else if(tmp<=256)mptr = GetMemory(STRINGSIZE);
else mptr = GetMemory(tmp);
}
// If we reached here the memory request was successful, so restore the details of
// the variable that were saved previously and set the variables pointer to the
// allocated memory
g_vartbl[ifree].type = vtype | (action & (T_IMPLIED | T_CONST));
if(suffix)g_vartbl[ifree].type|=T_EXPLICIT;
*g_vartbl[ifree].name = i;
g_vartbl[ifree].dims[0] = j;
g_vartbl[ifree].size = size;
g_vartbl[ifree].val.s = mptr;
return mptr;
}
/********************************************************************************************************************************************
utility routines
these routines form a library of functions that any command or function can use when dealing with its arguments
by centralising these routines it is hoped that bugs can be more easily found and corrected (unlike bwBasic !)
*********************************************************************************************************************************************/
// take a line of basic code and split it into arguments
// this function should always be called via the macro getargs
//
// a new argument is created by any of the chars in the string delim (not in brackets or quotes)
// with this function commands have much less work to do to evaluate the arguments
//
// The arguments are:
// pointer to a pointer which points to the string to be broken into arguments.
// the maximum number of arguments that are expected. an error will be thrown if more than this are found.
// buffer where the returned strings are to be stored
// pointer to an array of strings that will contain (after the function has returned) the values of each argument
// pointer to an integer that will contain (after the function has returned) the number of arguments found
// pointer to a string that contains the characters to be used in spliting up the line. If the first unsigned char of that
// string is an opening bracket '(' this function will expect the arg list to be enclosed in brackets.
void MIPS16 __not_in_flash_func(makeargs)(unsigned char **p, int maxargs, unsigned char *argbuf, unsigned char *argv[], int *argc, unsigned char *delim) {
unsigned char *op;
int inarg, expect_cmd, expect_bracket, then_tkn, else_tkn;
unsigned char *tp;
TestStackOverflow(); // throw an error if we have overflowed the PIC32's stack
tp = *p;
op = argbuf;
*argc = 0;
inarg = false;
expect_cmd = false;
expect_bracket = false;
then_tkn = tokenTHEN;
else_tkn = tokenELSE;
// skip leading spaces
while(*tp == ' ') tp++;
// check if we are processing a list enclosed in brackets and if so
// - skip the opening bracket
// - flag that a closing bracket should be found
if(*delim == '(') {
if(*tp != '(')
error("Syntax");
expect_bracket = true;
delim++;
tp++;
}
// the main processing loop
while(*tp) {
if(expect_bracket == true && *tp == ')') break;
// comment char causes the rest of the line to be skipped
if(*tp == '\'') {
break;
}
// the special characters that cause the line to be split up are in the string delim
// any other chars form part of the one argument
if(strchr((char *)delim, (char)*tp) != NULL && !expect_cmd) {
if(*tp == then_tkn || *tp == else_tkn) expect_cmd = true;
if(inarg) { // if we have been processing an argument
while(op > 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 ; i<MaxPcb ; i++){
if(state->client_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 */