0,0 → 1,535 |
/* |
* 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; |
} |