A port of TinyBasic Plus (https://github.com/BleuLlama/TinyBasicPlus) to mbed (focus on Nucleo)

Dependencies:   mbed

Revision:
0:c52ead8719b3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Wed Jan 21 15:32:36 2015 +0000
@@ -0,0 +1,1773 @@
+/*
+
+A C implementation of Tiny Basic, with a focus on support for mbed/nucleo. This has been ported from TinyBasic Plus (https://github.com/BleuLlama/TinyBasicPlus)
+
+Nucleo Port by Tom Van den Bon 
+
+*/
+
+#define kVersion "v0.1"
+
+/*
+    v0.1: 2015/01/21
+    
+    Initial code from TinyBasic Plus ported to run on the Nucleo
+    
+    - filesystem, eeprom and sdcard not implemented yet
+*/
+
+
+
+#include "mbed.h"
+
+Serial pc(SERIAL_TX, SERIAL_RX);
+ 
+#ifndef pgm_read_byte
+#define pgm_read_byte( A ) *(A)
+#endif
+
+#ifndef boolean 
+#define boolean int
+#define true 1
+#define false 0
+#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
+
+// size of our program ram
+#define kRamSize   4096 /* arbitrary */
+
+typedef short unsigned LINENUM;
+
+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[] = {
+  '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 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
+  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 ENABLE_EEPROM
+  KW_ECHAIN, KW_ELIST, KW_ELOAD, KW_EFORMAT, KW_ESAVE, 
+#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[] = {
+  '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[]  = {
+  'T','O'+0x80,
+  0
+};
+
+static unsigned char step_tab[]  = {
+  'S','T','E','P'+0x80,
+  0
+};
+
+static unsigned char relop_tab[]  = {
+  '>','='+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[]  = { 
+  '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[]            = "OK";
+static const unsigned char whatmsg[]          = "What? ";
+static const unsigned char howmsg[]           = "How?";
+static const unsigned char sorrymsg[]         = "Sorry!";
+static const unsigned char initmsg[]          = "TinyBasic Nucleo";
+static const unsigned char memorymsg[]        = " bytes free.";
+#ifdef ENABLE_EEPROM
+static const unsigned char eeprommsg[]        = " EEProm bytes total.";
+static const unsigned char eepromamsg[]       = " EEProm bytes available.";
+#endif
+static const unsigned char breakmsg[]         = "break!";
+static const unsigned char unimplimentedmsg[] = "Unimplemented";
+static const unsigned char backspacemsg[]     = "\b \b";
+static const unsigned char indentmsg[]        = "    ";
+static const unsigned char sderrormsg[]       = "SD card error.";
+static const unsigned char sdfilemsg[]        = "SD file error.";
+static const unsigned char dirextmsg[]        = "(dir)";
+static const unsigned char slashmsg[]         = "/";
+static const unsigned char spacemsg[]         = " ";
+
+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;
+
+/*
+// fix
+#ifdef ARDUINO
+    case FUNC_AREAD:
+      pinMode( a, INPUT );
+      return analogRead( a );                        
+    case FUNC_DREAD:
+      pinMode( a, INPUT );
+      return digitalRead( a );
+#endif
+*/
+
+    case FUNC_RND:
+        return 0;
+/*    
+#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 ENABLE_TONES
+  noTone( kPiezoPin );
+#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 ENABLE_EEPROM
+  // eprom size
+  printnum( E2END+1 );
+  printmsg( eeprommsg );
+#endif /* ENABLE_EEPROM */
+
+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:
+    {
+
+      expression_error = 0;
+      val = expression();
+      wait_ms( val );
+      goto execnextline;
+    }
+
+  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 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
+
+  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 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 */
+
+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 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 */
+  goto run_next_statement;
+
+
+  /*************************************************/
+
+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;
+    }
+    /* fix
+    pinMode( pinNo, OUTPUT );
+    if( isDigital ) {
+      digitalWrite( pinNo, value );
+    } 
+    else {
+      analogWrite( pinNo, value );
+    }
+    */
+  }
+  goto run_next_statement;
+
+  /*************************************************/
+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;
+
+
+/* fix
+#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);
+}
+/***********************************************************/
+static unsigned char breakcheck(void)
+{
+  if(pc.readable())
+    return pc.getc() == CTRLC;
+  return 0;
+}
+/***********************************************************/
+static int inchar()
+{
+  int v;
+  
+  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
+    v = EEPROM.read( eepos++ );
+    if( v == '\0' ) {
+      goto inchar_loadfinish;
+    }
+    return v;
+#endif
+     break;
+  case( kStreamSerial ):
+  default:
+    while(1)
+    {
+      if(pc.readable())
+        return pc.getc();
+    }
+  }
+  
+inchar_loadfinish:
+  inStream = kStreamSerial;
+  inhibitOutput = false;
+
+  if( runAfterLoad ) {
+    runAfterLoad = false;
+    triggerRun = true;
+  }
+  return NL; // trigger a prompt.
+  
+}
+/***********************************************************/
+static void outchar(unsigned char c)
+{
+  if( inhibitOutput ) return;
+
+  pc.putc(c);
+}
+/***********************************************************/
+int main() 
+{
+    while (1)
+    {
+        loop();
+    }
+}
+ 
\ No newline at end of file