historical/TinyBasicPlus.git/Arduino/TinyBasicPlus.ino
2024-01-16 11:20:27 -06:00

2143 lines
45 KiB
C++

////////////////////////////////////////////////////////////////////////////////
// TinyBasic Plus
////////////////////////////////////////////////////////////////////////////////
//
// Authors: Mike Field <hamster@snap.net.nz>
// Scott Lawrence <yorgle@gmail.com>
//
#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 <pin>, INPUT|IN|I|OUTPUT|OUT|O
// DWRITE <pin>, HIGH|HI|1|LOW|LO|0
// AWRITE <pin>, [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 <yorgle@gmail.com>
// Better FILES listings
//
// v0.02a: 2012-09-17 Scott Lawrence <yorgle@gmail.com>
// 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 <avr/pgmspace.h>
#endif
// includes, and settings for Arduino-specific functionality
#ifdef ENABLE_EEPROM
#include <EEPROM.h> /* NOTE: case sensitive */
int eepos = 0;
#endif
#ifdef ENABLE_FILEIO
#include <SD.h>
#include <SPI.h> /* 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 <stdio.h>
#include <stdlib.h>
#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 <pin>, HIGH|LOW
isDigital = false;
goto awrite;
case KW_DWRITE: // DWRITE <pin>, 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 <pin>,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 <pin>, I/O
awrite: // AWRITE <pin>,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