forked from KolibriOS/kolibrios
536 lines
14 KiB
C
536 lines
14 KiB
C
|
/*
|
||
|
* Tiny BASIC Interpreter and Compiler Project
|
||
|
* Interpreter module
|
||
|
*
|
||
|
* Released as Public Domain by Damian Gareth Walker 2019
|
||
|
* Created: 23-Aug-2019
|
||
|
*/
|
||
|
|
||
|
|
||
|
/* included headers */
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include "interpret.h"
|
||
|
#include "errors.h"
|
||
|
#include "options.h"
|
||
|
#include "statement.h"
|
||
|
|
||
|
|
||
|
/* forward declarations */
|
||
|
static int interpret_expression (ExpressionNode *expression);
|
||
|
static void interpret_statement (StatementNode *statement);
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Data Definitions
|
||
|
*/
|
||
|
|
||
|
/* The GOSUB Stack */
|
||
|
typedef struct gosub_stack_node GosubStackNode;
|
||
|
typedef struct gosub_stack_node {
|
||
|
ProgramLineNode *program_line; /* the line following the GOSUB */
|
||
|
GosubStackNode *next; /* stack node for the previous GOSUB */
|
||
|
} GosubStackNode;
|
||
|
|
||
|
/* private data */
|
||
|
typedef struct interpreter_data {
|
||
|
ProgramNode *program; /* the program to interpret */
|
||
|
ProgramLineNode *line; /* current line we're executing */
|
||
|
GosubStackNode *gosub_stack; /* the top of the GOSUB stack */
|
||
|
int gosub_stack_size; /* number of entries on the GOSUB stack */
|
||
|
int variables [26]; /* the numeric variables */
|
||
|
int stopped; /* set to 1 when an END is encountered */
|
||
|
ErrorHandler *errors; /* the error handler */
|
||
|
LanguageOptions *options; /* the language options */
|
||
|
} InterpreterData;
|
||
|
|
||
|
/* convenience variables */
|
||
|
static Interpreter *this; /* the object we are working with */
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Private Methods
|
||
|
*/
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Evaluate a factor for the interpreter
|
||
|
* params:
|
||
|
* FactorNode* factor the factor to evaluate
|
||
|
*/
|
||
|
static int interpret_factor (FactorNode *factor) {
|
||
|
|
||
|
/* local variables */
|
||
|
int result_store = 0; /* result of factor evaluation */
|
||
|
|
||
|
/* check factor class */
|
||
|
switch (factor->class) {
|
||
|
|
||
|
/* a regular variable */
|
||
|
case FACTOR_VARIABLE:
|
||
|
result_store = this->priv->variables [factor->data.variable - 1]
|
||
|
* (factor->sign == SIGN_POSITIVE ? 1 : -1);
|
||
|
break;
|
||
|
|
||
|
/* an integer constant */
|
||
|
case FACTOR_VALUE:
|
||
|
result_store = factor->data.value
|
||
|
* (factor->sign == SIGN_POSITIVE ? 1 : -1);
|
||
|
break;
|
||
|
|
||
|
/* an expression */
|
||
|
case FACTOR_EXPRESSION:
|
||
|
result_store = interpret_expression (factor->data.expression)
|
||
|
* (factor->sign == SIGN_POSITIVE ? 1 : -1);
|
||
|
break;
|
||
|
|
||
|
/* this only happens if the parser has failed in its duty */
|
||
|
default:
|
||
|
this->priv->errors->set_code
|
||
|
(this->priv->errors, E_INVALID_EXPRESSION, 0, this->priv->line->label);
|
||
|
}
|
||
|
|
||
|
/* check the result and return it*/
|
||
|
if (result_store < -32768 || result_store > 32767)
|
||
|
this->priv->errors->set_code
|
||
|
(this->priv->errors, E_OVERFLOW, 0, this->priv->line->label);
|
||
|
return result_store;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Evaluate a term for the interpreter
|
||
|
* params:
|
||
|
* TermNode* term the term to evaluate
|
||
|
*/
|
||
|
static int interpret_term (TermNode *term) {
|
||
|
|
||
|
/* local variables */
|
||
|
int result_store; /* the partial evaluation */
|
||
|
RightHandFactor *rhfactor; /* pointer to successive rh factor nodes */
|
||
|
int divisor; /* used to check for division by 0 before attempting */
|
||
|
|
||
|
/* calculate the first factor result */
|
||
|
result_store = interpret_factor (term->factor);
|
||
|
rhfactor = term->next;
|
||
|
|
||
|
/* adjust store according to successive rh factors */
|
||
|
while (rhfactor && ! this->priv->errors->get_code (this->priv->errors)) {
|
||
|
switch (rhfactor->op) {
|
||
|
case TERM_OPERATOR_MULTIPLY:
|
||
|
result_store *= interpret_factor (rhfactor->factor);
|
||
|
if (result_store < -32768 || result_store > 32767)
|
||
|
this->priv->errors->set_code
|
||
|
(this->priv->errors, E_OVERFLOW, 0, this->priv->line->label);
|
||
|
break;
|
||
|
case TERM_OPERATOR_DIVIDE:
|
||
|
if ((divisor = interpret_factor (rhfactor->factor)))
|
||
|
result_store /= divisor;
|
||
|
else
|
||
|
this->priv->errors->set_code
|
||
|
(this->priv->errors, E_DIVIDE_BY_ZERO, 0, this->priv->line->label);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
rhfactor = rhfactor->next;
|
||
|
}
|
||
|
|
||
|
/* return the result */
|
||
|
return result_store;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Evaluate an expression for the interpreter
|
||
|
* params:
|
||
|
* ExpressionNode* expression the expression to evaluate
|
||
|
*/
|
||
|
static int interpret_expression (ExpressionNode *expression) {
|
||
|
|
||
|
/* local variables */
|
||
|
int result_store; /* the partial evaluation */
|
||
|
RightHandTerm *rhterm; /* pointer to successive rh term nodes */
|
||
|
|
||
|
/* calculate the first term result */
|
||
|
result_store = interpret_term (expression->term);
|
||
|
rhterm = expression->next;
|
||
|
|
||
|
/* adjust store according to successive rh terms */
|
||
|
while (rhterm && ! this->priv->errors->get_code (this->priv->errors)) {
|
||
|
switch (rhterm->op) {
|
||
|
case EXPRESSION_OPERATOR_PLUS:
|
||
|
result_store += interpret_term (rhterm->term);
|
||
|
if (result_store < -32768 || result_store > 32767)
|
||
|
this->priv->errors->set_code
|
||
|
(this->priv->errors, E_OVERFLOW, 0, this->priv->line->label);
|
||
|
break;
|
||
|
case EXPRESSION_OPERATOR_MINUS:
|
||
|
result_store -= interpret_term (rhterm->term);
|
||
|
if (result_store < -32768 || result_store > 32767)
|
||
|
this->priv->errors->set_code
|
||
|
(this->priv->errors, E_OVERFLOW, 0, this->priv->line->label);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
rhterm = rhterm->next;
|
||
|
}
|
||
|
|
||
|
/* return the result */
|
||
|
return result_store;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Find a program line given its label
|
||
|
* returns:
|
||
|
* ProgramLineNode* the program line found
|
||
|
*/
|
||
|
static ProgramLineNode *find_label (int jump_label) {
|
||
|
|
||
|
/* local variables */
|
||
|
ProgramLineNode
|
||
|
*ptr, /* a line we're currently looking at */
|
||
|
*found = NULL; /* the line if found */
|
||
|
|
||
|
/* do the search */
|
||
|
for (ptr = this->priv->program->first; ptr && ! found; ptr = ptr->next)
|
||
|
if (ptr->label == jump_label)
|
||
|
found = ptr;
|
||
|
else if (ptr->label >= jump_label
|
||
|
&& this->priv->options->get_line_numbers (this->priv->options)
|
||
|
!= LINE_NUMBERS_OPTIONAL)
|
||
|
found = ptr;
|
||
|
|
||
|
/* check for errors and return what was found */
|
||
|
if (! found)
|
||
|
this->priv->errors->set_code
|
||
|
(this->priv->errors, E_INVALID_LINE_NUMBER, 0, this->priv->line->label);
|
||
|
return found;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Level 1 Routines
|
||
|
*/
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Initialise the variables
|
||
|
*/
|
||
|
static void initialise_variables (void) {
|
||
|
int count; /* counter for this->priv->variables */
|
||
|
for (count = 0; count < 26; ++count) {
|
||
|
this->priv->variables [count] = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Interpret a LET statement
|
||
|
* params:
|
||
|
* LetStatementNode* letn the LET statement details
|
||
|
*/
|
||
|
void interpret_let_statement (LetStatementNode *letn) {
|
||
|
this->priv->variables [letn->variable - 1]
|
||
|
= interpret_expression (letn->expression);
|
||
|
this->priv->line = this->priv->line->next;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Interpret an IF statement
|
||
|
* params:
|
||
|
* IfStatementNode* ifn the IF statement details
|
||
|
*/
|
||
|
void interpret_if_statement (IfStatementNode *ifn) {
|
||
|
|
||
|
/* local variables */
|
||
|
int
|
||
|
left, /* result of the left-hand expression */
|
||
|
right, /* result of the right-hand expression */
|
||
|
comparison; /* result of the comparison between the two */
|
||
|
|
||
|
/* get the expressions */
|
||
|
left = interpret_expression (ifn->left);
|
||
|
right = interpret_expression (ifn->right);
|
||
|
|
||
|
/* make the comparison */
|
||
|
switch (ifn->op) {
|
||
|
case RELOP_EQUAL: comparison = (left == right); break;
|
||
|
case RELOP_UNEQUAL: comparison = (left != right); break;
|
||
|
case RELOP_LESSTHAN: comparison = (left < right); break;
|
||
|
case RELOP_LESSOREQUAL: comparison = (left <= right); break;
|
||
|
case RELOP_GREATERTHAN: comparison = (left > right); break;
|
||
|
case RELOP_GREATEROREQUAL: comparison = (left >= right); break;
|
||
|
}
|
||
|
|
||
|
/* perform the conditional statement */
|
||
|
if (comparison && ! this->priv->errors->get_code (this->priv->errors))
|
||
|
interpret_statement (ifn->statement);
|
||
|
else
|
||
|
this->priv->line = this->priv->line->next;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Interpret a GOTO statement
|
||
|
* params:
|
||
|
* GotoStatementNode* goton the GOTO statement details
|
||
|
*/
|
||
|
void interpret_goto_statement (GotoStatementNode *goton) {
|
||
|
int label; /* the line label to go to */
|
||
|
label = interpret_expression (goton->label);
|
||
|
if (! this->priv->errors->get_code (this->priv->errors))
|
||
|
this->priv->line = find_label (label);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Interpret a GOSUB statement
|
||
|
* params:
|
||
|
* GosubStatementNode* gosubn the GOSUB statement details
|
||
|
*/
|
||
|
void interpret_gosub_statement (GosubStatementNode *gosubn) {
|
||
|
|
||
|
/* local variables */
|
||
|
GosubStackNode *gosub_node; /* indicates the program line to return to */
|
||
|
int label; /* the line label to go to */
|
||
|
|
||
|
/* create the new node on the GOSUB stack */
|
||
|
if (this->priv->gosub_stack_size < this->priv->options->get_gosub_limit
|
||
|
(this->priv->options)) {
|
||
|
gosub_node = malloc (sizeof (GosubStackNode));
|
||
|
gosub_node->program_line = this->priv->line->next;
|
||
|
gosub_node->next = this->priv->gosub_stack;
|
||
|
this->priv->gosub_stack = gosub_node;
|
||
|
++this->priv->gosub_stack_size;
|
||
|
} else
|
||
|
this->priv->errors->set_code (this->priv->errors,
|
||
|
E_TOO_MANY_GOSUBS, 0, this->priv->line->label);
|
||
|
|
||
|
/* branch to the subroutine requested */
|
||
|
if (! this->priv->errors->get_code (this->priv->errors))
|
||
|
label = interpret_expression (gosubn->label);
|
||
|
if (! this->priv->errors->get_code (this->priv->errors))
|
||
|
this->priv->line = find_label (label);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Interpret a RETURN statement
|
||
|
*/
|
||
|
void interpret_return_statement (void) {
|
||
|
|
||
|
/* local variables */
|
||
|
GosubStackNode *gosub_node; /* node popped off the GOSUB stack */
|
||
|
|
||
|
/* return to the statement following the most recent GOSUB */
|
||
|
if (this->priv->gosub_stack) {
|
||
|
this->priv->line = this->priv->gosub_stack->program_line;
|
||
|
gosub_node = this->priv->gosub_stack;
|
||
|
this->priv->gosub_stack = this->priv->gosub_stack->next;
|
||
|
free (gosub_node);
|
||
|
--this->priv->gosub_stack_size;
|
||
|
}
|
||
|
|
||
|
/* no GOSUBs led here, so raise an error */
|
||
|
else
|
||
|
this->priv->errors->set_code
|
||
|
(this->priv->errors, E_RETURN_WITHOUT_GOSUB, 0, this->priv->line->label);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Interpret a PRINT statement
|
||
|
* params:
|
||
|
* PrintStatementNode* printn the PRINT statement details
|
||
|
*/
|
||
|
void interpret_print_statement (PrintStatementNode *printn) {
|
||
|
|
||
|
/* local variables */
|
||
|
OutputNode *outn; /* current output node */
|
||
|
int
|
||
|
items = 0, /* counter ensures runtime errors appear on a new line */
|
||
|
result; /* the result of an expression */
|
||
|
|
||
|
/* print each of the output items */
|
||
|
outn = printn->first;
|
||
|
while (outn) {
|
||
|
switch (outn->class) {
|
||
|
case OUTPUT_STRING:
|
||
|
printf ("%s", outn->output.string);
|
||
|
++items;
|
||
|
break;
|
||
|
case OUTPUT_EXPRESSION:
|
||
|
result = interpret_expression (outn->output.expression);
|
||
|
if (! this->priv->errors->get_code (this->priv->errors)) {
|
||
|
printf ("%d", result);
|
||
|
++items;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
outn = outn->next;
|
||
|
}
|
||
|
|
||
|
/* print the linefeed */
|
||
|
if (items)
|
||
|
printf ("\n");
|
||
|
this->priv->line = this->priv->line->next;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Interpret an INPUT statement
|
||
|
* params:
|
||
|
* InputStatementNode* inputn the INPUT statement details
|
||
|
*/
|
||
|
void interpret_input_statement (InputStatementNode *inputn) {
|
||
|
|
||
|
/* local variables */
|
||
|
VariableListNode *variable; /* current variable to input */
|
||
|
int
|
||
|
value, /* value input from the user */
|
||
|
sign = 1, /* the default sign */
|
||
|
ch = 0; /* character from the input stream */
|
||
|
|
||
|
/* input each of the variables */
|
||
|
variable = inputn->first;
|
||
|
while (variable) {
|
||
|
do {
|
||
|
if (ch == '-') sign = -1; else sign = 1;
|
||
|
ch = getchar ();
|
||
|
} while (ch < '0' || ch > '9');
|
||
|
value = 0;
|
||
|
do {
|
||
|
value = 10 * value + (ch - '0');
|
||
|
if (value * sign < -32768 || value * sign > 32767)
|
||
|
this->priv->errors->set_code
|
||
|
(this->priv->errors, E_OVERFLOW, 0, this->priv->line->label);
|
||
|
ch = getchar ();
|
||
|
} while (ch >= '0' && ch <= '9'
|
||
|
&& ! this->priv->errors->get_code (this->priv->errors));
|
||
|
this->priv->variables [variable->variable - 1] = sign * value;
|
||
|
variable = variable->next;
|
||
|
}
|
||
|
|
||
|
/* advance to the next statement when done */
|
||
|
this->priv->line = this->priv->line->next;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Interpret an individual statement
|
||
|
* params:
|
||
|
* StatementNode* statement the statement to interpret
|
||
|
*/
|
||
|
void interpret_statement (StatementNode *statement) {
|
||
|
|
||
|
/* skip comments */
|
||
|
if (! statement) {
|
||
|
this->priv->line = this->priv->line->next;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* interpret real statements */
|
||
|
switch (statement->class) {
|
||
|
case STATEMENT_NONE:
|
||
|
break;
|
||
|
case STATEMENT_LET:
|
||
|
interpret_let_statement (statement->statement.letn);
|
||
|
break;
|
||
|
case STATEMENT_IF:
|
||
|
interpret_if_statement (statement->statement.ifn);
|
||
|
break;
|
||
|
case STATEMENT_GOTO:
|
||
|
interpret_goto_statement (statement->statement.goton);
|
||
|
break;
|
||
|
case STATEMENT_GOSUB:
|
||
|
interpret_gosub_statement (statement->statement.gosubn);
|
||
|
break;
|
||
|
case STATEMENT_RETURN:
|
||
|
interpret_return_statement ();
|
||
|
break;
|
||
|
case STATEMENT_END:
|
||
|
this->priv->stopped = 1;
|
||
|
break;
|
||
|
case STATEMENT_PRINT:
|
||
|
interpret_print_statement (statement->statement.printn);
|
||
|
break;
|
||
|
case STATEMENT_INPUT:
|
||
|
interpret_input_statement (statement->statement.inputn);
|
||
|
break;
|
||
|
default:
|
||
|
printf ("Statement type %d not implemented.\n", statement->class);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Interpret program starting from a particular line
|
||
|
* params:
|
||
|
* ProgramLineNode* program_line the starting line
|
||
|
*/
|
||
|
static void interpret_program_from (ProgramLineNode *program_line) {
|
||
|
this->priv->line = program_line;
|
||
|
while (this->priv->line
|
||
|
&& ! this->priv->stopped
|
||
|
&& ! this->priv->errors->get_code (this->priv->errors))
|
||
|
interpret_statement (this->priv->line->statement);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Public Methods
|
||
|
*/
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Interpret the program from the beginning
|
||
|
* params:
|
||
|
* Interpreter* interpreter the interpreter to use
|
||
|
* ProgramNode* program the program to interpret
|
||
|
*/
|
||
|
static void interpret (Interpreter *interpreter, ProgramNode *program) {
|
||
|
this = interpreter;
|
||
|
this->priv->program = program;
|
||
|
initialise_variables ();
|
||
|
interpret_program_from (this->priv->program->first);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Destroy the interpreter
|
||
|
* params:
|
||
|
* Interpreter* interpreter the doomed interpreter
|
||
|
*/
|
||
|
static void destroy (Interpreter *interpreter) {
|
||
|
if (interpreter) {
|
||
|
if (interpreter->priv)
|
||
|
free (interpreter->priv);
|
||
|
free (interpreter);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Constructors
|
||
|
*/
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Constructor
|
||
|
* returns:
|
||
|
* Interpreter* the new interpreter
|
||
|
*/
|
||
|
Interpreter *new_Interpreter (ErrorHandler *errors, LanguageOptions *options) {
|
||
|
|
||
|
/* allocate memory */
|
||
|
this = malloc (sizeof (Interpreter));
|
||
|
this->priv = malloc (sizeof (InterpreterData));
|
||
|
|
||
|
/* initialise methods */
|
||
|
this->interpret = interpret;
|
||
|
this->destroy = destroy;
|
||
|
|
||
|
/* initialise properties */
|
||
|
this->priv->gosub_stack = NULL;
|
||
|
this->priv->gosub_stack_size = 0;
|
||
|
this->priv->stopped = 0;
|
||
|
this->priv->errors = errors;
|
||
|
this->priv->options = options;
|
||
|
|
||
|
/* return the new object */
|
||
|
return this;
|
||
|
}
|