I like fooling around with tiny interpreters. A long time ago, I found DDS BASIC by Diomidis Spinellis, a winner in the 1990 IOCCC. I decided a fun project would be to modify DDS BASIC, adding enough features so that it could run (what else) Tiny Star Trek. So, I did the following to the original dds.c:
Removed enough obfuscation to allow modifications
All BASIC keywords are now lowercase
Accepts command line input
Added a single integer array: @
Added the statement continuation character: ":"
Added 'prompt' on input statement
Enhanced print statement
Added rnd(), asc() and abs() functions
Converted the recursive descent expression parser to precedence climbing
Enhanced 'if' to allow statements to follow (instead of just "if expr then line number")
In the spirit of the original DDS, no error checking - you've been warned!
And here it is (still pretty small at only 318 lines of code):
Code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
enum {false, true};
typedef char* CHARP;
int *gosub_stackp,gosub_stack[999],line[999],line_off[999], lim[999],var[999],linenum,i,inquote;
int *gosub_foop, gosub_foo[999], at[999];
char buff[999],two[2];
CHARP m[12*999],p,q,s,d; // p used as expr ptr
FILE *f;
#if !defined(randomize)
#define randomize()
#endif
#if !defined(random)
static int random_seed = 0;
static int random(int n) {
random_seed = random_seed * 1103515245 + 12345;
return ((((random_seed / 65536) % 32768) & 0x7fffffff) % n);
}
#endif
int findop(char op) {
switch (op) {
case '=': return 1;
case '#': return 1; // <>, not equal
case '<': return 2;
case '>': return 2;
case '$': return 2; // <=
case '!': return 2; // >=
case '+': return 3;
case '-': return 3;
case '*': return 4;
case '/': return 4;
}
return -1;
}
int evalbinary(char op, int l1, int l2) {
switch (op) {
case '=': return l1 == l2;
case '#': return l1 != l2; // <>, not equal
case '<': return l1 < l2;
case '>': return l1 > l2;
case '$': return l1 <= l2; // <=
case '!': return l1 >= l2; // >=
case '+': return l1 + l2;
case '-': return l1 - l2;
case '*': return l1 * l2;
case '/': return l1 / l2;
}
return 0;
}
int expr(int prec) {
int o, this_prec;
if (*p=='-') {
p++;
o = -expr(999);
} else if (isdigit(*p)) {
o = strtol(p,&p,10); // can't use 0 for base, because of hex pbm.
} else if (*p=='(') {
++p;
o = expr(0);
++p;
} else if (*p == '@') { // @(exp)
++p;
o = at[expr(999)];
} else if (memcmp(p, "rnd", 3) == 0) { // rnd(exp)
p += 3;
o = expr(999);
o = random(o) + 1;
} else if (memcmp(p, "abs", 3) == 0) { // abs(exp)
p += 3;
o = abs(expr(999));
} else if (memcmp(p, "asc", 3) == 0) { // asc("x")
p += 5;
o = *p;
p += 3;
} else
o = var[*p++];
while ((this_prec = findop(*p)) > 0 && this_prec >= prec) {
char op = *p++;
o = evalbinary(op, o, expr(this_prec + 1));
}
return o;
}
void print_string(void) {
int width;
++p;
width = strchr(p, '"') - p;
printf("%*.*s", width, width, p);
p += width + 1;
}
// 'print' [[#num ',' ] expr { ',' [#num ','] expr }] [','] {':' stmt} eol
// expr can also be a literal string
void print(void) {
int print_nl;
print_nl = true;
for (;;) {
int width = 0;
if (*p == ':' || *p == '\0')
break;
print_nl = true;
if (*p == '#') {
++p;
width = expr(0);
if (*p == ',')
++p;
}
if (*p == '"')
print_string();
else
printf("%*d", width, expr(0));
if (*p == ',' || *p == ';') {
++p;
print_nl = false;
} else
break;
}
if (print_nl)
printf("\n");
}
void enterline(void) {
char *s = buff;
while (*s && isspace(*s))
s++;
linenum=atoi(s);
if (m[linenum])
free(m[linenum]);
if ((p=strstr(s, " ")) != NULL)
strcpy(m[linenum]=malloc(strlen(p)),p+1);
else
m[linenum]=0;
}
void load(char *fn) {
f=fopen(fn,"r");
while(fgets(buff,999,f))
(*strstr(buff,"\n")=0,enterline());
fclose(f);
}
void run(void) {
int offset = 0;
gosub_stackp=gosub_stack;
gosub_foop=gosub_foo;
linenum=1;
for(i=0; i<999; var[i++]=0)
;
// set s to point to first char of line
while(linenum) {
int if_cont;
while((s=m[linenum]) == 0)
linenum++;
if (!strstr(s,"\"")) {
while((p=strstr(s,"<>")) != 0) *p++='#',*p=' ';
while((p=strstr(s,"<=")) != 0) *p++='$',*p=' ';
while((p=strstr(s,">=")) != 0) *p++='!',*p=' ';
}
// remove extra spaces, line copied to buff
d=buff;
while((*two=*s) != '\0') {
if(*s=='"')
inquote++;
if(inquote&1||!strstr(" \t",two))
*d++=*s;
s++;
}
inquote= *d = 0;
s = buff;
line_processed:
p = (s += offset);
offset = if_cont = 0;
if(s[1] == '=') { // assignment a=exp
p=s+2;
var[*s]=expr(0);
} else if (s[0] == '@') { // assignment: @(exp)=exp
int ndx;
p = s + 1;
ndx = expr(999); // use high prec to force end at ')'
++p;
at[ndx] = expr(0);
} else
switch(*s) {
case'e': // end
case's': // stop
linenum=-1;
break;
case'r': // rem and return
if (s[2]!='m') {
linenum=*--gosub_stackp; // return
offset=*--gosub_foop;
}
break;
case'i': // input [constant_string,] var and if
if (s[1]=='n') { // input
int tmp;
char in_buff[20];
d = p = &s[5];
if (*p == '"') {
print_string();
d = ++p; // skip ','
}
tmp = *d;
p = fgets(in_buff, sizeof(in_buff) - 2, stdin);
var[tmp] = isdigit(*p) ? expr(0) : *p;
p = ++d;
} else { // if
p=s+2;
if (expr(0)) {
--p;
if_cont = true;
} else
p = 0;
}
break;
case'p': // print string and expr
p = &s[5];
print();
break;
case'g': // goto, gosub
p=s+4;
if (s[2]=='s') { // gosub
*gosub_stackp++=linenum;
p++;
}
linenum=expr(0)-1;
if (s[2] == 's') // gosub
*gosub_foop++ = (*p == ':') ? p - buff + 1: 0;
p = 0;
break;
case'f': // for
*(q=strstr(s,"to"))=0;
p=s+5;
var[i=s[3]]=expr(0);
p=q+2;
lim=expr(0);
line=linenum;
line_off = (*p == ':') ? p - buff + 1: 0;
break;
case'n':