# A 'syntax-directed interpreter' (all execution is a side-effect of parsing). # Inspired by Dennis Allison's original Tiny BASIC grammar, circa 1975. # # Copyright (c) 2007 by Ian Piumarta # All rights reserved. # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the 'Software'), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, provided that the above copyright notice(s) and this # permission notice appear in all copies of the Software. Acknowledgement # of the use of this Software in supporting documentation would be # appreciated but is not required. # # THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. # # Last edited: 2012-04-29 15:14:06 by piumarta on emilia %{ # include typedef struct line line; struct line { int number; int length; char *text; }; line *lines= 0; int numLines= 0; int pc= -1, epc= -1; int batch= 0; int nextline(char *buf, int max); # define min(x, y) ((x) < (y) ? (x) : (y)) # define YY_INPUT(buf, result, max_size) \ { \ if ((pc >= 0) && (pc < numLines)) \ { \ line *linep= lines+pc++; \ result= min(max_size, linep->length); \ memcpy(buf, linep->text, result); \ } \ else \ result= nextline(buf, max_size); \ } union value { int number; char *string; int (*binop)(int lhs, int rhs); }; # define YYSTYPE union value int variables[26]; void accept(int number, char *line); void save(char *name); void load(char *name); void type(char *name); int lessThan(int lhs, int rhs) { return lhs < rhs; } int lessEqual(int lhs, int rhs) { return lhs <= rhs; } int notEqual(int lhs, int rhs) { return lhs != rhs; } int equalTo(int lhs, int rhs) { return lhs == rhs; } int greaterEqual(int lhs, int rhs) { return lhs >= rhs; } int greaterThan(int lhs, int rhs) { return lhs > rhs; } int input(void); int stack[1024], sp= 0; char *help; void error(char *fmt, ...); int findLine(int n, int create); %} line = - s:statement CR | - n:number < ( !CR . )* CR > { accept(n.number, yytext); } | - CR | - < ( !CR . )* CR > { epc= pc; error("syntax error"); } | - !. { exit(0); } statement = 'print'- expr-list | 'if'- e1:expression r:relop e2:expression { if (!r.binop(e1.number, e2.number)) yythunkpos= 0; } 'then'- statement | 'goto'- e:expression { epc= pc; if ((pc= findLine(e.number, 0)) < 0) error("no such line"); } | 'input'- var-list | 'let'- v:var EQUAL e:expression { variables[v.number]= e.number; } | 'gosub'- e:expression { epc= pc; if (sp < 1024) stack[sp++]= pc, pc= findLine(e.number, 0); else error("too many gosubs"); if (pc < 0) error("no such line"); } | 'return'- { epc= pc; if ((pc= sp ? stack[--sp] : -1) < 0) error("no gosub"); } | 'clear'- { while (numLines) accept(lines->number, "\n"); } | 'list'- { int i; for (i= 0; i < numLines; ++i) printf("%5d %s", lines[i].number, lines[i].text); } | 'run'- s:string { load(s.string); pc= 0; } | 'run'- { pc= 0; } | 'end'- { pc= -1; if (batch) exit(0); } | 'rem'- ( !CR . )* | ('bye'|'quit'|'exit')- { exit(0); } | 'save'- s:string { save(s.string); } | 'load'- s:string { load(s.string); } | 'type'- s:string { type(s.string); } | 'dir'- { system("ls *.bas"); } | 'help'- { fprintf(stderr, "%s", help); } expr-list = ( e:string { printf("%s", e.string); } | e:expression { printf("%d", e.number); } )? ( COMMA ( e:string { printf("%s", e.string); } | e:expression { printf("%d", e.number); } ) )* ( COMMA | !COMMA { printf("\n"); } ) var-list = v:var { variables[v.number]= input(); } ( COMMA v:var { variables[v.number]= input(); } )* expression = ( PLUS? l:term | MINUS l:term { l.number = -l.number } ) ( PLUS r:term { l.number += r.number } | MINUS r:term { l.number -= r.number } )* { $$.number = l.number } term = l:factor ( STAR r:factor { l.number *= r.number } | SLASH r:factor { l.number /= r.number } )* { $$.number = l.number } factor = v:var { $$.number = variables[v.number] } | n:number | OPEN expression CLOSE var = < [a-z] > - { $$.number = yytext[0] - 'a' } number = < digit+ > - { $$.number = atoi(yytext); } digit = [0-9] string = '"' < [^\"]* > '"' - { $$.string = yytext; } relop = '<=' - { $$.binop= lessEqual; } | '<>' - { $$.binop= notEqual; } | '<' - { $$.binop= lessThan; } | '>=' - { $$.binop= greaterEqual; } | '>' - { $$.binop= greaterThan; } | '=' - { $$.binop= equalTo; } EQUAL = '=' - CLOSE = ')' - OPEN = '(' - SLASH = '/' - STAR = '*' - MINUS = '-' - PLUS = '+' - COMMA = ',' - - = [ \t]* CR = '\n' | '\r' | '\r\n' %% #include #include char *help= "print | [, | ...] [,]\n" "if <|<=|<>|=|>=|> then \n" "input [, ...] let = \n" "goto gosub \n" "end return\n" "list clear\n" "run [\"filename\"] rem \n" "dir type \"filename\"\n" "save \"filename\" load \"filename\"\n" "bye|quit|exit help\n" ; void error(char *fmt, ...) { va_list ap; va_start(ap, fmt); if (epc > 0) fprintf(stderr, "\nline %d: %s", lines[epc-1].number, lines[epc-1].text); else fprintf(stderr, "\n"); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); epc= pc= -1; } #ifdef USE_READLINE # include # include #endif int nextline(char *buf, int max) { pc= -1; if (batch) exit(0); if (isatty(fileno(stdin))) { # ifdef USE_READLINE char *line= readline(">"); if (line) { int len= strlen(line); if (len >= max) len= max - 1; strncpy(buf, line, len); (buf)[len]= '\n'; add_history(line); free(line); return len + 1; } else { printf("\n"); return 0; } # endif putchar('>'); fflush(stdout); } return fgets(buf, max, stdin) ? strlen(buf) : 0; } int maxLines= 0; int findLine(int n, int create) { int lo= 0, hi= numLines - 1; while (lo <= hi) { int mid= (lo + hi) / 2, lno= lines[mid].number; if (lno > n) hi= mid - 1; else if (lno < n) lo= mid + 1; else return mid; } if (create) { if (numLines == maxLines) { maxLines *= 2; lines= realloc(lines, sizeof(line) * maxLines); } if (lo < numLines) memmove(lines + lo + 1, lines + lo, sizeof(line) * (numLines - lo)); ++numLines; lines[lo].number= n; lines[lo].text= 0; return lo; } return -1; } void accept(int n, char *s) { if (s[0] < 32) /* delete */ { int lno= findLine(n, 0); if (lno >= 0) { if (lno < numLines - 1) memmove(lines + lno, lines + lno + 1, sizeof(line) * (numLines - lno - 1)); --numLines; } } else /* insert */ { int lno= findLine(n, 1); if (lines[lno].text) free(lines[lno].text); lines[lno].length= strlen(s); lines[lno].text= strdup(s); } } char *extend(char *name) { static char path[1024]; int len= strlen(name); sprintf(path, "%s%s", name, (((len > 4) && !strcasecmp(".bas", name + len - 4)) ? "" : ".bas")); return path; } void save(char *name) { FILE *f= fopen(name= extend(name), "w"); if (!f) perror(name); else { int i; for (i= 0; i < numLines; ++i) fprintf(f, "%d %s", lines[i].number, lines[i].text); fclose(f); } } void load(char *name) { FILE *f= fopen(name= extend(name), "r"); if (!f) perror(name); else { int lineNumber; char lineText[1024]; while ((1 == fscanf(f, " %d ", &lineNumber)) && fgets(lineText, sizeof(lineText), f)) accept(lineNumber, lineText); fclose(f); } } void type(char *name) { FILE *f= fopen(name= extend(name), "r"); if (!f) perror(name); else { int c, d; while ((c= getc(f)) >= 0) putchar(d= c); fclose(f); if ('\n' != d && '\r' != d) putchar('\n'); } } int input(void) { char line[32]; fgets(line, sizeof(line), stdin); return atoi(line); } int main(int argc, char **argv) { lines= malloc(sizeof(line) * (maxLines= 32)); numLines= 0; if (argc > 1) { batch= 1; while (argc-- > 1) load(*++argv); pc= 0; } while (!feof(stdin)) yyparse(); return 0; }