Created
September 2, 2021 13:58
-
-
Save rw-r-r-0644/09b1fdc147f3929905745dcfe3e9dbac to your computer and use it in GitHub Desktop.
Basic interpreter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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