//////////////////////////////////////////////////////////////////////////////// // TinyBasic Plus //////////////////////////////////////////////////////////////////////////////// // // Authors: Mike Field // Scott Lawrence // #define kVersion "v0.13" // v0.13: 2013-03-04 // Support for Arduino 1.5 (SPI.h included, additional changes for DUE support) // // v0.12: 2013-03-01 // EEPROM load and save routines added: EFORMAT, ELIST, ELOAD, ESAVE, ECHAIN // added EAUTORUN option (chains to EEProm saved program on startup) // Bugfixes to build properly on non-arduino systems (PROGMEM #define workaround) // cleaned up a bit of the #define options wrt TONE // // v0.11: 2013-02-20 // all display strings and tables moved to PROGMEM to save space // removed second serial // removed pinMode completely, autoconf is explicit // beginnings of EEPROM related functionality (new,load,save,list) // // v0.10: 2012-10-15 // added kAutoConf, which eliminates the "PINMODE" statement. // now, DWRITE,DREAD,AWRITE,AREAD automatically set the PINMODE appropriately themselves. // should save a few bytes in your programs. // // v0.09: 2012-10-12 // Fixed directory listings. FILES now always works. (bug in the SD library) // ref: http://arduino.cc/forum/index.php/topic,124739.0.html // fixed filesize printouts (added printUnum for unsigned numbers) // #defineable baud rate for slow connection throttling //e // v0.08: 2012-10-02 // Tone generation through piezo added (TONE, TONEW, NOTONE) // // v0.07: 2012-09-30 // Autorun buildtime configuration feature // // v0.06: 2012-09-27 // Added optional second serial input, used for an external keyboard // // v0.05: 2012-09-21 // CHAIN to load and run a second file // RND,RSEED for random stuff // Added "!=" for "<>" synonym // Added "END" for "STOP" synonym (proper name for the functionality anyway) // // v0.04: 2012-09-20 // DELAY ms - for delaying // PINMODE , INPUT|IN|I|OUTPUT|OUT|O // DWRITE , HIGH|HI|1|LOW|LO|0 // AWRITE , [0..255] // fixed "save" appending to existing files instead of overwriting // Updated for building desktop command line app (incomplete) // // v0.03: 2012-09-19 // Integrated Jurg Wullschleger whitespace,unary fix // Now available through github // Project renamed from "Tiny Basic in C" to "TinyBasic Plus" // // v0.02b: 2012-09-17 Scott Lawrence // Better FILES listings // // v0.02a: 2012-09-17 Scott Lawrence // Support for SD Library // Added: SAVE, FILES (mostly works), LOAD (mostly works) (redirects IO) // Added: MEM, ? (PRINT) // Quirk: "10 LET A=B+C" is ok "10 LET A = B + C" is not. // Quirk: INPUT seems broken? // IF testing with Visual C, this needs to be the first thing in the file. //#include "stdafx.h" char eliminateCompileErrors = 1; // fix to suppress arduino build errors // hack to let makefiles work with this file unchanged #ifdef FORCE_DESKTOP #undef ARDUINO #else #define ARDUINO 1 #endif //////////////////////////////////////////////////////////////////////////////// // Feature option configuration... // This enables LOAD, SAVE, FILES commands through the Arduino SD Library // it adds 9k of usage as well. //#define ENABLE_FILEIO 1 #undef ENABLE_FILEIO // this turns on "autorun". if there's FileIO, and a file "autorun.bas", // then it will load it and run it when starting up //#define ENABLE_AUTORUN 1 #undef ENABLE_AUTORUN // and this is the file that gets run #define kAutorunFilename "autorun.bas" // this is the alternate autorun. Autorun the program in the eeprom. // it will load whatever is in the EEProm and run it #define ENABLE_EAUTORUN 1 //#undef ENABLE_EAUTORUN // this will enable the "TONE", "NOTONE" command using a piezo // element on the specified pin. Wire the red/positive/piezo to the kPiezoPin, // and the black/negative/metal disc to ground. // it adds 1.5k of usage as well. //#define ENABLE_TONES 1 #undef ENABLE_TONES #define kPiezoPin 5 // we can use the EEProm to store a program during powerdown. This is // 1kbyte on the '328, and 512 bytes on the '168. Enabling this here will // allow for this funcitonality to work. Note that this only works on AVR // arduino. Disable it for DUE/other devices. #define ENABLE_EEPROM 1 //#undef ENABLE_EEPROM // Sometimes, we connect with a slower device as the console. // Set your console D0/D1 baud rate here (9600 baud default) #define kConsoleBaud 9600 //////////////////////////////////////////////////////////////////////////////// #ifdef ARDUINO #ifndef RAMEND // okay, this is a hack for now // if we're in here, we're a DUE probably (ARM instead of AVR) #define RAMEND 4096-1 // turn off EEProm #undef ENABLE_EEPROM #undef ENABLE_TONES #else // we're an AVR! // we're moving our data strings into progmem #include #endif // includes, and settings for Arduino-specific functionality #ifdef ENABLE_EEPROM #include /* NOTE: case sensitive */ int eepos = 0; #endif #ifdef ENABLE_FILEIO #include #include /* needed as of 1.5 beta */ // Arduino-specific configuration // set this to the card select for your SD shield #define kSD_CS 10 #define kSD_Fail 0 #define kSD_OK 1 File fp; #endif // set up our RAM buffer size for program and user input // NOTE: This number will have to change if you include other libraries. #ifdef ARDUINO #ifdef ENABLE_FILEIO #define kRamFileIO (1030) /* approximate */ #else #define kRamFileIO (0) #endif #ifdef ENABLE_TONES #define kRamTones (40) #else #define kRamTones (0) #endif #endif /* ARDUINO */ #define kRamSize (RAMEND - 1160 - kRamFileIO - kRamTones) #ifndef ARDUINO // Not arduino setup #include #include #undef ENABLE_TONES // size of our program ram #define kRamSize 4096 /* arbitrary */ #ifdef ENABLE_FILEIO FILE * fp; #endif #endif #ifdef ENABLE_FILEIO // functions defined elsehwere void cmd_Files( void ); #endif //////////////////// #ifndef boolean #define boolean int #define true 1 #define false 0 #endif #endif #ifndef byte typedef unsigned char byte; #endif // some catches for AVR based text string stuff... #ifndef PROGMEM #define PROGMEM #endif #ifndef pgm_read_byte #define pgm_read_byte( A ) *(A) #endif //////////////////// #ifdef ENABLE_FILEIO unsigned char * filenameWord(void); static boolean sd_is_initialized = false; #endif boolean inhibitOutput = false; static boolean runAfterLoad = false; static boolean triggerRun = false; // these will select, at runtime, where IO happens through for load/save enum { kStreamSerial = 0, kStreamEEProm, kStreamFile }; static unsigned char inStream = kStreamSerial; static unsigned char outStream = kStreamSerial; //////////////////////////////////////////////////////////////////////////////// // ASCII Characters #define CR '\r' #define NL '\n' #define LF 0x0a #define TAB '\t' #define BELL '\b' #define SPACE ' ' #define SQUOTE '\'' #define DQUOTE '\"' #define CTRLC 0x03 #define CTRLH 0x08 #define CTRLS 0x13 #define CTRLX 0x18 typedef short unsigned LINENUM; #ifdef ARDUINO #define ECHO_CHARS 1 #else #define ECHO_CHARS 0 #endif static unsigned char program[kRamSize]; static const char * sentinel = "HELLO"; static unsigned char *txtpos,*list_line; static unsigned char expression_error; static unsigned char *tempsp; /***********************************************************/ // Keyword table and constants - the last character has 0x80 added to it static unsigned char keywords[] PROGMEM = { 'L','I','S','T'+0x80, 'L','O','A','D'+0x80, 'N','E','W'+0x80, 'R','U','N'+0x80, 'S','A','V','E'+0x80, 'N','E','X','T'+0x80, 'L','E','T'+0x80, 'I','F'+0x80, 'G','O','T','O'+0x80, 'G','O','S','U','B'+0x80, 'R','E','T','U','R','N'+0x80, 'R','E','M'+0x80, 'F','O','R'+0x80, 'I','N','P','U','T'+0x80, 'P','R','I','N','T'+0x80, 'P','O','K','E'+0x80, 'S','T','O','P'+0x80, 'B','Y','E'+0x80, 'F','I','L','E','S'+0x80, 'M','E','M'+0x80, '?'+ 0x80, '\''+ 0x80, 'A','W','R','I','T','E'+0x80, 'D','W','R','I','T','E'+0x80, 'D','E','L','A','Y'+0x80, 'E','N','D'+0x80, 'R','S','E','E','D'+0x80, 'C','H','A','I','N'+0x80, #ifdef ENABLE_TONES 'T','O','N','E','W'+0x80, 'T','O','N','E'+0x80, 'N','O','T','O','N','E'+0x80, #endif #ifdef ARDUINO #ifdef ENABLE_EEPROM 'E','C','H','A','I','N'+0x80, 'E','L','I','S','T'+0x80, 'E','L','O','A','D'+0x80, 'E','F','O','R','M','A','T'+0x80, 'E','S','A','V','E'+0x80, #endif #endif 0 }; // by moving the command list to an enum, we can easily remove sections // above and below simultaneously to selectively obliterate functionality. enum { KW_LIST = 0, KW_LOAD, KW_NEW, KW_RUN, KW_SAVE, KW_NEXT, KW_LET, KW_IF, KW_GOTO, KW_GOSUB, KW_RETURN, KW_REM, KW_FOR, KW_INPUT, KW_PRINT, KW_POKE, KW_STOP, KW_BYE, KW_FILES, KW_MEM, KW_QMARK, KW_QUOTE, KW_AWRITE, KW_DWRITE, KW_DELAY, KW_END, KW_RSEED, KW_CHAIN, #ifdef ENABLE_TONES KW_TONEW, KW_TONE, KW_NOTONE, #endif #ifdef ARDUINO #ifdef ENABLE_EEPROM KW_ECHAIN, KW_ELIST, KW_ELOAD, KW_EFORMAT, KW_ESAVE, #endif #endif KW_DEFAULT /* always the final one*/ }; struct stack_for_frame { char frame_type; char for_var; short int terminal; short int step; unsigned char *current_line; unsigned char *txtpos; }; struct stack_gosub_frame { char frame_type; unsigned char *current_line; unsigned char *txtpos; }; static unsigned char func_tab[] PROGMEM = { 'P','E','E','K'+0x80, 'A','B','S'+0x80, 'A','R','E','A','D'+0x80, 'D','R','E','A','D'+0x80, 'R','N','D'+0x80, 0 }; #define FUNC_PEEK 0 #define FUNC_ABS 1 #define FUNC_AREAD 2 #define FUNC_DREAD 3 #define FUNC_RND 4 #define FUNC_UNKNOWN 5 static unsigned char to_tab[] PROGMEM = { 'T','O'+0x80, 0 }; static unsigned char step_tab[] PROGMEM = { 'S','T','E','P'+0x80, 0 }; static unsigned char relop_tab[] PROGMEM = { '>','='+0x80, '<','>'+0x80, '>'+0x80, '='+0x80, '<','='+0x80, '<'+0x80, '!','='+0x80, 0 }; #define RELOP_GE 0 #define RELOP_NE 1 #define RELOP_GT 2 #define RELOP_EQ 3 #define RELOP_LE 4 #define RELOP_LT 5 #define RELOP_NE_BANG 6 #define RELOP_UNKNOWN 7 static unsigned char highlow_tab[] PROGMEM = { 'H','I','G','H'+0x80, 'H','I'+0x80, 'L','O','W'+0x80, 'L','O'+0x80, 0 }; #define HIGHLOW_HIGH 1 #define HIGHLOW_UNKNOWN 4 #define STACK_SIZE (sizeof(struct stack_for_frame)*5) #define VAR_SIZE sizeof(short int) // Size of variables in bytes static unsigned char *stack_limit; static unsigned char *program_start; static unsigned char *program_end; static unsigned char *stack; // Software stack for things that should go on the CPU stack static unsigned char *variables_begin; static unsigned char *current_line; static unsigned char *sp; #define STACK_GOSUB_FLAG 'G' #define STACK_FOR_FLAG 'F' static unsigned char table_index; static LINENUM linenum; static const unsigned char okmsg[] PROGMEM = "OK"; static const unsigned char whatmsg[] PROGMEM = "What? "; static const unsigned char howmsg[] PROGMEM = "How?"; static const unsigned char sorrymsg[] PROGMEM = "Sorry!"; static const unsigned char initmsg[] PROGMEM = "TinyBasic Plus " kVersion; static const unsigned char memorymsg[] PROGMEM = " bytes free."; #ifdef ARDUINO #ifdef ENABLE_EEPROM static const unsigned char eeprommsg[] PROGMEM = " EEProm bytes total."; static const unsigned char eepromamsg[] PROGMEM = " EEProm bytes available."; #endif #endif static const unsigned char breakmsg[] PROGMEM = "break!"; static const unsigned char unimplimentedmsg[] PROGMEM = "Unimplemented"; static const unsigned char backspacemsg[] PROGMEM = "\b \b"; static const unsigned char indentmsg[] PROGMEM = " "; static const unsigned char sderrormsg[] PROGMEM = "SD card error."; static const unsigned char sdfilemsg[] PROGMEM = "SD file error."; static const unsigned char dirextmsg[] PROGMEM = "(dir)"; static const unsigned char slashmsg[] PROGMEM = "/"; static const unsigned char spacemsg[] PROGMEM = " "; static int inchar(void); static void outchar(unsigned char c); static void line_terminator(void); static short int expression(void); static unsigned char breakcheck(void); /***************************************************************************/ static void ignore_blanks(void) { while(*txtpos == SPACE || *txtpos == TAB) txtpos++; } /***************************************************************************/ static void scantable(unsigned char *table) { int i = 0; table_index = 0; while(1) { // Run out of table entries? if(pgm_read_byte( table ) == 0) return; // Do we match this character? if(txtpos[i] == pgm_read_byte( table )) { i++; table++; } else { // do we match the last character of keywork (with 0x80 added)? If so, return if(txtpos[i]+0x80 == pgm_read_byte( table )) { txtpos += i+1; // Advance the pointer to following the keyword ignore_blanks(); return; } // Forward to the end of this keyword while((pgm_read_byte( table ) & 0x80) == 0) table++; // Now move on to the first character of the next word, and reset the position index table++; table_index++; ignore_blanks(); i = 0; } } } /***************************************************************************/ static void pushb(unsigned char b) { sp--; *sp = b; } /***************************************************************************/ static unsigned char popb() { unsigned char b; b = *sp; sp++; return b; } /***************************************************************************/ void printnum(int num) { int digits = 0; if(num < 0) { num = -num; outchar('-'); } do { pushb(num%10+'0'); num = num/10; digits++; } while (num > 0); while(digits > 0) { outchar(popb()); digits--; } } void printUnum(unsigned int num) { int digits = 0; do { pushb(num%10+'0'); num = num/10; digits++; } while (num > 0); while(digits > 0) { outchar(popb()); digits--; } } /***************************************************************************/ static unsigned short testnum(void) { unsigned short num = 0; ignore_blanks(); while(*txtpos>= '0' && *txtpos <= '9' ) { // Trap overflows if(num >= 0xFFFF/10) { num = 0xFFFF; break; } num = num *10 + *txtpos - '0'; txtpos++; } return num; } /***************************************************************************/ static unsigned char print_quoted_string(void) { int i=0; unsigned char delim = *txtpos; if(delim != '"' && delim != '\'') return 0; txtpos++; // Check we have a closing delimiter while(txtpos[i] != delim) { if(txtpos[i] == NL) return 0; i++; } // Print the characters while(*txtpos != delim) { outchar(*txtpos); txtpos++; } txtpos++; // Skip over the last delimiter return 1; } /***************************************************************************/ void printmsgNoNL(const unsigned char *msg) { while( pgm_read_byte( msg ) != 0 ) { outchar( pgm_read_byte( msg++ ) ); }; } /***************************************************************************/ void printmsg(const unsigned char *msg) { printmsgNoNL(msg); line_terminator(); } /***************************************************************************/ static void getln(char prompt) { outchar(prompt); txtpos = program_end+sizeof(LINENUM); while(1) { char c = inchar(); switch(c) { case NL: //break; case CR: line_terminator(); // Terminate all strings with a NL txtpos[0] = NL; return; case CTRLH: if(txtpos == program_end) break; txtpos--; printmsg(backspacemsg); break; default: // We need to leave at least one space to allow us to shuffle the line into order if(txtpos == variables_begin-2) outchar(BELL); else { txtpos[0] = c; txtpos++; outchar(c); } } } } /***************************************************************************/ static unsigned char *findline(void) { unsigned char *line = program_start; while(1) { if(line == program_end) return line; if(((LINENUM *)line)[0] >= linenum) return line; // Add the line length onto the current address, to get to the next line; line += line[sizeof(LINENUM)]; } } /***************************************************************************/ static void toUppercaseBuffer(void) { unsigned char *c = program_end+sizeof(LINENUM); unsigned char quote = 0; while(*c != NL) { // Are we in a quoted string? if(*c == quote) quote = 0; else if(*c == '"' || *c == '\'') quote = *c; else if(quote == 0 && *c >= 'a' && *c <= 'z') *c = *c + 'A' - 'a'; c++; } } /***************************************************************************/ void printline() { LINENUM line_num; line_num = *((LINENUM *)(list_line)); list_line += sizeof(LINENUM) + sizeof(char); // Output the line */ printnum(line_num); outchar(' '); while(*list_line != NL) { outchar(*list_line); list_line++; } list_line++; line_terminator(); } /***************************************************************************/ static short int expr4(void) { // fix provided by Jurg Wullschleger wullschleger@gmail.com // fixes whitespace and unary operations ignore_blanks(); if( *txtpos == '-' ) { txtpos++; return -expr4(); } // end fix if(*txtpos == '0') { txtpos++; return 0; } if(*txtpos >= '1' && *txtpos <= '9') { short int a = 0; do { a = a*10 + *txtpos - '0'; txtpos++; } while(*txtpos >= '0' && *txtpos <= '9'); return a; } // Is it a function or variable reference? if(txtpos[0] >= 'A' && txtpos[0] <= 'Z') { short int a; // Is it a variable reference (single alpha) if(txtpos[1] < 'A' || txtpos[1] > 'Z') { a = ((short int *)variables_begin)[*txtpos - 'A']; txtpos++; return a; } // Is it a function with a single parameter scantable(func_tab); if(table_index == FUNC_UNKNOWN) goto expr4_error; unsigned char f = table_index; if(*txtpos != '(') goto expr4_error; txtpos++; a = expression(); if(*txtpos != ')') goto expr4_error; txtpos++; switch(f) { case FUNC_PEEK: return program[a]; case FUNC_ABS: if(a < 0) return -a; return a; #ifdef ARDUINO case FUNC_AREAD: pinMode( a, INPUT ); return analogRead( a ); case FUNC_DREAD: pinMode( a, INPUT ); return digitalRead( a ); #endif case FUNC_RND: #ifdef ARDUINO return( random( a )); #else return( rand() % a ); #endif } } if(*txtpos == '(') { short int a; txtpos++; a = expression(); if(*txtpos != ')') goto expr4_error; txtpos++; return a; } expr4_error: expression_error = 1; return 0; } /***************************************************************************/ static short int expr3(void) { short int a,b; a = expr4(); ignore_blanks(); // fix for eg: 100 a = a + 1 while(1) { if(*txtpos == '*') { txtpos++; b = expr4(); a *= b; } else if(*txtpos == '/') { txtpos++; b = expr4(); if(b != 0) a /= b; else expression_error = 1; } else return a; } } /***************************************************************************/ static short int expr2(void) { short int a,b; if(*txtpos == '-' || *txtpos == '+') a = 0; else a = expr3(); while(1) { if(*txtpos == '-') { txtpos++; b = expr3(); a -= b; } else if(*txtpos == '+') { txtpos++; b = expr3(); a += b; } else return a; } } /***************************************************************************/ static short int expression(void) { short int a,b; a = expr2(); // Check if we have an error if(expression_error) return a; scantable(relop_tab); if(table_index == RELOP_UNKNOWN) return a; switch(table_index) { case RELOP_GE: b = expr2(); if(a >= b) return 1; break; case RELOP_NE: case RELOP_NE_BANG: b = expr2(); if(a != b) return 1; break; case RELOP_GT: b = expr2(); if(a > b) return 1; break; case RELOP_EQ: b = expr2(); if(a == b) return 1; break; case RELOP_LE: b = expr2(); if(a <= b) return 1; break; case RELOP_LT: b = expr2(); if(a < b) return 1; break; } return 0; } /***************************************************************************/ void loop() { unsigned char *start; unsigned char *newEnd; unsigned char linelen; boolean isDigital; boolean alsoWait = false; int val; #ifdef ARDUINO #ifdef ENABLE_TONES noTone( kPiezoPin ); #endif #endif program_start = program; program_end = program_start; sp = program+sizeof(program); // Needed for printnum stack_limit = program+sizeof(program)-STACK_SIZE; variables_begin = stack_limit - 27*VAR_SIZE; // memory free printnum(variables_begin-program_end); printmsg(memorymsg); #ifdef ARDUINO #ifdef ENABLE_EEPROM // eprom size printnum( E2END+1 ); printmsg( eeprommsg ); #endif /* ENABLE_EEPROM */ #endif /* ARDUINO */ warmstart: // this signifies that it is running in 'direct' mode. current_line = 0; sp = program+sizeof(program); printmsg(okmsg); prompt: if( triggerRun ){ triggerRun = false; current_line = program_start; goto execline; } getln( '>' ); toUppercaseBuffer(); txtpos = program_end+sizeof(unsigned short); // Find the end of the freshly entered line while(*txtpos != NL) txtpos++; // Move it to the end of program_memory { unsigned char *dest; dest = variables_begin-1; while(1) { *dest = *txtpos; if(txtpos == program_end+sizeof(unsigned short)) break; dest--; txtpos--; } txtpos = dest; } // Now see if we have a line number linenum = testnum(); ignore_blanks(); if(linenum == 0) goto direct; if(linenum == 0xFFFF) goto qhow; // Find the length of what is left, including the (yet-to-be-populated) line header linelen = 0; while(txtpos[linelen] != NL) linelen++; linelen++; // Include the NL in the line length linelen += sizeof(unsigned short)+sizeof(char); // Add space for the line number and line length // Now we have the number, add the line header. txtpos -= 3; *((unsigned short *)txtpos) = linenum; txtpos[sizeof(LINENUM)] = linelen; // Merge it into the rest of the program start = findline(); // If a line with that number exists, then remove it if(start != program_end && *((LINENUM *)start) == linenum) { unsigned char *dest, *from; unsigned tomove; from = start + start[sizeof(LINENUM)]; dest = start; tomove = program_end - from; while( tomove > 0) { *dest = *from; from++; dest++; tomove--; } program_end = dest; } if(txtpos[sizeof(LINENUM)+sizeof(char)] == NL) // If the line has no txt, it was just a delete goto prompt; // Make room for the new line, either all in one hit or lots of little shuffles while(linelen > 0) { unsigned int tomove; unsigned char *from,*dest; unsigned int space_to_make; space_to_make = txtpos - program_end; if(space_to_make > linelen) space_to_make = linelen; newEnd = program_end+space_to_make; tomove = program_end - start; // Source and destination - as these areas may overlap we need to move bottom up from = program_end; dest = newEnd; while(tomove > 0) { from--; dest--; *dest = *from; tomove--; } // Copy over the bytes into the new space for(tomove = 0; tomove < space_to_make; tomove++) { *start = *txtpos; txtpos++; start++; linelen--; } program_end = newEnd; } goto prompt; unimplemented: printmsg(unimplimentedmsg); goto prompt; qhow: printmsg(howmsg); goto prompt; qwhat: printmsgNoNL(whatmsg); if(current_line != NULL) { unsigned char tmp = *txtpos; if(*txtpos != NL) *txtpos = '^'; list_line = current_line; printline(); *txtpos = tmp; } line_terminator(); goto prompt; qsorry: printmsg(sorrymsg); goto warmstart; run_next_statement: while(*txtpos == ':') txtpos++; ignore_blanks(); if(*txtpos == NL) goto execnextline; goto interperateAtTxtpos; direct: txtpos = program_end+sizeof(LINENUM); if(*txtpos == NL) goto prompt; interperateAtTxtpos: if(breakcheck()) { printmsg(breakmsg); goto warmstart; } scantable(keywords); switch(table_index) { case KW_DELAY: { #ifdef ARDUINO expression_error = 0; val = expression(); delay( val ); goto execnextline; #else goto unimplemented; #endif } case KW_FILES: goto files; case KW_LIST: goto list; case KW_CHAIN: goto chain; case KW_LOAD: goto load; case KW_MEM: goto mem; case KW_NEW: if(txtpos[0] != NL) goto qwhat; program_end = program_start; goto prompt; case KW_RUN: current_line = program_start; goto execline; case KW_SAVE: goto save; case KW_NEXT: goto next; case KW_LET: goto assignment; case KW_IF: short int val; expression_error = 0; val = expression(); if(expression_error || *txtpos == NL) goto qhow; if(val != 0) goto interperateAtTxtpos; goto execnextline; case KW_GOTO: expression_error = 0; linenum = expression(); if(expression_error || *txtpos != NL) goto qhow; current_line = findline(); goto execline; case KW_GOSUB: goto gosub; case KW_RETURN: goto gosub_return; case KW_REM: case KW_QUOTE: goto execnextline; // Ignore line completely case KW_FOR: goto forloop; case KW_INPUT: goto input; case KW_PRINT: case KW_QMARK: goto print; case KW_POKE: goto poke; case KW_END: case KW_STOP: // This is the easy way to end - set the current line to the end of program attempt to run it if(txtpos[0] != NL) goto qwhat; current_line = program_end; goto execline; case KW_BYE: // Leave the basic interperater return; case KW_AWRITE: // AWRITE , HIGH|LOW isDigital = false; goto awrite; case KW_DWRITE: // DWRITE , HIGH|LOW isDigital = true; goto dwrite; case KW_RSEED: goto rseed; #ifdef ENABLE_TONES case KW_TONEW: alsoWait = true; case KW_TONE: goto tonegen; case KW_NOTONE: goto tonestop; #endif #ifdef ARDUINO #ifdef ENABLE_EEPROM case KW_EFORMAT: goto eformat; case KW_ESAVE: goto esave; case KW_ELOAD: goto eload; case KW_ELIST: goto elist; case KW_ECHAIN: goto echain; #endif #endif case KW_DEFAULT: goto assignment; default: break; } execnextline: if(current_line == NULL) // Processing direct commands? goto prompt; current_line += current_line[sizeof(LINENUM)]; execline: if(current_line == program_end) // Out of lines to run goto warmstart; txtpos = current_line+sizeof(LINENUM)+sizeof(char); goto interperateAtTxtpos; #ifdef ARDUINO #ifdef ENABLE_EEPROM elist: { int i; for( i = 0 ; i < (E2END +1) ; i++ ) { val = EEPROM.read( i ); if( val == '\0' ) { goto execnextline; } if( ((val < ' ') || (val > '~')) && (val != NL) && (val != CR)) { outchar( '?' ); } else { outchar( val ); } } } goto execnextline; eformat: { for( int i = 0 ; i < E2END ; i++ ) { if( (i & 0x03f) == 0x20 ) outchar( '.' ); EEPROM.write( i, 0 ); } outchar( LF ); } goto execnextline; esave: { outStream = kStreamEEProm; eepos = 0; // copied from "List" list_line = findline(); while(list_line != program_end) printline(); // go back to standard output, close the file outStream = kStreamSerial; goto warmstart; } echain: runAfterLoad = true; eload: // clear the program program_end = program_start; // load from a file into memory eepos = 0; inStream = kStreamEEProm; inhibitOutput = true; goto warmstart; #endif /* ENABLE_EEPROM */ #endif input: { unsigned char var; ignore_blanks(); if(*txtpos < 'A' || *txtpos > 'Z') goto qwhat; var = *txtpos; txtpos++; ignore_blanks(); if(*txtpos != NL && *txtpos != ':') goto qwhat; ((short int *)variables_begin)[var-'A'] = 99; goto run_next_statement; } forloop: { unsigned char var; short int initial, step, terminal; ignore_blanks(); if(*txtpos < 'A' || *txtpos > 'Z') goto qwhat; var = *txtpos; txtpos++; ignore_blanks(); if(*txtpos != '=') goto qwhat; txtpos++; ignore_blanks(); expression_error = 0; initial = expression(); if(expression_error) goto qwhat; scantable(to_tab); if(table_index != 0) goto qwhat; terminal = expression(); if(expression_error) goto qwhat; scantable(step_tab); if(table_index == 0) { step = expression(); if(expression_error) goto qwhat; } else step = 1; ignore_blanks(); if(*txtpos != NL && *txtpos != ':') goto qwhat; if(!expression_error && *txtpos == NL) { struct stack_for_frame *f; if(sp + sizeof(struct stack_for_frame) < stack_limit) goto qsorry; sp -= sizeof(struct stack_for_frame); f = (struct stack_for_frame *)sp; ((short int *)variables_begin)[var-'A'] = initial; f->frame_type = STACK_FOR_FLAG; f->for_var = var; f->terminal = terminal; f->step = step; f->txtpos = txtpos; f->current_line = current_line; goto run_next_statement; } } goto qhow; gosub: expression_error = 0; linenum = expression(); if(!expression_error && *txtpos == NL) { struct stack_gosub_frame *f; if(sp + sizeof(struct stack_gosub_frame) < stack_limit) goto qsorry; sp -= sizeof(struct stack_gosub_frame); f = (struct stack_gosub_frame *)sp; f->frame_type = STACK_GOSUB_FLAG; f->txtpos = txtpos; f->current_line = current_line; current_line = findline(); goto execline; } goto qhow; next: // Fnd the variable name ignore_blanks(); if(*txtpos < 'A' || *txtpos > 'Z') goto qhow; txtpos++; ignore_blanks(); if(*txtpos != ':' && *txtpos != NL) goto qwhat; gosub_return: // Now walk up the stack frames and find the frame we want, if present tempsp = sp; while(tempsp < program+sizeof(program)-1) { switch(tempsp[0]) { case STACK_GOSUB_FLAG: if(table_index == KW_RETURN) { struct stack_gosub_frame *f = (struct stack_gosub_frame *)tempsp; current_line = f->current_line; txtpos = f->txtpos; sp += sizeof(struct stack_gosub_frame); goto run_next_statement; } // This is not the loop you are looking for... so Walk back up the stack tempsp += sizeof(struct stack_gosub_frame); break; case STACK_FOR_FLAG: // Flag, Var, Final, Step if(table_index == KW_NEXT) { struct stack_for_frame *f = (struct stack_for_frame *)tempsp; // Is the the variable we are looking for? if(txtpos[-1] == f->for_var) { short int *varaddr = ((short int *)variables_begin) + txtpos[-1] - 'A'; *varaddr = *varaddr + f->step; // Use a different test depending on the sign of the step increment if((f->step > 0 && *varaddr <= f->terminal) || (f->step < 0 && *varaddr >= f->terminal)) { // We have to loop so don't pop the stack txtpos = f->txtpos; current_line = f->current_line; goto run_next_statement; } // We've run to the end of the loop. drop out of the loop, popping the stack sp = tempsp + sizeof(struct stack_for_frame); goto run_next_statement; } } // This is not the loop you are looking for... so Walk back up the stack tempsp += sizeof(struct stack_for_frame); break; default: //printf("Stack is stuffed!\n"); goto warmstart; } } // Didn't find the variable we've been looking for goto qhow; assignment: { short int value; short int *var; if(*txtpos < 'A' || *txtpos > 'Z') goto qhow; var = (short int *)variables_begin + *txtpos - 'A'; txtpos++; ignore_blanks(); if (*txtpos != '=') goto qwhat; txtpos++; ignore_blanks(); expression_error = 0; value = expression(); if(expression_error) goto qwhat; // Check that we are at the end of the statement if(*txtpos != NL && *txtpos != ':') goto qwhat; *var = value; } goto run_next_statement; poke: { short int value; unsigned char *address; // Work out where to put it expression_error = 0; value = expression(); if(expression_error) goto qwhat; address = (unsigned char *)value; // check for a comma ignore_blanks(); if (*txtpos != ',') goto qwhat; txtpos++; ignore_blanks(); // Now get the value to assign expression_error = 0; value = expression(); if(expression_error) goto qwhat; //printf("Poke %p value %i\n",address, (unsigned char)value); // Check that we are at the end of the statement if(*txtpos != NL && *txtpos != ':') goto qwhat; } goto run_next_statement; list: linenum = testnum(); // Retuns 0 if no line found. // Should be EOL if(txtpos[0] != NL) goto qwhat; // Find the line list_line = findline(); while(list_line != program_end) printline(); goto warmstart; print: // If we have an empty list then just put out a NL if(*txtpos == ':' ) { line_terminator(); txtpos++; goto run_next_statement; } if(*txtpos == NL) { goto execnextline; } while(1) { ignore_blanks(); if(print_quoted_string()) { ; } else if(*txtpos == '"' || *txtpos == '\'') goto qwhat; else { short int e; expression_error = 0; e = expression(); if(expression_error) goto qwhat; printnum(e); } // At this point we have three options, a comma or a new line if(*txtpos == ',') txtpos++; // Skip the comma and move onto the next else if(txtpos[0] == ';' && (txtpos[1] == NL || txtpos[1] == ':')) { txtpos++; // This has to be the end of the print - no newline break; } else if(*txtpos == NL || *txtpos == ':') { line_terminator(); // The end of the print statement break; } else goto qwhat; } goto run_next_statement; mem: // memory free printnum(variables_begin-program_end); printmsg(memorymsg); #ifdef ARDUINO #ifdef ENABLE_EEPROM { // eprom size printnum( E2END+1 ); printmsg( eeprommsg ); // figure out the memory usage; val = ' '; int i; for( i=0 ; (i<(E2END+1)) && (val != '\0') ; i++ ) { val = EEPROM.read( i ); } printnum( (E2END +1) - (i-1) ); printmsg( eepromamsg ); } #endif /* ENABLE_EEPROM */ #endif /* ARDUINO */ goto run_next_statement; /*************************************************/ #ifdef ARDUINO awrite: // AWRITE ,val dwrite: { short int pinNo; short int value; unsigned char *txtposBak; // Get the pin number expression_error = 0; pinNo = expression(); if(expression_error) goto qwhat; // check for a comma ignore_blanks(); if (*txtpos != ',') goto qwhat; txtpos++; ignore_blanks(); txtposBak = txtpos; scantable(highlow_tab); if(table_index != HIGHLOW_UNKNOWN) { if( table_index <= HIGHLOW_HIGH ) { value = 1; } else { value = 0; } } else { // and the value (numerical) expression_error = 0; value = expression(); if(expression_error) goto qwhat; } pinMode( pinNo, OUTPUT ); if( isDigital ) { digitalWrite( pinNo, value ); } else { analogWrite( pinNo, value ); } } goto run_next_statement; #else pinmode: // PINMODE , I/O awrite: // AWRITE ,val dwrite: goto unimplemented; #endif /*************************************************/ files: // display a listing of files on the device. // version 1: no support for subdirectories #ifdef ENABLE_FILEIO cmd_Files(); goto warmstart; #else goto unimplemented; #endif // ENABLE_FILEIO chain: runAfterLoad = true; load: // clear the program program_end = program_start; // load from a file into memory #ifdef ENABLE_FILEIO { unsigned char *filename; // Work out the filename expression_error = 0; filename = filenameWord(); if(expression_error) goto qwhat; #ifdef ARDUINO // Arduino specific if( !SD.exists( (char *)filename )) { printmsg( sdfilemsg ); } else { fp = SD.open( (const char *)filename ); inStream = kStreamFile; inhibitOutput = true; } #else // ARDUINO // Desktop specific #endif // ARDUINO // this will kickstart a series of events to read in from the file. } goto warmstart; #else // ENABLE_FILEIO goto unimplemented; #endif // ENABLE_FILEIO save: // save from memory out to a file #ifdef ENABLE_FILEIO { unsigned char *filename; // Work out the filename expression_error = 0; filename = filenameWord(); if(expression_error) goto qwhat; #ifdef ARDUINO // remove the old file if it exists if( SD.exists( (char *)filename )) { SD.remove( (char *)filename ); } // open the file, switch over to file output fp = SD.open( (const char *)filename, FILE_WRITE ); outStream = kStreamFile; // copied from "List" list_line = findline(); while(list_line != program_end) printline(); // go back to standard output, close the file outStream = kStreamSerial; fp.close(); #else // ARDUINO // desktop #endif // ARDUINO goto warmstart; } #else // ENABLE_FILEIO goto unimplemented; #endif // ENABLE_FILEIO rseed: { short int value; //Get the pin number expression_error = 0; value = expression(); if(expression_error) goto qwhat; #ifdef ARDUINO randomSeed( value ); #else // ARDUINO srand( value ); #endif // ARDUINO goto run_next_statement; } #ifdef ENABLE_TONES tonestop: noTone( kPiezoPin ); goto run_next_statement; tonegen: { // TONE freq, duration // if either are 0, tones turned off short int freq; short int duration; //Get the frequency expression_error = 0; freq = expression(); if(expression_error) goto qwhat; ignore_blanks(); if (*txtpos != ',') goto qwhat; txtpos++; ignore_blanks(); //Get the duration expression_error = 0; duration = expression(); if(expression_error) goto qwhat; if( freq == 0 || duration == 0 ) goto tonestop; tone( kPiezoPin, freq, duration ); if( alsoWait ) { delay( duration ); alsoWait = false; } goto run_next_statement; } #endif /* ENABLE_TONES */ } // returns 1 if the character is valid in a filename static int isValidFnChar( char c ) { if( c >= '0' && c <= '9' ) return 1; // number if( c >= 'A' && c <= 'Z' ) return 1; // LETTER if( c >= 'a' && c <= 'z' ) return 1; // letter (for completeness) if( c == '_' ) return 1; if( c == '+' ) return 1; if( c == '.' ) return 1; if( c == '~' ) return 1; // Window~1.txt return 0; } unsigned char * filenameWord(void) { // SDL - I wasn't sure if this functionality existed above, so I figured i'd put it here unsigned char * ret = txtpos; expression_error = 0; // make sure there are no quotes or spaces, search for valid characters //while(*txtpos == SPACE || *txtpos == TAB || *txtpos == SQUOTE || *txtpos == DQUOTE ) txtpos++; while( !isValidFnChar( *txtpos )) txtpos++; ret = txtpos; if( *ret == '\0' ) { expression_error = 1; return ret; } // now, find the next nonfnchar txtpos++; while( isValidFnChar( *txtpos )) txtpos++; if( txtpos != ret ) *txtpos = '\0'; // set the error code if we've got no string if( *ret == '\0' ) { expression_error = 1; } return ret; } /***************************************************************************/ static void line_terminator(void) { outchar(NL); outchar(CR); } /***********************************************************/ void setup() { #ifdef ARDUINO Serial.begin(kConsoleBaud); // opens serial port while( !Serial ); // for Leonardo Serial.println( sentinel ); printmsg(initmsg); #ifdef ENABLE_FILEIO initSD(); #ifdef ENABLE_AUTORUN if( SD.exists( kAutorunFilename )) { program_end = program_start; fp = SD.open( kAutorunFilename ); inStream = kStreamFile; inhibitOutput = true; runAfterLoad = true; } #endif /* ENABLE_AUTORUN */ #endif /* ENABLE_FILEIO */ #ifdef ENABLE_EEPROM #ifdef ENABLE_EAUTORUN // read the first byte of the eeprom. if it's a number, assume it's a program we can load int val = EEPROM.read(0); if( val >= '0' && val <= '9' ) { program_end = program_start; inStream = kStreamEEProm; eepos = 0; inhibitOutput = true; runAfterLoad = true; } #endif /* ENABLE_EAUTORUN */ #endif /* ENABLE_EEPROM */ #endif /* ARDUINO */ } /***********************************************************/ static unsigned char breakcheck(void) { #ifdef ARDUINO if(Serial.available()) return Serial.read() == CTRLC; return 0; #else #ifdef __CONIO__ if(kbhit()) return getch() == CTRLC; else #endif return 0; #endif } /***********************************************************/ static int inchar() { int v; #ifdef ARDUINO switch( inStream ) { case( kStreamFile ): #ifdef ENABLE_FILEIO v = fp.read(); if( v == NL ) v=CR; // file translate if( !fp.available() ) { fp.close(); goto inchar_loadfinish; } return v; #else #endif break; case( kStreamEEProm ): #ifdef ENABLE_EEPROM #ifdef ARDUINO v = EEPROM.read( eepos++ ); if( v == '\0' ) { goto inchar_loadfinish; } return v; #endif #else inStream = kStreamSerial; return NL; #endif break; case( kStreamSerial ): default: while(1) { if(Serial.available()) return Serial.read(); } } inchar_loadfinish: inStream = kStreamSerial; inhibitOutput = false; if( runAfterLoad ) { runAfterLoad = false; triggerRun = true; } return NL; // trigger a prompt. #else // otherwise. desktop! int got = getchar(); // translation for desktop systems if( got == LF ) got = CR; return got; #endif } /***********************************************************/ static void outchar(unsigned char c) { if( inhibitOutput ) return; #ifdef ARDUINO #ifdef ENABLE_FILEIO if( outStream == kStreamFile ) { // output to a file fp.write( c ); } else #endif #ifdef ARDUINO #ifdef ENABLE_EEPROM if( outStream == kStreamEEProm ) { EEPROM.write( eepos++, c ); } else #endif /* ENABLE_EEPROM */ #endif /* ARDUINO */ Serial.write(c); #else putchar(c); #endif } /***********************************************************/ /* SD Card helpers */ #if ARDUINO && ENABLE_FILEIO static int initSD( void ) { // if the card is already initialized, we just go with it. // there is no support (yet?) for hot-swap of SD Cards. if you need to // swap, pop the card, reset the arduino.) if( sd_is_initialized == true ) return kSD_OK; // due to the way the SD Library works, pin 10 always needs to be // an output, even when your shield uses another line for CS pinMode(10, OUTPUT); // change this to 53 on a mega if( !SD.begin( kSD_CS )) { // failed printmsg( sderrormsg ); return kSD_Fail; } // success - quietly return 0 sd_is_initialized = true; // and our file redirection flags outStream = kStreamSerial; inStream = kStreamSerial; inhibitOutput = false; return kSD_OK; } #endif #if ENABLE_FILEIO void cmd_Files( void ) { File dir = SD.open( "/" ); dir.seek(0); while( true ) { File entry = dir.openNextFile(); if( !entry ) { entry.close(); break; } // common header printmsgNoNL( indentmsg ); printmsgNoNL( (const unsigned char *)entry.name() ); if( entry.isDirectory() ) { printmsgNoNL( slashmsg ); } if( entry.isDirectory() ) { // directory ending for( int i=strlen( entry.name()) ; i<16 ; i++ ) { printmsgNoNL( spacemsg ); } printmsgNoNL( dirextmsg ); } else { // file ending for( int i=strlen( entry.name()) ; i<17 ; i++ ) { printmsgNoNL( spacemsg ); } printUnum( entry.size() ); } line_terminator(); entry.close(); } dir.close(); } #endif