/* * This is an example of terminal being the output for Mike Field's Tiny BASIC interpreter * Input comes from the serial monitor with the baud rate set to 9600. * * Dave Curran 2013-09-28 * * Arduino Terminal Library - Dave Curran (www.tynemouthsoftware.co.uk) * Concept and Microcontroller Firmware - Daryl Rictor, Grant Searle * Tiny BASIC - Mike Field * * The display microcontroller is wired as follows: * pin 28 (SCL) ==> Display pin 28 (SCL) * pin 27 (SDA) ==> Display pin 27 (SDA) * Arduino Reset ==> Display pin 1 (Reset) - Reset (Optional) * * The two I2C data lines need to be pulled high to 5V via 2K2 resistors. * */ #include // Create a terminal in TWI mode Terminal term; #define ARDUINO 1 // ASCII Characters #define CR '\r' #define NL '\n' #define TAB '\t' #define BELL '\b' #define SPACE ' ' #define CTRLC 0x03 #define CTRLH 0x08 #define CTRLS 0x13 #define CTRLX 0x18 typedef short unsigned LINENUM; #if ARDUINO #define ECHO_CHARS 1 #else #define ECHO_CHARS 0 #endif static unsigned char program[1128]; // gives 1024 bytes free at startup, 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, 0 }; #define KW_LIST 0 #define KW_LOAD 1 #define KW_NEW 2 #define KW_RUN 3 #define KW_SAVE 4 #define KW_NEXT 5 #define KW_LET 6 #define KW_IF 7 #define KW_GOTO 8 #define KW_GOSUB 9 #define KW_RETURN 10 #define KW_REM 11 #define KW_FOR 12 #define KW_INPUT 13 #define KW_PRINT 14 #define KW_POKE 15 #define KW_STOP 16 #define KW_BYE 17 #define KW_DEFAULT 18 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, 0 }; #define FUNC_PEEK 0 #define FUNC_ABS 1 #define FUNC_UNKNOWN 2 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, 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_UNKNOWN 6 #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 in C V0.02."; static const unsigned char memorymsg[] = " bytes free."; static const unsigned char breakmsg[] = "break!"; static const unsigned char unimplimentedmsg[] = "Unimplemented"; static const unsigned char backspacemsg[] = "\b \b"; 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(table[0] == 0) return; // Do we match this character? if(txtpos[i] == table[0]) { i++; table++; } else { // do we match the last character of keywork (with 0x80 added)? If so, return if(txtpos[i]+0x80 == table[0]) { txtpos += i+1; // Advance the pointer to following the keyword ignore_blanks(); return; } // Forward to the end of this keyword while((table[0] & 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; } /***************************************************************************/ static 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--; } } /***************************************************************************/ 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 void printmsgNoNL(const unsigned char *msg) { while(*msg) { outchar(*msg); msg++; } } /***************************************************************************/ 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; } /***************************************************************************/ static 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++; #if ECHO_CHARS outchar(c); #endif } } } } /***************************************************************************/ 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) { 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; } } 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(); 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: 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; 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; printmsg(initmsg); printnum(variables_begin-program_end); printmsg(memorymsg); warmstart: // this signifies that it is running in 'direct' mode. current_line = 0; sp = program+sizeof(program); printmsg(okmsg); prompt: 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_LIST: goto list; case KW_LOAD: goto unimplemented; ///////////////// 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 unimplemented; ////////////////////// 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: goto execnextline; // Ignore line completely case KW_FOR: goto forloop; case KW_INPUT: goto input; case KW_PRINT: goto print; case KW_POKE: goto poke; 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_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; 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; } /***************************************************************************/ static void line_terminator(void) { outchar(NL); outchar(CR); } /***********************************************************/ void setup() { #if ARDUINO Serial.begin(9600); // opens serial port, sets data rate to 9600 bps #endif } /***********************************************************/ static unsigned char breakcheck(void) { #if ARDUINO if(Serial.available()) return Serial.read() == CTRLC; return 0; #else if(kbhit()) return getch() == CTRLC; else return 0; #endif } /***********************************************************/ static int inchar() { #if ARDUINO while(1) { if(Serial.available()) return Serial.read(); } #else return getchar(); #endif } /***********************************************************/ static void outchar(unsigned char c) { #if ARDUINO //Serial.write(c); term.send(c); #else putchar(c); #endif }