/***********************************************************************************************************************
PicoMite MMBasic

* @file commands.c

<COPYRIGHT HOLDERS>  @author 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. 


************************************************************************************************************************/
/**
* @file Commands.c
* @author Geoff Graham, Peter Mather
* @brief Source for standard MMBasic commands
*/
/**
 * @cond
 * The following section will be excluded from the documentation.
 */


#include "MMBasic_Includes.h"
#include "Hardware_Includes.h"
#include "hardware/flash.h"
#include "hardware/dma.h"
#include "hardware/structs/watchdog.h"
#ifdef PICOMITE
#include "pico/multicore.h"
#endif
#define overlap (VRes % (FontTable[gui_font >> 4][1] * (gui_font & 0b1111)) ? 0 : 1)
#include <math.h>
void flist(int, int, int);
//void clearprog(void);
char *KeyInterrupt=NULL;
unsigned char* SaveNextDataLine = NULL;
void execute_one_command(unsigned char *p);
void ListNewLine(int *ListCnt, int all);
int printWrappedText(const char *text, int screenWidth, int listcnt, int all) ;
char MMErrMsg[MAXERRMSG];                                           // the error message
volatile bool Keycomplete=false;
int keyselect=0;
extern volatile unsigned int ScrewUpTimer;
int SaveNextData = 0;
struct sa_data datastore[MAXRESTORE];
int restorepointer = 0;
uint64_t g_flag=0;
const uint8_t pinlist[]={ //this is a Basic program to print out the status of all the pins
	1,132,128,95,113,37,0,
	1,153,128,95,113,37,144,48,32,204,32,241,109,97,120,32,103,112,41,0,
	1,168,128,34,71,80,34,130,186,95,113,37,41,44,32,241,112,105,110,110,111,32,34,71,80,34,130,186,
	95,113,37,41,41,44,241,112,105,110,32,241,112,105,110,110,111,32,34,71,80,34,130,186,95,113,37,41,41,41,0,
	1,166,128,0,
    1,147,128,95,113,37,0,0
};
const uint8_t i2clist[]={ //this is a Basic program to print out the I2C devices connected to the SYSTEM I2C pins
  1, 132, 128, 105, 110, 116, 101, 103, 101, 114, 32, 95, 97, 100, 0,
  1, 132, 128, 105, 110, 116, 101, 103, 101, 114, 32, 120, 95, 44, 121, 95, 0, 
  1, 168, 128, 34, 32, 72, 69, 88, 32, 32, 48, 32, 32, 49, 32, 32, 50, 32, 32, 51, 32, 32, 52, 32, 32, 53, 32, 32, 54, 32, 32, 55, 32, 32, 
  56, 32, 32, 57, 32, 32, 65, 32, 32, 66, 32, 32, 67, 32, 32, 68, 32, 32, 69, 32, 32, 70, 34, 0, 
  1, 153, 128, 121, 95, 32, 144, 32, 48, 32, 204, 32, 55, 0, 
  1, 168, 128, 34, 32, 34, 59, 32, 164, 121, 95, 44, 32, 49, 41, 59, 32, 34, 48, 58, 32, 34, 59, 0, 
  1, 153, 128, 120, 95, 32, 144, 32, 48, 32, 204, 32, 49, 53, 0, 
  1, 161, 128, 95, 97, 100, 32, 144, 32, 121, 95, 32, 133, 32, 49, 54, 32, 130, 32, 120, 95, 0, 
  1, 158, 128, 241, 83, 89, 83, 84, 69, 77, 32, 73, 50, 67, 41, 144, 34, 73, 50, 67, 34, 32, 203, 32, 228, 128, 99, 104, 
  101, 99, 107, 32, 95, 97, 100, 32, 199, 32, 229, 128, 32, 99, 104, 101, 99, 107, 32, 95, 97, 100, 0, 
  1, 158, 128, 243, 68, 41, 32, 144, 32, 48, 32, 203, 0, 
  1, 158, 128, 95, 97, 100, 32, 144, 32, 48, 32, 203, 32, 168, 128, 34, 45, 45, 32, 34, 59, 0, 
  1, 158, 128, 95, 97, 100, 32, 143, 32, 48, 32, 203, 32, 168, 128, 164, 95, 97, 100, 44, 32, 50, 41, 59, 34, 32, 34, 59, 0, 
  1, 139, 128, 0, 1, 168, 128, 34, 45, 45, 32, 34, 59, 0, 1, 143, 128, 0, 1, 166, 128, 120, 95, 0, 
  1, 168, 128, 0, 1, 166, 128, 121, 95, 0, 1, 147, 128, 120, 95, 44, 121, 95, 0,
  1, 147, 128, 95, 97, 100, 0,0
 };
// stack to keep track of nested FOR/NEXT loops
struct s_forstack g_forstack[MAXFORLOOPS + 1];
int g_forindex;



// stack to keep track of nested DO/LOOP loops
struct s_dostack g_dostack[MAXDOLOOPS];
int g_doindex;                                // counts the number of nested DO/LOOP loops


// stack to keep track of GOSUBs, SUBs and FUNCTIONs
unsigned char *gosubstack[MAXGOSUB];
unsigned char *errorstack[MAXGOSUB];
int gosubindex;

unsigned char g_DimUsed = false;						// used to catch OPTION BASE after DIM has been used

int TraceOn;                                // used to track the state of TRON/TROFF
unsigned char *TraceBuff[TRACE_BUFF_SIZE];
int TraceBuffIndex;                       // used for listing the contents of the trace buffer
int OptionErrorSkip;                                               // how to handle an error
int MMerrno;                                                        // the error number
unsigned char cmdlinebuff[STRINGSIZE];
const unsigned int CaseOption = 0xffffffff;	// used to store the case of the listed output

static inline CommandToken commandtbl_decode(const unsigned char *p){
    return ((CommandToken)(p[0] & 0x7f)) | ((CommandToken)(p[1] & 0x7f)<<7);
}

void __not_in_flash_func(cmd_null)(void) {
	// do nothing (this is just a placeholder for commands that have no action)
}
/** @endcond */
/** 
 * This command increments an integer or a float or concatenates two strings
 * @param a the integer, float or string to be changed
 * @param b OPTIONAL for integers and floats - defaults to 1. Otherwise the amount to increment the number or the string to concatenate
 */
#ifdef rp2350
void MIPS16 __not_in_flash_func(cmd_inc)(void){
#else
#ifdef PICOMITEVGA
void MIPS16 cmd_inc(void){
#else
void MIPS16 __not_in_flash_func(cmd_inc)(void){
#endif
#endif
	unsigned char *p, *q;
    int vtype;
	getargs(&cmdline,3,(unsigned char *)",");
	if(argc==1){
		p = findvar(argv[0], V_FIND);
		if(g_vartbl[g_VarIndex].type & T_CONST) error("Cannot change a constant");
        vtype = TypeMask(g_vartbl[g_VarIndex].type);
        if(vtype & T_STR) error("Invalid variable");                // sanity check
		if(vtype & T_NBR)
            (*(MMFLOAT *)p) = (*(MMFLOAT *)p) + 1.0;
		else if(vtype & T_INT)*(long long int *)p = *(long long int *)p + 1;
		else error("Syntax");
	} else {
		p = findvar(argv[0], V_FIND);
		if(g_vartbl[g_VarIndex].type & T_CONST) error("Cannot change a constant");
        vtype = TypeMask(g_vartbl[g_VarIndex].type);
        if(vtype & T_STR){
			int size=g_vartbl[g_VarIndex].size;
        	q=getstring(argv[2]);
    		if(*p + *q > size) error("String too long");
			Mstrcat(p, q);
        } else if(vtype & T_NBR){
        	 (*(MMFLOAT *)p) = (*(MMFLOAT *)p)+getnumber(argv[2]);
        } else if(vtype & T_INT){
        	*(long long int *)p = *(long long int *)p+getinteger(argv[2]);
        } else error("syntax");
 	}
}
// the PRINT command
void cmd_print(void) {
	unsigned char *s, *p;
    unsigned char *ss;
	MMFLOAT f;
    long long int  i64;
	int i, t, fnbr;
	int docrlf;														// this is used to suppress the cr/lf if needed

	getargs(&cmdline, (MAX_ARG_COUNT * 2) - 1, (unsigned char *)";,");				// this is a macro and must be the first executable stmt

//    s = 0; *s = 56;											    // for testing the exception handler

	docrlf = true;

	if(argc > 0 && *argv[0] == '#') {								// check if the first arg is a file number
		argv[0]++;
         if((*argv[0] == 'G') || (*argv[0] == 'g')){
            argv[0]++;
            if(!((*argv[0] == 'P') || (*argv[0] == 'p')))error("Syntax");
            argv[0]++;
            if(!((*argv[0] == 'S') || (*argv[0] == 's')))error("Syntax");
            if(!GPSchannel) error("GPS not activated");
            if(argc!=3) error("Only a single string parameter allowed");
            p = argv[2];
			t = T_NOTYPE;
			p = evaluate(p, &f, &i64, &s, &t, true);			// get the value and type of the argument
            ss=(unsigned char *)s;
            if(!(t & T_STR)) error("Only a single string parameter allowed");
            int i,xsum=0;
            if(ss[1]!='$' || ss[ss[0]]!='*')error("GPS command must start with dollar and end with star");
            for(i=1;i<=ss[0];i++){
                SerialPutchar(GPSchannel, s[i]);
                if(s[i]=='$')xsum=0;
                if(s[i]!='*')xsum ^=s[i];
            }
            i=xsum/16;
            i=i+'0';
            if(i>'9')i=i-'0'+'A';
            SerialPutchar(GPSchannel, i);
            i=xsum % 16;
            i=i+'0';
            if(i>'9')i=i-'0'+'A';
            SerialPutchar(GPSchannel, i);
            SerialPutchar(GPSchannel, 13);
            SerialPutchar(GPSchannel, 10);
            return;
        } else {
			fnbr = getinteger(argv[0]);									// get the number
			i = 1;
			if(argc >= 2 && *argv[1] == ',') i = 2;						// and set the next argument to be looked at
		}
	} else {
		fnbr = 0;													// no file number so default to the standard output
		i = 0;
	}

	for(; i < argc; i++) {											// step through the arguments
		if(*argv[i] == ',') {
			MMfputc('\t', fnbr);									// print a tab for a comma
			docrlf = false;                                         // a trailing comma should suppress CR/LF
		}
		else if(*argv[i] == ';') {
			docrlf = false;											// other than suppress cr/lf do nothing for a semicolon
		}
		else {														// we have a normal expression
			p = argv[i];
			while(*p) {
				t = T_NOTYPE;
				p = evaluate(p, &f, &i64, &s, &t, true);			// get the value and type of the argument
                if(t & T_NBR) {
                    *inpbuf = ' ';                                  // preload a space
                    FloatToStr((char *)inpbuf + ((f >= 0) ? 1:0), f, 0, STR_AUTO_PRECISION, (unsigned char)' ');// if positive output a space instead of the sign
					MMfputs((unsigned char *)CtoM(inpbuf), fnbr);					// convert to a MMBasic string and output
				} else if(t & T_INT) {
                    *inpbuf = ' ';                                  // preload a space
                    IntToStr((char *)inpbuf + ((i64 >= 0) ? 1:0), i64, 10); // if positive output a space instead of the sign
					MMfputs((unsigned char *)CtoM(inpbuf), fnbr);					// convert to a MMBasic string and output
				} else if(t & T_STR) {
					MMfputs((unsigned char *)s, fnbr);								// print if a string (s is a MMBasic string)
				} else error("Attempt to print reserved word");	
			}
			docrlf = true;
		}
	}
	if(docrlf) MMfputs((unsigned char *)"\2\r\n", fnbr);								// print the terminating cr/lf unless it has been suppressed
	if(PrintPixelMode!=0)SSPrintString("\033[m");
	PrintPixelMode=0;
}
void cmd_arrayset(void){
	array_set(cmdline);
}
void array_set(unsigned char *tp){
    MMFLOAT f;
    long long int i64;
    unsigned char *s;
	#ifdef rp2350
	int dims[MAXDIM]={0};
	#else
	short dims[MAXDIM]={0};
	#endif
	int i,t,copy,card1=1;
	unsigned char size=0;
	MMFLOAT *a1float=NULL;
	int64_t *a1int=NULL;
	unsigned char *a1str=NULL;
	getargs(&tp, 3,(unsigned char *)",");
	if(!(argc == 3)) error("Argument count");
	findvar(argv[2], V_FIND | V_EMPTY_OK | V_NOFIND_ERR);
	t=g_vartbl[g_VarIndex].type;
	evaluate(argv[0], &f, &i64, &s, &t, false);
	if(t & T_STR){
		card1=parsestringarray(argv[2],&a1str,2,0, dims, true, &size);
		copy=(int)size+1;
		memset(a1str,0,copy*card1);
		if(*s){
			for(i=0; i< card1;i++){
				Mstrcpy(&a1str[i*copy],s);
			}
		}
	} else {
		card1=parsenumberarray(argv[2],&a1float,&a1int,2,0, dims, true);
		if(t & T_STR) error("Syntax");

		if(a1float!=NULL){
			for(i=0; i< card1;i++)*a1float++ = ((t & T_INT) ? (MMFLOAT)i64 : f);
		} else {
			for(i=0; i< card1;i++)*a1int++ = ((t & T_INT) ? i64 : FloatToInt64(f));
		}
	}
}
void cmd_add(void){
	array_add(cmdline);
}

void array_add(unsigned char *tp){
    MMFLOAT f;
    long long int i64;
    unsigned char *s;
	#ifdef rp2350
	int dims[MAXDIM]={0};
	#else
	short dims[MAXDIM]={0};
	#endif
	int i,t,card1=1, card2=1;
	MMFLOAT *a1float=NULL,*a2float=NULL, scale;
	int64_t *a1int=NULL, *a2int=NULL;
	unsigned char *a1str=NULL, *a2str=NULL;
	getargs(&tp, 5,(unsigned char *)",");
	if(!(argc == 5)) error("Argument count");
	findvar(argv[0], V_FIND | V_EMPTY_OK | V_NOFIND_ERR);
	t=g_vartbl[g_VarIndex].type;
	if(t & T_STR){
		unsigned char size=0,size2=0;
		unsigned char *toadd;
		card1=parsestringarray(argv[0], &a1str, 1, 0,dims, false, &size);
		evaluate(argv[2], &f, &i64, &s, &t, false);
		if(!(t & T_STR)) error("Syntax");
		toadd=getstring(argv[2]);
		card2=parsestringarray(argv[4], &a2str, 3, 0,dims, true, &size2);
		if(card1 != card2)error("Array size mismatch");
		unsigned char *buff = GetTempMemory(STRINGSIZE);								// this will last for the life of the command
		int copy=size+1;
		int copy2=size2+1;
		for(i=0; i< card1;i++){
			unsigned char *sarg1=a1str+i*copy;
			unsigned char *sarg2=a2str+i*copy2;
			if(*sarg1 + *toadd > size2) error("String too long");
			Mstrcpy(buff, sarg1);
			Mstrcat(buff, toadd);
			Mstrcpy(sarg2,buff);
		}
	} else {
		card1=parsenumberarray(argv[0], &a1float, &a1int, 1, 0,dims, false);
		evaluate(argv[2], &f, &i64, &s, &t, false);
		if(t & T_STR) error("Syntax");
		scale=getnumber(argv[2]);
		card2=parsenumberarray(argv[4], &a2float, &a2int, 3, 0,dims, true);
		if(card1 != card2)error("Array size mismatch");
		if(scale!=0.0){
			if(a2float!=NULL && a1float!=NULL){
				for(i=0; i< card1;i++)*a2float++ = ((t & T_INT) ? (MMFLOAT)i64 : f) + (*a1float++);
			} else if(a2float!=NULL && a1float==NULL){
				for(i=0; i< card1;i++)(*a2float++) = ((t & T_INT) ? (MMFLOAT)i64 : f) + ((MMFLOAT)*a1int++);
			} else if(a2float==NULL && a1float!=NULL){
				for(i=0; i< card1;i++)(*a2int++) = FloatToInt64(((t & T_INT) ? i64 : FloatToInt64(f)) + (*a1float++));
			} else {
				for(i=0; i< card1;i++)(*a2int++) = ((t & T_INT) ? i64 : FloatToInt64(f)) + (*a1int++);
			}
		} else {
			if(a2float!=NULL && a1float!=NULL){
				for(i=0; i< card1;i++)*a2float++ = *a1float++;
			} else if(a2float!=NULL && a1float==NULL){
				for(i=0; i< card1;i++)(*a2float++) = ((MMFLOAT)*a1int++);
			} else if(a2float==NULL && a1float!=NULL){
				for(i=0; i< card1;i++)(*a2int++) = FloatToInt64(*a1float++);
			} else {
				for(i=0; i< card1;i++)*a2int++ = *a1int++;
			}
		}
	}
}
void cmd_insert(void){
	array_insert(cmdline);
}
void array_insert(unsigned char *tp){
	int i, j, t, start, increment, dim[MAXDIM], pos[MAXDIM],off[MAXDIM], dimcount=0, target=-1;
	int64_t *a1int=NULL,*a2int=NULL;
	MMFLOAT *afloat=NULL;
	unsigned char *a1str=NULL, *a2str=NULL;
	unsigned char size=0,size2=0;
#ifdef rp2350
    int dims[MAXDIM]={0}; 
#else
    short dims[MAXDIM]={0};
#endif
	getargs(&tp, 15,(unsigned char *)",");
	if(argc<7)error("Argument count");
	findvar(argv[0], V_FIND | V_EMPTY_OK | V_NOFIND_ERR);
	t=g_vartbl[g_VarIndex].type;
	if(t & T_STR){
		parsestringarray(argv[0],&a1str,1,0,dims, false,&size);
	} else {
		parsenumberarray(argv[0],&afloat,&a1int,1,0,dims, false);
		if(!a1int)a1int=(int64_t *)afloat;
	}
	if(dims[1]<=0)error("Argument 1 must be a 2D or more array");
	for(i=0;i<MAXDIM;i++){
		if(dims[i]-g_OptionBase>0){
			dimcount++;
			dim[i]=dims[i]-g_OptionBase;
		} else dim[i]=0;
	}
	if(((argc-1)/2-1)!=dimcount)error("Argument count");
	for(i=0; i<dimcount;i++ ){
		if(*argv[i*2 +2]) pos[i]=getint(argv[i*2 +2],g_OptionBase,dim[i]+g_OptionBase)-g_OptionBase;
		else {
			if(target!=-1)error("Only one index can be omitted");
			target=i;
			pos[i]=1;
		}
	}
	if(t & T_STR){
		parsestringarray(argv[i*2 +2],&a2str,i+1,1,dims, true,&size2);
	} else {
		parsenumberarray(argv[i*2 +2],&afloat,&a2int,i+1,1,dims,true);
		if(!a2int)a2int=(int64_t *)afloat;
	}
	if(target==-1)return;
	if(dim[target]+g_OptionBase!=dims[0])error("Size mismatch between insert and target array");
	if(size!=size2)error("String arrays differ in string length");
	i=dimcount-1;
	while(i>=0){
		off[i]=1;
		for(j=0; j<i; j++)off[i]*=(dim[j]+1);
		i--;
	}
	start=1;
	for(i=0;i<dimcount;i++){
		start+= (pos[i]*off[i]);
	}
	start--;
	increment=off[target];
	start-=increment;
	if(t & T_STR){
		int copy=(int)size+1;
		for(i=0;i<=dim[target];i++) {
			unsigned char *p=a2str+i*copy;
			unsigned char *q=&a1str[(start+i*increment)*copy];
			memcpy(q,p,copy);
		}
	} else	{
		for(i=0;i<=dim[target];i++) a1int[start+i*increment]=*a2int++;
	}
	return;

}
void cmd_slice(void){
	array_slice(cmdline);
}
void array_slice(unsigned char *tp){
	int i, j, t, start, increment, dim[MAXDIM], pos[MAXDIM],off[MAXDIM], dimcount=0, target=-1, toarray=0;
	int64_t *a1int=NULL,*a2int=NULL;
	MMFLOAT *afloat=NULL;
	unsigned char *a1str=NULL,*a2str=NULL;
	unsigned char size=0,size2=0;
#ifdef rp2350
    int dims[MAXDIM]={0};
#else
    short dims[MAXDIM]={0};
#endif
	getargs(&tp, 15,(unsigned char *)",");
	if(argc<7)error("Argument count");
	findvar(argv[0], V_FIND | V_EMPTY_OK | V_NOFIND_ERR);
	t=g_vartbl[g_VarIndex].type;
	if(t & T_STR){
		parsestringarray(argv[0],&a1str,1,0,dims, false, &size);
	} else {
		parsenumberarray(argv[0],&afloat,&a1int,1,0,dims, false);
		if(!a1int)a1int=(int64_t *)afloat;
	}
	if(dims[1]<=0)error("Argument 1 must be a 2D or more array");
	for(i=0;i<MAXDIM;i++){
		if(dims[i]-g_OptionBase>0){
			dimcount++;
			dim[i]=dims[i]-g_OptionBase;
		} else dim[i]=0;
	}
	if(((argc-1)/2-1)!=dimcount)error("Argument count");
	for(i=0; i<dimcount;i++ ){
		if(*argv[i*2 +2]) pos[i]=getint(argv[i*2 +2],g_OptionBase,dim[i]+g_OptionBase)-g_OptionBase;
		else {
			if(target!=-1)error("Only one index can be omitted");
			target=i;
			pos[i]=1;
		}
	}
	if(t & T_STR){
		toarray=parsestringarray(argv[i*2 +2],&a2str,i+1,1,dims, true, &size2)-1;
	} else {
		toarray=parsenumberarray(argv[i*2 +2],&afloat,&a2int,i+1,1,dims, true)-1;
		if(!a2int)a2int=(int64_t *)afloat;
	}
	if(dim[target]!=toarray)error("Size mismatch between slice and target array");
	if(size!=size2)error("String arrays differ in string length");
	i=dimcount-1;
	while(i>=0){
		off[i]=1;
		for(j=0; j<i; j++)off[i]*=(dim[j]+1);
		i--;
	}
	start=1;
	for(i=0;i<dimcount;i++){
		start+= (pos[i]*off[i]);
	}
	start--;
	increment=off[target];
	start-=increment;
	if(t & T_STR){
		int copy=(int)size+1; //allow for the length character of the string
		for(i=0;i<=dim[target];i++){
			unsigned char *p=a2str+i*copy;
			unsigned char *q=&a1str[(start+i*increment)*copy];
			memcpy(p,q,copy);
		}
	} else {
		for(i=0;i<=dim[target];i++)*a2int++ = a1int[start+i*increment];
	}
	return;
}

// the LET command
// because the LET is implied (ie, line does not have a recognisable command)
// it ends up as the place where mistyped commands are discovered.  This is why
// the error message is "Unknown command"
void  MIPS16 __not_in_flash_func(cmd_let)(void) {
	int t, size;
	MMFLOAT f;
    long long int  i64;
	unsigned char *s;
	unsigned char *p1, *p2;

	p1 = cmdline;
	// search through the line looking for the equals sign
	while(*p1 && tokenfunction(*p1) != op_equal) p1++;
	if(!*p1) error("Unknown command");

	// check that we have a straight forward variable
	p2 = skipvar(cmdline, false);
	skipspace(p2);
	if(p1 != p2) error("Syntax");

	// create the variable and get the length if it is a string
	p2 = findvar(cmdline, V_FIND);
    size = g_vartbl[g_VarIndex].size;
    if(g_vartbl[g_VarIndex].type & T_CONST) error("Cannot change a constant");

	// step over the equals sign, evaluate the rest of the command and save in the variable
	p1++;
	if(g_vartbl[g_VarIndex].type & T_STR) {
		t = T_STR;
		p1 = evaluate(p1, &f, &i64, &s, &t, false);
		if(*s > size) error("String too long");
		Mstrcpy(p2, s);
	}
	else if(g_vartbl[g_VarIndex].type & T_NBR) {
		t = T_NBR;
		p1 = evaluate(p1, &f, &i64, &s, &t, false);
		if(t & T_NBR)
            (*(MMFLOAT *)p2) = f;
        else
            (*(MMFLOAT *)p2) = (MMFLOAT)i64;
	} else {
		t = T_INT;
		p1 = evaluate(p1, &f, &i64, &s, &t, false);
		if(t & T_INT)
            (*(long long int  *)p2) = i64;
        else
            (*(long long int  *)p2) = FloatToInt64(f);
	}
	checkend(p1);
}
/**
 * @cond
 * The following section will be excluded from the documentation.
 */
int MIPS16 as_strcmpi (const char *s1, const char *s2)
{
  const unsigned char *p1 = (const unsigned char *) s1;
  const unsigned char *p2 = (const unsigned char *) s2;
  unsigned char c1, c2;

  if (p1 == p2)
    return 0;

  do
    {
      c1 = tolower (*p1++);
      c2 = tolower (*p2++);
      if (c1 == '\0')
	break;
    }
  while (c1 == c2);

  return c1 - c2;
}
void MIPS16 sortStrings(char **arr, int n)
{
    char temp[STRINGSIZE];
    int i,j;
    // Sorting strings using bubble sort
    for (j=0; j<n-1; j++)
    {
        for (i=j+1; i<n; i++)
        {
            if (as_strcmpi(arr[j], arr[i]) > 0)
            {
                strcpy(temp, arr[j]);
                strcpy(arr[j], arr[i]);
                strcpy(arr[i], temp);
            }
        }
    }
}
void MIPS16 ListFile(char *pp, int all) {
	char buff[STRINGSIZE];
    int fnbr;
    int i,ListCnt = CurrentY/(FontTable[gui_font >> 4][1] * (gui_font & 0b1111)) + 2;
	fnbr = FindFreeFileNbr();
	if(!BasicFileOpen(pp, fnbr, FA_READ)) return;
	while(!FileEOF(fnbr)) {                                     // while waiting for the end of file
		memset(buff,0,256);
		MMgetline(fnbr, (char *)buff);									    // get the input line
		for(i=0;i<strlen(buff);i++)if(buff[i] == TAB) buff[i] = ' ';
		ListCnt=printWrappedText(buff,Option.Width,ListCnt,all);
	}
	FileClose(fnbr);
}

void MIPS16 ListNewLine(int *ListCnt, int all) {
	unsigned char noscroll=Option.NoScroll;
	if(!all && (void *)ReadBuffer!=(void *)DisplayNotSet)Option.NoScroll=0;
	MMPrintString("\r\n");
	(*ListCnt)++;
    if(!all && *ListCnt >= Option.Height-overlap) {
		#ifdef USBKEYBOARD
		clearrepeat();
		#endif
    	MMPrintString("PRESS ANY KEY ...");
    	MMgetchar();
    	MMPrintString("\r                 \r");
        if(Option.DISPLAY_CONSOLE){ClearScreen(gui_bcolour);CurrentX=0;CurrentY=0;}
    	*ListCnt = 2;
    }
	Option.NoScroll=noscroll;
}


void MIPS16 ListProgram(unsigned char *p, int all) {
	char b[STRINGSIZE];
	char *pp;
    int ListCnt = CurrentY/(FontTable[gui_font >> 4][1] * (gui_font & 0b1111)) + 2;
	while(!(*p == 0 || *p == 0xff)) {                               // normally a LIST ends at the break so this is a safety precaution
        if(*p == T_NEWLINE) {
			p = llist((unsigned char *)b, p);                                        // otherwise expand the line
            if(!(b[0]=='\'' && b[1]=='#')){
				pp = b;
				if(Option.continuation){
					format_string(pp,Option.Width);
					while(*pp) {
						if(*pp=='\n'){
							ListNewLine(&ListCnt, all);
							pp++;
							continue;
						}
						MMputchar(*pp++,0);
					}
				} else {
					while(*pp) {
						if(MMCharPos > Option.Width) ListNewLine(&ListCnt, all);
						MMputchar(*pp++,0);
					}
				}
				fflush(stdout);
				ListNewLine(&ListCnt, all);
				if(p[0] == 0 && p[1] == 0) break;                       // end of the listing ?
			}
		}
	}
}


void MIPS16 do_run(unsigned char *cmdline, bool CMM2mode) {
    // RUN [ filename$ ] [, cmd_args$ ]
    unsigned char *filename = (unsigned char *)"", *cmd_args = (unsigned char *)"";
	unsigned char *cmdbuf=GetMemory(256);
	memcpy(cmdbuf,cmdline,STRINGSIZE);
    getargs(&cmdbuf, 3, (unsigned char *)",");
	    switch (argc) {
        case 0:
            break;
        case 1:
            filename = getCstring(argv[0]);
            break;
        case 2:
            cmd_args = getCstring(argv[1]);
            break;
        default:
            filename = getCstring(argv[0]);
            if(*argv[2])cmd_args = getCstring(argv[2]);
            break;
    }

    // The memory allocated by getCstring() is not preserved across
    // a call to FileLoadProgram() so we need to cache 'filename' and
    // 'cmd_args' on the stack.
    unsigned char buf[MAXSTRLEN + 1];
    if (snprintf((char *)buf, MAXSTRLEN + 1, "\"%s\",%s", filename, cmd_args) > MAXSTRLEN) {
        error("RUN command line too long");
    }
    unsigned char *pcmd_args = buf + strlen((char *)filename) + 3; // *** THW 16/4/23

#ifdef rp2350
    if(CMM2mode){
		if (*filename && !FileLoadCMM2Program((char *)buf,false)) return;
	} else {
#endif
		if (*filename && !FileLoadProgram(buf, false)) return;
#ifdef rp2350
	}
#endif
    ClearRuntime(true);
    PrepareProgram(true);
    if(Option.DISPLAY_CONSOLE && (SPIREAD  || Option.NoScroll)){ClearScreen(gui_bcolour);CurrentX=0;CurrentY=0;}
    // Create a global constant MM.CMDLINE$ containing 'cmd_args'.
//    void *ptr = findvar((unsigned char *)"MM.CMDLINE$", V_FIND | V_DIM_VAR | T_CONST);
    CtoM(pcmd_args);
//    memcpy(cmdlinebuff, pcmd_args, *pcmd_args + 1); // *** THW 16/4/23
	Mstrcpy(cmdlinebuff, pcmd_args);
    IgnorePIN = false;
	if(Option.LIBRARY_FLASH_SIZE == MAX_PROG_SIZE) ExecuteProgram(LibMemory );       // run anything that might be in the library
    if(*ProgMemory != T_NEWLINE) return;                             // no program to run
#ifdef PICOMITEWEB
	cleanserver();
#endif
#ifndef USBKEYBOARD
    if(mouse0==false && Option.MOUSE_CLOCK)initMouse0(0);  //see if there is a mouse to initialise 
#endif
	nextstmt = ProgMemory;
}
/** @endcond */
void MIPS16 cmd_list(void) {
	unsigned char *p;
	int i,j,k,m,step;
    if((p = checkstring(cmdline, (unsigned char *)"ALL"))) {
        if(!(*p == 0 || *p == '\'')) {
        	if(Option.DISPLAY_CONSOLE && (SPIREAD  || Option.NoScroll)){ClearScreen(gui_bcolour);CurrentX=0;CurrentY=0;}
        	getargs(&p,1,(unsigned char *)",");
        	char *buff=GetTempMemory(STRINGSIZE);
        	strcpy(buff,(char *)getCstring(argv[0]));
    		if(strchr(buff, '.') == NULL) strcat(buff, ".bas");
            ListFile(buff, true);
        } else {
        	if(Option.DISPLAY_CONSOLE && (SPIREAD  || Option.NoScroll)){ClearScreen(gui_bcolour);CurrentX=0;CurrentY=0;}
        	ListProgram(ProgMemory, true);
        	checkend(p);
        }
	} else if((p = checkstring(cmdline, (unsigned char *)"VARIABLES"))) {
		int count=0;
		int64_t *dest=NULL;
		char *buff=NULL;
		int j=0;
		getargs(&p,1,(unsigned char *)",");
		if(argc){
			j=(parseintegerarray(argv[0],&dest,1,1,NULL,true)-1)*8;
			dest[0] = 0;
		}
		for(int i=0;i<MAXVARS;i++){
			if(g_vartbl[i].type & (T_INT|T_STR|T_NBR)){
				count++;
			}
		}
		if(!count)return;
		char** c=GetTempMemory(count*sizeof(*c)+count*(MAXVARLEN+30));
		for(int i=0,j=0;i<MAXVARS;i++){
			char out[MAXVARLEN+30];
			if(g_vartbl[i].type & (T_INT|T_STR|T_NBR)){
				if(g_vartbl[i].level==0)strcpy(out,"DIM ");
				else strcpy(out,"LOCAL ");
				if(!(g_vartbl[i].type & T_EXPLICIT)){
					if(g_vartbl[i].type & T_INT){
						if(!(g_vartbl[i].type & T_EXPLICIT))strcat(out,"INTEGER ");
					}
					if(g_vartbl[i].type & T_STR){
						if(!(g_vartbl[i].type & T_EXPLICIT))strcat(out,"STRING ");
					}
					if(g_vartbl[i].type & T_NBR){
						if(!(g_vartbl[i].type & T_EXPLICIT))strcat(out,"FLOAT ");
					}
				}
				strcat(out,(char *)g_vartbl[i].name);
				if(g_vartbl[i].type & T_INT){
					if(g_vartbl[i].type & T_EXPLICIT)strcat(out,"%");
				}
				if(g_vartbl[i].type & T_STR){
					if(g_vartbl[i].type & T_EXPLICIT)strcat(out,"$");
				}
				if(g_vartbl[i].type & T_NBR){
					if(g_vartbl[i].type & T_EXPLICIT)strcat(out,"!");
				}
				if(g_vartbl[i].dims[0]>0){
					strcat(out,"(");
					for(int k=0;k<MAXDIM;k++){
						if(g_vartbl[i].dims[k]>0){
							char s[20];
							IntToStr(s, (int64_t)g_vartbl[i].dims[k], 10);
							strcat(out,s)					;
						}
						if(k<MAXDIM-1 && g_vartbl[i].dims[k+1]>0)strcat(out,",");
					}
					strcat(out,")");
				}
				c[j]= (char *)((int)c + sizeof(char *) * count + j*(MAXVARLEN+30));
				strcpy(c[j],out);
				j++;
			}
		}
		sortStrings(c,count);
    	int ListCnt = 2;
		if(dest==NULL){
			for(int i=0;i<count;i++){
				MMPrintString(c[i]);
				if(Option.DISPLAY_CONSOLE)ListNewLine(&ListCnt, 0);
				else MMPrintString("\r\n");
			}
		} else {
			int ol=0;
			buff=(char *)&dest[1];
			for(int i=0;i<count;i++){
				if(ol+strlen(c[i])+2 > j)error("Destination array too small");
				else ol+=strlen(c[i])+2;
				strcat(buff,c[i]);
				strcat(buff,"\r\n");
			}
			dest[0]=ol;
		}
		
   	} else if((p = checkstring(cmdline, (unsigned char *)"PINS"))) {
#if defined(PICOMITEWEB) && defined(rp2350)
		if(!rp2350a)error("Incompatible board, RP2350A only" );
#endif
		CallExecuteProgram((char *)pinlist);
		return;
   	} else if((p = checkstring(cmdline, (unsigned char *)"SYSTEM I2C"))) {
		if(I2C0locked || I2C1locked)CallExecuteProgram((char *)i2clist);
		else error("System I2c not defined");
		return;
   	} else if((p = checkstring(cmdline, (unsigned char *)"COMMANDS"))) {
    	int ListCnt = 2;
    	step=Option.DISPLAY_CONSOLE ? HRes/gui_font_width/20 : 5;
        if(Option.DISPLAY_CONSOLE && (SPIREAD  || Option.NoScroll)){ClearScreen(gui_bcolour);CurrentX=0;CurrentY=0;}
    	m=0;
		int x=0;
		char** c=GetTempMemory((CommandTableSize+x)*sizeof(*c)+(CommandTableSize+x)*18);
		for(i=0;i<CommandTableSize+x;i++){
				c[m]= (char *)((int)c + sizeof(char *) * (CommandTableSize+x) + m*18);
				if(m<CommandTableSize)strcpy(c[m],(char *)commandtbl[i].name);
				if(*c[m]=='_' && c[m][1]!='(')*c[m]='.';
    			m++;
		}
    	sortStrings(c,m);
    	for(i=1;i<m;i+=step){
    		for(k=0;k<step;k++){
        		if(i+k<m){
        			MMPrintString(c[i+k]);
        			if(k!=(step-1))for(j=strlen(c[i+k]);j<15;j++)MMputchar(' ',1);
        		}
    		}
			if(Option.DISPLAY_CONSOLE)ListNewLine(&ListCnt, 0);
    		else MMPrintString("\r\n");
    	}
		MMPrintString("Total of ");PInt(m-1);MMPrintString(" commands\r\n");
    } else if((p = checkstring(cmdline, (unsigned char *)"FUNCTIONS"))) {
    	m=0;
    	int ListCnt = 2;
    	step=Option.DISPLAY_CONSOLE ? HRes/gui_font_width/20 : 5;
        if(Option.DISPLAY_CONSOLE && (SPIREAD  || Option.NoScroll)){ClearScreen(gui_bcolour);CurrentX=0;CurrentY=0;}
		int x=3+MMEND;
		char** c=GetTempMemory((TokenTableSize+x)*sizeof(*c)+(TokenTableSize+x)*20);
		for(i=0;i<TokenTableSize+x;i++){
				c[m]= (char *)((int)c + sizeof(char *) * (TokenTableSize+x) + m*20);
				if(m<TokenTableSize)strcpy(c[m],(char *)tokentbl[i].name);
	   			else if(m<TokenTableSize+MMEND && m>=TokenTableSize)strcpy(c[m],overlaid_functions[i-TokenTableSize]);
    			else if(m==TokenTableSize+MMEND)strcpy(c[m],"=<");
    			else if(m==TokenTableSize+MMEND+1)strcpy(c[m],"=>");
    			else strcpy(c[m],"MM.Info$(");
				m++;
		}
    	sortStrings(c,m);
    	for(i=1;i<m-1;i+=step){
    		for(k=0;k<step;k++){
        		if(i+k<m-1){
        			MMPrintString(c[i+k]);
        			if(k!=(step-1))for(j=strlen(c[i+k]);j<15;j++)MMputchar(' ',1);
        		}
    		}
			if(Option.DISPLAY_CONSOLE)ListNewLine(&ListCnt, 0);
    		else MMPrintString("\r\n");
    	}
		MMPrintString("Total of ");PInt(m-1);MMPrintString(" functions and operators\r\n");
    } else {
        if(!(*cmdline == 0 || *cmdline == '\'')) {
        	getargs(&cmdline,1,(unsigned char *)",");
        	if(Option.DISPLAY_CONSOLE && (SPIREAD  || Option.NoScroll)){ClearScreen(gui_bcolour);CurrentX=0;CurrentY=0;}
        	char *buff=GetTempMemory(STRINGSIZE);
        	strcpy(buff,(char *)getCstring(argv[0]));
    		if(strchr(buff, '.') == NULL) {
				if(!ExistsFile(buff))strcat(buff, ".bas");
			}
			ListFile(buff, false);
        } else {
        	if(Option.DISPLAY_CONSOLE && (SPIREAD  || Option.NoScroll)){ClearScreen(gui_bcolour);CurrentX=0;CurrentY=0;}
			ListProgram(ProgMemory, false);
			checkend(cmdline);
		}
    }
}
#include <stdio.h>
#include <string.h>

int printWrappedText(const char *text, int screenWidth, int listcnt, int all) {
    int length = strlen(text);
    int start = 0; // Start index of the current line
	char buff[STRINGSIZE];
    while (start < length) {
        int end = start + screenWidth; // Calculate the end index for the current line
        if (end >= length) {
            // If end is beyond the text length, just print the remaining text
			memset(buff,0,STRINGSIZE);
            sprintf(buff,"%s", text + start);
			MMPrintString(buff);
			ListNewLine(&listcnt, all);
            break;
        }

        // Find the last space within the current screen width
        int lastSpace = -1;
        for (int i = start; i < end; i++) {
            if (text[i] == ' ') {
                lastSpace = i;
            }
        }

        if (lastSpace != -1) {
            // If a space is found, break at the space
			memset(buff,0,STRINGSIZE);
            sprintf(buff, "%.*s", lastSpace - start, text + start);
			MMPrintString(buff);
			ListNewLine(&listcnt, all);
            start = lastSpace + 1; // Skip the space
        } else {
            // If no space is found, truncate at screen width
			memset(buff,0,STRINGSIZE);
            sprintf(buff,"%.*s", screenWidth, text + start);
			MMPrintString(buff);
			ListNewLine(&listcnt, all);
            start += screenWidth;
        }
    }
	return listcnt;
}

void cmd_help(void){
	getargs(&cmdline,1,(unsigned char *)",");
	if(!ExistsFile("B:/help.txt"))error("B:/help.txt not found");
	if(!argc){
		MMPrintString("Enter help and the name of the command or function\r\nUse * for multicharacter wildcard or ? for single character wildcard\r\n");
	} else {
		int fnbr = FindFreeFileNbr();
		char *buff=GetTempMemory(STRINGSIZE);
		BasicFileOpen("B:/help.txt",fnbr, FA_READ);
		int ListCnt = CurrentY/(FontTable[gui_font >> 4][1] * (gui_font & 0b1111)) + 2;
		char *p=(char *)getCstring(argv[0]);
		bool end=false;
		while (!FileEOF(fnbr))            { // while waiting for the end of file
			memset(buff,0,STRINGSIZE);
			char *in=buff;
			while(1){
				if(FileEOF(fnbr)){end=true;break;}
				char c = FileGetChar(fnbr);
				if(c=='\n')break;
				if(c=='\r')continue;
				*in++=c;
			}
			if(end)break;
			skipspace(p);
			if(buff[0]=='~'){
				if(pattern_matching(p,&buff[1],0,0)){
					while(1){ //loop through all lines for the command
						memset(buff,0,STRINGSIZE);
						char *in=buff;
							while(1){ //get this line
							if(FileEOF(fnbr)){end=true;break;}
							char c = FileGetChar(fnbr);
							if(c=='\n')break;
							if(c=='\r')continue;
							*in++=c;
						}
						if(end)break;
						if(buff[0]=='~'){ //now we need to rewind the file to check this line
							ListNewLine(&ListCnt, false);
							lfs_file_seek(&lfs, FileTable[fnbr].lfsptr, -(strlen(buff)+2), LFS_SEEK_CUR);
							break;
						} else {
							ListCnt=printWrappedText(buff,Option.Width-1,ListCnt,false);
						}
					}
				}
			} 
		}
	FileClose(fnbr);
	}
}
void MIPS16 cmd_run(void){
	do_run(cmdline,false);
}

void MIPS16 cmd_RunCMM2(void){
	do_run(cmdline,true);
}

void  MIPS16 cmd_continue(void) {
    if(*cmdline == tokenFOR) {
        if(g_forindex == 0) error("No FOR loop is in effect");
        nextstmt = g_forstack[g_forindex - 1].nextptr;
        return;
    }
    if(checkstring(cmdline, (unsigned char *)"DO")) {
        if(g_doindex == 0) error("No DO loop is in effect");
        nextstmt = g_dostack[g_doindex - 1].loopptr;
        return;
    }
    // must be a normal CONTINUE
	checkend(cmdline);
	if(CurrentLinePtr) error("Invalid in a program");
	if(ContinuePoint == NULL) error("Cannot continue");
//    IgnorePIN = false;
	nextstmt = ContinuePoint;
}

void MIPS16 cmd_new(void) {
	closeframebuffer('A');
	checkend(cmdline);
	ClearProgram(true);
	FlashLoad=0;
	uSec(250000);
    FlashWriteInit(PROGRAM_FLASH);
    flash_range_erase(realflashpointer, MAX_PROG_SIZE);
    FlashWriteByte(0); FlashWriteByte(0); FlashWriteByte(0);    // terminate the program in flash
    FlashWriteClose();
#ifdef PICOMITEVGA
	int mode = DISPLAY_TYPE-SCREENMODE1+1;
	setmode(mode, true);
#endif
    memset(inpbuf,0,STRINGSIZE);
	longjmp(mark, 1);							                    // jump back to the input prompt
}


void MIPS16 cmd_erase(void) {
	int i,j,k, len;
	char p[MAXVARLEN + 1], *s, *x;

	getargs(&cmdline, (MAX_ARG_COUNT * 2) - 1, (unsigned char *)",");				// getargs macro must be the first executable stmt in a block
	if((argc & 0x01) == 0) error("Argument count");
	for(i = 0; i < argc; i += 2) {
		strcpy((char *)p, (char *)argv[i]);
		if(*argv[i] & 0x80)error("You can't erase an in-built function");
        while(!isnamechar(p[strlen(p) - 1])) p[strlen(p) - 1] = 0;
		makeupper((unsigned char *)p);  
		for(j = MAXVARS/2; j < MAXVARS; j++) {
            s = p;  x = (char *)g_vartbl[j].name; len = strlen(p);
            while(len > 0 && *s == *x) {                            // compare the variable to the name that we have
                len--; s++; x++;
            }
            if(!(len == 0 && (*x == 0 || strlen(p) == MAXVARLEN))) continue;
    		// found the variable
			if(((g_vartbl[j].type & T_STR) || g_vartbl[j].dims[0] != 0) && !(g_vartbl[j].type & T_PTR)) {
				FreeMemory(g_vartbl[j].val.s);                        // free any memory (if allocated)
				g_vartbl[j].val.s=NULL;
			}
			k=j+1;
			if(k==MAXVARS)k=MAXVARS/2;
			if(g_vartbl[k].type){
				g_vartbl[j].name[0]='~';
				g_vartbl[j].type=T_BLOCKED;
			} else {
				g_vartbl[j].name[0]=0;
				g_vartbl[j].type=T_NOTYPE;
			}
			g_vartbl[j].dims[0] = 0;                                    // and again
			g_vartbl[j].level = 0;
			g_Globalvarcnt--;
			break;
		}
		if(j == MAXVARS) error("Cannot find $", p);
	}
}
void MIPS16 cmd_clear(void) {
	checkend(cmdline);
	if(g_LocalIndex)error("Invalid in a subroutine");
	ClearVars(0,true);
}


void cmd_goto(void) {
	if(isnamestart(*cmdline))
		nextstmt = findlabel(cmdline);								// must be a label
	else
		nextstmt = findline(getinteger(cmdline), true);				// try for a line number
	CurrentLinePtr = nextstmt;
}



#ifdef PICOMITEWEB
#ifdef rp2350
void MIPS16 __not_in_flash_func(cmd_if)(void) {
#else
void cmd_if(void) {
#endif
#else
#ifndef rp2350
#ifdef PICOMITEVGA
void cmd_if(void) {
#else
void MIPS16 __not_in_flash_func(cmd_if)(void) {
#endif
#else
void MIPS16 __not_in_flash_func(cmd_if)(void) {
#endif
#endif
 	int r, i, testgoto, testelseif;
	unsigned char ss[3];														// this will be used to split up the argument line
	unsigned char *p, *tp;
	unsigned char *rp = NULL;

	ss[0] = tokenTHEN;
	ss[1] = tokenELSE;
	ss[2] = 0;

	testgoto = false;
	testelseif = false;

retest_an_if:
	{																// start a new block
		getargs(&cmdline, 20, ss);									// getargs macro must be the first executable stmt in a block

		if(testelseif && argc > 2) error("Unexpected text");

		// if there is no THEN token retry the test with a GOTO.  If that fails flag an error
		if(argc < 2 || *argv[1] != ss[0]) {
			if(testgoto) error("IF without THEN");
			ss[0] = tokenGOTO;
			testgoto = true;
			goto retest_an_if;
		}


		// allow for IF statements embedded inside this IF
		if (argc >= 3 && commandtbl_decode(argv[2]) == cmdIF) argc = 3;  // this is IF xx=yy THEN IF ... so we want to evaluate only the first 3
		if (argc >= 5 && commandtbl_decode(argv[4]) == cmdIF) argc = 5;  // this is IF xx=yy THEN cmd ELSE IF ... so we want to evaluate only the first 5

		if(argc == 4 || (argc == 5 && *argv[3] != ss[1])) error("Syntax");

		r = (getnumber(argv[0]) != 0);								// evaluate the expression controlling the if statement

		if(r) {
			// the test returned TRUE
			// first check if it is a multiline IF (ie, only 2 args)
			if(argc == 2) {
				// if multiline do nothing, control will fall through to the next line (which is what we want to execute next)
				;
			}
			else {
				// This is a standard single line IF statement
				// Because the test was TRUE we are just interested in the THEN cmd stage.
				if(*argv[1] == tokenGOTO) {
					cmdline = argv[2];
					cmd_goto();
					return;
				} else if(isdigit(*argv[2])) {
					nextstmt = findline(getinteger(argv[2]), true);
				} else {
					if(argc == 5) {
						// this is a full IF THEN ELSE and the statement we want to execute is between the THEN & ELSE
						// this is handled by a special routine
						execute_one_command(argv[2]);
					} else {
						// easy - there is no ELSE clause so just point the next statement pointer to the byte after the THEN token
						for(p = cmdline; *p && *p != ss[0]; p++);	// search for the token
						nextstmt = p + 1;							// and point to the byte after
					}
				}
			}
		} else {
			// the test returned FALSE so we are just interested in the ELSE stage (if present)
			// first check if it is a multiline IF (ie, only 2 args)
			if(argc == 2) {
				// search for the next ELSE, or ENDIF and pass control to the following line
				// if an ELSEIF is found re execute this function to evaluate the condition following the ELSEIF
				i = 1; p = nextstmt;
				while(1) {
                    p = GetNextCommand(p, &rp, (unsigned char *)"No matching ENDIF");
        			CommandToken tkn=commandtbl_decode(p);
					if(tkn == cmdtoken) {
						// found a nested IF command, we now need to determine if it is a single or multiline IF
						// search for a THEN, then check if only white space follows.  If so, it is multiline.
						tp = p + sizeof(CommandToken);
						while(*tp && *tp != ss[0]) tp++;
						if(*tp) tp++;								// step over the THEN
						skipspace(tp);
						if(*tp == 0 || *tp == '\'')					// yes, only whitespace follows
							i++;									// count it as a nested IF
						else										// no, it is a single line IF
							skipelement(p);							// skip to the end so that we avoid an ELSE
						continue;
					}

					if(tkn == cmdELSE && i == 1) {
						// found an ELSE at the same level as this IF.  Step over it and continue with the statement after it
						skipelement(p);
						nextstmt = p;
						break;
					}

					if((tkn == cmdELSEIF || tkn==cmdELSE_IF) && i == 1) {
						// we have found an ELSEIF statement at the same level as our IF statement
						// setup the environment to make this function evaluate the test following ELSEIF and jump back
						// to the start of the function.  This is not very clean (it uses the dreaded goto for a start) but it works
						p+=sizeof(CommandToken);                                        // step over the token
						skipspace(p);
						CurrentLinePtr = rp;
						if(*p == 0) error("Syntax");        // there must be a test after the elseif
						cmdline = p;
						skipelement(p);
						nextstmt = p;
						testgoto = false;
						testelseif = true;
						goto retest_an_if;
					}

					if(tkn == cmdENDIF || tkn==cmdEND_IF) i--;						// found an ENDIF so decrement our nested counter
					if(i == 0) {
						// found our matching ENDIF stmt.  Step over it and continue with the statement after it
						skipelement(p);
						nextstmt = p;
						break;
					}
				}
			}
			else {
				// this must be a single line IF statement
				// check if there is an ELSE on the same line
				if(argc == 5) {
					// there is an ELSE command
					if(isdigit(*argv[4]))
						// and it is just a number, so get it and find the line
						nextstmt = findline(getinteger(argv[4]), true);
					else {
/*						// there is a statement after the ELSE clause  so just point to it (the byte after the ELSE token)
						for(p = cmdline; *p && *p != ss[1]; p++);	// search for the token
						nextstmt = p + 1;							// and point to the byte after
*/
		                // IF <condition> THEN <statement1> ELSE <statement2>
						// Find and read the THEN function token.
						for(p = cmdline; *p && *p != ss[0]; p++){}
						// Skip the command that <statement1> must start with.
						p++;
						skipspace(p);
						p += sizeof(CommandToken);
						// Find and read the ELSE function token.
						for(; *p && *p != ss[1]; p++);
		                nextstmt = p+1;  // The statement after the ELSE token.
					}
				} else {
					// no ELSE on a single line IF statement, so just continue with the next statement
					skipline(cmdline);
					nextstmt = cmdline;
				}
			}
		}
	}
}



#ifdef PICOMITEWEB
#ifdef rp2350
void MIPS16 __not_in_flash_func(cmd_else)(void) {
#else
void cmd_else(void) {
#endif
#else
#ifndef rp2350
#ifdef PICOMITEVGA
void cmd_else(void) {
#else
void MIPS16 __not_in_flash_func(cmd_else)(void) {
#endif
#else
void MIPS16 __not_in_flash_func(cmd_else)(void) {
#endif
#endif
	int i;
	unsigned char *p, *tp;

	// search for the next ENDIF and pass control to the following line
	i = 1; p = nextstmt;

	if(cmdtoken ==  cmdELSE) checkend(cmdline);

	while(1) {
        p = GetNextCommand(p, NULL, (unsigned char *)"No matching ENDIF");
        CommandToken tkn=commandtbl_decode(p);
		if(tkn == cmdIF) { 
			// found a nested IF command, we now need to determine if it is a single or multiline IF
			// search for a THEN, then check if only white space follows.  If so, it is multiline.
			tp = p + sizeof(CommandToken);
			while(*tp && *tp != tokenTHEN) tp++;
			if(*tp) tp++;											// step over the THEN
			skipspace(tp);
			if(*tp == 0 || *tp == '\'')								// yes, only whitespace follows
				i++;												// count it as a nested IF
		}
		if(tkn == cmdENDIF || tkn==cmdEND_IF) i--;				    // found an ENDIF so decrement our nested counter
		if(i == 0) break;											// found our matching ENDIF stmt
	}
	// found a matching ENDIF.  Step over it and continue with the statement after it
	skipelement(p);
	nextstmt = p;
}



void do_end(bool ecmd) {
#ifdef PICOMITE
    if(mergerunning){
        multicore_fifo_push_blocking(0xFF);
        mergerunning=false;
        busy_wait_ms(100);
    }
#endif
if(Option.SerialConsole)while(ConsoleTxBufHead!=ConsoleTxBufTail)routinechecks();
	fflush(stdout);
	if(ecmd){
		getargs(&cmdline,1,(unsigned char *)",");
		if(argc==1){
			if(FindSubFun((unsigned char *)"MM.END", 0) >= 0 && checkstring(argv[0],(unsigned char *)"NOEND")==NULL) {
				ExecuteProgram((unsigned char *)"MM.END\0");
				if(Option.SerialConsole)while(ConsoleTxBufHead!=ConsoleTxBufTail)routinechecks();
				fflush(stdout);
				memset(inpbuf,0,STRINGSIZE);
			} else {
				unsigned char *cmd_args = (unsigned char *)"";
				cmd_args = getCstring(argv[0]);
				void *ptr = findvar((unsigned char *)"MM.ENDLINE$", T_STR| V_NOFIND_NULL);  
				if(ptr==NULL)ptr = findvar((unsigned char *)"MM.ENDLINE$", V_FIND |V_DIM_VAR);
				strcpy(ptr, (char *)cmd_args ); // *** THW 16/4/23
				CtoM(ptr);
			}
		} else if(FindSubFun((unsigned char *)"MM.END", 0) >= 0) {
			ExecuteProgram((unsigned char *)"MM.END\0");
			if(Option.SerialConsole)while(ConsoleTxBufHead!=ConsoleTxBufTail)routinechecks();
			fflush(stdout);
			memset(inpbuf,0,STRINGSIZE);
		}
	}
    if(!(MMerrno == 16))hw_clear_bits(&watchdog_hw->ctrl, WATCHDOG_CTRL_ENABLE_BITS);
    irq_set_enabled(DMA_IRQ_1, false);
    dma_hw->abort = ((1u << dma_rx_chan2) | (1u << dma_rx_chan));
    if(dma_channel_is_busy(dma_rx_chan))dma_channel_abort(dma_rx_chan);
    if(dma_channel_is_busy(dma_rx_chan2))dma_channel_abort(dma_rx_chan2);
//    dma_channel_cleanup(dma_rx_chan);
//    dma_channel_cleanup(dma_rx_chan2);
    dma_hw->abort = ((1u << dma_tx_chan2) | (1u << dma_tx_chan));
    if(dma_channel_is_busy(dma_tx_chan))dma_channel_abort(dma_tx_chan);
    if(dma_channel_is_busy(dma_tx_chan2))dma_channel_abort(dma_tx_chan2);
//    dma_channel_cleanup(dma_tx_chan);
//    dma_channel_cleanup(dma_tx_chan2);
    dma_hw->abort = ((1u << ADC_dma_chan2) | (1u << ADC_dma_chan));
    if(dma_channel_is_busy(ADC_dma_chan))dma_channel_abort(ADC_dma_chan);
    if(dma_channel_is_busy(ADC_dma_chan2))dma_channel_abort(ADC_dma_chan2);
//    dma_channel_cleanup(ADC_dma_chan);
//    dma_channel_cleanup(ADC_dma_chan2);
	for(int i=0; i< NBRSETTICKS;i++){
		TickPeriod[i]=0;
		TickTimer[i]=0;
		TickInt[i]=NULL;
		TickActive[i]=0;
	}
	InterruptUsed=0;       
    InterruptReturn = NULL ; 
    memset(inpbuf,0,STRINGSIZE);
	CloseAudio(1);
	CloseAllFiles();
    ADCDualBuffering=0;
	WatchdogSet = false;
    WDTimer = 0;
	hw_clear_bits(&watchdog_hw->ctrl, WATCHDOG_CTRL_ENABLE_BITS);
	_excep_code=0;
	dmarunning = false;
    multi=false;
	WAVInterrupt = NULL;
	WAVcomplete = 0;
	if(g_myrand)FreeMemory((void *)g_myrand);
	g_myrand=NULL;
	OptionConsole=3;
#ifdef PICOMITEVGA
	int mode = DISPLAY_TYPE-SCREENMODE1+1;
	setmode(mode,false);
#endif
	SSPrintString("\033[?25h"); //in case application has turned the cursor off
	SSPrintString("\033[97;40m");
#ifdef PICOMITEWEB
	close_tcpclient();
#endif
#ifndef USBKEYBOARD
    if(mouse0==false && Option.MOUSE_CLOCK)initMouse0(0);  //see if there is a mouse to initialise 
#endif
}
void cmd_end(void) {
	do_end(true);
	longjmp(mark, 1);												// jump back to the input prompt
}
extern unsigned int mmap[HEAP_MEMORY_SIZE/ PAGESIZE / PAGESPERWORD];
extern unsigned int psmap[7*1024*1024/ PAGESIZE / PAGESPERWORD];
extern struct s_hash g_hashlist[MAXVARS/2];
extern int g_hashlistpointer;
extern short g_StrTmpIndex;
extern bool g_TempMemoryIsChanged;
extern volatile char *g_StrTmp[MAXTEMPSTRINGS];                                       // used to track temporary string space on the heap
extern volatile char g_StrTmpLocalIndex[MAXTEMPSTRINGS];                              // used to track the g_LocalIndex for each temporary string space on the heap
void SaveContext(void){
	CloseAudio(1);
	#if defined(rp2350) && !defined(PICOMITEWEB)
	if(PSRAMsize){
		ClearTempMemory();
		uint8_t *p=(uint8_t *)PSRAMbase+PSRAMsize;
		memcpy(p,  &g_StrTmpIndex, sizeof(g_StrTmpIndex));
		p+=sizeof(g_StrTmpIndex);
		memcpy(p,  &g_TempMemoryIsChanged, sizeof(g_TempMemoryIsChanged));
		p+=sizeof(g_TempMemoryIsChanged);
		memcpy(p,  (void *)g_StrTmp, sizeof(g_StrTmp));
		p+=sizeof(g_StrTmp);
		memcpy(p,  (void *)g_StrTmpLocalIndex, sizeof(g_StrTmpLocalIndex));
		p+=sizeof(g_StrTmpLocalIndex);
		memcpy(p,  &g_LocalIndex, sizeof(g_LocalIndex));
		p+=sizeof(g_LocalIndex);
		memcpy(p,  &g_OptionBase, sizeof(g_OptionBase));
		p+=sizeof(g_OptionBase);
		memcpy(p,  &g_DimUsed, sizeof(g_DimUsed));
		p+=sizeof(g_DimUsed);
		memcpy(p,  &g_varcnt, sizeof(g_varcnt));
		p+=sizeof(g_varcnt);
		memcpy(p,  &g_Globalvarcnt, sizeof(g_Globalvarcnt));
		p+=sizeof(g_Globalvarcnt);
		memcpy(p,  &g_Localvarcnt, sizeof(g_Localvarcnt));
		p+=sizeof(g_Localvarcnt);
		memcpy(p,  &g_hashlistpointer, sizeof(g_hashlistpointer));
		p+=sizeof(g_hashlistpointer);
		memcpy(p,  &g_forindex, sizeof(g_forindex));
		p+=sizeof(g_forindex);
		memcpy(p,  &g_doindex, sizeof(g_doindex));
		p+=sizeof(g_doindex);
		memcpy(p,  g_forstack, sizeof(struct s_forstack)*MAXFORLOOPS);
		p+=sizeof(struct s_forstack)*MAXFORLOOPS;
		memcpy(p,  g_dostack, sizeof(struct s_dostack)*MAXDOLOOPS);
		p+=sizeof(struct s_dostack)*MAXDOLOOPS;
		memcpy(p,  g_vartbl, sizeof(struct s_vartbl)*MAXVARS);
		p+=sizeof(struct s_vartbl)*MAXVARS;
		memcpy(p,  g_hashlist, sizeof(struct s_hash)*MAXVARS/2);
		p+=sizeof(struct s_hash)*MAXVARS/2;
		memcpy(p,  MMHeap, heap_memory_size+256);
		p+=heap_memory_size+256;
		memcpy(p,  mmap, sizeof(mmap));
		p+=sizeof(mmap);
		memcpy(p, psmap, sizeof(psmap));
		p+=sizeof(psmap);
	} else {
#endif
		lfs_file_t lfs_file;
		if(ExistsFile("A:/.vars")){
			lfs_remove(&lfs, "/.vars");
		}
		int sizeneeded= sizeof(g_StrTmpIndex)+ sizeof(g_TempMemoryIsChanged)+sizeof(g_StrTmp)+sizeof(g_StrTmpLocalIndex)+sizeof(g_Localvarcnt)+
		sizeof(g_LocalIndex)+sizeof(g_OptionBase)+sizeof(g_DimUsed)+sizeof(g_varcnt)+sizeof(g_Globalvarcnt)+
		sizeof(g_hashlistpointer)+sizeof(g_forindex)+sizeof(g_doindex)+sizeof(struct s_forstack)*MAXFORLOOPS+sizeof(struct s_dostack)*MAXDOLOOPS+
		sizeof(struct s_vartbl)*MAXVARS+sizeof(struct s_hash)*MAXVARS/2+heap_memory_size+256+sizeof(mmap);
		if(sizeneeded>=Option.FlashSize-(Option.modbuff ? 1024*Option.modbuffsize : 0)-RoundUpK4(TOP_OF_SYSTEM_FLASH)-lfs_fs_size(&lfs)*4096)error("Not enough free space on A: drive: % needed",sizeneeded);
		lfs_file_open(&lfs, &lfs_file, ".vars", LFS_O_RDWR | LFS_O_CREAT);;
		int dt=get_fattime();
		ClearTempMemory();
		lfs_setattr(&lfs, ".vars", 'A', &dt,   4);
		lfs_file_write(&lfs, &lfs_file, &g_StrTmpIndex, sizeof(g_StrTmpIndex));
		lfs_file_write(&lfs, &lfs_file, &g_TempMemoryIsChanged, sizeof(g_TempMemoryIsChanged));
		lfs_file_write(&lfs, &lfs_file, (void *)g_StrTmp, sizeof(g_StrTmp));
		lfs_file_write(&lfs, &lfs_file, (void *)g_StrTmpLocalIndex, sizeof(g_StrTmpLocalIndex));
		lfs_file_write(&lfs, &lfs_file, &g_LocalIndex, sizeof(g_LocalIndex));
		lfs_file_write(&lfs, &lfs_file, &g_OptionBase, sizeof(g_OptionBase));
		lfs_file_write(&lfs, &lfs_file, &g_DimUsed, sizeof(g_DimUsed));
		lfs_file_write(&lfs, &lfs_file, &g_varcnt, sizeof(g_varcnt));
		lfs_file_write(&lfs, &lfs_file, &g_Globalvarcnt, sizeof(g_Globalvarcnt));
		lfs_file_write(&lfs, &lfs_file, &g_Localvarcnt, sizeof(g_Globalvarcnt));
		lfs_file_write(&lfs, &lfs_file, &g_hashlistpointer, sizeof(g_hashlistpointer));
		lfs_file_write(&lfs, &lfs_file, &g_forindex, sizeof(g_forindex));
		lfs_file_write(&lfs, &lfs_file, &g_doindex, sizeof(g_doindex));
		lfs_file_write(&lfs, &lfs_file, g_forstack, sizeof(struct s_forstack)*MAXFORLOOPS);
		lfs_file_write(&lfs, &lfs_file, g_dostack, sizeof(struct s_dostack)*MAXDOLOOPS);
		lfs_file_write(&lfs, &lfs_file, g_vartbl, sizeof(struct s_vartbl)*MAXVARS);
		lfs_file_write(&lfs, &lfs_file, g_hashlist, sizeof(struct s_hash)*MAXVARS/2);
		lfs_file_write(&lfs, &lfs_file, MMHeap, heap_memory_size+256);
		lfs_file_write(&lfs, &lfs_file, mmap, sizeof(mmap));
		lfs_file_close(&lfs, &lfs_file);
#if defined(rp2350) && !defined(PICOMITEWEB)
	}
#endif
}
void RestoreContext(bool keep){
	CloseAudio(1);
	#if defined(rp2350) && !defined(PICOMITEWEB)
	if(PSRAMsize){
		uint8_t *p=(uint8_t *)PSRAMbase+PSRAMsize;
		memcpy(&g_StrTmpIndex, p, sizeof(g_StrTmpIndex));
		p+=sizeof(g_StrTmpIndex);
		memcpy(&g_TempMemoryIsChanged, p, sizeof(g_TempMemoryIsChanged));
		p+=sizeof(g_TempMemoryIsChanged);
		memcpy((void *)g_StrTmp, p, sizeof(g_StrTmp));
		p+=sizeof(g_StrTmp);
		memcpy((void *)g_StrTmpLocalIndex, p, sizeof(g_StrTmpLocalIndex));
		p+=sizeof(g_StrTmpLocalIndex);
		memcpy(&g_LocalIndex, p, sizeof(g_LocalIndex));
		p+=sizeof(g_LocalIndex);
		memcpy(&g_OptionBase, p, sizeof(g_OptionBase));
		p+=sizeof(g_OptionBase);
		memcpy(&g_DimUsed, p, sizeof(g_DimUsed));
		p+=sizeof(g_DimUsed);
		memcpy(&g_varcnt, p, sizeof(g_varcnt));
		p+=sizeof(g_varcnt);
		memcpy(&g_Globalvarcnt, p, sizeof(g_Globalvarcnt));
		p+=sizeof(g_Globalvarcnt);
		memcpy(&g_Localvarcnt, p, sizeof(g_Localvarcnt));
		p+=sizeof(g_Localvarcnt);
		memcpy(&g_hashlistpointer, p, sizeof(g_hashlistpointer));
		p+=sizeof(g_hashlistpointer);
		memcpy(&g_forindex, p, sizeof(g_forindex));
		p+=sizeof(g_forindex);
		memcpy(&g_doindex, p, sizeof(g_doindex));
		p+=sizeof(g_doindex);
		memcpy(g_forstack, p, sizeof(struct s_forstack)*MAXFORLOOPS);
		p+=sizeof(struct s_forstack)*MAXFORLOOPS;
		memcpy(g_dostack, p, sizeof(struct s_dostack)*MAXDOLOOPS);
		p+=sizeof(struct s_dostack)*MAXDOLOOPS;
		memcpy(g_vartbl, p, sizeof(struct s_vartbl)*MAXVARS);
		p+=sizeof(struct s_vartbl)*MAXVARS;
		memcpy(g_hashlist, p, sizeof(struct s_hash)*MAXVARS/2);
		p+=sizeof(struct s_hash)*MAXVARS/2;
		memcpy(MMHeap, p, heap_memory_size+256);
		p+=heap_memory_size+256;
		memcpy(mmap, p, sizeof(mmap));
		p+=sizeof(mmap);
		memcpy(psmap, p, sizeof(psmap));
		p+=sizeof(psmap);
	} else {
#endif
		lfs_file_t lfs_file;
		if(!ExistsFile("A:/.vars"))error("Internal error");
		lfs_file_open(&lfs, &lfs_file, "/.vars", LFS_O_RDONLY);
		lfs_file_read(&lfs, &lfs_file, &g_StrTmpIndex, sizeof(g_StrTmpIndex));
		lfs_file_read(&lfs, &lfs_file, &g_TempMemoryIsChanged, sizeof(g_TempMemoryIsChanged));
		lfs_file_read(&lfs, &lfs_file, (void *)g_StrTmp, sizeof(g_StrTmp));
		lfs_file_read(&lfs, &lfs_file, (void *)g_StrTmpLocalIndex, sizeof(g_StrTmpLocalIndex));
		lfs_file_read(&lfs, &lfs_file, &g_LocalIndex, sizeof(g_LocalIndex));
		lfs_file_read(&lfs, &lfs_file, &g_OptionBase, sizeof(g_OptionBase));
		lfs_file_read(&lfs, &lfs_file, &g_DimUsed, sizeof(g_DimUsed));
		lfs_file_read(&lfs, &lfs_file, &g_varcnt, sizeof(g_varcnt));
		lfs_file_read(&lfs, &lfs_file, &g_Globalvarcnt, sizeof(g_Globalvarcnt));
		lfs_file_read(&lfs, &lfs_file, &g_Localvarcnt, sizeof(g_Globalvarcnt));
		lfs_file_read(&lfs, &lfs_file, &g_hashlistpointer, sizeof(g_hashlistpointer));
		lfs_file_read(&lfs, &lfs_file, &g_forindex, sizeof(g_forindex));
		lfs_file_read(&lfs, &lfs_file, &g_doindex, sizeof(g_doindex));
		lfs_file_read(&lfs, &lfs_file, g_forstack, sizeof(struct s_forstack)*MAXFORLOOPS);
		lfs_file_read(&lfs, &lfs_file, g_dostack, sizeof(struct s_dostack)*MAXDOLOOPS);
		lfs_file_read(&lfs, &lfs_file, g_vartbl, sizeof(struct s_vartbl)*MAXVARS);
		lfs_file_read(&lfs, &lfs_file, g_hashlist, sizeof(struct s_hash)*MAXVARS/2);
		lfs_file_read(&lfs, &lfs_file, MMHeap, heap_memory_size+256);
		lfs_file_read(&lfs, &lfs_file, mmap, sizeof(mmap));
		lfs_file_close(&lfs, &lfs_file);
		if(!keep)lfs_remove(&lfs, "/.vars");
#if defined(rp2350) && !defined(PICOMITEWEB)
	}
#endif
}
extern void chdir(char *p);
void MIPS16 do_chain(unsigned char *cmdline){
    unsigned char *filename = (unsigned char *)"", *cmd_args = (unsigned char *)"";
	unsigned char *cmdbuf=GetMemory(256);
	memcpy(cmdbuf,cmdline,STRINGSIZE);
    getargs(&cmdbuf, 3, (unsigned char *)",");
	    switch (argc) {
        case 0:
            break;
        case 1:
            filename = getCstring(argv[0]);
            break;
        case 2:
            cmd_args = getCstring(argv[1]);
            break;
        default:
            filename = getCstring(argv[0]);
            if(*argv[2])cmd_args = getCstring(argv[2]);
            break;
    }

    // The memory allocated by getCstring() is not preserved across
    // a call to FileLoadProgram() so we need to cache 'filename' and
    // 'cmd_args' on the stack.
    unsigned char buf[MAXSTRLEN + 1];
    if (snprintf((char *)buf, MAXSTRLEN + 1, "\"%s\",%s", filename, cmd_args) > MAXSTRLEN) {
        error("RUN command line too long");
    }
	FreeMemory(cmdbuf);
    unsigned char *pcmd_args = buf + strlen((char *)filename) + 3; // *** THW 16/4/23
    *cmdline=0;
	do_end(false);
	SaveContext();
	ClearVars(0,false);
	InitHeap(false);
	if (*buf && !FileLoadProgram(buf, true)) return;
    ClearRuntime(false);
    PrepareProgram(true);
	RestoreContext(false);
    if(Option.DISPLAY_CONSOLE && (SPIREAD  || Option.NoScroll)){ClearScreen(gui_bcolour);CurrentX=0;CurrentY=0;}
    // Create a global constant MM.CMDLINE$ containing 'cmd_args'.
//    void *ptr = findvar((unsigned char *)"MM.CMDLINE$", V_NOFIND_ERR);
    CtoM(pcmd_args);
//    memcpy(cmdlinebuff, pcmd_args, *pcmd_args + 1); // *** THW 16/4/23
	Mstrcpy(cmdlinebuff, pcmd_args);
    IgnorePIN = false;
	if(Option.LIBRARY_FLASH_SIZE == MAX_PROG_SIZE) ExecuteProgram(LibMemory );       // run anything that might be in the library
    if(*ProgMemory != T_NEWLINE) return;                             // no program to run
#ifdef PICOMITEWEB
	cleanserver();
#endif
#ifndef USBKEYBOARD
    if(mouse0==false && Option.MOUSE_CLOCK)initMouse0(0);  //see if there is a mouse to initialise 
#endif
	nextstmt = ProgMemory;
}
void cmd_chain(void){
	do_chain(cmdline);
}

void cmd_select(void) {
    int i, type;
    unsigned char *p, *rp = NULL, *SaveCurrentLinePtr;
    void *v;
    MMFLOAT f = 0;
    long long int  i64 = 0;
    unsigned char s[STRINGSIZE];

    // these are the tokens that we will be searching for
    // they are cached the first time this command is called

    type = T_NOTYPE;
    v = DoExpression(cmdline, &type);                               // evaluate the select case value
    type = TypeMask(type);
    if(type & T_NBR) f = *(MMFLOAT *)v;
    if(type & T_INT) i64 = *(long long int  *)v;
    if(type & T_STR) Mstrcpy((unsigned char *)s, (unsigned char *)v);

    // now search through the program looking for a matching CASE statement
    // i tracks the nesting level of any nested SELECT CASE commands
    SaveCurrentLinePtr = CurrentLinePtr;                            // save where we are because we will have to fake CurrentLinePtr to get errors reported correctly
    i = 1; p = nextstmt;
    while(1) {
        p = GetNextCommand(p, &rp, (unsigned char *)"No matching END SELECT");
        CommandToken tkn=commandtbl_decode(p);

        if(tkn == cmdSELECT_CASE) i++;                                  // found a nested SELECT CASE command, increase the nested count and carry on searching
        // is this a CASE stmt at the same level as this SELECT CASE.
        if(tkn == cmdCASE && i == 1) {
            int t;
            MMFLOAT ft, ftt;
            long long int  i64t, i64tt;
            unsigned char *st, *stt;

            CurrentLinePtr = rp;                                    // and report errors at the line we are on
			p++; //step past rest of command token
            // loop through the comparison elements on the CASE line.  Each element is separated by a comma
            do {
                p++;
                skipspace(p);
                t = type;
                // check for CASE IS,  eg  CASE IS > 5  -or-  CASE > 5  and process it if it is
                // an operator can be >, <>, etc but it can also be a prefix + or - so we must not catch them
                if((SaveCurrentLinePtr = checkstring(p, (unsigned char *)"IS")) || ((tokentype(*p) & T_OPER) && !(*p == GetTokenValue((unsigned char *)"+") || *p == GetTokenValue((unsigned char *)"-")))) {
                    int o;
                    if(SaveCurrentLinePtr) p += 2;
                    skipspace(p);
                    if(tokentype(*p) & T_OPER)
                        o = *p++ - C_BASETOKEN;                     // get the operator
                    else
                        error("Syntax");
                    if(type & T_NBR) ft = f;
                    if(type & T_INT) i64t = i64;
                    if(type & T_STR) st = s;
                    while(o != E_END) p = doexpr(p, &ft, &i64t, &st, &o, &t); // get the right hand side of the expression and evaluate the operator in o
                    if(!(t & T_INT)) error("Syntax");     // comparisons must always return an integer
                    if(i64t) {                                      // evaluates to true
                        skipelement(p);
                        nextstmt = p;
                        CurrentLinePtr = SaveCurrentLinePtr;
                        return;                                     // if we have a match just return to the interpreter and let it execute the code
                    } else {                                        // evaluates to false
                        skipspace(p);
                        continue;
                    }
                }

                // it must be either a single value (eg, "foo") or a range (eg, "foo" TO "zoo")
                // evaluate the first value
                p = evaluate(p, &ft, &i64t, &st, &t, true);
                skipspace(p);
                if(*p == tokenTO) {                      // is there is a TO keyword?
                    p++;
                    t = type;
                    p = evaluate(p, &ftt, &i64tt, &stt, &t, false); // evaluate the right hand side of the TO expression
                    if(((type & T_NBR) && f >= ft && f <= ftt) || ((type & T_INT) && i64 >= i64t && i64 <= i64tt) || (((type & T_STR) && Mstrcmp(s, st) >= 0) && (Mstrcmp(s, stt) <= 0))) {
                        skipelement(p);
                        nextstmt = p;
                        CurrentLinePtr = SaveCurrentLinePtr;
                        return;                                     // if we have a match just return to the interpreter and let it execute the code
                    } else {
                        skipspace(p);
                        continue;                                   // otherwise continue searching
                    }
                }

                // if we got to here the element must be just a single match.  So make the test
                if(((type & T_NBR) && f == ft) ||  ((type & T_INT) && i64 == i64t) ||  ((type & T_STR) && Mstrcmp(s, st) == 0)) {
                    skipelement(p);
                    nextstmt = p;
                    CurrentLinePtr = SaveCurrentLinePtr;
                    return;                                         // if we have a match just return to the interpreter and let it execute the code
                }
                skipspace(p);
            } while(*p == ',');                                     // keep looping through the elements on the CASE line
            checkend(p);
            CurrentLinePtr = SaveCurrentLinePtr;
        }

        // test if we have found a CASE ELSE statement at the same level as this SELECT CASE
        // if true it means that we did not find a matching CASE - so execute this code
        if(tkn == cmdCASE_ELSE && i == 1) {
            p+=sizeof(CommandToken);                                                    // step over the token
            checkend(p);
            skipelement(p);
            nextstmt = p;
            CurrentLinePtr = SaveCurrentLinePtr;
            return;
        }

        if(tkn == cmdEND_SELECT) {i--;  p++;}                             // found an END SELECT so decrement our nested counter

        if(i == 0) {
            // found our matching END SELECT stmt.  Step over it and continue with the statement after it
            skipelement(p);
            nextstmt = p;
            CurrentLinePtr = SaveCurrentLinePtr;
            return;
        }
    }
}


// if we have hit a CASE or CASE ELSE we must search for a END SELECT at this level and resume at that point
void cmd_case(void) {
    int i;
    unsigned char *p;

    // search through the program looking for a END SELECT statement
    // i tracks the nesting level of any nested SELECT CASE commands
    i = 1; p = nextstmt;
    while(1) {
        p = GetNextCommand(p, NULL, (unsigned char *)"No matching END SELECT");
        CommandToken tkn=commandtbl_decode(p);
        if(tkn == cmdSELECT_CASE) i++;                               // found a nested SELECT CASE command, we now need to search for its END CASE
        if(tkn == cmdEND_SELECT) i--;                                // found an END SELECT so decrement our nested counter
        if(i == 0) {
            // found our matching END SELECT stmt.  Step over it and continue with the statement after it
            skipelement(p);
            nextstmt = p;
            break;
        }
    }
}


void cmd_input(void) {
	unsigned char s[STRINGSIZE];
	unsigned char *p, *sp, *tp;
	int i, fnbr;
	getargs(&cmdline, (MAX_ARG_COUNT * 2) - 1, (unsigned char *)",;");				// this is a macro and must be the first executable stmt

	// is the first argument a file number specifier?  If so, get it
	if(argc >= 3 && *argv[0] == '#') {
		argv[0]++;
		fnbr = getinteger(argv[0]);
		i = 2;
	}
	else {
		fnbr = 0;
		// is the first argument a prompt?
		// if so, print it followed by an optional question mark
		if(argc >= 3 && *argv[0] == '"' && (*argv[1] == ',' || *argv[1] == ';')) {
			*(argv[0] + strlen((char *)argv[0]) - 1) = 0;
			argv[0]++;
			MMPrintString((char *)argv[0]);
			if(*argv[1] == ';') MMPrintString((char *)"? ");
			i = 2;
		} else {
			MMPrintString((char *)"? ");									// no prompt?  then just print the question mark
			i = 0;
		}
	}

	if(argc - i < 1) error("Syntax");						// no variable to input to
	*inpbuf = 0;													// start with an empty buffer
	MMgetline(fnbr, (char *)inpbuf);									    // get the line
	p = inpbuf;

	// step through the variables listed for the input statement
	// and find the next item on the line and assign it to the variable
	for(; i < argc; i++) {
		sp = s;														// sp is a temp pointer into s[]
		if(*argv[i] == ',' || *argv[i] == ';') continue;
		skipspace(p);
		if(*p != 0) {
			if(*p == '"') {											// if it is a quoted string
				p++;												// step over the quote
				while(*p && *p != '"')  *sp++ = *p++;				// and copy everything upto the next quote
				while(*p && *p != ',') p++;							// then find the next comma
			} else {												// otherwise it is a normal string of characters
				while(*p && *p != ',') *sp++ = *p++;				// copy up to the comma
				while(sp > s && sp[-1] == ' ') sp--;				// and trim trailing whitespace
			}
		}
		*sp = 0;													// terminate the string
		tp = findvar(argv[i], V_FIND);								// get the variable and save its new value
        if(g_vartbl[g_VarIndex].type & T_CONST) error("Cannot change a constant");
		if(g_vartbl[g_VarIndex].type & T_STR) {
    		if(strlen((char *)s) > g_vartbl[g_VarIndex].size) error("String too long");
			strcpy((char *)tp, (char *)s);
			CtoM(tp);												// convert to a MMBasic string
		} else
		if(g_vartbl[g_VarIndex].type & T_INT) {
    		*((long long int  *)tp) = strtoll((char *)s, (char **)&sp, 10);			// convert to an integer
		}
		else
			*((MMFLOAT *)tp) = (MMFLOAT)atof((char *)s);
		if(*p == ',') p++;
	}
}


void MIPS16 cmd_trace(void) {
    if(checkstring(cmdline, (unsigned char *)"ON"))
    	TraceOn = true;
    else if(checkstring(cmdline, (unsigned char *)"OFF"))
        TraceOn = false;
    else if(checkstring(cmdline, (unsigned char *)"LIST")) {
        int i;
        cmdline += 4;
        skipspace(cmdline);
        if(*cmdline == 0 || *cmdline ==(unsigned char)'\'')  //'
        	i = TRACE_BUFF_SIZE - 1;
        else
        	i = getint(cmdline, 0, TRACE_BUFF_SIZE - 1);
        i = TraceBuffIndex - i;
        if(i < 0) i += TRACE_BUFF_SIZE;
        while(i != TraceBuffIndex) {
			if(TraceBuff[i] >= ProgMemory && TraceBuff[i] <= ProgMemory+MAX_PROG_SIZE){
           		 inpbuf[0] = '[';
            	IntToStr((char *)inpbuf + 1, CountLines(TraceBuff[i]), 10);
            	strcat((char *)inpbuf, "]");
			}else if(TraceBuff[i]){
                strcpy((char *)inpbuf, "[Lib]");	
			}else{
			    inpbuf[0] = 0;
			}	
            MMPrintString((char *)inpbuf);
            if(++i >= TRACE_BUFF_SIZE) i = 0;
        }
    }
    else error("Unknown command");
}



// FOR command
#ifndef PICOMITE
#ifdef rp2350
void MIPS16 __not_in_flash_func(cmd_for)(void) {
#else
void cmd_for(void) {
#endif
#else
void MIPS16 __not_in_flash_func(cmd_for)(void) {
#endif
	int i, t, vlen, test;
	unsigned char ss[4];														// this will be used to split up the argument line
	unsigned char *p, *tp, *xp;
	void *vptr;
	unsigned char *vname, vtype;
//	static unsigned char fortoken, nexttoken;

    // cache these tokens for speed
//	if(!fortoken) fortoken = GetCommandValue((unsigned char *)"For");
//	if(!nexttoken) nexttoken = GetCommandValue((unsigned char *)"Next");

	ss[0] = tokenEQUAL;
	ss[1] = tokenTO;
	ss[2] = tokenSTEP;
	ss[3] = 0;
	{																// start a new block
		getargs(&cmdline, 7, ss);									// getargs macro must be the first executable stmt in a block
		if(argc < 5 || argc == 6 || *argv[1] != ss[0] || *argv[3] != ss[1]) error("FOR with misplaced = or TO");
		if(argc == 6 || (argc == 7 && *argv[5] != ss[2])) error("Syntax");

		// get the variable name and trim any spaces
		vname = argv[0];
		if(*vname && *vname == ' ') vname++;
		while(*vname && vname[strlen((char *)vname) - 1] == ' ') vname[strlen((char *)vname) - 1] = 0;
		vlen = strlen((char *)vname);
		vptr = findvar(argv[0], V_FIND);					        // create the variable
        if(g_vartbl[g_VarIndex].type & T_CONST) error("Cannot change a constant");
        vtype = TypeMask(g_vartbl[g_VarIndex].type);
		if(vtype & T_STR) error("Invalid variable");                // sanity check

		// check if the FOR variable is already in the stack and remove it if it is
		// this is necessary as the program can jump out of the loop without hitting
		// the NEXT statement and this will eventually result in a stack overflow
		for(i = 0; i < g_forindex ;i++) {
			if(g_forstack[i].var == vptr && g_forstack[i].level == g_LocalIndex) {
				while(i < g_forindex - 1) {
					g_forstack[i].forptr = g_forstack[i+1].forptr;
					g_forstack[i].nextptr = g_forstack[i+1].nextptr;
					g_forstack[i].var = g_forstack[i+1].var;
					g_forstack[i].vartype = g_forstack[i+1].vartype;
					g_forstack[i].level = g_forstack[i+1].level;
					g_forstack[i].tovalue.i = g_forstack[i+1].tovalue.i;
					g_forstack[i].stepvalue.i = g_forstack[i+1].stepvalue.i;
					i++;
				}
				g_forindex--;
				break;
			}
		}

        if(g_forindex == MAXFORLOOPS) error("Too many nested FOR loops");

		g_forstack[g_forindex].var = vptr;								// save the variable index
		g_forstack[g_forindex].vartype = vtype;							// save the type of the variable
		g_forstack[g_forindex].level = g_LocalIndex;						// save the level of the variable in terms of sub/funs
        g_forindex++;                                                 // incase functions use for loops
        if(vtype & T_NBR) {
            *(MMFLOAT *)vptr = getnumber(argv[2]);					// get the starting value for a float and save
            g_forstack[g_forindex - 1].tovalue.f = getnumber(argv[4]);		// get the to value and save
            if(argc == 7)
                g_forstack[g_forindex - 1].stepvalue.f = getnumber(argv[6]);// get the step value for a float and save
            else
                g_forstack[g_forindex - 1].stepvalue.f = 1.0;				// default is +1
        } else {
            *(long long int  *)vptr = getinteger(argv[2]);			// get the starting value for an integer and save
            g_forstack[g_forindex - 1].tovalue.i = getinteger(argv[4]);		// get the to value and save
            if(argc == 7)
                g_forstack[g_forindex - 1].stepvalue.i = getinteger(argv[6]);// get the step value for an integer and save
            else
                g_forstack[g_forindex - 1].stepvalue.i = 1;					// default is +1
        }
        g_forindex--;

		g_forstack[g_forindex].forptr = nextstmt + 1;					// return to here when looping

		// now find the matching NEXT command
        t = 1; p = nextstmt;
        while(1) {
              p = GetNextCommand(p, &tp, (unsigned char *)"No matching NEXT");
//            if(*p == fortoken) t++;                                 // count the FOR
//            if(*p == nexttoken) {                                   // is it NEXT
        	CommandToken tkn=commandtbl_decode(p);

            if(tkn == cmdFOR) t++;                                 // count the FOR

            if(tkn == cmdNEXT) {                                   // is it NEXT
				xp = p + sizeof(CommandToken);											// point to after the NEXT token
				while(*xp && mystrncasecmp(xp, vname, vlen)) xp++;	// step through looking for our variable
				if(*xp && !isnamechar(xp[vlen]))					// is it terminated correctly?
					t = 0;											// yes, found the matching NEXT
				else
					t--;											// no luck, just decrement our stack counter
			}
			if(t == 0) {											// found the matching NEXT
				g_forstack[g_forindex].nextptr = p;					// pointer to the start of the NEXT command
				break;
			}
		}

        // test the loop value at the start
        if(g_forstack[g_forindex].vartype & T_INT)
            test = (g_forstack[g_forindex].stepvalue.i >= 0 && *(long long int  *)vptr > g_forstack[g_forindex].tovalue.i) || (g_forstack[g_forindex].stepvalue.i < 0 && *(long long int  *)vptr < g_forstack[g_forindex].tovalue.i) ;
        else
            test = (g_forstack[g_forindex].stepvalue.f >= 0 && *(MMFLOAT *)vptr > g_forstack[g_forindex].tovalue.f) || (g_forstack[g_forindex].stepvalue.f < 0 && *(MMFLOAT *)vptr < g_forstack[g_forindex].tovalue.f) ;

        if(test) {
			// loop is invalid at the start, so go to the end of the NEXT command
			skipelement(p);            // find the command after the NEXT command
			nextstmt = p;              // this is where we will continue
		} else {
			g_forindex++;					// save the loop data and continue on with the command after the FOR statement
        }
	}
}



#ifndef PICOMITE
#ifdef rp2350
void MIPS16 __not_in_flash_func(cmd_next)(void) {
#else
void cmd_next(void) {
#endif
#else
void MIPS16 __not_in_flash_func(cmd_next)(void) {
#endif
	int i, vindex, test;
	void *vtbl[MAXFORLOOPS];
	int vcnt;
	unsigned char *p;
	getargs(&cmdline, MAXFORLOOPS * 2, (unsigned char *)",");						// getargs macro must be the first executable stmt in a block

	vindex = 0;														// keep lint happy

	for(vcnt = i = 0; i < argc; i++) {
		if(i & 0x01) {
			if(*argv[i] != ',') error("Syntax");
		} else
			vtbl[vcnt++] = findvar(argv[i], V_FIND | V_NOFIND_ERR); // find the variable and error if not found
	}

	loopback:
	// first search the for stack for a loop with the same variable specified on the NEXT's line
	if(vcnt) {
		for(i = g_forindex - 1; i >= 0; i--)
			for(vindex = vcnt - 1; vindex >= 0 ; vindex--)
				if(g_forstack[i].var == vtbl[vindex])
					goto breakout;
	} else {
		// if no variables specified search the for stack looking for an entry with the same program position as
		// this NEXT statement. This cheats by using the cmdline as an identifier and may not work inside an IF THEN ELSE
        for(i = 0; i < g_forindex; i++) {
            p = g_forstack[i].nextptr + sizeof(CommandToken);
            skipspace(p);
            if(p == cmdline) goto breakout;
        }
	}

	error("Cannot find a matching FOR");

	breakout:

	// found a match
	// apply the STEP value to the variable and test against the TO value
    if(g_forstack[i].vartype & T_INT) {
        *(long long int  *)g_forstack[i].var += g_forstack[i].stepvalue.i;
	    test = (g_forstack[i].stepvalue.i >= 0 && *(long long int  *)g_forstack[i].var > g_forstack[i].tovalue.i) || (g_forstack[i].stepvalue.i < 0 && *(long long int  *)g_forstack[i].var < g_forstack[i].tovalue.i) ;
    } else {
        *(MMFLOAT *)g_forstack[i].var += g_forstack[i].stepvalue.f;
	    test = (g_forstack[i].stepvalue.f >= 0 && *(MMFLOAT *)g_forstack[i].var > g_forstack[i].tovalue.f) || (g_forstack[i].stepvalue.f < 0 && *(MMFLOAT *)g_forstack[i].var < g_forstack[i].tovalue.f) ;
    }

    if(test) {
		// the loop has terminated
		// remove the entry in the table, then skip forward to the next element and continue on from there
		while(i < g_forindex - 1) {
			g_forstack[i].forptr = g_forstack[i+1].forptr;
			g_forstack[i].nextptr = g_forstack[i+1].nextptr;
			g_forstack[i].var = g_forstack[i+1].var;
			g_forstack[i].vartype = g_forstack[i+1].vartype;
			g_forstack[i].level = g_forstack[i+1].level;
			g_forstack[i].tovalue.i = g_forstack[i+1].tovalue.i;
			g_forstack[i].stepvalue.i = g_forstack[i+1].stepvalue.i;
			i++;
		}
		g_forindex--;
		if(vcnt > 0) {
			// remove that entry from our FOR stack
			for(; vindex < vcnt - 1; vindex++) vtbl[vindex] = vtbl[vindex + 1];
			vcnt--;
			if(vcnt > 0)
				goto loopback;
			else
				return;
		}

	} else {
		// we have not reached the terminal value yet, so go back and loop again
		nextstmt = g_forstack[i].forptr;
	}
}




#ifndef PICOMITE
#ifdef rp2350
void MIPS16 __not_in_flash_func(cmd_do)(void) {
#else
void cmd_do(void) {
#endif
#else
void MIPS16 __not_in_flash_func(cmd_do)(void) {
#endif
	int i;
	unsigned char *p, *tp, *evalp;
    if(cmdtoken==cmdWHILE)error("Unknown command");
	// if it is a DO loop find the WHILE token and (if found) get a pointer to its expression
	while(*cmdline && *cmdline != tokenWHILE && *cmdline != tokenUNTIL) cmdline++;
	if(*cmdline == tokenUNTIL)error("Syntax");
	if(*cmdline == tokenWHILE) {
			evalp = ++cmdline;
		}
		else
			evalp = NULL;
	// check if this loop is already in the stack and remove it if it is
	// this is necessary as the program can jump out of the loop without hitting
	// the LOOP or WEND stmt and this will eventually result in a stack overflow
	for(i = 0; i < g_doindex ;i++) {
		if(g_dostack[i].doptr == nextstmt) {
			while(i < g_doindex - 1) {
				g_dostack[i].evalptr = g_dostack[i+1].evalptr;
				g_dostack[i].loopptr = g_dostack[i+1].loopptr;
				g_dostack[i].doptr = g_dostack[i+1].doptr;
				g_dostack[i].level = g_dostack[i+1].level;
				i++;
			}
			g_doindex--;
			break;
		}
	}

	// add our pointers to the top of the stack
	if(g_doindex == MAXDOLOOPS) error("Too many nested DO or WHILE loops");
	g_dostack[g_doindex].evalptr = evalp;
	g_dostack[g_doindex].doptr = nextstmt;
	g_dostack[g_doindex].level = g_LocalIndex;

	// now find the matching LOOP command
	i = 1; p = nextstmt;
	while(1) {
        p = GetNextCommand(p, &tp, (unsigned char *)"No matching LOOP");
        CommandToken tkn=commandtbl_decode(p);
		if(tkn == cmdtoken) i++;                                     // entered a nested DO or WHILE loop
		if(tkn == cmdLOOP) i--;									// exited a nested loop

		if(i == 0) {												// found our matching LOOP or WEND stmt
			g_dostack[g_doindex].loopptr = p;
			break;
		}
	}

    if(g_dostack[g_doindex].evalptr != NULL) {
		// if this is a DO WHILE ... LOOP statement
		// search the LOOP statement for a WHILE or UNTIL token (p is pointing to the matching LOOP statement)
		p+=sizeof(CommandToken);
		while(*p && *p < 0x80) p++;
		if(*p == tokenWHILE) error("LOOP has a WHILE test");
		if(*p == tokenUNTIL) error("LOOP has an UNTIL test");
	}

	g_doindex++;

    // do the evaluation (if there is something to evaluate) and if false go straight to the command after the LOOP or WEND statement
    if(g_dostack[g_doindex - 1].evalptr != NULL && getnumber(g_dostack[g_doindex - 1].evalptr) == 0) {
        g_doindex--;                                                  // remove the entry in the table
        nextstmt = g_dostack[g_doindex].loopptr;                        // point to the LOOP or WEND statement
        skipelement(nextstmt);                                      // skip to the next command
    }

}




#ifdef PICOMITEWEB
#ifdef rp2350
void MIPS16 __not_in_flash_func(cmd_loop)(void) {
#else
void cmd_loop(void) {
#endif
#else
void MIPS16 __not_in_flash_func(cmd_loop)(void) {
#endif
    unsigned char *p;
	int tst = 0;                                                    // initialise tst to stop the compiler from complaining
	int i;

	// search the do table looking for an entry with the same program position as this LOOP statement
	for(i = 0; i < g_doindex ;i++) {
        p = g_dostack[i].loopptr + sizeof(CommandToken);
        skipspace(p);
        if(p == cmdline) {
			// found a match
			// first check if the DO statement had a WHILE component
			// if not find the WHILE statement here and evaluate it
			if(g_dostack[i].evalptr == NULL) {						// if it was a DO without a WHILE
				if(*cmdline >= 0x80) {								// if there is something
					if(*cmdline == tokenWHILE)
						tst = (getnumber(++cmdline) != 0);			// evaluate the expression
					else if(*cmdline == tokenUNTIL)
						tst = (getnumber(++cmdline) == 0);			// evaluate the expression
					else
						error("Syntax");
				}
				else {
					tst = 1;										// and loop forever
					checkend(cmdline);								// make sure that there is nothing else
				}
			}
			else {													// if was DO WHILE
				tst = (getnumber(g_dostack[i].evalptr) != 0);			// evaluate its expression
				checkend(cmdline);									// make sure that there is nothing else
			}

			// test the expression value and reset the program pointer if we are still looping
			// otherwise remove this entry from the do stack
			if(tst)
				nextstmt = g_dostack[i].doptr;						// loop again
			else {
				// the loop has terminated
				// remove the entry in the table, then just let the default nextstmt run and continue on from there
                g_doindex = i;
				// just let the default nextstmt run
			}
			return;
		}
	}
	error("LOOP without a matching DO");
}



void cmd_exitfor(void) {
	if(g_forindex == 0) error("No FOR loop is in effect");
	nextstmt = g_forstack[--g_forindex].nextptr;
	checkend(cmdline);
	skipelement(nextstmt);
}



void cmd_exit(void) {
	if(g_doindex == 0) error("No DO loop is in effect");
	nextstmt = g_dostack[--g_doindex].loopptr;
	checkend(cmdline);
	skipelement(nextstmt);
}
 


/*void cmd_error(void) {
	unsigned char *s;
	if(*cmdline && *cmdline != '\'') {
		s = getCstring(cmdline);
		char *p=GetTempMemory(STRINGSIZE);
		strcpy(p,"[");
		int ln=CountLines(CurrentLinePtr);
		IntToStr(&p[1],ln,10);
		SaveCurrentLinePtr=CurrentLinePtr;
		CurrentLinePtr = NULL;                                      // suppress printing the line that caused the issue
		strcat((char *)p,"] ");
		strcat((char *)p,(char *)s);
		error(p);
	}
	else
		error("");
}*/
void cmd_error(void) {
	unsigned char *s;
	if(*cmdline && *cmdline != '\'') {
		s = getCstring(cmdline);
		// CurrentLinePtr = NULL;                                      // suppress printing the line that caused the issue
		error((char *) s);
	}
	else
		error("");
}


#ifndef rp2350
	void cmd_randomize(void) {
	int i;
	getargs(&cmdline,1,(unsigned char *)",");
	if(argc==1)i = getinteger(argv[0]);
	else i=time_us_32();
	if(i < 0) error("Number out of bounds");
	srand(i);
}
#endif

// this is the Sub or Fun command
// it simply skips over text until it finds the end of it
void cmd_subfun(void) {
	unsigned char *p;
	unsigned short returntoken, errtoken;

    if(gosubindex != 0) error("No matching END declaration");       // we have hit a SUB/FUN while in another SUB or FUN
	if(cmdtoken == cmdSUB) {
	    returntoken = cmdENDSUB;
	    errtoken = cmdENDFUNCTION;
	} else {
	    returntoken = cmdENDFUNCTION;
	    errtoken = cmdENDSUB;
    }
	p = nextstmt;
	while(1) {
        p = GetNextCommand(p, NULL, (unsigned char *)"No matching END declaration");
        CommandToken tkn=commandtbl_decode(p);
        if(tkn == cmdSUB || tkn == cmdFUN || tkn == errtoken) error("No matching END declaration");
		if(tkn == returntoken) {                                     // found the next return
    		skipelement(p);
    		nextstmt = p;                                           // point to the next command
    		break;
        }
    }
}
// this is the Sub or Fun command
// it simply skips over text until it finds the end of it
void cmd_comment(void) {
	unsigned char *p;
	unsigned short returntoken;

	returntoken = GetCommandValue((unsigned char *)"*/");
//	errtoken = cmdENDSUB;
	p = nextstmt;
	while(1) {
        p = GetNextCommand(p, NULL, (unsigned char *)"No matching END declaration");
        CommandToken tkn=commandtbl_decode(p);
        if(tkn == cmdComment) error("No matching END declaration");
		if(tkn == returntoken) {                                     // found the next return
    		skipelement(p);
    		nextstmt = p;                                           // point to the next command
    		break;
        }
    }
}
void cmd_endcomment(void){

}


void cmd_gosub(void) {
   if(gosubindex >= MAXGOSUB) error("Too many nested GOSUB");
   char *return_to = (char *)nextstmt;
   if(isnamestart(*cmdline))
       nextstmt = findlabel(cmdline);
   else
       nextstmt = findline(getinteger(cmdline), true);
   IgnorePIN = false;

   errorstack[gosubindex] = CurrentLinePtr;
   gosubstack[gosubindex++] = (unsigned char *)return_to;
   g_LocalIndex++;
   CurrentLinePtr = nextstmt;
}

void cmd_mid(void){
	unsigned char *p;
	getargs(&cmdline,5,(unsigned char *)",");
	findvar(argv[0], V_NOFIND_ERR);
    if(g_vartbl[g_VarIndex].type & T_CONST) error("Cannot change a constant");
	if(!(g_vartbl[g_VarIndex].type & T_STR)) error("Not a string");
	int size=g_vartbl[g_VarIndex].size;
	char *sourcestring=(char *)getstring(argv[0]);
	int start=getint(argv[2],1,sourcestring[0]);
	int num=0;
	if(argc==5)num=getint(argv[4],1,sourcestring[0]);
	if(start+num-1>sourcestring[0])error("Selection exceeds length of string");
	while(*cmdline && tokenfunction(*cmdline) != op_equal) cmdline++;
	if(!*cmdline) error("Syntax");
	++cmdline;
	if(!*cmdline) error("Syntax");
	char *value = (char *)getstring(cmdline);
	if(num==0)num=value[0];
	p=(unsigned char *)&value[1];
	if(num==value[0]) memcpy(&sourcestring[start],p,num);
	else {
		int change=value[0]-num;
		if(sourcestring[0]+change>size)error("String too long");
		memmove(&sourcestring[start+value[0]],&sourcestring[start+num],sourcestring[0]-(start+num-1));
		sourcestring[0]+=change;
		memcpy(&sourcestring[start],p,value[0]);
	}
}
void cmd_byte(void){
	getargs(&cmdline,3,(unsigned char *)",");
	findvar(argv[0], V_NOFIND_ERR);
    if(g_vartbl[g_VarIndex].type & T_CONST) error("Cannot change a constant");
	if(!(g_vartbl[g_VarIndex].type & T_STR)) error("Not a string");
	unsigned char *sourcestring=(unsigned char *)getstring(argv[0]);
	int start=getint(argv[2],1,sourcestring[0]);
	while(*cmdline && tokenfunction(*cmdline) != op_equal) cmdline++;
	if(!*cmdline) error("Syntax");
	++cmdline;
	if(!*cmdline) error("Syntax");
	int value = getint(cmdline,0,255);
	sourcestring[start]=value;
}
void cmd_bit(void){
	getargs(&cmdline,3,(unsigned char *)",");
	uint64_t *source=(uint64_t *)findvar(argv[0], V_NOFIND_ERR);
    if(g_vartbl[g_VarIndex].type & T_CONST) error("Cannot change a constant");
	if(!(g_vartbl[g_VarIndex].type & T_INT)) error("Not an integer");
	uint64_t bit=(uint64_t)1<<(uint64_t)getint(argv[2],0,63);
	while(*cmdline && tokenfunction(*cmdline) != op_equal) cmdline++;
	if(!*cmdline) error("Syntax");
	++cmdline;
	if(!*cmdline) error("Syntax");
	int value = getint(cmdline,0,1);
	if(value)*source|=bit;
	else *source&=(~bit);
}
void cmd_flags(void) {
	while(*cmdline && tokenfunction(*cmdline) != op_equal) cmdline++;
	if(!*cmdline) error("Syntax");
	g_flag=getinteger(++cmdline);
}
  
void cmd_flag(void){
	getargs(&cmdline,1,(unsigned char *)",");
	uint64_t bit=(uint64_t)1<<(uint64_t)getint(argv[0],0,63);
	while(*cmdline && tokenfunction(*cmdline) != op_equal) cmdline++;
	if(!*cmdline) error("Syntax");
	++cmdline;
	if(!*cmdline) error("Syntax");
	int value = getint(cmdline,0,1);
	if(value)g_flag |=bit;
	else g_flag &=~bit;
}

void MIPS16 __not_in_flash_func(cmd_return)(void) {
 	checkend(cmdline);
	if(gosubindex == 0 || gosubstack[gosubindex - 1] == NULL) error("Nothing to return to");
    ClearVars(g_LocalIndex--, true);                                        // delete any local variables
    g_TempMemoryIsChanged = true;                                     // signal that temporary memory should be checked
	nextstmt = gosubstack[--gosubindex];                            // return to the caller
    CurrentLinePtr = errorstack[gosubindex];
}
/*frame
#define c_topleft  218
#define c_topright 191
#define c_bottomleft 192
#define c_bottomright 217
#define c_horizontal 196
#define c_vertical 179
#define c_cross 197
#define c_tup 193
#define c_tdown 194
#define c_tleft 195
#define c_tright 180
#define c_d_topleft  201
#define c_d_topright 187
#define c_d_bottomleft 200
#define c_d_bottomright 188
#define c_d_horizontal 205
#define c_d_vertical 186
#define c_d_cross 206
#define c_d_tup 202
#define c_d_tdown 203
#define c_d_tleft 204
#define c_d_tright 185


extern const int colours[16];
extern void setterminal(void);
unsigned short *frame=NULL, *outframe=NULL;
bool framecursor=true;
static int framex=0,framey=0;
static inline void framewritechar(int x, int y, uint8_t ascii, uint8_t fcolour, uint8_t attributes){
	if(x>=framex || y>=framey)return;
	frame[(y*framex)+x]=ascii | (fcolour<<8) | (attributes<<12);
}
static inline uint16_t framegetchar(int x, int y ){
	return frame[(y*framex)+x];
}
static void SCursor(int x, int y) {
    char s[30];
	ShowCursor(0);
	CurrentX=x*gui_font_width;
	CurrentY=y*gui_font_height;
	sprintf(s,"\033[%d;%dH",y+1,x+1);
	SSPrintString(s);
	ShowCursor(framecursor);
}
static void SColour(int colour, int fore){
    char s[24]={0};
	int r=colour>>16;
	int g=(colour>>8) & 0xFF;
	int b=colour & 0xff;
	if(fore){
		strcpy(s,"\033[38;2;");
		gui_fcolour=colour;
	} else {
		strcpy(s,"\033[48;2;");
		gui_bcolour=colour;
	}
	sprintf(&s[7],"%d;%d;%dm",r,g,b);
	SSPrintString(s);
}

void cmd_frame(void){
	unsigned char *p=NULL;
	if((p=checkstring(cmdline,(unsigned char *)"CREATE"))){
		if(frame)error("Frame already exists");
		framex=HRes/gui_font_width;
		framey=VRes/gui_font_height;
		frame=(uint16_t *)GetMemory(framex*framey*sizeof(uint16_t));
		outframe=(uint16_t *)GetMemory(framex*framey*sizeof(uint16_t));
		for(int i=0;i<framex*framey;i++)outframe[i]=0xFFFF;
		Option.Width=framex;
		Option.Height=framey;
		char sp[20]={0};
		strcpy(sp,"\033[8;");
		IntToStr(&sp[strlen(sp)],framey,10);
		strcat(sp,";");
		IntToStr(&sp[strlen(sp)],framex+1,10);
		strcat(sp,"t");
		SSPrintString(sp);	
		SSPrintString("\0337\033[2J\033[H");                            // vt100 clear screen and home cursor
		ClearScreen(gui_bcolour);					//
		return;
	} 
	if(!frame)error("Frame not created");
	if((p=checkstring(cmdline,(unsigned char *)"CURSOR"))){
		if(checkstring(p,(unsigned char *)"ON")){
			framecursor=true;
			SSPrintString("\033[?25h");
		} else if(checkstring(p,(unsigned char *)"OFF")){
			ShowCursor(0);
			framecursor=false;
			SSPrintString("\033[?25l");
		} else {
			getargs(&p,3,(unsigned char *)",");
			if(argc<3)error("Syntax");
			int x=getint(argv[0],0,framex-1);
			int y=getint(argv[2],0,framey-1);
			if(DISPLAY_TYPE==SCREENMODE1)tilefcols[y*X_TILE+x]=RGB121pack(gui_fcolour);
			SCursor(x,y);
		}
	} else if((p=checkstring(cmdline,(unsigned char *)"BOX"))){
		int fc=gui_fcolour;
		bool dual=false;
		getargs(&p,11,(unsigned char *)",");
		if(argc<7)error("Syntax");
		int x=getint(argv[0],0,framex-1);
		int y=getint(argv[2],0,framey-1);
		int w=getint(argv[4],1,framex-1-x);
		int h=getint(argv[6],1,framey-1-y);
		if(argc>=9 && *argv[8])fc=getint(argv[8],0,WHITE);
		if(argc==11){
			if(checkstring(argv[10],(unsigned char *)"DOUBLE"))dual=true;
		}
		fc=RGB121(fc);
		framewritechar(x,y,dual ? c_d_topleft : c_topleft,fc,0);
		framewritechar(x+w,y,dual ? c_d_topright : c_topright,fc,0);
		framewritechar(x+w,y+h,dual ? c_d_bottomright : c_bottomright,fc,0);
		framewritechar(x,y+h,dual ? c_d_bottomleft : c_bottomleft,fc,0);
		for(int i=x+1;i<x+w;i++){
			framewritechar(i,y,dual ? c_d_horizontal : c_horizontal,fc,0);
			framewritechar(i,y+h,dual ? c_d_horizontal : c_horizontal,fc,0);
		}
		for(int i=y+1;i<y+h;i++){
			framewritechar(x,i,dual ? c_d_vertical : c_vertical,fc,0);
			framewritechar(x+w,i,dual ? c_d_vertical : c_vertical,fc,0);
		}
		SCursor(1,1);
	} else if((p=checkstring(cmdline,(unsigned char *)"QBOX"))){
		int fc=gui_fcolour;
		bool dual=false;
		getargs(&p,15,(unsigned char *)",");
		if(argc<9)error("Syntax");
		int x=getint(argv[0],0,framex-1);
		int y=getint(argv[2],0,framey-1);
		int w=getint(argv[4],1,framex-1-x);
		int w2=getint(argv[6],1,framex-1-x-w);
		int h=getint(argv[8],1,framey-1-y);
		int h2=getint(argv[10],1,framey-1-y-h);
		if(argc>=13 && *argv[12])fc=getint(argv[12],0,WHITE);
		if(argc==15){
			if(checkstring(argv[14],(unsigned char *)"DOUBLE"))dual=true;
		}
		fc=RGB121(fc);
		framewritechar(x,y,dual ? c_d_topleft : c_topleft,fc,0);
		framewritechar(x+w,y,dual ? c_d_tdown : c_tdown,fc,0);
		framewritechar(x+w+w2,y,dual ? c_d_topright : c_topright,fc,0);
		framewritechar(x,y+h,dual ? c_d_tleft : c_tleft,fc,0);
		framewritechar(x+w,y+h,dual ? c_d_cross : c_cross,fc,0);
		framewritechar(x+w+w2,y+h,dual ? c_d_tright : c_tright,fc,0);
		framewritechar(x,y+h+h2,dual ? c_d_bottomleft : c_bottomleft,fc,0);
		framewritechar(x+w,y+h+h2,dual ? c_d_tup : c_tup,fc,0);
		framewritechar(x+w+w2,y+h+h2,dual ? c_d_bottomright : c_bottomright,fc,0);
		for(int i=x+1;i<x+w;i++){
			framewritechar(i,y,dual ? c_d_horizontal : c_horizontal,fc,0);
			framewritechar(i,y+h,dual ? c_d_horizontal : c_horizontal,fc,0);
			framewritechar(i,y+h+h2,dual ? c_d_horizontal : c_horizontal,fc,0);
		}
		for(int i=x+1+w;i<x+w+w2;i++){
			framewritechar(i,y,dual ? c_d_horizontal : c_horizontal,fc,0);
			framewritechar(i,y+h,dual ? c_d_horizontal : c_horizontal,fc,0);
			framewritechar(i,y+h+h2,dual ? c_d_horizontal : c_horizontal,fc,0);
		}
		for(int i=y+1;i<y+h;i++){
			framewritechar(x,i,dual ? c_d_vertical : c_vertical,fc,0);
			framewritechar(x+w,i,dual ? c_d_vertical : c_vertical,fc,0);
			framewritechar(x+w+w2,i,dual ? c_d_vertical : c_vertical,fc,0);
		}
		for(int i=y+1+h;i<y+h+h2;i++){
			framewritechar(x,i,dual ? c_d_vertical : c_vertical,fc,0);
			framewritechar(x+w,i,dual ? c_d_vertical : c_vertical,fc,0);
			framewritechar(x+w+w2,i,dual ? c_d_vertical : c_vertical,fc,0);
		}
		SCursor(1,1);
	} else if((p=checkstring(cmdline,(unsigned char *)"VBOX"))){
		int fc=gui_fcolour;
		bool dual=false;
		getargs(&p,13,(unsigned char *)",");
		if(argc<9)error("Syntax");
		int x=getint(argv[0],0,framex-1);
		int y=getint(argv[2],0,framey-1);
		int w=getint(argv[4],1,framex-1-x);
		int h=getint(argv[6],1,framey-1-y);
		int h2=getint(argv[8],1,framey-1-y-h);
		if(argc>=11 && *argv[10])fc=getint(argv[10],0,WHITE);
		if(argc==13){
			if(checkstring(argv[12],(unsigned char *)"DOUBLE"))dual=true;
		}
		fc=RGB121(fc);
		framewritechar(x,y,dual ? c_d_topleft : c_d_topleft,fc,0);
		framewritechar(x+w,y,dual ? c_d_topright : c_d_topright,fc,0);
		framewritechar(x+w,y+h,dual ? c_d_tright : c_d_tright,fc,0);
		framewritechar(x+w,y+h+h2,dual ? c_d_bottomright : c_d_bottomright,fc,0);
		framewritechar(x,y+h+h2,dual ? c_d_bottomleft : c_d_bottomleft,fc,0);
		framewritechar(x,y+h,dual ? c_d_tleft : c_d_tleft,fc,0);
		for(int i=x+1;i<x+w;i++){
			framewritechar(i,y,dual ? c_d_horizontal : c_d_horizontal,fc,0);
			framewritechar(i,y+h,dual ? c_d_horizontal : c_d_horizontal,fc,0);
			framewritechar(i,y+h+h2,dual ? c_d_horizontal : c_d_horizontal,fc,0);
		}
		for(int i=y+1;i<y+h;i++){
			framewritechar(x,i,dual ? c_d_vertical : c_d_vertical,fc,0);
			framewritechar(x+w,i,dual ? c_d_vertical : c_d_vertical,fc,0);
		}
		for(int i=y+1+h;i<y+h+h2;i++){
			framewritechar(x,i,dual ? c_d_vertical : c_d_vertical,fc,0);
			framewritechar(x+w,i,dual ? c_d_vertical : c_d_vertical,fc,0);
		}
		SCursor(1,1);
	} else if((p=checkstring(cmdline,(unsigned char *)"HBOX"))){
		int fc=gui_fcolour;
		bool dual=false;
		getargs(&p,13,(unsigned char *)",");
		if(argc<9)error("Syntax");
		int x=getint(argv[0],0,framex-1);
		int y=getint(argv[2],0,framey-1);
		int w=getint(argv[4],1,framex-1-x);
		int w2=getint(argv[6],1,framex-1-x-w);
		int h=getint(argv[8],1,framey-1-y);
		if(argc>=11 && *argv[10])fc=getint(argv[10],0,WHITE);
		if(argc==13){
			if(checkstring(argv[12],(unsigned char *)"DOUBLE"))dual=true;
		}
		fc=RGB121(fc);
		framewritechar(x,y,dual ? c_d_topleft : c_topleft,fc,0);
		framewritechar(x+w,y,dual ? c_d_tdown : c_tdown,fc,0);
		framewritechar(x+w,y+h,dual ? c_d_tup : c_tup,fc,0);
		framewritechar(x+w+w2,y,dual ? c_d_topright : c_topright,fc,0);
		framewritechar(x+w+w2,y+h,dual ? c_d_bottomright : c_bottomright,fc,0);
		framewritechar(x,y+h,dual ? c_d_bottomleft : c_bottomleft,fc,0);
		for(int i=x+1;i<x+w;i++){
			framewritechar(i,y,dual ? c_d_horizontal : c_horizontal,fc,0);
			framewritechar(i,y+h,dual ? c_d_horizontal : c_horizontal,fc,0);
		}
		for(int i=x+1+w;i<x+w+w2;i++){
			framewritechar(i,y,dual ? c_d_horizontal : c_horizontal,fc,0);
			framewritechar(i,y+h,dual ? c_d_horizontal : c_horizontal,fc,0);
		}
		for(int i=y+1;i<y+h;i++){
			framewritechar(x,i,dual ? c_d_vertical : c_vertical,fc,0);
			framewritechar(x+w,i,dual ? c_d_vertical : c_vertical,fc,0);
			framewritechar(x+w+w2,i,dual ? c_d_vertical : c_vertical,fc,0);
		}
		SCursor(1,1);
	} else if((p=checkstring(cmdline,(unsigned char *)"CLEAR"))){
		memset(frame,0,framex*framey*sizeof(uint16_t));
	} else if((p=checkstring(cmdline,(unsigned char *)"CLOSE"))){
		if(!frame)error("Frame does not exist");
		FreeMemorySafe((void **)&frame);
		FreeMemorySafe((void **)&outframe);
	} else if((p=checkstring(cmdline,(unsigned char *)"SCROLL"))){
		int xstart=0,ystart=0,xend=framex-1,yend=framey-1, xmax=framex-1, ymax=framey-1;
		getargs(&p,11,(unsigned char *)",");
//		if(argc<7)error("Syntax");
		int xs=getint(argv[0],-(xmax),xmax);
		int ys=getint(argv[2],-(ymax),ymax);
		if(argc>=5 && *argv[4])xstart=getint(argv[4],0,xmax);
		if(argc>=7 && *argv[6])ystart=getint(argv[6],0,ymax);
		if(argc>=9 && *argv[8])xend=getint(argv[8],1,xmax);
		if(argc==11)yend=getint(argv[10],1,ymax);
		SCursor(xstart,ystart);
		if(DISPLAY_TYPE==SCREENMODE1)tilefcols[ystart*X_TILE+xstart]=RGB;
		if(abs(xs)>=0){
			for(int y=ystart;y<=yend;y++){
				uint16_t *line=&frame[y*framex];
				if(xs>0){
					memmove((uint8_t*)&line[xstart+xs],(uint8_t*)&line[xstart],(xend-xstart-xs+1)*sizeof(uint16_t));
					memset((uint8_t*)&line[xstart],0,xs*sizeof(uint16_t));
				} else {
					memmove((uint8_t*)&line[xstart],(uint8_t*)&line[xstart-xs],(xend-xstart+xs+1)*sizeof(uint16_t));
					memset((uint8_t*)&line[xend+xs+1],0,abs(xs)*sizeof(uint16_t));
				}
			}
		}
		if(ys>0){
			uint16_t *line1=&frame[(yend)*framex+xstart];
			uint16_t *line2=&frame[(yend-ys)*framex+xstart];
			for(int y=yend;y>ystart-ys+1;y--){
				memcpy((uint8_t*)line1,(uint8_t*)line2,(xend-xstart+1)*sizeof(uint16_t));
				line1-=framex;
				line2-=framex;
			}
			line1=&frame[ystart*framex+xstart];
			for(int y=ystart;y<ystart+ys;y++){
				memset((uint8_t*)line1,0,(xend-xstart+1)*sizeof(uint16_t));
				line1+=framex;
			}
		} else if(ys<0){
			ys=-ys;
			uint16_t *line1=&frame[ystart*framex+xstart];
			uint16_t *line2=&frame[(ystart+ys)*framex+xstart];
			for(int y=ystart;y<=yend-ys;y++){
				memcpy((uint8_t*)line1,(uint8_t*)line2,(xend-xstart+1)*sizeof(uint16_t));
				line1+=framex;
				line2+=framex;
			}
			line1=&frame[(yend-ys+1)*framex+xstart];
			for(int y=yend-ys+1;y<=yend;y++){
				memset((uint8_t*)line1,0,(xend-xstart+1)*sizeof(uint16_t));
				line1+=framex;
			}
		}
	} else if((p=checkstring(cmdline,(unsigned char *)"WRITE"))){
		int savefcol=gui_fcolour;
		int lasty=-1,lastx=-1,lastc=-1;
		int sx=CurrentX/gui_font_width,sy=CurrentY/gui_font_height;
		int ccursor=framecursor;
		framecursor=0;
		ShowCursor(0);
		SColour(gui_bcolour,0);
		for(int y=0;y<framey;y++){
			for(int x=0;x<framex;x++){
				uint16_t c=framegetchar(x,y);
				if(c!=outframe[(y*framex)+x]){
					outframe[(y*framex)+x]=c;
					if(y!=lasty || x!=lastx){
						SCursor(x,y);
						lastx=x+1;
						lasty=y;
					} else lastx=x+1;
					int outc=colours[(c>>8) & 0xF];
					if(outc!=lastc){
						lastc=outc;
						SColour(outc,1);
					}
					if(c==0)c=' ';
					DisplayPutC(c&0xFF);
					SerialConsolePutC(c&0xFF,0);
				}
			}
		}
		fflush(stdout);
		gui_fcolour=savefcol;
		SCursor(sx,sy);
		framecursor=ccursor;
		ShowCursor(framecursor);
		if(DISPLAY_TYPE==SCREENMODE1)tilefcols[sy*X_TILE+sx]=Option.VGAFC;
		SColour(gui_fcolour,1);
	} else {
		int attributes=0, fc=gui_fcolour;
		getargs(&cmdline,9,(unsigned char *)",");
		if(argc<5)error("Syntax");
		int x=getint(argv[0],0,framex-1);
		int y=getint(argv[2],0,framey-1);
		p=getCstring(argv[4]);
		if(argc>=7 && *argv[6])fc=getint(argv[6],0,WHITE);
		if(argc==9)attributes=getint(argv[8],0,15);
		int l=strlen((char *)p);
		fc=RGB121(fc);
		while(l--){
			if(x==framex){y++;x=0;}
			if(y==framey)return;
			framewritechar(x++,y,*p++,fc,attributes);
		}
	}
}*/


void cmd_endfun(void) {
 	checkend(cmdline);
	if(gosubindex == 0 || gosubstack[gosubindex - 1] != NULL) error("Nothing to return to");
	nextstmt = (unsigned char *)"\0\0\0";                                            // now terminate this run of ExecuteProgram()
}



void MIPS16 cmd_read(void) {
    int i, j, k, len, card;
    unsigned char *p, *lineptr = NULL, *ptr;
	unsigned short  datatoken;
    int vcnt, vidx, num_to_read=0;
	if (checkstring(cmdline, (unsigned char*)"SAVE")) {
		if(restorepointer== MAXRESTORE - 1)error((char*)"Too many saves");
		datastore[restorepointer].SaveNextDataLine = NextDataLine;
		datastore[restorepointer].SaveNextData = NextData;
		restorepointer++;
		return;
	}
	if (checkstring(cmdline, (unsigned char*)"RESTORE")) {
		if (!restorepointer)error((char*)"Nothing to restore");
		restorepointer--;
		NextDataLine = datastore[restorepointer].SaveNextDataLine;
		NextData = datastore[restorepointer].SaveNextData;
		return;
	}
    getargs(&cmdline, (MAX_ARG_COUNT * 2) - 1, (unsigned char *)",");                // getargs macro must be the first executable stmt in a block
    if(argc == 0) error("Syntax");
	// first count the elements and do the syntax checking
    for(vcnt = i = 0; i < argc; i++) {
        if(i & 0x01) {
            if(*argv[i] != ',') error("Syntax");
        } else {
			findvar(argv[i], V_FIND | V_EMPTY_OK);
			if(g_vartbl[g_VarIndex].type & T_CONST) error("Cannot change a constant");
			card=1;
			if(emptyarray){ //empty array
				for(k=0;k<MAXDIM;k++){
					j=(g_vartbl[g_VarIndex].dims[k] - g_OptionBase + 1);
					if(j)card *= j;
				}
			}
			num_to_read+=card;
		}
	}
    char **vtbl=GetTempMemory(num_to_read * sizeof (char *));
    int *vtype=GetTempMemory(num_to_read * sizeof (int));
    int *vsize=GetTempMemory(num_to_read * sizeof (int));
    // step through the arguments and save the pointer and type
    for(vcnt = i = 0; i < argc; i+=2) {
		ptr = findvar(argv[i], V_FIND | V_EMPTY_OK);
		vtbl[vcnt] = (char *)ptr;
		card=1;
		if(emptyarray){ //empty array
			for(k=0;k<MAXDIM;k++){
				j=(g_vartbl[g_VarIndex].dims[k] - g_OptionBase + 1);
				if(j)card *= j;
			}
		}
		for(k=0;k<card;k++){
			if(k){
				if(g_vartbl[g_VarIndex].type & (T_INT | T_NBR))ptr+=8;
				else ptr+=g_vartbl[g_VarIndex].size+1;
				vtbl[vcnt]=(char *)ptr;
			}
			vtype[vcnt] = TypeMask(g_vartbl[g_VarIndex].type);
			vsize[vcnt] = g_vartbl[g_VarIndex].size;
			vcnt++;
		}
    }

    // setup for a search through the whole memory
    vidx = 0;
    datatoken = GetCommandValue((unsigned char *)"Data");
    p = lineptr = NextDataLine;
    if(*p == 0xff) error("No DATA to read");                        // error if there is no program

  // search looking for a DATA statement.  We keep returning to this point until all the data is found
search_again:
    while(1) {
        if(*p == 0) p++;                                            // if it is at the end of an element skip the zero marker
        if(*p == 0/* || *p == 0xff*/) error("No DATA to read");         // end of the program and we still need more data
        if(*p == T_NEWLINE) lineptr = p++;
        if(*p == T_LINENBR) p += 3;
        skipspace(p);
        if(*p == T_LABEL) {                                         // if there is a label here
            p += p[1] + 2;                                          // skip over the label
            skipspace(p);                                           // and any following spaces
        }
        CommandToken tkn=commandtbl_decode(p);
        if(tkn == datatoken) break;                                  // found a DATA statement
        while(*p) p++;                                              // look for the zero marking the start of the next element
    }
    NextDataLine = lineptr;
    p+=sizeof(CommandToken);                                                            // step over the token
    skipspace(p);
    if(!*p || *p == '\'') { CurrentLinePtr = lineptr; error("No DATA to read"); }

        // we have a DATA statement, first split the line into arguments
        {                                                           // new block, the getargs macro must be the first executable stmt in a block
        getargs(&p, (MAX_ARG_COUNT * 2) - 1, (unsigned char *)",");
        if((argc & 1) == 0) { CurrentLinePtr = lineptr; error("Syntax"); }
        // now step through the variables on the READ line and get their new values from the argument list
        // we set the line number to the number of the DATA stmt so that any errors are reported correctly
        while(vidx < vcnt) {
            // check that there is some data to read if not look for another DATA stmt
            if(NextData > argc) {
                skipline(p);
                NextData = 0;
                goto search_again;
            }
            CurrentLinePtr = lineptr;
            if(vtype[vidx] & T_STR) {
                char *p1, *p2;
                if(*argv[NextData] == '"') {                               // if quoted string
                  	int toggle=0;
                    for(len = 0, p1 = vtbl[vidx], p2 = (char *)argv[NextData] + 1; *p2 && *p2 != '"'; len++) {
                    	if(*p2=='\\' && p2[1]!='"' && OptionEscape)toggle^=1;
	                    if(toggle){
	                        if(*p2=='\\' && isdigit((unsigned char)p2[1]) && isdigit((unsigned char)p2[2]) && isdigit((unsigned char)p2[3])){
	                            p2++;
	                            i=(*p2++)-48;
	                            i*=10;
	                            i+=(*p2++)-48;
	                            i*=10;
	                            i+=(*p2++)-48;
                                if(i==0)error("Null character \\000 in escape sequence - use CHR$(0)","$");
	                            *p1++=i;
	                        } else {
	                            p2++;
	                            switch(*p2){
	                                case '\\':
	                                    *p1++='\\';
	                                    p2++;
	                                    break;
	                                case 'a':
	                                    *p1++='\a';
	                                    p2++;
	                                    break;
	                                case 'b':
	                                    *p1++='\b';
	                                    p2++;
	                                    break;
	                                case 'e':
	                                    *p1++='\e';
	                                    p2++;
	                                    break;
	                                case 'f':
	                                    *p1++='\f';
	                                    p2++;
	                                    break;
	                                case 'n':
	                                    *p1++='\n';
	                                    p2++;
	                                    break;
	                                case 'q':
	                                    *p1++='\"';
	                                    p2++;
	                                    break;
	                                case 'r':
	                                    *p1++='\r';
	                                    p2++;
	                                    break;
	                                case 't':
	                                    *p1++='\t';
	                                    p2++;
	                                    break;
	                                case 'v':
	                                    *p1++='\v';
	                                    p2++;
	                                    break;
	                                case '&':
	                                    p2++;
	                                    if(isxdigit((unsigned char)*p2) && isxdigit((unsigned char)p2[1])){
	                                        i=0;
	                                        i = (i << 4) | ((mytoupper(*p2) >= 'A') ? mytoupper(*p2) - 'A' + 10 : *p2 - '0');
	                                        p++;
	                                        i = (i << 4) | ((mytoupper(*p2) >= 'A') ? mytoupper(*p2) - 'A' + 10 : *p2 - '0');
                                			if(i==0)error("Null character \\&00 in escape sequence - use CHR$(0)","$");
	                                        p2++;
	                                        *p1++=i;
	                                    } else *p1++='x';
	                                    break;
	                                default:
	                                    *p1++=*p2++;
	                            }
	                        }
	                        toggle=0;
	                    } else *p1++ = *p2++;
                    }
                } else {                                            // else if not quoted
                    for(len = 0, p1 = vtbl[vidx], p2 = (char *)argv[NextData]; *p2 && *p2 != '\'' ; len++, p1++, p2++) {
                        if(*p2 < 0x20 || *p2 >= 0x7f) error("Invalid character");
                        *p1 = *p2;                                  // copy up to the comma
                    }
                }
                if(len > vsize[vidx]) error("String too long");
                *p1 = 0;                                            // terminate the string
                CtoM((unsigned char *)vtbl[vidx]);                                   // convert to a MMBasic string
            }
            else if(vtype[vidx] & T_INT)
                *((long long int *)vtbl[vidx]) = getinteger(argv[NextData]); // much easier if integer variable
            else
                *((MMFLOAT *)vtbl[vidx]) = getnumber(argv[NextData]);      // same for numeric variable

            vidx++;
            NextData += 2;
        }
    }
}

void cmd_call(void){
	int i;
	unsigned char *p=getCstring(cmdline); //get the command we want to call
    unsigned char *q = skipexpression(cmdline);
	if(*q==',')q++;
	i = FindSubFun(p, false);                   // it could be a defined command
	strcat((char *)p," ");
	strcat((char *)p,(char *)q);
	if(i >= 0) {                                // >= 0 means it is a user defined command
		DefinedSubFun(false, p, i, NULL, NULL, NULL, NULL);
	}
	else
		error("Unknown user subroutine");
}



void MIPS16 cmd_restore(void) {
   if(*cmdline == 0 || *cmdline == '\'') {
       if(CurrentLinePtr >= ProgMemory && CurrentLinePtr < ProgMemory + MAX_PROG_SIZE )
           NextDataLine = ProgMemory;
       else
           NextDataLine = LibMemory;
       NextData = 0;
	} else {
		skipspace(cmdline);
		if(*cmdline=='"') {
			NextDataLine = findlabel(getCstring(cmdline));
			NextData = 0;
		}
		else if(isdigit(*cmdline) || *cmdline==GetTokenValue((unsigned char *)"+") || *cmdline==GetTokenValue((unsigned char *)"-")  || *cmdline=='.'){
			NextDataLine = findline(getinteger(cmdline), true); // try for a line number
			NextData = 0;
		} else {
			void *ptr=findvar(cmdline,V_NOFIND_NULL);
			if(ptr){
				if(g_vartbl[g_VarIndex].type & T_NBR) {
					if(g_vartbl[g_VarIndex].dims[0] > 0) { // Not an array
						error("Syntax");
					}
					NextDataLine = findline(getinteger(cmdline), true);
				} else if(g_vartbl[g_VarIndex].type & T_INT) {
					if(g_vartbl[g_VarIndex].dims[0] > 0) { // Not an array
						error("Syntax");
					}
					NextDataLine = findline(getinteger(cmdline), true);
				} else {
					NextDataLine = findlabel(getCstring(cmdline));    // must be a label
				}
			} else if(isnamestart(*cmdline)) {
				NextDataLine = findlabel(cmdline);    // must be a label
			}
			NextData = 0;
		}
	}
}



void cmd_lineinput(void) {
	unsigned char *vp;
	int i, fnbr;
	getargs(&cmdline, 3, (unsigned char *)",;");										// this is a macro and must be the first executable stmt
	if(argc == 0 || argc == 2) error("Syntax");

	i = 0;
	fnbr = 0;
	if(argc == 3) {
		// is the first argument a file number specifier?  If so, get it
		if(*argv[0] == '#' && *argv[1] == ',') {
			argv[0]++;
			fnbr = getinteger(argv[0]);
		}
		else {
			// is the first argument a prompt?  if so, print it otherwise there are too many arguments
			if(*argv[1] != ',' && *argv[1] != ';') error("Syntax");
			MMfputs((unsigned char *)getstring(argv[0]), 0);
		}
	i = 2;
	}

	if(argc - i != 1) error("Syntax");
	vp = findvar(argv[i], V_FIND);
    if(g_vartbl[g_VarIndex].type & T_CONST) error("Cannot change a constant");
	if(!(g_vartbl[g_VarIndex].type & T_STR)) error("Invalid variable");
	MMgetline(fnbr, (char *)inpbuf);									    // get the input line
	if(strlen((char *)inpbuf) > g_vartbl[g_VarIndex].size) error("String too long");
	strcpy((char *)vp, (char *)inpbuf);
	CtoM(vp);														// convert to a MMBasic string
}


void cmd_on(void) {
	int r;
	unsigned char ss[4];													    // this will be used to split up the argument line
    unsigned char *p;
	// first check if this is:   ON KEY location
	p = checkstring(cmdline, (unsigned char *)"PS2");
	if(p){
		getargs(&p,1,(unsigned char *)",");
		if(*argv[0] == '0' && !isdigit(*(argv[0]+1))){
			OnPS2GOSUB = NULL;                                      // the program wants to turn the interrupt off
		} else {
			OnPS2GOSUB = GetIntAddress(argv[0]);						    // get a pointer to the interrupt routine
			InterruptUsed = true;
		}
		return;
	}
	p = checkstring(cmdline, (unsigned char *)"KEY");
	if(p) {
		getargs(&p,3,(unsigned char *)",");
		if(argc==1){
			if(*argv[0] == '0' && !isdigit(*(argv[0]+1))){
				OnKeyGOSUB = NULL;                                      // the program wants to turn the interrupt off
			} else {
				OnKeyGOSUB = GetIntAddress(argv[0]);						    // get a pointer to the interrupt routine
				InterruptUsed = true;
			}
			return;
		} else {
			keyselect=getint(argv[0],0,255);
			if(keyselect==0){
				KeyInterrupt = NULL;                                      // the program wants to turn the interrupt off
			} else {
				if(*argv[2] == '0' && !isdigit(*(argv[2]+1))){
					KeyInterrupt = NULL;                                      // the program wants to turn the interrupt off
				} else {
					KeyInterrupt = (char *)GetIntAddress(argv[2]);						    // get a pointer to the interrupt routine
					InterruptUsed = true;
				}
			}
			return;
		}
	}
    p = checkstring(cmdline, (unsigned char *)"ERROR");
	if(p) {
		if(checkstring(p, (unsigned char *)"ABORT")) {
            OptionErrorSkip = 0;
            return;
        }
        MMerrno = 0;                                                // clear the error flags
        *MMErrMsg = 0;
        if(checkstring(p, (unsigned char *)"CLEAR")) return;
		if(checkstring(p, (unsigned char *)"IGNORE")) {
            OptionErrorSkip = -1;
            return;
        }
		if((p = checkstring(p, (unsigned char *)"SKIP"))) {
            if(*p == 0 || *p == (unsigned char)'\'')
                OptionErrorSkip = 2;
            else
                OptionErrorSkip = getint(p, 1, 10000) + 1;
            return;
        }
        error("Syntax");
	}

	// if we got here the command must be the traditional:  ON nbr GOTO|GOSUB line1, line2,... etc

	ss[0] = tokenGOTO;
	ss[1] = tokenGOSUB;
	ss[2] = ',';
	ss[3] = 0;
	{																// start a new block
		getargs(&cmdline, (MAX_ARG_COUNT * 2) - 1, ss);				// getargs macro must be the first executable stmt in a block
		if(argc < 3 || !(*argv[1] == ss[0] || *argv[1] == ss[1])) error("Syntax");
		if(argc%2 == 0) error("Syntax");

		r = getint(argv[0], 0, 255);									// evaluate the expression controlling the statement
		if(r == 0 || r > argc/2) return;							// microsoft say that we just go on to the next line

		if(*argv[1] == ss[1]) {
			// this is a GOSUB, same as a GOTO but we need to first push the return pointer
			if(gosubindex >= MAXGOSUB) error("Too many nested GOSUB");
            errorstack[gosubindex] = CurrentLinePtr;
			gosubstack[gosubindex++] = nextstmt;
        	g_LocalIndex++;
		}

		if(isnamestart(*argv[r*2]))
			nextstmt = findlabel(argv[r*2]);						// must be a label
		else
			nextstmt = findline(getinteger(argv[r*2]), true);		// try for a line number
	}
//    IgnorePIN = false;
}

/**
 * @cond
 * The following section will be excluded from the documentation.
 */
// utility routine used by DoDim() below and other places in the interpreter
// checks if the type has been explicitly specified as in DIM FLOAT A, B, ... etc
unsigned char *CheckIfTypeSpecified(unsigned char *p, int *type, int AllowDefaultType) {
    unsigned char *tp;

    if((tp = checkstring(p, (unsigned char *)"INTEGER")) != NULL)
        *type = T_INT | T_IMPLIED;
    else if((tp = checkstring(p, (unsigned char *)"STRING")) != NULL)
        *type = T_STR | T_IMPLIED;
    else if((tp = checkstring(p, (unsigned char *)"FLOAT")) != NULL)
        *type = T_NBR | T_IMPLIED;
    else {
        if(!AllowDefaultType) error("Variable type");
        tp = p;
        *type = DefaultType;                                        // if the type is not specified use the default
    }
    return tp;
}



unsigned char *SetValue(unsigned char *p, int t, void *v) {
    MMFLOAT f;
    long long int  i64;
    unsigned char *s;
    char TempCurrentSubFunName[MAXVARLEN + 1];
    strcpy(TempCurrentSubFunName, (char *)CurrentSubFunName);			    // save the current sub/fun name
	if(t & T_STR) {
		p = evaluate(p, &f, &i64, &s, &t, true);
		Mstrcpy(v, s);
	}
	else if(t & T_NBR) {
		p = evaluate(p, &f, &i64, &s, &t, false);
		if(t & T_NBR)
            (*(MMFLOAT *)v) = f;
        else
            (*(MMFLOAT *)v) = (MMFLOAT)i64;
	} else {
		p = evaluate(p, &f, &i64, &s, &t, false);
		if(t & T_INT)
            (*(long long int  *)v) = i64;
        else
            (*(long long int  *)v) = FloatToInt64(f);
	}
	strcpy((char *)CurrentSubFunName, TempCurrentSubFunName);			    // restore the current sub/fun name
    return p;
}

/** @endcond */


// define a variable
// DIM [AS INTEGER|FLOAT|STRING] var[(d1 [,d2,...]] [AS INTEGER|FLOAT|STRING] [, ..., ...]
// LOCAL also uses this function the routines only differ in that LOCAL can only be used in a sub/fun
void MIPS16 cmd_dim(void) {
	int i, j, k, type, typeSave, ImpliedType = 0, VIndexSave, StaticVar = false;
    unsigned char *p, chSave, *chPosit;
    unsigned char VarName[(MAXVARLEN * 2) + 1];
    void *v, *tv;

    if(*cmdline == tokenAS) cmdline++;                              // this means that we can use DIM AS INTEGER a, b, etc
    p = CheckIfTypeSpecified(cmdline, &type, true);                 // check for DIM FLOAT A, B, ...
    ImpliedType = type;
    {                                                               // getargs macro must be the first executable stmt in a block
        getargs(&p, (MAX_ARG_COUNT * 2) - 1, (unsigned char *)",");
        if((argc & 0x01) == 0) error("Syntax");

        for(i = 0; i < argc; i += 2) {
            p = skipvar(argv[i], false);                            // point to after the variable
            while(!(*p == 0 || *p == tokenAS || *p == (unsigned char)'\'' || *p == tokenEQUAL))
                p++;                                                // skip over a LENGTH keyword if there and see if we can find "AS"
            chSave = *p; chPosit = p; *p = 0;                       // save the char then terminate the string so that LENGTH is evaluated correctly
            if(chSave == tokenAS) {                                 // are we using Microsoft syntax (eg, AS INTEGER)?
                if(ImpliedType & T_IMPLIED) error("Type specified twice");
                p++;                                                // step over the AS token
                p = CheckIfTypeSpecified(p, &type, true);           // and get the type
                if(!(type & T_IMPLIED)) error("Variable type");
            }

            if(cmdtoken == cmdLOCAL) {
                if(g_LocalIndex == 0) error("Invalid here");
                type |= V_LOCAL;                                    // local if defined in a sub/fun
            }

            if(cmdtoken == cmdSTATIC) {
                if(g_LocalIndex == 0) error("Invalid here");
                // create a unique global name
                if(*CurrentInterruptName)
                    strcpy((char *)VarName, CurrentInterruptName);          // we must be in an interrupt sub
                else
                    strcpy((char *)VarName, CurrentSubFunName);             // normal sub/fun
                for(k = 1; k <= MAXVARLEN; k++) if(!isnamechar(VarName[k])) {
                    VarName[k] = 0;                                 // terminate the string on a non valid char
                    break;
                }
				strcat((char *)VarName,".");
                strcat((char *)VarName, (char *)argv[i]);					        // by prefixing the var name with the sub/fun name
            	StaticVar = true;
            } else
            	strcpy((char *)VarName, (char *)argv[i]);

            v = findvar(VarName, type | V_NOFIND_NULL);             // check if the variable exists
            typeSave = type;
            VIndexSave = g_VarIndex;
			if(v == NULL) {											// if not found
                v = findvar(VarName, type | V_FIND | V_DIM_VAR);    // create the variable
                type = TypeMask(g_vartbl[g_VarIndex].type);
                VIndexSave = g_VarIndex;
                *chPosit = chSave;                                  // restore the char previously removed
                if(g_vartbl[g_VarIndex].dims[0] == -1) error("Array dimensions");
                if(g_vartbl[g_VarIndex].dims[0] > 0) {
                    g_DimUsed = true;                                 // prevent OPTION BASE from being used
                    v = g_vartbl[g_VarIndex].val.s;
                }
                while(*p && *p != '\'' && tokenfunction(*p) != op_equal) p++;	// search through the line looking for the equals sign
            	if(tokenfunction(*p) == op_equal) {
                    p++;                                            // step over the equals sign
                    skipspace(p);
                    if(g_vartbl[g_VarIndex].dims[0] > 0 && *p == '(') {
                        // calculate the overall size of the array
                        for(j = 1, k = 0; k < MAXDIM && g_vartbl[VIndexSave].dims[k]; k++) {
                            j *= (g_vartbl[VIndexSave].dims[k] + 1 - g_OptionBase);
                        }
                        do {
                            p++;                                    // step over the opening bracket or terminating comma
                            p = SetValue(p, type, v);
                            if(type & T_STR) v = (char *)v + g_vartbl[VIndexSave].size + 1;
                            if(type & T_NBR) v = (char *)v + sizeof(MMFLOAT);
                            if(type & T_INT) v = (char *)v + sizeof(long long int);
                            skipspace(p); j--;
                        } while(j > 0 && *p == ',');
                        if(*p != ')') error("Number of initialising values");
                        if(j != 0) error("Number of initialising values");
                    } else
                        SetValue(p, type, v);
                }
                type = ImpliedType;
             } else {
             	if(!StaticVar) error("$ already declared", VarName);
             }


			 // if it is a STATIC var create a local var pointing to the global var
             if(StaticVar) {
                tv = findvar(argv[i], typeSave | V_LOCAL | V_NOFIND_NULL);    						// check if the local variable exists
                if(tv != NULL) error("$ already declared", argv[i]);
                tv = findvar(argv[i], typeSave | V_LOCAL | V_FIND | V_DIM_VAR);         			// create the variable
                if(g_vartbl[VIndexSave].dims[0] > 0 || (g_vartbl[VIndexSave].type & T_STR)) {
                    FreeMemory(tv);                                                                 // we don't need the memory allocated to the local
                    g_vartbl[g_VarIndex].val.s = g_vartbl[VIndexSave].val.s;                              // point to the memory of the global variable
                } else
                    g_vartbl[g_VarIndex].val.ia = &(g_vartbl[VIndexSave].val.i);                    		// point to the data of the variable
                g_vartbl[g_VarIndex].type = g_vartbl[VIndexSave].type | T_PTR;           					// set the type to a pointer
                g_vartbl[g_VarIndex].size = g_vartbl[VIndexSave].size;                   					// just in case it is a string copy the size
                for(j = 0; j < MAXDIM; j++) g_vartbl[g_VarIndex].dims[j] = g_vartbl[VIndexSave].dims[j];  // just in case it is an array copy the dimensions
			}
        }
    }
}




void  cmd_const(void) {
    unsigned char *p;
    void *v;
    int i, type;

	getargs(&cmdline, (MAX_ARG_COUNT * 2) - 1, (unsigned char *)",");				// getargs macro must be the first executable stmt in a block
	if((argc & 0x01) == 0) error("Syntax");

    for(i = 0; i < argc; i += 2) {
        p = skipvar(argv[i], false);                                // point to after the variable
        skipspace(p);
        if(tokenfunction(*p) != op_equal) error("Syntax");  // must be followed by an equals sign
        p++;                                                        // step over the equals sign
        type = T_NOTYPE;
        v = DoExpression(p, &type);                                 // evaluate the constant's value
        type = TypeMask(type);
        type |= V_FIND | V_DIM_VAR | T_CONST | T_IMPLIED;
        if(g_LocalIndex != 0) type |= V_LOCAL;                        // local if defined in a sub/fun
        findvar(argv[i], type);                                     // create the variable
        if(g_vartbl[g_VarIndex].dims[0] != 0) error("Invalid constant");
        if(TypeMask(g_vartbl[g_VarIndex].type) != TypeMask(type)) error("Invalid constant");
        else {
            if(type & T_NBR) g_vartbl[g_VarIndex].val.f = *(MMFLOAT *)v;           // and set its value
            if(type & T_INT) g_vartbl[g_VarIndex].val.i = *(long long int  *)v;
            if(type & T_STR) {
				if((unsigned char)*(unsigned char *)v<(MAXDIM-1)*sizeof(g_vartbl[g_VarIndex].dims[1])){
					FreeMemorySafe((void **)&g_vartbl[g_VarIndex].val.s);
					g_vartbl[g_VarIndex].val.s=(void *)&g_vartbl[g_VarIndex].dims[1];
				}
				Mstrcpy((unsigned char *)g_vartbl[g_VarIndex].val.s, (unsigned char *)v);
			}
        }
    }
}

/**
 * @cond
 * The following section will be excluded from the documentation.
 */

// utility function used by llist() below
// it copys a command or function honouring the case selected by the user
void strCopyWithCase(char *d, char *s) {
    if(Option.Listcase == CONFIG_LOWER) {
        while(*s) *d++ = tolower(*s++);
    } else if(Option.Listcase == CONFIG_UPPER) {
        while(*s) *d++ = mytoupper(*s++);
    } else {
        while(*s) *d++ = *s++;
    }
    *d = 0;
}

void replaceAlpha(char *str, const char *replacements[MMEND]){
    char buffer[STRINGSIZE]; // Buffer to store the modified string
    int bufferIndex = 0;
    int len = strlen(str);
    int i = 0;

    while (i < len) {
        // Check for the pattern "~(X)" where X is an uppercase letter
        if (i<len-3 && str[i] == '~' && str[i + 1] == '(' && isupper((int)str[i + 2]) && str[i + 3] == ')') {
            char alpha = str[i + 2]; // Extract the letter 'alpha'
            const char *replacement = replacements[alpha - 'A']; // Get the replacement string

            // Copy the replacement string into the buffer
            strcpy(&buffer[bufferIndex], replacement);
            bufferIndex += strlen(replacement);

            i += 4; // Move past "~(X)"
        } else {
            // Copy the current character to the buffer
            buffer[bufferIndex++] = str[i++];
        }
    }

    buffer[bufferIndex] = '\0'; // Null-terminate the buffer
    strcpy(str,  buffer); // Copy the buffer back into the original string
}
int format_string(char *c, int n) {
	int count=0;
	n-=2;
    int len = strlen(c);
	if(*c==0)return 0;
    char *result = GetMemory(len * 2); // Allocate enough space for the modified string
    int pos = 0, start = 0;

    while (start < len) {
        int split_pos = start + n - 1;

        if (split_pos >= len) { // If remaining text fits in one line
            strcpy(result + pos, c + start);
            pos += strlen(c + start);
            break;
        }

        while (split_pos > start && !(c[split_pos] == ' ' || c[split_pos] == ',')) {
            split_pos--; // Try to find a space to break at
        }

        if (split_pos == start) {
            split_pos = start + n - 1; // No space found, force a split
        }

        strncpy(result + pos, c + start, split_pos - start + 1);
        pos += (split_pos - start + 1);

        start = split_pos + 1;

        if (start < len) { // Only add underscore if not the last substring
            result[pos++] = ' ';
            result[pos++] = Option.continuation;
            result[pos++] = '\n';
			count++;
        }
    }

    result[pos] = '\0';
	strcpy(c,result);
	FreeMemory((void *)result);
	return count;
}
// list a line into a buffer (b) given a pointer to the beginning of the line (p).
// the returned string is a C style string (terminated with a zero)
// this is used by cmd_list(), cmd_edit() and cmd_xmodem()
unsigned char  *llist(unsigned char *b, unsigned char *p) {
	int i, firstnonwhite = true;
    unsigned char *b_start = b;

	while(1) {
        if(*p == T_NEWLINE) {
            p++;
            firstnonwhite = true;
            continue;
        }

		if(*p == T_LINENBR) {
			i = (((p[1]) << 8) | (p[2]));							// get the line number
			p += 3;													// and step over the number
                IntToStr((char *)b, i, 10);
                b += strlen((char *)b);
				if(*p != ' ') *b++ = ' ';
			}

		if(*p == T_LABEL) {											// got a label
			for(i = p[1], p += 2; i > 0; i--)
				*b++ = *p++;										// copy to the buffer
			*b++ = ':';												// terminate with a colon
			if(*p && *p != ' ') *b++ = ' ';							// and a space if necessary
			firstnonwhite = true;
			}														// this deliberately drops through in case the label is the only thing on the line

		if(*p >= C_BASETOKEN) {
			if(firstnonwhite) {
        		CommandToken tkn=commandtbl_decode(p);
				if(tkn == GetCommandValue( (unsigned char *)"Let"))
					*b = 0;											// use nothing if it LET
				else {
					strCopyWithCase((char *)b, (char *)commandname(tkn));			// expand the command (if it is not LET)
					if(*b=='_'){
						if(!strncasecmp((char *)&b[1],"SIDE SET",8) ||
							!strncasecmp((char *)&b[1],"END PROGRAM",11) ||
							!strncasecmp((char *)&b[1],"WRAP",4) ||
							!strncasecmp((char *)&b[1],"LINE",4) ||
							!strncasecmp((char *)&b[1],"PROGRAM",7) ||
							!strncasecmp((char *)&b[1],"LABEL",5)
						) *b='.';
						else if(b[1]=='(')*b='&';
					} 
						b += strlen((char *)b);                                 // update pointer to the end of the buffer
                    if(isalpha(*(b - 1))) *b++ = ' ';               // add a space to the end of the command name
                }
				firstnonwhite = false;
				p+=sizeof(CommandToken);
			} else {												// not a command so must be a token
				strCopyWithCase((char *)b, (char *)tokenname(*p));					// expand the token
                    b += strlen((char *)b);                                 // update pointer to the end of the buffer
				if(*p == tokenTHEN || *p == tokenELSE)
					firstnonwhite = true;
				else
					firstnonwhite = false;
				p++;
			}
			continue;
		}

		// hey, an ordinary char, just copy it to the output
		if(*p) {
			*b = *p;												// place the char in the buffer
			if(*p != ' ') firstnonwhite = false;
			p++;  b++;												// move the pointers
			continue;
		}

        // at this point the char must be a zero
        // zero char can mean both a separator or end of line
		if(!(p[1] == T_NEWLINE || p[1] == 0)) {
			*b++ = ':';												// just a separator
			firstnonwhite = true;
			p++;
			continue;
		}

		// must be the end of a line - so return to the caller
        while(*(b-1) == ' ' && b > b_start) --b;                    // eat any spaces on the end of the line
		*b = 0;	
		replaceAlpha((char *)b_start, overlaid_functions) ;  //replace the user version of all the MM. functions
		STR_REPLACE((char *)b_start, "PEEK(INT8", "PEEK(BYTE",0);
		return ++p;
	} // end while
}



void execute_one_command(unsigned char *p) {
    int i;

	CheckAbort();
	targ = T_CMD;
	skipspace(p);													// skip any whitespace
	if(p[0]>= C_BASETOKEN && p[1]>=C_BASETOKEN){
//                    if(*(char*)p >= C_BASETOKEN && *(char*)p - C_BASETOKEN < CommandTableSize - 1 && (commandtbl[*(char*)p - C_BASETOKEN].type & T_CMD)) {
        CommandToken cmd=commandtbl_decode(p);
        if(cmd == cmdWHILE || cmd == cmdDO || cmd == cmdFOR) error("Invalid inside THEN ... ELSE") ;
		cmdtoken=cmd;
		cmdline = p + sizeof(CommandToken);
        skipspace(cmdline);
		commandtbl[cmd].fptr(); // execute the command
	} else {
	    if(!isnamestart(*p)) error("Invalid character");
        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");
	}
	ClearTempMemory();											    // at the end of each command we need to clear any temporary string vars
}

void execute(char* mycmd) {
	//    char *temp_tknbuf;
	unsigned char* ttp=NULL;
	int i = 0, toggle = 0;
	//    temp_tknbuf = GetTempStrMemory();
	//    strcpy(temp_tknbuf, tknbuf);
		// first save the current token buffer in case we are in immediate mode
		// we have to fool the tokeniser into thinking that it is processing a program line entered at the console
	skipspace(mycmd);
	strcpy((char *)inpbuf, (const char *)getCstring((unsigned char *)mycmd));                                      // then copy the argument
	if (!(toupper(inpbuf[0]) == 'R' && toupper(inpbuf[1]) == 'U' && toupper(inpbuf[2]) == 'N')) { //convert the string to upper case
		while (inpbuf[i]) {
			if (inpbuf[i] == 34) {
				if (toggle == 0)toggle = 1;
				else toggle = 0;
			}
			if (!toggle) {
				if (inpbuf[i] == ':')error((char *)"Only single statements allowed");
				inpbuf[i] = toupper(inpbuf[i]);
			}
			i++;
		}
		multi=false;
		tokenise(true);                                                 // and tokenise it (the result is in tknbuf)
		memset(inpbuf, 0, STRINGSIZE);
		tknbuf[strlen((char *)tknbuf)] = 0;
		tknbuf[strlen((char*)tknbuf) + 1] = 0;
		if(CurrentLinePtr)ttp = nextstmt;                                                 // save the globals used by commands
		ScrewUpTimer = 1000;
		ExecuteProgram(tknbuf);                                              // execute the function's code
		ScrewUpTimer = 0;
		// g_TempMemoryIsChanged = true;                                     // signal that temporary memory should be checked
		if(CurrentLinePtr)nextstmt = ttp;
		return;
	}
	else {
		unsigned char* p = inpbuf;
//		char* q;
//		char fn[STRINGSIZE] = { 0 };
        unsigned short tkn=GetCommandValue((unsigned char *)"RUN");
        tknbuf[0] = (tkn & 0x7f ) + C_BASETOKEN;
        tknbuf[1] = (tkn >> 7) + C_BASETOKEN; //tokens can be 14-bit
		p[0] = (tkn & 0x7f ) + C_BASETOKEN;
		p[1] = (tkn >> 7) + C_BASETOKEN; //tokens can be 14-bit
		memmove(&p[2], &p[4], strlen((char *)p) - 4);
/*		if ((q = strchr((char *)p, ':'))) {
			q--;
			*q = '0';
		}*/
		p[strlen((char*)p) - 2] = 0;
//		MMPrintString(fn); PRet();
//		CloseAudio(1);
		strcpy((char *)tknbuf, (char*)inpbuf);
		if (CurrentlyPlaying != P_NOTHING)CloseAudio(1);
		longjmp(jmprun, 1);
	}
}
/** @endcond */

void cmd_execute(void) {
	execute((char*)cmdline);
}


