Skip to content

Instantly share code, notes, and snippets.

@rw-r-r-0644
Created September 2, 2021 13:58
Show Gist options
  • Save rw-r-r-0644/09b1fdc147f3929905745dcfe3e9dbac to your computer and use it in GitHub Desktop.
Save rw-r-r-0644/09b1fdc147f3929905745dcfe3e9dbac to your computer and use it in GitHub Desktop.
Basic interpreter
/* tinybasic.c
*
* A simple TinyBASIC interpreter.
*
* NOTE: this was written one evening just for fun, with the
* explicit purpose of using a fairly small number of
* lines of code so the code is not at all readable ^^'
* I wouldn't recommend using this as base for anything else.
*
* Copyright (C) 2020 rw-r-r-0644
* This file is under GNU GPLv2.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct tb_line {
char *str;
int num;
struct tb_line *next;
} *tbline_list = NULL, *tbline_current = NULL, *tbline_next = NULL;
struct tb_line *tbsub_stack[128];
int tbsub_size = 0, tb_vars[26] = {0};
int tb_error(const char *error) {
printf("?ERROR");
if (tbline_current)
printf(" IN %d", tbline_current->num);
printf(": %s\n", error);
tbline_next = NULL;
return 1;
}
int tb_parse_word(char *s, char **n, const char *str) {
while (*s == ' ') s++;
while (*s && *str && *s == *str) s++, str++;
return (*str == '\0') && (*n = s, 1);
}
int tb_parse_number(char *s, char **n, int *o) {
while (*s == ' ') s++;
for (*n = s, *o = 0; **n >= '0' && **n <= '9'; *o = (*o * 10) + (**n - '0'), (*n)++);
return *n != s;
}
int tb_parse_var(char *s, char **n, int **var) {
while (*s == ' ') s++;
return (*s >= 'A' && *s <= 'Z') &&
(*var = &tb_vars[*s - 'A'], *n = ++s, 1);
}
int tb_parse_string(char *s, char **n, char **str, int *len) {
if (!tb_parse_word(s, str, "\""))
return 0;
for (s = *str; *s && *s != '"'; s++);
return *s ? (*len = s - *str, *n = s + 1, 1) : 0;
}
int tb_parse_relop(char *s, char **n, int *o) {
while (*s == ' ') s++;
*o = 0;
while ((*s & ~3) == 60)
*o |= 1 << (*s++ & 3);
return *o && (*o < 8) && (*n = s, 1);
}
int tb_relop_result(int relop, int a, int b) {
int o = 0;
o |= (relop & 1) && (a < b);
o |= (relop & 2) && (a == b);
o |= (relop & 4) && (a > b);
return o;
}
int tb_parse_factor(char *s, char **n, int *o);
int tb_parse_term(char *s, char **n, int *o) {
int m = 1, f;
*o = 1;
do {
if (!tb_parse_factor(s, &s, &f))
return 0;
if (m)
*o *= f;
else if (f)
*o /= f;
else
return tb_error("DIVISION BY ZERO");
} while ((tb_parse_word(s, &s, "*") && (m = 1, 1)) ||
(tb_parse_word(s, &s, "/") && (m = 0, 1)));
*n = s;
return 1;
}
int tb_parse_expression(char *s, char **n, int *out) {
int nfirst = 0, value = 0, sign = 1, term;
while ((tb_parse_word(s, &s, "+") && (sign = 1, 1)) ||
(tb_parse_word(s, &s, "-") && (sign = -1, 1)) ||
!(nfirst++)) {
if (!tb_parse_term(s, &s, &term))
return 0;
value += sign * term;
}
*n = s;
*out = value;
return 1;
}
int tb_parse_factor(char *s, char **n, int *out) {
int *var;
return (tb_parse_var(s, n, &var) && (*out = *var, 1)) ||
tb_parse_number(s, n, out) ||
(tb_parse_word(s, &s, "(") &&
tb_parse_expression(s, &s, out) &&
tb_parse_word(s, n, ")"));
}
int tb_set_next(int linenum) {
struct tb_line *i;
for (i = tbline_list; i && i->num != linenum; i = i->next);
return ((tbline_next = i) != NULL) || tb_error("UNDEFINED LINE NUMBER");
}
int tb_exec_statement(char *s, char **n);
int tbsta_if(char *s, char **n) {
int v1, v2, relop;
return (tb_parse_expression(s, &s, &v1) &&
tb_parse_relop(s, &s, &relop) &&
tb_parse_expression(s, &s, &v2) &&
tb_parse_word(s, &s, "THEN") &&
((!tb_relop_result(relop, v1, v2) && (*n = s + strlen(s))) ||
tb_exec_statement(s, n)));
}
int tbsta_end(char *s, char **n) {
tbline_next = NULL;
return 1;
}
int tbsta_let(char *s, char **n) {
int *var = NULL;
return (tb_parse_var(s, &s, &var) &&
tb_parse_word(s, &s, "=") &&
tb_parse_expression(s, n, var));
}
int tbsta_run(char *s, char **n) {
tbline_next = tbline_list;
return 1;
}
int tbsta_rem(char *s, char **n) {
for (*n = s; **n && **n != '\n'; (*n)++);
return 1;
}
int tbsta_goto(char *s, char **n) {
int value;
return tb_parse_expression(s, n, &value) &&
tb_set_next(value);
}
int tbsta_list(char *s, char **n) {
for (struct tb_line *i = tbline_list; i; i = i->next)
printf("%d%s", i->num, i->str);
return 1;
}
int tbsta_clear(char *s, char **n) {
printf("\033[2J\033[H");
return 1;
}
int tbsta_gosub(char *s, char **n) {
int value = 0;
if (tb_parse_expression(s, n, &value)) {
if (tbsub_size >= 128)
return tb_error("OUT OF MEMORY");
tbsub_stack[tbsub_size++] = tbline_next;
return tb_set_next(value);
}
return 0;
}
int tbsta_input(char *s, char **n) {
int *var = NULL;
do {
if (!tb_parse_var(s, n, &var))
break;
printf("? ");
scanf("%d", var);
} while(tb_parse_word(*n, &s, ","));
return var != NULL;
}
int tbsta_print(char *s, char **n) {
int printed = 0, pr_value = 0;
char *pr_string;
do {
if (tb_parse_string(s, &s, &pr_string, &pr_value))
printf("%.*s", pr_value, pr_string);
else if (tb_parse_expression(s, &s, &pr_value))
printf("%d", pr_value);
else
break;
printed = 1;
} while(tb_parse_word(s, &s, ","));
return printed && printf("\n") && (*n = s, 1);
}
int tbsta_return(char *s, char **n) {
if (!tbsub_size)
return tb_error("RETURN OUTSIDE A PROCEDURE");
tbline_next = tbsub_stack[--tbsub_size];
return 1;
}
struct {
char *str;
int (*call)(char *s, char **n);
} instr[] = {
{"IF", &tbsta_if },
{"END", &tbsta_end },
{"LET", &tbsta_let },
{"RUN", &tbsta_run },
{"REM", &tbsta_rem },
{"GOTO", &tbsta_goto },
{"LIST", &tbsta_list },
{"CLEAR", &tbsta_clear },
{"GOSUB", &tbsta_gosub },
{"INPUT", &tbsta_input },
{"PRINT", &tbsta_print },
{"RETURN", &tbsta_return},
};
int tb_exec_statement(char *s, char **n) {
int r = 1;
for (int i = 0; i < (sizeof(instr) / sizeof(*instr)); i++)
if (tb_parse_word(s, &s, instr[i].str)) {
r = instr[i].call(s, &s);
break;
}
return (r && (tb_parse_word(s, n, "\n") ||
tb_parse_word(s, n, "\r") ||
tb_parse_word(s, n, "\0"))) ||
tb_error("SYNTAX");
}
int tb_run_next()
{
if (tbline_next) {
char *sta = tbline_next->str;
tbline_current = tbline_next;
tbline_next = tbline_current->next;
tb_exec_statement(sta, &sta);
return 1;
}
return 0;
}
void tb_run_input()
{
static char inbuf[256];
struct tb_line **p, *i;
char *s = inbuf;
int linenum;
tbline_current = NULL;
tbline_next = NULL;
fgets(inbuf, sizeof(inbuf), stdin);
if (tb_parse_number(s, &s, &linenum)) {
for (p = &tbline_list; (*p) && (*p)->num < linenum; p = &(*p)->next);
if ((*p) && (*p)->num == linenum) {
i = *p;
free(i->str);
} else {
i = malloc(sizeof(struct tb_line));
i->num = linenum;
i->next = *p;
*p = i;
}
i->str = strdup(s);
} else {
tb_exec_statement(s, &s);
}
}
int main(int argc, char **argv) {
printf("TinyBASIC interpreter v0\n");
printf("READY\n");
while (1) {
if (tbline_next) tb_run_next();
else tb_run_input();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment