forked from KolibriOS/kolibrios
Added new port TinyBasic
(An improved version in conjunction with ktcc can generate executable files.) git-svn-id: svn://kolibrios.org@8733 a494cfbc-eb01-0410-851d-a64ba20cac60
This commit is contained in:
43
programs/develop/tinybasic-1.0.4/src/common.c
Normal file
43
programs/develop/tinybasic-1.0.4/src/common.c
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Tiny BASIC Interpreter and Compiler Project
|
||||
* Common service routines module
|
||||
*
|
||||
* Released as Public Domain by Damian Gareth Walker 2019
|
||||
* Created: 20-Sep-2019
|
||||
*/
|
||||
|
||||
|
||||
/* included headers */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "common.h"
|
||||
|
||||
|
||||
/*
|
||||
* Top Level Routines
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Portable case-insensitive comparison
|
||||
* params:
|
||||
* char* a string to compare
|
||||
* char* b string to compare to
|
||||
* returns:
|
||||
* int -1 if a<b, 0 if a==b, 1 if a>b
|
||||
*/
|
||||
int tinybasic_strcmp (char *a, char *b) {
|
||||
do {
|
||||
if (toupper (*a) != toupper (*b))
|
||||
return (toupper (*a) > toupper (*b))
|
||||
- (toupper (*a) < toupper (*b));
|
||||
else {
|
||||
a++;
|
||||
b++;
|
||||
}
|
||||
} while (*a && *b);
|
||||
return 0;
|
||||
}
|
||||
|
223
programs/develop/tinybasic-1.0.4/src/errors.c
Normal file
223
programs/develop/tinybasic-1.0.4/src/errors.c
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Tiny BASIC
|
||||
* Error Handling Module
|
||||
*
|
||||
* Released as Public Domain by Damian Gareth Walker 2019
|
||||
* Created: 18-Aug-2019
|
||||
*/
|
||||
|
||||
|
||||
/* included headers */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "errors.h"
|
||||
|
||||
|
||||
/*
|
||||
* Internal Data Structures
|
||||
*/
|
||||
|
||||
|
||||
/* Private data */
|
||||
typedef struct {
|
||||
ErrorCode error; /* the last error encountered */
|
||||
int line; /* the source line on which the error occurred */
|
||||
int label; /* the label for the source line */
|
||||
} Private;
|
||||
|
||||
|
||||
/*
|
||||
* Internal Data
|
||||
*/
|
||||
|
||||
|
||||
/* convenience variables */
|
||||
ErrorHandler *this; /* object being worked on */
|
||||
Private *data; /* private data of object being worked on */
|
||||
|
||||
/* global variables */
|
||||
static char *messages[E_LAST] = { /* the error messages */
|
||||
"Successful",
|
||||
"Invalid line number",
|
||||
"Unrecognised command",
|
||||
"Invalid variable",
|
||||
"Invalid assignment",
|
||||
"Invalid expression",
|
||||
"Missing )",
|
||||
"Invalid PRINT output",
|
||||
"Bad command line",
|
||||
"File not found",
|
||||
"Invalid operator",
|
||||
"THEN expected",
|
||||
"Unexpected parameter",
|
||||
"RETURN without GOSUB",
|
||||
"Divide by zero",
|
||||
"Overflow",
|
||||
"Out of memory",
|
||||
"Too many gosubs"
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Public Methods
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Record an error encountered
|
||||
* globals:
|
||||
* ErrorCode error the last error encountered
|
||||
* int line the source line
|
||||
* int label the line's label
|
||||
* params:
|
||||
* ErrorCode new_error the error code to set
|
||||
* int new_line the source line to set
|
||||
* int new_label the label to set
|
||||
*/
|
||||
static void set_code (ErrorHandler *errors, ErrorCode new_error, int new_line,
|
||||
int new_label) {
|
||||
|
||||
/* initialise */
|
||||
this = errors;
|
||||
data = this->data;
|
||||
|
||||
/* set the properties */
|
||||
data->error = new_error;
|
||||
data->line = new_line;
|
||||
data->label = new_label;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the last error code encountered
|
||||
* params:
|
||||
* ErrorHandler* errors the error handler
|
||||
* returns:
|
||||
* ErrorCode the last error encountered
|
||||
*/
|
||||
static ErrorCode get_code (ErrorHandler *errors) {
|
||||
this = errors;
|
||||
data = this->data;
|
||||
return data->error;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the last error line encountered
|
||||
* params:
|
||||
* ErrorHandler* errors the error handler
|
||||
* returns:
|
||||
* int the source line of the last error
|
||||
*/
|
||||
static int get_line (ErrorHandler *errors) {
|
||||
this = errors;
|
||||
data = this->data;
|
||||
return data->line;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the last error label encountered
|
||||
* params:
|
||||
* ErrorHandler* errors the error handler
|
||||
* returns:
|
||||
* int the line label of the last error
|
||||
*/
|
||||
static int get_label (ErrorHandler *errors) {
|
||||
this = errors;
|
||||
data = this->data;
|
||||
return data->label;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate an error message
|
||||
* params:
|
||||
* ErrorHandler* errors the error handler
|
||||
* globals:
|
||||
* char* messages a list of error messages
|
||||
* returns:
|
||||
* char* the full error message
|
||||
*/
|
||||
static char *get_text (ErrorHandler *errors) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
*message, /* the complete message */
|
||||
*line_text, /* source line N */
|
||||
*label_text; /* label N */
|
||||
|
||||
/* initialise the error object */
|
||||
this = errors;
|
||||
data = this->data;
|
||||
|
||||
/* get the source line, if there is one */
|
||||
line_text = malloc (20);
|
||||
if (data->line)
|
||||
sprintf (line_text, ", source line %d", data->line);
|
||||
else
|
||||
strcpy (line_text, "");
|
||||
|
||||
/* get the source label, if there is one */
|
||||
label_text = malloc (19);
|
||||
if (data->label)
|
||||
sprintf (label_text, ", line label %d", data->label);
|
||||
else
|
||||
strcpy (label_text, "");
|
||||
|
||||
/* put the error message together */
|
||||
message = malloc (strlen (messages[data->error]) + strlen (line_text)
|
||||
+ strlen (label_text) + 1);
|
||||
strcpy (message, messages[data->error]);
|
||||
strcat (message, line_text);
|
||||
strcat (message, label_text);
|
||||
free (line_text);
|
||||
free (label_text);
|
||||
|
||||
/* return the assembled error message */
|
||||
return message;
|
||||
}
|
||||
|
||||
/*
|
||||
* ErrorHandler destructor
|
||||
* params:
|
||||
* ErrorHandler* errors the doomed error handler
|
||||
*/
|
||||
static void destroy (ErrorHandler *errors) {
|
||||
if ((this = errors)) {
|
||||
data = this->data;
|
||||
free (data);
|
||||
free (this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Constructors
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Principal constructor
|
||||
* returns:
|
||||
* ErrorHandler* the new error handler object
|
||||
*/
|
||||
ErrorHandler *new_ErrorHandler (void) {
|
||||
|
||||
/* allocate memory */
|
||||
this = malloc (sizeof (ErrorHandler));
|
||||
this->data = data = malloc (sizeof (Private));
|
||||
|
||||
/* initialise the methods */
|
||||
this->set_code = set_code;
|
||||
this->get_code = get_code;
|
||||
this->get_line = get_line;
|
||||
this->get_label = get_label;
|
||||
this->get_text = get_text;
|
||||
this->destroy = destroy;
|
||||
|
||||
/* initialise the properties */
|
||||
data->error = E_NONE;
|
||||
data->line = 0;
|
||||
data->label = 0;
|
||||
|
||||
/* return the new object */
|
||||
return this;
|
||||
}
|
203
programs/develop/tinybasic-1.0.4/src/expression.c
Normal file
203
programs/develop/tinybasic-1.0.4/src/expression.c
Normal file
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Tiny BASIC
|
||||
* Expression Handling Module
|
||||
*
|
||||
* Released as Public Domain by Damian Gareth Walker 2019
|
||||
* Created: 16-Aug-2019
|
||||
*/
|
||||
|
||||
|
||||
/* included headers */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "parser.h"
|
||||
#include "expression.h"
|
||||
#include "errors.h"
|
||||
|
||||
|
||||
/*
|
||||
* Functions for Dealing with Factors
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Constructor for a factor
|
||||
* returns:
|
||||
* FactorNode* the new factor
|
||||
*/
|
||||
FactorNode *factor_create (void) {
|
||||
|
||||
/* local variables */
|
||||
FactorNode *factor; /* the new factor */
|
||||
|
||||
/* allocate memory and initialise members */
|
||||
factor = malloc (sizeof (FactorNode));
|
||||
factor->class = FACTOR_NONE;
|
||||
factor->sign = SIGN_POSITIVE;
|
||||
|
||||
/* return the factor */
|
||||
return factor;
|
||||
}
|
||||
|
||||
/*
|
||||
* Destructor for a factor
|
||||
* params:
|
||||
* FactorNode* factor the doomed factor
|
||||
*/
|
||||
void factor_destroy (FactorNode *factor) {
|
||||
if (factor->class == FACTOR_EXPRESSION && factor->data.expression) {
|
||||
expression_destroy (factor->data.expression);
|
||||
}
|
||||
free (factor);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Functions for Dealing with Terms
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Constructor for a right-hand factor of a term
|
||||
* returns:
|
||||
* RightHandFactor* the new RH factor of a term
|
||||
*/
|
||||
RightHandFactor *rhfactor_create (void) {
|
||||
|
||||
/* local variables */
|
||||
RightHandFactor *rhfactor; /* the RH factor of a term to create */
|
||||
|
||||
/* allocate memory and initialise members */
|
||||
rhfactor = malloc (sizeof (RightHandFactor));
|
||||
rhfactor->op = TERM_OPERATOR_NONE;
|
||||
rhfactor->factor = NULL;
|
||||
rhfactor->next = NULL;
|
||||
|
||||
/* return the new RH term */
|
||||
return rhfactor;
|
||||
}
|
||||
|
||||
/*
|
||||
* Recursive destructor for a right-hand factor of a term
|
||||
* params:
|
||||
* RightHandFactor* rhfactor the doomed RH factor of a term
|
||||
*/
|
||||
void rhfactor_destroy (RightHandFactor *rhfactor) {
|
||||
if (rhfactor->next)
|
||||
rhfactor_destroy (rhfactor->next);
|
||||
if (rhfactor->factor)
|
||||
factor_destroy (rhfactor->factor);
|
||||
free (rhfactor);
|
||||
}
|
||||
|
||||
/*
|
||||
* Constructor for a term
|
||||
* returns:
|
||||
* TermNode* the new term
|
||||
*/
|
||||
TermNode *term_create (void) {
|
||||
|
||||
/* local variables */
|
||||
TermNode *term; /* the new term */
|
||||
|
||||
/* allocate memory and initialise members */
|
||||
term = malloc (sizeof (TermNode));
|
||||
term->factor = NULL;
|
||||
term->next = NULL;
|
||||
|
||||
/* return the new term */
|
||||
return term;
|
||||
}
|
||||
|
||||
/*
|
||||
* Destructor for a term
|
||||
* params:
|
||||
* TermNode* term the doomed term
|
||||
*/
|
||||
void term_destroy (TermNode *term) {
|
||||
|
||||
/* destroy the consituent parts of the term */
|
||||
if (term->factor)
|
||||
factor_destroy (term->factor);
|
||||
if (term->next)
|
||||
rhfactor_destroy (term->next);
|
||||
|
||||
/* destroy the term itself */
|
||||
free (term);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Functions for dealing with Expressions
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Constructor for a right-hand expression
|
||||
* returns:
|
||||
* RightHandTerm* the RH term of an expression
|
||||
*/
|
||||
RightHandTerm *rhterm_create (void) {
|
||||
|
||||
/* local variables */
|
||||
RightHandTerm *rhterm; /* the new RH expression */
|
||||
|
||||
/* allocate memory and initialise members */
|
||||
rhterm = malloc (sizeof (RightHandTerm));
|
||||
rhterm->op = EXPRESSION_OPERATOR_NONE;
|
||||
rhterm->term = NULL;
|
||||
rhterm->next = NULL;
|
||||
|
||||
/* return the new right-hand expression */
|
||||
return rhterm;
|
||||
}
|
||||
|
||||
/*
|
||||
* Recursive destructor for a right-hand term of an expression
|
||||
* params:
|
||||
* RightHandTerm* rhterm the doomed RH expression
|
||||
*/
|
||||
void rhterm_destroy (RightHandTerm *rhterm) {
|
||||
if (rhterm->next)
|
||||
rhterm_destroy (rhterm->next);
|
||||
if (rhterm->term)
|
||||
term_destroy (rhterm->term);
|
||||
free (rhterm);
|
||||
}
|
||||
|
||||
/*
|
||||
* Constructor for an expression
|
||||
* returns:
|
||||
* ExpressionNode* the new expression
|
||||
*/
|
||||
ExpressionNode *expression_create (void) {
|
||||
|
||||
/* local variables */
|
||||
ExpressionNode *expression; /* the new expression */
|
||||
|
||||
/* allocate memory and initialise members */
|
||||
expression = malloc (sizeof (ExpressionNode));
|
||||
expression->term = NULL;
|
||||
expression->next = NULL;
|
||||
|
||||
/* return the new expression */
|
||||
return expression;
|
||||
}
|
||||
|
||||
/*
|
||||
* Destructor for a expression
|
||||
* params:
|
||||
* ExpressionNode* expression the doomed expression
|
||||
*/
|
||||
void expression_destroy (ExpressionNode *expression) {
|
||||
|
||||
/* destroy the consituent parts of the expression */
|
||||
if (expression->term)
|
||||
term_destroy (expression->term);
|
||||
if (expression->next)
|
||||
rhterm_destroy (expression->next);
|
||||
|
||||
/* destroy the expression itself */
|
||||
free (expression);
|
||||
}
|
619
programs/develop/tinybasic-1.0.4/src/formatter.c
Normal file
619
programs/develop/tinybasic-1.0.4/src/formatter.c
Normal file
@@ -0,0 +1,619 @@
|
||||
/*
|
||||
* Tiny BASIC
|
||||
* Listing Output Module
|
||||
*
|
||||
* Released as Public Domain by Damian Gareth Walker, 2019
|
||||
* Created: 18-Sep-2019
|
||||
*/
|
||||
|
||||
|
||||
/* included headers */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "formatter.h"
|
||||
#include "expression.h"
|
||||
#include "errors.h"
|
||||
#include "parser.h"
|
||||
|
||||
|
||||
/*
|
||||
* Data Definitions
|
||||
*/
|
||||
|
||||
|
||||
/* private formatter data */
|
||||
typedef struct formatter_data {
|
||||
ErrorHandler *errors; /* the error handler */
|
||||
} FormatterData;
|
||||
|
||||
/* convenience variables */
|
||||
static Formatter *this; /* the object being worked on */
|
||||
|
||||
/*
|
||||
* Forward References
|
||||
*/
|
||||
|
||||
|
||||
/* factor_output() has a forward reference to output_expression() */
|
||||
static char *output_expression (ExpressionNode *expression);
|
||||
|
||||
/* output_statement() has a forward reference from output_if() */
|
||||
static char *output_statement (StatementNode *statement);
|
||||
|
||||
|
||||
/*
|
||||
* Functions
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Output a factor
|
||||
* params:
|
||||
* FactorNode* factor the factor to output
|
||||
* return:
|
||||
* char* the text representation of the factor
|
||||
*/
|
||||
static char *output_factor (FactorNode *factor) {
|
||||
|
||||
/* local variables */
|
||||
char *factor_text = NULL, /* the text of the whole factor */
|
||||
*factor_buffer = NULL, /* temporary buffer for prepending to factor_text */
|
||||
*expression_text = NULL; /* the text of a subexpression */
|
||||
|
||||
/* work out the main factor text */
|
||||
switch (factor->class) {
|
||||
case FACTOR_VARIABLE:
|
||||
factor_text = malloc (2);
|
||||
sprintf (factor_text, "%c", factor->data.variable + 'A' - 1);
|
||||
break;
|
||||
case FACTOR_VALUE:
|
||||
factor_text = malloc (7);
|
||||
sprintf (factor_text, "%d", factor->data.value);
|
||||
break;
|
||||
case FACTOR_EXPRESSION:
|
||||
if ((expression_text = output_expression (factor->data.expression))) {
|
||||
factor_text = malloc (strlen (expression_text) + 3);
|
||||
sprintf (factor_text, "(%s)", expression_text);
|
||||
free (expression_text);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_INVALID_EXPRESSION, 0, 0);
|
||||
}
|
||||
|
||||
/* apply a negative sign, if necessary */
|
||||
if (factor_text && factor->sign == SIGN_NEGATIVE) {
|
||||
factor_buffer = malloc (strlen (factor_text) + 2);
|
||||
sprintf (factor_buffer, "-%s", factor_text);
|
||||
free (factor_text);
|
||||
factor_text = factor_buffer;
|
||||
}
|
||||
|
||||
/* return the final factor representation */
|
||||
return factor_text;
|
||||
}
|
||||
|
||||
/*
|
||||
* Output a term
|
||||
* params:
|
||||
* TermNode* term the term to output
|
||||
* returns:
|
||||
* char* the text representation of the term
|
||||
*/
|
||||
static char *output_term (TermNode *term) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
*term_text = NULL, /* the text of the whole term */
|
||||
*factor_text = NULL, /* the text of each factor */
|
||||
operator_char; /* the operator that joins the righthand factor */
|
||||
RightHandFactor *rhfactor; /* right hand factors of the expression */
|
||||
|
||||
/* begin with the initial factor */
|
||||
if ((term_text = output_factor (term->factor))) {
|
||||
rhfactor = term->next;
|
||||
while (! this->priv->errors->get_code (this->priv->errors) && rhfactor) {
|
||||
|
||||
/* ascertain the operator text */
|
||||
switch (rhfactor->op) {
|
||||
case TERM_OPERATOR_MULTIPLY:
|
||||
operator_char = '*';
|
||||
break;
|
||||
case TERM_OPERATOR_DIVIDE:
|
||||
operator_char = '/';
|
||||
break;
|
||||
default:
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_INVALID_EXPRESSION, 0, 0);
|
||||
free (term_text);
|
||||
term_text = NULL;
|
||||
}
|
||||
|
||||
/* get the factor that follows the operator */
|
||||
if (! this->priv->errors->get_code (this->priv->errors)
|
||||
&& (factor_text = output_factor (rhfactor->factor))) {
|
||||
term_text = realloc (term_text,
|
||||
strlen (term_text) + strlen (factor_text) + 2);
|
||||
sprintf (term_text, "%s%c%s", term_text, operator_char, factor_text);
|
||||
free (factor_text);
|
||||
}
|
||||
|
||||
/* look for another term on the right of the expression */
|
||||
rhfactor = rhfactor->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* return the expression text */
|
||||
return term_text;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Output an expression for a program listing
|
||||
* params:
|
||||
* ExpressionNode* expression the expression to output
|
||||
* returns:
|
||||
* char* new string containint the expression text
|
||||
*/
|
||||
static char *output_expression (ExpressionNode *expression) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
*expression_text = NULL, /* the text of the whole expression */
|
||||
*term_text = NULL, /* the text of each term */
|
||||
operator_char; /* the operator that joins the righthand term */
|
||||
RightHandTerm *rhterm; /* right hand terms of the expression */
|
||||
|
||||
/* begin with the initial term */
|
||||
if ((expression_text = output_term (expression->term))) {
|
||||
rhterm = expression->next;
|
||||
while (! this->priv->errors->get_code (this->priv->errors) && rhterm) {
|
||||
|
||||
/* ascertain the operator text */
|
||||
switch (rhterm->op) {
|
||||
case EXPRESSION_OPERATOR_PLUS:
|
||||
operator_char = '+';
|
||||
break;
|
||||
case EXPRESSION_OPERATOR_MINUS:
|
||||
operator_char = '-';
|
||||
break;
|
||||
default:
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_INVALID_EXPRESSION, 0, 0);
|
||||
free (expression_text);
|
||||
expression_text = NULL;
|
||||
}
|
||||
|
||||
/* get the terms that follow the operators */
|
||||
if (! this->priv->errors->get_code (this->priv->errors)
|
||||
&& (term_text = output_term (rhterm->term))) {
|
||||
expression_text = realloc (expression_text,
|
||||
strlen (expression_text) + strlen (term_text) + 2);
|
||||
sprintf (expression_text, "%s%c%s", expression_text, operator_char,
|
||||
term_text);
|
||||
free (term_text);
|
||||
}
|
||||
|
||||
/* look for another term on the right of the expression */
|
||||
rhterm = rhterm->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* return the expression text */
|
||||
return expression_text;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* LET statement output
|
||||
* params:
|
||||
* LetStatementNode* letn data for the LET statement
|
||||
* returns:
|
||||
* char* the LET statement text
|
||||
*/
|
||||
static char *output_let (LetStatementNode *letn) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
*let_text = NULL, /* the LET text to be assembled */
|
||||
*expression_text = NULL; /* the text of the expression */
|
||||
|
||||
/* assemble the expression */
|
||||
expression_text = output_expression (letn->expression);
|
||||
|
||||
/* assemble the final LET text, if we have an expression */
|
||||
if (expression_text) {
|
||||
let_text = malloc (7 + strlen (expression_text));
|
||||
sprintf (let_text, "LET %c=%s", 'A' - 1 + letn->variable, expression_text);
|
||||
free (expression_text);
|
||||
}
|
||||
|
||||
/* return it */
|
||||
return let_text;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* IF statement output
|
||||
* params:
|
||||
* IfStatementNode* ifn data for the IF statement
|
||||
* returns:
|
||||
* char* the IF statement text
|
||||
*/
|
||||
static char *output_if (IfStatementNode *ifn) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
*if_text = NULL, /* the LET text to be assembled */
|
||||
*left_text = NULL, /* the text of the left expression */
|
||||
*op_text = NULL, /* the operator text */
|
||||
*right_text = NULL, /* the text of the right expression */
|
||||
*statement_text = NULL; /* the text of the conditional statement */
|
||||
|
||||
/* assemble the expressions and conditional statement */
|
||||
left_text = output_expression (ifn->left);
|
||||
right_text = output_expression (ifn->right);
|
||||
statement_text = output_statement (ifn->statement);
|
||||
|
||||
/* work out the operator text */
|
||||
op_text = malloc (3);
|
||||
switch (ifn->op) {
|
||||
case RELOP_EQUAL: strcpy (op_text, "="); break;
|
||||
case RELOP_UNEQUAL: strcpy (op_text, "<>"); break;
|
||||
case RELOP_LESSTHAN: strcpy (op_text, "<"); break;
|
||||
case RELOP_LESSOREQUAL: strcpy (op_text, "<="); break;
|
||||
case RELOP_GREATERTHAN: strcpy (op_text, ">"); break;
|
||||
case RELOP_GREATEROREQUAL: strcpy (op_text, ">="); break;
|
||||
}
|
||||
|
||||
/* assemble the final IF text, if we have everything we need */
|
||||
if (left_text && op_text && right_text && statement_text) {
|
||||
if_text = malloc (3 + strlen (left_text) + strlen (op_text) +
|
||||
strlen (right_text) + 6 + strlen (statement_text) + 1);
|
||||
sprintf (if_text, "IF %s%s%s THEN %s", left_text, op_text, right_text,
|
||||
statement_text);
|
||||
}
|
||||
|
||||
/* free up the temporary bits of memory we've reserved */
|
||||
if (left_text) free (left_text);
|
||||
if (op_text) free (op_text);
|
||||
if (right_text) free (right_text);
|
||||
if (statement_text) free (statement_text);
|
||||
|
||||
/* return it */
|
||||
return if_text;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GOTO statement output
|
||||
* params:
|
||||
* GotoStatementNode* goton data for the GOTO statement
|
||||
* returns:
|
||||
* char* the GOTO statement text
|
||||
*/
|
||||
static char *output_goto (GotoStatementNode *goton) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
*goto_text = NULL, /* the GOTO text to be assembled */
|
||||
*expression_text = NULL; /* the text of the expression */
|
||||
|
||||
/* assemble the expression */
|
||||
expression_text = output_expression (goton->label);
|
||||
|
||||
/* assemble the final LET text, if we have an expression */
|
||||
if (expression_text) {
|
||||
goto_text = malloc (6 + strlen (expression_text));
|
||||
sprintf (goto_text, "GOTO %s", expression_text);
|
||||
free (expression_text);
|
||||
}
|
||||
|
||||
/* return it */
|
||||
return goto_text;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GOSUB statement output
|
||||
* params:
|
||||
* GosubStatementNode* gosubn data for the GOSUB statement
|
||||
* returns:
|
||||
* char* the GOSUB statement text
|
||||
*/
|
||||
static char *output_gosub (GosubStatementNode *gosubn) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
*gosub_text = NULL, /* the GOSUB text to be assembled */
|
||||
*expression_text = NULL; /* the text of the expression */
|
||||
|
||||
/* assemble the expression */
|
||||
expression_text = output_expression (gosubn->label);
|
||||
|
||||
/* assemble the final LET text, if we have an expression */
|
||||
if (expression_text) {
|
||||
gosub_text = malloc (7 + strlen (expression_text));
|
||||
sprintf (gosub_text, "GOSUB %s", expression_text);
|
||||
free (expression_text);
|
||||
}
|
||||
|
||||
/* return it */
|
||||
return gosub_text;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* END statement output
|
||||
* returns:
|
||||
* char* A new string with the text "END"
|
||||
*/
|
||||
static char *output_end (void) {
|
||||
char *end_text; /* the full text of the END command */
|
||||
end_text = malloc (4);
|
||||
strcpy (end_text, "END");
|
||||
return end_text;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* RETURN statement output
|
||||
* returns:
|
||||
* char* A new string with the text "RETURN"
|
||||
*/
|
||||
static char *output_return (void) {
|
||||
char *return_text; /* the full text of the RETURN command */
|
||||
return_text = malloc (7);
|
||||
strcpy (return_text, "RETURN");
|
||||
return return_text;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PRINT statement output
|
||||
* params:
|
||||
* PrintStatementNode* printn data for the PRINT statement
|
||||
* returns:
|
||||
* char* the PRINT statement text
|
||||
*/
|
||||
static char *output_print (PrintStatementNode *printn) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
*print_text, /* the PRINT text to be assembled */
|
||||
*output_text = NULL; /* the text of the current output item */
|
||||
OutputNode *output; /* the current output item */
|
||||
|
||||
/* initialise the PRINT statement */
|
||||
print_text = malloc (6);
|
||||
strcpy (print_text, "PRINT");
|
||||
|
||||
/* add the output items */
|
||||
if ((output = printn->first)) {
|
||||
do {
|
||||
|
||||
/* add the separator */
|
||||
print_text = realloc (print_text, strlen (print_text) + 2);
|
||||
strcat (print_text, output == printn->first ? " " : ",");
|
||||
|
||||
/* format the output item */
|
||||
switch (output->class) {
|
||||
case OUTPUT_STRING:
|
||||
output_text = malloc (strlen (output->output.string) + 3);
|
||||
sprintf (output_text, "%c%s%c", '"', output->output.string, '"');
|
||||
break;
|
||||
case OUTPUT_EXPRESSION:
|
||||
output_text = output_expression (output->output.expression);
|
||||
break;
|
||||
}
|
||||
|
||||
/* add the output item */
|
||||
print_text = realloc (print_text,
|
||||
strlen (print_text) + strlen (output_text) + 1);
|
||||
strcat (print_text, output_text);
|
||||
free (output_text);
|
||||
|
||||
/* look for the next output item */
|
||||
} while ((output = output->next));
|
||||
}
|
||||
|
||||
/* return the assembled text */
|
||||
return print_text;
|
||||
}
|
||||
|
||||
/*
|
||||
* INPUT statement output
|
||||
* params:
|
||||
* InputStatementNode* inputn the input statement node to show
|
||||
* returns:
|
||||
* char * the text of the INPUT statement
|
||||
*/
|
||||
static char *output_input (InputStatementNode *inputn) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
*input_text, /* the INPUT text to be assembled */
|
||||
var_text[3]; /* text representation of each variable with separator */
|
||||
VariableListNode *variable; /* the current output item */
|
||||
|
||||
/* initialise the INPUT statement */
|
||||
input_text = malloc (6);
|
||||
strcpy (input_text, "INPUT");
|
||||
|
||||
/* add the output items */
|
||||
if ((variable = inputn->first)) {
|
||||
do {
|
||||
sprintf (var_text, "%c%c",
|
||||
(variable == inputn->first) ? ' ' : ',',
|
||||
variable->variable + 'A' - 1);
|
||||
input_text = realloc (input_text, strlen (input_text) + 3);
|
||||
strcat (input_text, var_text);
|
||||
} while ((variable = variable->next));
|
||||
}
|
||||
|
||||
/* return the assembled text */
|
||||
return input_text;
|
||||
}
|
||||
|
||||
/*
|
||||
* Statement output
|
||||
* params:
|
||||
* StatementNode* statement the statement to output
|
||||
* returns:
|
||||
* char* a string containing the statement line
|
||||
*/
|
||||
static char *output_statement (StatementNode *statement) {
|
||||
|
||||
/* local variables */
|
||||
char *output = NULL; /* the text output */
|
||||
|
||||
/* return null output for comments */
|
||||
if (! statement)
|
||||
return NULL;
|
||||
|
||||
/* build the statement itself */
|
||||
switch (statement->class) {
|
||||
case STATEMENT_LET:
|
||||
output = output_let (statement->statement.letn);
|
||||
break;
|
||||
case STATEMENT_IF:
|
||||
output = output_if (statement->statement.ifn);
|
||||
break;
|
||||
case STATEMENT_GOTO:
|
||||
output = output_goto (statement->statement.goton);
|
||||
break;
|
||||
case STATEMENT_GOSUB:
|
||||
output = output_gosub (statement->statement.gosubn);
|
||||
break;
|
||||
case STATEMENT_RETURN:
|
||||
output = output_return ();
|
||||
break;
|
||||
case STATEMENT_END:
|
||||
output = output_end ();
|
||||
break;
|
||||
case STATEMENT_PRINT:
|
||||
output = output_print (statement->statement.printn);
|
||||
break;
|
||||
case STATEMENT_INPUT:
|
||||
output = output_input (statement->statement.inputn);
|
||||
break;
|
||||
default:
|
||||
output = malloc (24);
|
||||
strcpy (output, "Unrecognised statement.");
|
||||
}
|
||||
|
||||
/* return the listing line */
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Program Line Output
|
||||
* params:
|
||||
* ProgramLineNode* program_line the line to output
|
||||
*/
|
||||
static void generate_line (ProgramLineNode *program_line) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
label_text [7], /* line label text */
|
||||
*output = NULL, /* the rest of the output */
|
||||
*line_text = NULL; /* the assembled line */
|
||||
|
||||
/* initialise the line label */
|
||||
if (program_line->label)
|
||||
sprintf (label_text, "%5d ", program_line->label);
|
||||
else
|
||||
strcpy (label_text, " ");
|
||||
|
||||
/* build the statement itself */
|
||||
output = output_statement (program_line->statement);
|
||||
|
||||
/* if this wasn't a comment, add it to the program */
|
||||
if (output) {
|
||||
line_text = malloc (strlen (label_text) + strlen (output) + 2);
|
||||
sprintf (line_text, "%s%s\n", label_text, output);
|
||||
free (output);
|
||||
this->output = realloc (this->output,
|
||||
strlen (this->output) + strlen (line_text) + 1);
|
||||
strcat (this->output, line_text);
|
||||
free (line_text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Public Methods
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Create a formatted version of the program
|
||||
* params:
|
||||
* Formatter* fomatter the formatter
|
||||
* ProgramNode* program the syntax tree
|
||||
*/
|
||||
static void generate (Formatter *formatter, ProgramNode *program) {
|
||||
|
||||
/* local variables */
|
||||
ProgramLineNode *program_line; /* line to process */
|
||||
|
||||
/* initialise this object */
|
||||
this = formatter;
|
||||
|
||||
/* generate the code for the lines */
|
||||
program_line = program->first;
|
||||
while (program_line) {
|
||||
generate_line (program_line);
|
||||
program_line = program_line->next;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Destroy the formatter when no longer needed
|
||||
* params:
|
||||
* Formatter* formatter the doomed formatter
|
||||
*/
|
||||
static void destroy (Formatter *formatter) {
|
||||
if (formatter) {
|
||||
if (formatter->output)
|
||||
free (formatter->output);
|
||||
if (formatter->priv)
|
||||
free (formatter->priv);
|
||||
free (formatter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Constructors
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* The Formatter constructor
|
||||
* params:
|
||||
* ErrorHandler *errors the error handler object
|
||||
* returns:
|
||||
* Formatter* the new formatter
|
||||
*/
|
||||
Formatter *new_Formatter (ErrorHandler *errors) {
|
||||
|
||||
/* allocate memory */
|
||||
this = malloc (sizeof (Formatter));
|
||||
this->priv = malloc (sizeof (FormatterData));
|
||||
|
||||
/* initialise methods */
|
||||
this->generate = generate;
|
||||
this->destroy = destroy;
|
||||
|
||||
/* initialise properties */
|
||||
this->output = malloc (sizeof (char));
|
||||
*this->output = '\0';
|
||||
this->priv->errors = errors;
|
||||
|
||||
/* return the new object */
|
||||
return this;
|
||||
}
|
882
programs/develop/tinybasic-1.0.4/src/generatec.c
Normal file
882
programs/develop/tinybasic-1.0.4/src/generatec.c
Normal file
@@ -0,0 +1,882 @@
|
||||
/*
|
||||
* Tiny BASIC Interpreter and Compiler Project
|
||||
* C Output Module
|
||||
*
|
||||
* Copyright (C) Damian Gareth Walker 2019
|
||||
* Created: 03-Oct-2019
|
||||
*/
|
||||
|
||||
|
||||
/* included headers */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "statement.h"
|
||||
#include "expression.h"
|
||||
#include "errors.h"
|
||||
#include "parser.h"
|
||||
#include "options.h"
|
||||
#include "generatec.h"
|
||||
|
||||
|
||||
/*
|
||||
* Internal Data
|
||||
*/
|
||||
|
||||
|
||||
/* label list */
|
||||
typedef struct label {
|
||||
int number; /* the label number */
|
||||
struct label *next; /* the next label */
|
||||
} CLabel;
|
||||
|
||||
/* private data */
|
||||
typedef struct {
|
||||
unsigned int input_used:1; /* true if we need the input routine */
|
||||
unsigned long int vars_used:26; /* true for each variable used */
|
||||
CLabel *first_label; /* the start of a list of labels */
|
||||
char *code; /* the main block of generated code */
|
||||
ErrorHandler *errors; /* error handler for compilation */
|
||||
LanguageOptions *options; /* the language options for compilation */
|
||||
} Private;
|
||||
|
||||
/* convenience variables */
|
||||
static CProgram *this; /* the object being worked on */
|
||||
static Private *data; /* the private data of the object */
|
||||
static ErrorHandler *errors; /* the error handler */
|
||||
static LanguageOptions *options; /* the language options */
|
||||
|
||||
|
||||
/*
|
||||
* Forward References
|
||||
*/
|
||||
|
||||
|
||||
/* factor_output() has a forward reference to output_expression() */
|
||||
static char *output_expression (ExpressionNode *expression);
|
||||
|
||||
/* output_statement() has a forward reference from output_if() */
|
||||
static char *output_statement (StatementNode *statement);
|
||||
|
||||
|
||||
/*
|
||||
* Level 6 Functions
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Output a factor
|
||||
* params:
|
||||
* FactorNode* factor the factor to output
|
||||
* return:
|
||||
* char* the text representation of the factor
|
||||
*/
|
||||
static char *output_factor (FactorNode *factor) {
|
||||
|
||||
/* local variables */
|
||||
char *factor_text = NULL, /* the text of the whole factor */
|
||||
*factor_buffer = NULL, /* temporary buffer for prepending to factor_text */
|
||||
*expression_text = NULL; /* the text of a subexpression */
|
||||
|
||||
/* work out the main factor text */
|
||||
switch (factor->class) {
|
||||
case FACTOR_VARIABLE:
|
||||
factor_text = malloc (2);
|
||||
sprintf (factor_text, "%c", factor->data.variable + 'a' - 1);
|
||||
data->vars_used |= 1 << (factor->data.variable - 1);
|
||||
break;
|
||||
case FACTOR_VALUE:
|
||||
factor_text = malloc (7);
|
||||
sprintf (factor_text, "%d", factor->data.value);
|
||||
break;
|
||||
case FACTOR_EXPRESSION:
|
||||
if ((expression_text = output_expression (factor->data.expression))) {
|
||||
factor_text = malloc (strlen (expression_text) + 3);
|
||||
sprintf (factor_text, "(%s)", expression_text);
|
||||
free (expression_text);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
errors->set_code (errors, E_INVALID_EXPRESSION, 0, 0);
|
||||
}
|
||||
|
||||
/* apply a negative sign, if necessary */
|
||||
if (factor_text && factor->sign == SIGN_NEGATIVE) {
|
||||
factor_buffer = malloc (strlen (factor_text) + 2);
|
||||
sprintf (factor_buffer, "-%s", factor_text);
|
||||
free (factor_text);
|
||||
factor_text = factor_buffer;
|
||||
}
|
||||
|
||||
/* return the final factor representation */
|
||||
return factor_text;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Level 5 Functions
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Output a term
|
||||
* params:
|
||||
* TermNode* term the term to output
|
||||
* returns:
|
||||
* char* the text representation of the term
|
||||
*/
|
||||
static char *output_term (TermNode *term) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
*term_text = NULL, /* the text of the whole term */
|
||||
*factor_text = NULL, /* the text of each factor */
|
||||
operator_char; /* the operator that joins the righthand factor */
|
||||
RightHandFactor *rhfactor; /* right hand factors of the expression */
|
||||
|
||||
/* begin with the initial factor */
|
||||
if ((term_text = output_factor (term->factor))) {
|
||||
rhfactor = term->next;
|
||||
while (! errors->get_code (errors) && rhfactor) {
|
||||
|
||||
/* ascertain the operator text */
|
||||
switch (rhfactor->op) {
|
||||
case TERM_OPERATOR_MULTIPLY:
|
||||
operator_char = '*';
|
||||
break;
|
||||
case TERM_OPERATOR_DIVIDE:
|
||||
operator_char = '/';
|
||||
break;
|
||||
default:
|
||||
errors->set_code (errors, E_INVALID_EXPRESSION, 0, 0);
|
||||
free (term_text);
|
||||
term_text = NULL;
|
||||
}
|
||||
|
||||
/* get the factor that follows the operator */
|
||||
if (! errors->get_code (errors)
|
||||
&& (factor_text = output_factor (rhfactor->factor))) {
|
||||
term_text = realloc (term_text,
|
||||
strlen (term_text) + strlen (factor_text) + 2);
|
||||
sprintf (term_text, "%s%c%s", term_text, operator_char, factor_text);
|
||||
free (factor_text);
|
||||
}
|
||||
|
||||
/* look for another term on the right of the expression */
|
||||
rhfactor = rhfactor->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* return the expression text */
|
||||
return term_text;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Level 4 Functions
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Output an expression for a program listing
|
||||
* params:
|
||||
* ExpressionNode* expression the expression to output
|
||||
* returns:
|
||||
* char* new string containint the expression text
|
||||
*/
|
||||
static char *output_expression (ExpressionNode *expression) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
*expression_text = NULL, /* the text of the whole expression */
|
||||
*term_text = NULL, /* the text of each term */
|
||||
operator_char; /* the operator that joins the righthand term */
|
||||
RightHandTerm *rhterm; /* right hand terms of the expression */
|
||||
|
||||
/* begin with the initial term */
|
||||
if ((expression_text = output_term (expression->term))) {
|
||||
rhterm = expression->next;
|
||||
while (! errors->get_code (errors) && rhterm) {
|
||||
|
||||
/* ascertain the operator text */
|
||||
switch (rhterm->op) {
|
||||
case EXPRESSION_OPERATOR_PLUS:
|
||||
operator_char = '+';
|
||||
break;
|
||||
case EXPRESSION_OPERATOR_MINUS:
|
||||
operator_char = '-';
|
||||
break;
|
||||
default:
|
||||
errors->set_code (errors, E_INVALID_EXPRESSION, 0, 0);
|
||||
free (expression_text);
|
||||
expression_text = NULL;
|
||||
}
|
||||
|
||||
/* get the terms that follow the operators */
|
||||
if (! errors->get_code (errors)
|
||||
&& (term_text = output_term (rhterm->term))) {
|
||||
expression_text = realloc (expression_text,
|
||||
strlen (expression_text) + strlen (term_text) + 2);
|
||||
sprintf (expression_text, "%s%c%s", expression_text, operator_char,
|
||||
term_text);
|
||||
free (term_text);
|
||||
}
|
||||
|
||||
/* look for another term on the right of the expression */
|
||||
rhterm = rhterm->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* return the expression text */
|
||||
return expression_text;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Level 3 Functions
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* LET statement output
|
||||
* params:
|
||||
* LetStatementNode* letn data for the LET statement
|
||||
* returns:
|
||||
* char* the LET statement text
|
||||
*/
|
||||
static char *output_let (LetStatementNode *letn) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
*let_text = NULL, /* the LET text to be assembled */
|
||||
*expression_text = NULL; /* the text of the expression */
|
||||
|
||||
/* assemble the expression */
|
||||
expression_text = output_expression (letn->expression);
|
||||
|
||||
/* assemble the final LET text, if we have an expression */
|
||||
if (expression_text) {
|
||||
let_text = malloc (4 + strlen (expression_text));
|
||||
sprintf (let_text, "%c=%s;", 'a' - 1 + letn->variable, expression_text);
|
||||
free (expression_text);
|
||||
data->vars_used |= 1 << (letn->variable - 1);
|
||||
}
|
||||
|
||||
/* return it */
|
||||
return let_text;
|
||||
}
|
||||
|
||||
/*
|
||||
* IF statement output
|
||||
* params:
|
||||
* IfStatementNode* ifn data for the IF statement
|
||||
* returns:
|
||||
* char* the IF statement text
|
||||
*/
|
||||
static char *output_if (IfStatementNode *ifn) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
*if_text = NULL, /* the LET text to be assembled */
|
||||
*left_text = NULL, /* the text of the left expression */
|
||||
*op_text = NULL, /* the operator text */
|
||||
*right_text = NULL, /* the text of the right expression */
|
||||
*statement_text = NULL; /* the text of the conditional statement */
|
||||
|
||||
/* assemble the expressions and conditional statement */
|
||||
left_text = output_expression (ifn->left);
|
||||
right_text = output_expression (ifn->right);
|
||||
statement_text = output_statement (ifn->statement);
|
||||
|
||||
/* work out the operator text */
|
||||
op_text = malloc (3);
|
||||
switch (ifn->op) {
|
||||
case RELOP_EQUAL: strcpy (op_text, "=="); break;
|
||||
case RELOP_UNEQUAL: strcpy (op_text, "!="); break;
|
||||
case RELOP_LESSTHAN: strcpy (op_text, "<"); break;
|
||||
case RELOP_LESSOREQUAL: strcpy (op_text, "<="); break;
|
||||
case RELOP_GREATERTHAN: strcpy (op_text, ">"); break;
|
||||
case RELOP_GREATEROREQUAL: strcpy (op_text, ">="); break;
|
||||
}
|
||||
|
||||
/* assemble the final IF text, if we have everything we need */
|
||||
if (left_text && op_text && right_text && statement_text) {
|
||||
if_text = malloc (4 + strlen (left_text) + strlen (op_text) +
|
||||
strlen (right_text) + 3 + strlen (statement_text) + 2);
|
||||
sprintf (if_text, "if (%s%s%s) {%s}", left_text, op_text, right_text,
|
||||
statement_text);
|
||||
}
|
||||
|
||||
/* free up the temporary bits of memory we've reserved */
|
||||
if (left_text) free (left_text);
|
||||
if (op_text) free (op_text);
|
||||
if (right_text) free (right_text);
|
||||
if (statement_text) free (statement_text);
|
||||
|
||||
/* return it */
|
||||
return if_text;
|
||||
}
|
||||
|
||||
/*
|
||||
* GOTO statement output
|
||||
* params:
|
||||
* GotoStatementNode* goton data for the GOTO statement
|
||||
* returns:
|
||||
* char* the GOTO statement text
|
||||
*/
|
||||
static char *output_goto (GotoStatementNode *goton) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
*goto_text = NULL, /* the GOTO text to be assembled */
|
||||
*expression_text = NULL; /* the text of the expression */
|
||||
|
||||
/* assemble the expression */
|
||||
expression_text = output_expression (goton->label);
|
||||
|
||||
/* assemble the final LET text, if we have an expression */
|
||||
if (expression_text) {
|
||||
goto_text = malloc (27 + strlen (expression_text));
|
||||
sprintf (goto_text, "label=%s; goto goto_block;", expression_text);
|
||||
free (expression_text);
|
||||
}
|
||||
|
||||
/* return it */
|
||||
return goto_text;
|
||||
}
|
||||
|
||||
/*
|
||||
* GOSUB statement output
|
||||
* params:
|
||||
* GosubStatementNode* gosubn data for the GOSUB statement
|
||||
* returns:
|
||||
* char* the GOSUB statement text
|
||||
*/
|
||||
static char *output_gosub (GosubStatementNode *gosubn) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
*gosub_text = NULL, /* the GOSUB text to be assembled */
|
||||
*expression_text = NULL; /* the text of the expression */
|
||||
|
||||
/* assemble the expression */
|
||||
expression_text = output_expression (gosubn->label);
|
||||
|
||||
/* assemble the final LET text, if we have an expression */
|
||||
if (expression_text) {
|
||||
gosub_text = malloc (12 + strlen (expression_text));
|
||||
sprintf (gosub_text, "bas_exec(%s);", expression_text);
|
||||
free (expression_text);
|
||||
}
|
||||
|
||||
/* return it */
|
||||
return gosub_text;
|
||||
}
|
||||
|
||||
/*
|
||||
* END statement output
|
||||
* returns:
|
||||
* char* A new string with the text "END"
|
||||
*/
|
||||
static char *output_end (void) {
|
||||
char *end_text; /* the full text of the END command */
|
||||
end_text = malloc (9);
|
||||
strcpy (end_text, "exit(0);");
|
||||
return end_text;
|
||||
}
|
||||
|
||||
/*
|
||||
* RETURN statement output
|
||||
* returns:
|
||||
* char* A new string with the text "RETURN"
|
||||
*/
|
||||
static char *output_return (void) {
|
||||
char *return_text; /* the full text of the RETURN command */
|
||||
return_text = malloc (8);
|
||||
strcpy (return_text, "return;");
|
||||
return return_text;
|
||||
}
|
||||
|
||||
/*
|
||||
* PRINT statement output
|
||||
* params:
|
||||
* PrintStatementNode* printn data for the PRINT statement
|
||||
* returns:
|
||||
* char* the PRINT statement text
|
||||
*/
|
||||
static char *output_print (PrintStatementNode *printn) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
*format_text = NULL, /* the printf format string */
|
||||
*output_list = NULL, /* the printf output list */
|
||||
*output_text = NULL, /* a single output item */
|
||||
*print_text = NULL; /* the PRINT text to be assembled */
|
||||
OutputNode *output; /* the current output item */
|
||||
|
||||
/* initialise format and output text */
|
||||
format_text = malloc (1);
|
||||
*format_text = '\0';
|
||||
output_list = malloc (1);
|
||||
*output_list = '\0';
|
||||
|
||||
/* build the format and output text */
|
||||
if ((output = printn->first)) {
|
||||
do {
|
||||
|
||||
/* format the output item */
|
||||
switch (output->class) {
|
||||
case OUTPUT_STRING:
|
||||
format_text = realloc (format_text,
|
||||
strlen (format_text) + strlen (output->output.string) + 1);
|
||||
strcat (format_text, output->output.string);
|
||||
break;
|
||||
case OUTPUT_EXPRESSION:
|
||||
format_text = realloc (format_text, strlen (format_text) + 3);
|
||||
strcat (format_text, "%d");
|
||||
output_text = output_expression (output->output.expression);
|
||||
output_list = realloc (output_list,
|
||||
strlen (output_list) + 1 + strlen (output_text) + 1);
|
||||
strcat (output_list, ",");
|
||||
strcat (output_list, output_text);
|
||||
free (output_text);
|
||||
break;
|
||||
}
|
||||
|
||||
/* look for the next output item */
|
||||
} while ((output = output->next));
|
||||
}
|
||||
|
||||
/* assemble the whole print text and return it */
|
||||
print_text = malloc (8 + strlen (format_text) + 3 + strlen (output_list) + 3);
|
||||
sprintf (print_text, "printf(\"%s\\n\"%s);", format_text, output_list);
|
||||
free (format_text);
|
||||
free (output_list);
|
||||
return print_text;
|
||||
}
|
||||
|
||||
/*
|
||||
* INPUT statement output
|
||||
* params:
|
||||
* InputStatementNode* inputn the input statement node to show
|
||||
* returns:
|
||||
* char * the text of the INPUT statement
|
||||
*/
|
||||
static char *output_input (InputStatementNode *inputn) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
*var_text, /* input text for a single variable */
|
||||
*input_text; /* the INPUT text to be assembled */
|
||||
VariableListNode *variable; /* the current output item */
|
||||
|
||||
/* generate an input line for each variable listed */
|
||||
input_text = malloc (1);
|
||||
*input_text = '\0';
|
||||
if ((variable = inputn->first)) {
|
||||
do {
|
||||
var_text = malloc (18);
|
||||
sprintf (var_text, "%s%c = bas_input();",
|
||||
(variable == inputn->first) ? "" : "\n",
|
||||
variable->variable + 'a' - 1);
|
||||
input_text = realloc (input_text,
|
||||
strlen (input_text) + strlen (var_text) + 1);
|
||||
strcat (input_text, var_text);
|
||||
free (var_text);
|
||||
data->vars_used |= 1 << (variable->variable - 1);
|
||||
} while ((variable = variable->next));
|
||||
}
|
||||
data->input_used = 1;
|
||||
|
||||
/* return the assembled text */
|
||||
return input_text;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Level 2 Functions
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Statement output
|
||||
* params:
|
||||
* StatementNode* statement the statement to output
|
||||
* returns:
|
||||
* char* a string containing the statement line
|
||||
*/
|
||||
static char *output_statement (StatementNode *statement) {
|
||||
|
||||
/* local variables */
|
||||
char *output = NULL; /* the text output */
|
||||
|
||||
/* return null output for comments */
|
||||
if (! statement)
|
||||
return NULL;
|
||||
|
||||
/* build the statement itself */
|
||||
switch (statement->class) {
|
||||
case STATEMENT_LET:
|
||||
output = output_let (statement->statement.letn);
|
||||
break;
|
||||
case STATEMENT_IF:
|
||||
output = output_if (statement->statement.ifn);
|
||||
break;
|
||||
case STATEMENT_GOTO:
|
||||
output = output_goto (statement->statement.goton);
|
||||
break;
|
||||
case STATEMENT_GOSUB:
|
||||
output = output_gosub (statement->statement.gosubn);
|
||||
break;
|
||||
case STATEMENT_RETURN:
|
||||
output = output_return ();
|
||||
break;
|
||||
case STATEMENT_END:
|
||||
output = output_end ();
|
||||
break;
|
||||
case STATEMENT_PRINT:
|
||||
output = output_print (statement->statement.printn);
|
||||
break;
|
||||
case STATEMENT_INPUT:
|
||||
output = output_input (statement->statement.inputn);
|
||||
break;
|
||||
default:
|
||||
output = malloc (24);
|
||||
strcpy (output, "Unrecognised statement.");
|
||||
}
|
||||
|
||||
/* return the listing line */
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Level 1 Functions
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Program Line Generation
|
||||
* params:
|
||||
* ProgramLineNode* program_line the program line to convert
|
||||
*/
|
||||
static void generate_line (ProgramLineNode *program_line) {
|
||||
|
||||
/* local variables */
|
||||
CLabel
|
||||
*prior_label, /* label before potential insertion point */
|
||||
*next_label, /* label after potential insertion point */
|
||||
*new_label; /* a label to insert */
|
||||
char
|
||||
label_text[12], /* text of a line label */
|
||||
*statement_text; /* the text of a statement */
|
||||
|
||||
/* generate a line label */
|
||||
if (program_line->label) {
|
||||
|
||||
/* insert the label into the label list */
|
||||
new_label = malloc (sizeof (CLabel));
|
||||
new_label->number = program_line->label;
|
||||
new_label->next = NULL;
|
||||
prior_label = NULL;
|
||||
next_label = data->first_label;
|
||||
while (next_label && next_label->number < new_label->number) {
|
||||
prior_label = next_label;
|
||||
next_label = prior_label->next;
|
||||
}
|
||||
new_label->next = next_label;
|
||||
if (prior_label)
|
||||
prior_label->next = new_label;
|
||||
else
|
||||
data->first_label = new_label;
|
||||
|
||||
/* append the label to the code block */
|
||||
sprintf (label_text, "lbl_%d:\n", program_line->label);
|
||||
data->code = realloc (data->code,
|
||||
strlen (data->code) + strlen (label_text) + 1);
|
||||
strcat (data->code, label_text);
|
||||
}
|
||||
|
||||
/* generate the statement, and append it if it is not a comment */
|
||||
statement_text = output_statement (program_line->statement);
|
||||
if (statement_text) {
|
||||
data->code = realloc (data->code,
|
||||
strlen (data->code) + strlen (statement_text) + 2);
|
||||
strcat (data->code, statement_text);
|
||||
strcat (data->code, "\n");
|
||||
free (statement_text);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate the #include lines and #defines
|
||||
* changes:
|
||||
* Private* data appends headers to the output
|
||||
*/
|
||||
static void generate_includes (void) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
include_text[1024], /* the whole include and #define text */
|
||||
define_text[80]; /* a single #define line */
|
||||
|
||||
/* build up includes and defines */
|
||||
strcpy (include_text, "#include <stdio.h>\n");
|
||||
strcat (include_text, "#include <stdlib.h>\n");
|
||||
sprintf (define_text, "#define E_RETURN_WITHOUT_GOSUB %d\n",
|
||||
E_RETURN_WITHOUT_GOSUB);
|
||||
strcat (include_text, define_text);
|
||||
|
||||
/* add the #includes and #defines to the output */
|
||||
this->c_output = realloc (this->c_output, strlen (this->c_output)
|
||||
+ strlen (include_text) + 1);
|
||||
strcat (this->c_output, include_text);
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate the variable declarations
|
||||
* changes:
|
||||
* Private* data appends declaration to the output
|
||||
*/
|
||||
static void generate_variables (void) {
|
||||
|
||||
/* local variables */
|
||||
int vcount; /* variable counter */
|
||||
char
|
||||
var_text [12], /* individual variable text */
|
||||
declaration[60]; /* declaration text */
|
||||
|
||||
/* build the declaration */
|
||||
*declaration = '\0';
|
||||
for (vcount = 0; vcount < 26; ++vcount) {
|
||||
if (data->vars_used & 1 << vcount) {
|
||||
if (*declaration)
|
||||
sprintf (var_text, ",%c", 'a' + vcount);
|
||||
else
|
||||
sprintf (var_text, "short int %c", 'a' + vcount);
|
||||
strcat (declaration, var_text);
|
||||
}
|
||||
}
|
||||
|
||||
/* if there are any variables, add the declaration to the output */
|
||||
if (*declaration) {
|
||||
strcat (declaration, ";\n");
|
||||
this->c_output = realloc (this->c_output, strlen (this->c_output)
|
||||
+ strlen (declaration) + 1);
|
||||
strcat (this->c_output, declaration);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate the bas_input function
|
||||
* changes:
|
||||
* Private* data appends declaration to the output
|
||||
*/
|
||||
static void generate_bas_input (void) {
|
||||
|
||||
/* local variables */
|
||||
char function_text[1024]; /* the entire function */
|
||||
|
||||
/* construct the function text */
|
||||
strcpy (function_text, "short int bas_input (void) {\n");
|
||||
strcat (function_text, "short int ch, sign, value;\n");
|
||||
strcat (function_text, "do {\n");
|
||||
strcat (function_text, "if (ch == '-') sign = -1; else sign = 1;\n");
|
||||
strcat (function_text, "ch = getchar ();\n");
|
||||
strcat (function_text, "} while (ch < '0' || ch > '9');\n");
|
||||
strcat (function_text, "value = 0;\n");
|
||||
strcat (function_text, "do {\n");
|
||||
strcat (function_text, "value = 10 * value + (ch - '0');\n");
|
||||
strcat (function_text, "ch = getchar ();\n");
|
||||
strcat (function_text, "} while (ch >= '0' && ch <= '9');\n");
|
||||
strcat (function_text, "return sign * value;\n");
|
||||
strcat (function_text, "}\n");
|
||||
|
||||
/* add the function text to the output */
|
||||
this->c_output = realloc (this->c_output, strlen (this->c_output)
|
||||
+ strlen (function_text) + 1);
|
||||
strcat (this->c_output, function_text);
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate the bas_exec function
|
||||
* changes:
|
||||
* Private* data appends declaration to the output
|
||||
*/
|
||||
static void generate_bas_exec (void) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
*op, /* comparison operator to use for line numbers */
|
||||
goto_line[80], /* a line in the goto block */
|
||||
*goto_block, /* the goto block */
|
||||
*function_text; /* the complete function text */
|
||||
CLabel *label; /* label pointer for construction goto block */
|
||||
|
||||
/* decide which operator to use for comparison */
|
||||
op = (options->get_line_numbers (options) == LINE_NUMBERS_OPTIONAL)
|
||||
? "=="
|
||||
: "<=";
|
||||
|
||||
/* create the goto block */
|
||||
goto_block = malloc (128);
|
||||
strcpy (goto_block, "goto_block:\n");
|
||||
strcat (goto_block, "if (!label) goto lbl_start;\n");
|
||||
label = data->first_label;
|
||||
while (label) {
|
||||
sprintf (goto_line, "if (label%s%d) goto lbl_%d;\n",
|
||||
op, label->number, label->number);
|
||||
goto_block = realloc (goto_block,
|
||||
strlen (goto_block) + strlen (goto_line) + 1);
|
||||
strcat (goto_block, goto_line);
|
||||
label = label->next;
|
||||
}
|
||||
goto_block = realloc (goto_block, strlen (goto_block) + 12);
|
||||
strcat (goto_block, "lbl_start:\n");
|
||||
|
||||
/* put the function together */
|
||||
function_text = malloc (28 + strlen (goto_block) + strlen (data->code) + 3);
|
||||
strcpy (function_text, "void bas_exec (int label) {\n");
|
||||
strcat (function_text, goto_block);
|
||||
strcat (function_text, data->code);
|
||||
strcat (function_text, "}\n");
|
||||
|
||||
/* add the function text to the output */
|
||||
this->c_output = realloc (this->c_output, strlen (this->c_output)
|
||||
+ strlen (function_text) + 1);
|
||||
strcat (this->c_output, function_text);
|
||||
free (goto_block);
|
||||
free (function_text);
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate the main function
|
||||
* changes:
|
||||
* Private* data appends declaration to the output
|
||||
*/
|
||||
void generate_main (void) {
|
||||
|
||||
/* local variables */
|
||||
char function_text[1024]; /* the entire function */
|
||||
|
||||
/* construct the function text */
|
||||
strcpy (function_text, "int main (void) {\n");
|
||||
strcat (function_text, "bas_exec (0);\n");
|
||||
strcat (function_text, "exit (E_RETURN_WITHOUT_GOSUB);\n");
|
||||
strcat (function_text, "}\n");
|
||||
|
||||
/* add the function text to the output */
|
||||
this->c_output = realloc (this->c_output, strlen (this->c_output)
|
||||
+ strlen (function_text) + 1);
|
||||
strcat (this->c_output, function_text);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Top Level Functions
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Program Generation
|
||||
* params:
|
||||
* ProgramNode* program the program parse tree to convert to C
|
||||
* returns:
|
||||
* char* the program output
|
||||
*/
|
||||
static void generate (CProgram *c_program, ProgramNode *program) {
|
||||
|
||||
/* local variables */
|
||||
ProgramLineNode *program_line; /* line to process */
|
||||
|
||||
/* initialise this object */
|
||||
this = c_program;
|
||||
data = (Private *) c_program->private_data;
|
||||
|
||||
/* generate the code for the lines */
|
||||
program_line = program->first;
|
||||
while (program_line) {
|
||||
generate_line (program_line);
|
||||
program_line = program_line->next;
|
||||
}
|
||||
|
||||
/* put the code together */
|
||||
generate_includes ();
|
||||
generate_variables ();
|
||||
if (data->input_used)
|
||||
generate_bas_input ();
|
||||
generate_bas_exec ();
|
||||
generate_main ();
|
||||
}
|
||||
|
||||
/*
|
||||
* Destructor
|
||||
* params:
|
||||
* CProgram* c_program The C program to destroy
|
||||
*/
|
||||
static void destroy (CProgram *c_program) {
|
||||
|
||||
/* local variables */
|
||||
CLabel
|
||||
*current_label, /* pointer to label to destroy */
|
||||
*next_label; /* pointer to next label to destroy */
|
||||
|
||||
/* destroy the private data */
|
||||
this = c_program;
|
||||
if (this->private_data) {
|
||||
data = (Private *) c_program->private_data;
|
||||
next_label = data->first_label;
|
||||
while (next_label) {
|
||||
current_label = next_label;
|
||||
next_label = current_label->next;
|
||||
free (current_label);
|
||||
}
|
||||
if (data->code)
|
||||
free (data->code);
|
||||
free (data);
|
||||
}
|
||||
|
||||
/* destroy the generated output */
|
||||
if (this->c_output)
|
||||
free (this->c_output);
|
||||
|
||||
/* destroy the containing structure */
|
||||
free (c_program);
|
||||
}
|
||||
|
||||
/*
|
||||
* Constructor
|
||||
* params:
|
||||
* ErrorHandler* compiler_errors the error handler
|
||||
* changes:
|
||||
* CProgram* this the object being created
|
||||
* Private* data the object's private data
|
||||
* returns:
|
||||
* CProgram* the created object
|
||||
*/
|
||||
CProgram *new_CProgram (ErrorHandler *compiler_errors,
|
||||
LanguageOptions *compiler_options) {
|
||||
|
||||
/* allocate space */
|
||||
this = malloc (sizeof (CProgram));
|
||||
this->private_data = data = malloc (sizeof (Private));
|
||||
|
||||
/* initialise methods */
|
||||
this->generate = generate;
|
||||
this->destroy = destroy;
|
||||
|
||||
/* initialise properties */
|
||||
errors = data->errors = compiler_errors;
|
||||
options = data->options = compiler_options;
|
||||
data->input_used = 0;
|
||||
data->vars_used = 0;
|
||||
data->first_label = NULL;
|
||||
data->code = malloc (1);
|
||||
*data->code = '\0';
|
||||
this->c_output = malloc (1);
|
||||
*this->c_output = '\0';
|
||||
|
||||
/* return the created structure */
|
||||
return this;
|
||||
}
|
535
programs/develop/tinybasic-1.0.4/src/interpret.c
Normal file
535
programs/develop/tinybasic-1.0.4/src/interpret.c
Normal file
@@ -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;
|
||||
}
|
184
programs/develop/tinybasic-1.0.4/src/options.c
Normal file
184
programs/develop/tinybasic-1.0.4/src/options.c
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Tiny BASIC Interpreter and Compiler Project
|
||||
* Language Options Module
|
||||
*
|
||||
* Released as Public Domain by Damian Gareth Walker 2019
|
||||
* Created: 18-Aug-2019
|
||||
*/
|
||||
|
||||
|
||||
/* included headers */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "options.h"
|
||||
|
||||
|
||||
/*
|
||||
* Data Definitions
|
||||
*/
|
||||
|
||||
|
||||
/* private data */
|
||||
typedef struct {
|
||||
LineNumberOption line_numbers; /* mandatory, implied, optional */
|
||||
int line_limit; /* highest line number allowed */
|
||||
CommentOption comments; /* enabled, disabled */
|
||||
int gosub_limit; /* how many nested gosubs */
|
||||
} Private;
|
||||
|
||||
/* convenience variables */
|
||||
static LanguageOptions *this; /* object being worked on */
|
||||
static Private *data; /* the object's private data */
|
||||
|
||||
|
||||
/*
|
||||
* Public Methods
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Set the line number option individually
|
||||
* params:
|
||||
* LanguageOptions* options the options
|
||||
* LineNumberOption line_numbers line number option to set
|
||||
*/
|
||||
static void set_line_numbers (LanguageOptions *options,
|
||||
LineNumberOption line_numbers) {
|
||||
this = options;
|
||||
data = this->data;
|
||||
data->line_numbers = line_numbers;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the line number limit individually
|
||||
* params:
|
||||
* LanguageOptions* options the options
|
||||
* int line_limit line number limit to set
|
||||
*/
|
||||
static void set_line_limit (LanguageOptions *options, int line_limit) {
|
||||
this = options;
|
||||
data = this->data;
|
||||
data->line_limit = line_limit;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the comments option individually
|
||||
* params:
|
||||
* LanguageOptions* options the options
|
||||
* CommentOption comments comment option to set
|
||||
*/
|
||||
static void set_comments (LanguageOptions *options, CommentOption comments) {
|
||||
this = options;
|
||||
data = this->data;
|
||||
data->comments = comments;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the GOSUB stack limit
|
||||
* params:
|
||||
* LanuageOptions* options the options
|
||||
* int limit the desired stack limit
|
||||
*/
|
||||
static void set_gosub_limit (LanguageOptions *options, int gosub_limit) {
|
||||
this = options;
|
||||
data = this->data;
|
||||
data->gosub_limit = gosub_limit;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the line number setting
|
||||
* params:
|
||||
* LanguageOptions* options the options
|
||||
* returns:
|
||||
* LineNumberOption the line number setting
|
||||
*/
|
||||
static LineNumberOption get_line_numbers (LanguageOptions *options) {
|
||||
this = options;
|
||||
data = this->data;
|
||||
return data->line_numbers;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the line number limit
|
||||
* params:
|
||||
* LanguageOptions* options the options
|
||||
* returns:
|
||||
* int the line number setting
|
||||
*/
|
||||
static int get_line_limit (LanguageOptions *options) {
|
||||
this = options;
|
||||
data = this->data;
|
||||
return data->line_limit;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the comments setting
|
||||
* params:
|
||||
* LanguageOptions* options the options
|
||||
* returns:
|
||||
* CommentOption the line number setting
|
||||
*/
|
||||
static CommentOption get_comments (LanguageOptions *options) {
|
||||
this = options;
|
||||
data = this->data;
|
||||
return data->comments;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the GOSUB stack limit setting
|
||||
* params:
|
||||
* LanguageOptions* options the options
|
||||
* returns:
|
||||
* int the current GOSUB stack limit
|
||||
*/
|
||||
static int get_gosub_limit (LanguageOptions *options) {
|
||||
this = options;
|
||||
data = this->data;
|
||||
return data->gosub_limit;
|
||||
}
|
||||
|
||||
/*
|
||||
* Destroy the settings object
|
||||
* params:
|
||||
* LanguageOptions* options the options
|
||||
*/
|
||||
static void destroy (LanguageOptions *options) {
|
||||
if (options) {
|
||||
if (options->data)
|
||||
free (options->data);
|
||||
free (options);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Constructor for language options
|
||||
* returns:
|
||||
* LanguageOptions* the new language options object
|
||||
*/
|
||||
LanguageOptions *new_LanguageOptions (void) {
|
||||
|
||||
/* allocate memory */
|
||||
this = malloc (sizeof (LanguageOptions));
|
||||
data = this->data = malloc (sizeof (Private));
|
||||
|
||||
/* initialise methods */
|
||||
this->set_line_numbers = set_line_numbers;
|
||||
this->set_line_limit = set_line_limit;
|
||||
this->set_comments = set_comments;
|
||||
this->set_gosub_limit = set_gosub_limit;
|
||||
this->get_line_numbers = get_line_numbers;
|
||||
this->get_line_limit = get_line_limit;
|
||||
this->get_comments = get_comments;
|
||||
this->get_gosub_limit = get_gosub_limit;
|
||||
this->destroy = destroy;
|
||||
|
||||
/* initialise properties */
|
||||
data->line_numbers = LINE_NUMBERS_OPTIONAL;
|
||||
data->line_limit = 32767;
|
||||
data->comments = COMMENTS_ENABLED;
|
||||
data->gosub_limit = 64;
|
||||
|
||||
/* return the new object */
|
||||
return this;
|
||||
}
|
947
programs/develop/tinybasic-1.0.4/src/parser.c
Normal file
947
programs/develop/tinybasic-1.0.4/src/parser.c
Normal file
@@ -0,0 +1,947 @@
|
||||
/*
|
||||
* Tiny BASIC Interpreter and Compiler Project
|
||||
* Parser module
|
||||
*
|
||||
* Copyright (C) Damian Gareth Walker 2019
|
||||
* Created: 12-Aug-2019
|
||||
*/
|
||||
|
||||
|
||||
/* included headers */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "common.h"
|
||||
#include "errors.h"
|
||||
#include "options.h"
|
||||
#include "token.h"
|
||||
#include "tokeniser.h"
|
||||
#include "parser.h"
|
||||
#include "expression.h"
|
||||
|
||||
|
||||
/*
|
||||
* Internal Function Declarations
|
||||
*/
|
||||
|
||||
|
||||
/* parse_expression() has a forward reference from parse_factor() */
|
||||
static ExpressionNode *parse_expression (void);
|
||||
|
||||
/* parse_statement() has a forward reference from parse_if_statement() */
|
||||
static StatementNode *parse_statement (void);
|
||||
|
||||
|
||||
/*
|
||||
* Data Definitions
|
||||
*/
|
||||
|
||||
|
||||
/* private data */
|
||||
typedef struct parser_data {
|
||||
int last_label; /* last line label encountered */
|
||||
int current_line; /* the last source line parsed */
|
||||
int end_of_file; /* end of file signal */
|
||||
Token *stored_token; /* token read ahead */
|
||||
TokenStream *stream; /* the input stream */
|
||||
ErrorHandler *errors; /* the parse error handler */
|
||||
LanguageOptions *options; /* the language options */
|
||||
} ParserData;
|
||||
|
||||
/* convenience variables */
|
||||
static Parser *this; /* the current parser being worked on */
|
||||
|
||||
|
||||
/*
|
||||
* Private methods
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Get next token to parse, from read-ahead buffer or tokeniser.
|
||||
*/
|
||||
static Token *get_token_to_parse () {
|
||||
|
||||
/* local variables */
|
||||
Token *token; /* token to return */
|
||||
|
||||
/* get the token one way or another */
|
||||
if (this->priv->stored_token) {
|
||||
token = this->priv->stored_token;
|
||||
this->priv->stored_token = NULL;
|
||||
} else
|
||||
token = this->priv->stream->next (this->priv->stream);
|
||||
|
||||
/* store the line, check EOF and return the token */
|
||||
this->priv->current_line = token->get_line (token);
|
||||
if (token->get_class (token) == TOKEN_EOF)
|
||||
this->priv->end_of_file = !0;
|
||||
return token;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse a factor
|
||||
* returns:
|
||||
* FactorNode* a new factor node holding the parsed data
|
||||
*/
|
||||
static FactorNode *parse_factor (void) {
|
||||
|
||||
/* local variables */
|
||||
Token *token; /* token to read */
|
||||
FactorNode *factor = NULL; /* the factor we're building */
|
||||
ExpressionNode *expression = NULL; /* any parenthesised expression */
|
||||
int start_line; /* the line on which this factor occurs */
|
||||
|
||||
/* initialise the factor and grab the next token */
|
||||
factor = factor_create ();
|
||||
token = get_token_to_parse ();
|
||||
start_line = token->get_line (token);
|
||||
|
||||
/* interpret a sign */
|
||||
if (token->get_class (token) == TOKEN_PLUS
|
||||
|| token->get_class (token) == TOKEN_MINUS) {
|
||||
factor->sign = (token->get_class (token) == TOKEN_PLUS)
|
||||
? SIGN_POSITIVE
|
||||
: SIGN_NEGATIVE;
|
||||
token->destroy (token);
|
||||
token = get_token_to_parse ();
|
||||
}
|
||||
|
||||
/* interpret a number */
|
||||
if (token->get_class (token) == TOKEN_NUMBER) {
|
||||
factor->class = FACTOR_VALUE;
|
||||
factor->data.value = atoi (token->get_content (token));
|
||||
if (factor->data.value < -32768 || factor->data.value > 32767)
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_OVERFLOW, start_line, this->priv->last_label);
|
||||
token->destroy (token);
|
||||
}
|
||||
|
||||
/* interpret a variable */
|
||||
else if (token->get_class (token) == TOKEN_VARIABLE) {
|
||||
factor->class = FACTOR_VARIABLE;
|
||||
factor->data.variable = (int) *token->get_content (token) & 0x1F;
|
||||
token->destroy (token);
|
||||
}
|
||||
|
||||
/* interpret an parenthesised expression */
|
||||
else if (token->get_class (token) == TOKEN_LEFT_PARENTHESIS) {
|
||||
|
||||
/* parse the parenthesised expression and complete the factor */
|
||||
token->destroy (token);
|
||||
expression = parse_expression ();
|
||||
if (expression) {
|
||||
token = get_token_to_parse ();
|
||||
if (token->get_class (token) == TOKEN_RIGHT_PARENTHESIS) {
|
||||
factor->class = FACTOR_EXPRESSION;
|
||||
factor->data.expression = expression;
|
||||
} else {
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_MISSING_RIGHT_PARENTHESIS, start_line,
|
||||
this->priv->last_label);
|
||||
factor_destroy (factor);
|
||||
factor = NULL;
|
||||
expression_destroy (expression);
|
||||
}
|
||||
token->destroy (token);
|
||||
}
|
||||
|
||||
/* clean up after invalid parenthesised expression */
|
||||
else {
|
||||
this->priv->errors->set_code (this->priv->errors, E_INVALID_EXPRESSION,
|
||||
token->get_line (token), this->priv->last_label);
|
||||
token->destroy (token);
|
||||
factor_destroy (factor);
|
||||
factor = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* deal with other errors */
|
||||
else {
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_INVALID_EXPRESSION, token->get_line (token),
|
||||
this->priv->last_label);
|
||||
factor_destroy (factor);
|
||||
token->destroy (token);
|
||||
factor = NULL;
|
||||
}
|
||||
|
||||
/* return the factor */
|
||||
return factor;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse a term
|
||||
* globals:
|
||||
* Token* stored_token the token read past the end of the term
|
||||
* returns:
|
||||
* TermNode* a new term node holding the parsed term
|
||||
*/
|
||||
static TermNode *parse_term (void) {
|
||||
|
||||
/* local variables */
|
||||
TermNode *term = NULL; /* the term we're building */
|
||||
FactorNode *factor = NULL; /* factor detected */
|
||||
RightHandFactor
|
||||
*rhptr = NULL, /* previous right-hand factor */
|
||||
*rhfactor = NULL; /* right-hand factor detected */
|
||||
Token *token = NULL; /* token read while looking for operator */
|
||||
|
||||
/* scan the first factor */
|
||||
if ((factor = parse_factor ())) {
|
||||
term = term_create ();
|
||||
term->factor = factor;
|
||||
term->next = NULL;
|
||||
|
||||
/* look for subsequent factors */
|
||||
while ((token = get_token_to_parse ())
|
||||
&& ! this->priv->errors->get_code (this->priv->errors)
|
||||
&& (token->get_class (token) == TOKEN_MULTIPLY
|
||||
|| token->get_class (token) == TOKEN_DIVIDE)) {
|
||||
|
||||
/* parse the sign and the factor */
|
||||
rhfactor = rhfactor_create ();
|
||||
rhfactor->op = token->get_class (token) == TOKEN_MULTIPLY
|
||||
? TERM_OPERATOR_MULTIPLY
|
||||
: TERM_OPERATOR_DIVIDE;
|
||||
if ((rhfactor->factor = parse_factor ())) {
|
||||
rhfactor->next = NULL;
|
||||
if (rhptr)
|
||||
rhptr->next = rhfactor;
|
||||
else
|
||||
term->next = rhfactor;
|
||||
rhptr = rhfactor;
|
||||
}
|
||||
|
||||
/* set an error if we read an operator but not a factor */
|
||||
else {
|
||||
rhfactor_destroy (rhfactor);
|
||||
if (! this->priv->errors->get_code (this->priv->errors))
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_INVALID_EXPRESSION, token->get_line (token),
|
||||
this->priv->last_label);
|
||||
}
|
||||
|
||||
/* clean up token */
|
||||
token->destroy (token);
|
||||
}
|
||||
|
||||
/* we've read past the end of the term; put the token back */
|
||||
this->priv->stored_token = token;
|
||||
}
|
||||
|
||||
/* return the evaluated term, if any */
|
||||
return term;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse an expression
|
||||
* returns:
|
||||
* ExpressionNode* the parsed expression
|
||||
*/
|
||||
static ExpressionNode *parse_expression (void) {
|
||||
|
||||
/* local variables */
|
||||
ExpressionNode *expression = NULL; /* the expression we build */
|
||||
TermNode *term; /* term detected */
|
||||
RightHandTerm
|
||||
*rhterm = NULL, /* the right-hand term detected */
|
||||
*rhptr = NULL; /* pointer to the previous right-hand term */
|
||||
Token *token; /* token read when scanning for right-hand terms */
|
||||
|
||||
/* scan the first term */
|
||||
if ((term = parse_term ())) {
|
||||
expression = expression_create ();
|
||||
expression->term = term;
|
||||
expression->next = NULL;
|
||||
|
||||
/* look for subsequent terms */
|
||||
while ((token = get_token_to_parse ())
|
||||
&& ! this->priv->errors->get_code (this->priv->errors)
|
||||
&& (token->get_class (token) == TOKEN_PLUS
|
||||
|| token->get_class (token) == TOKEN_MINUS)) {
|
||||
|
||||
/* parse the sign and the factor */
|
||||
rhterm = rhterm_create ();
|
||||
rhterm->op = token->get_class (token) == TOKEN_PLUS
|
||||
? EXPRESSION_OPERATOR_PLUS
|
||||
: EXPRESSION_OPERATOR_MINUS;
|
||||
if ((rhterm->term = parse_term ())) {
|
||||
rhterm->next = NULL;
|
||||
if (rhptr)
|
||||
rhptr->next = rhterm;
|
||||
else
|
||||
expression->next = rhterm;
|
||||
rhptr = rhterm;
|
||||
}
|
||||
|
||||
/* set an error condition if we read a sign but not a factor */
|
||||
else {
|
||||
rhterm_destroy (rhterm);
|
||||
if (! this->priv->errors->get_code (this->priv->errors))
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_INVALID_EXPRESSION, token->get_line (token),
|
||||
this->priv->last_label);
|
||||
}
|
||||
|
||||
/* clean up token */
|
||||
token->destroy (token);
|
||||
}
|
||||
|
||||
/* we've read past the end of the term; put the token back */
|
||||
this->priv->stored_token = token;
|
||||
}
|
||||
|
||||
/* return the evaluated expression, if any */
|
||||
return expression;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate numeric line label according to language options.
|
||||
* This will be used if the line has no label specified.
|
||||
* returns:
|
||||
* int numeric line label
|
||||
*/
|
||||
static int generate_default_label (void) {
|
||||
if (this->priv->options->get_line_numbers (this->priv->options)
|
||||
== LINE_NUMBERS_IMPLIED)
|
||||
return this->priv->last_label + 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate a line label according to the language options
|
||||
* params:
|
||||
* int label the numeric label to verify.
|
||||
* returns:
|
||||
* int !0 if the number is valid, 0 if invalid
|
||||
*/
|
||||
static int validate_line_label (int label) {
|
||||
|
||||
/* line labels should be non-negative and within the set limit */
|
||||
if (label < 0
|
||||
|| label > this->priv->options->get_line_limit (this->priv->options))
|
||||
return 0;
|
||||
|
||||
/* line labels should be non-zero unless they're optional */
|
||||
if (label == 0
|
||||
&& this->priv->options->get_line_numbers (this->priv->options)
|
||||
!= LINE_NUMBERS_OPTIONAL)
|
||||
return 0;
|
||||
|
||||
/* line labels should be ascending unless they're optional */
|
||||
if (label <= this->priv->last_label
|
||||
&& this->priv->options->get_line_numbers (this->priv->options)
|
||||
!= LINE_NUMBERS_OPTIONAL)
|
||||
return 0;
|
||||
|
||||
/* if all the above tests passed, the line label is valid */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse a LET statement
|
||||
* returns:
|
||||
* StatementNode* The statement assembled
|
||||
*/
|
||||
static StatementNode *parse_let_statement (void) {
|
||||
|
||||
/* local variables */
|
||||
Token *token; /* tokens read as part of LET statement */
|
||||
int line; /* line containing the LET token */
|
||||
StatementNode *statement; /* the new statement */
|
||||
|
||||
/* initialise the statement */
|
||||
statement = statement_create ();
|
||||
statement->class = STATEMENT_LET;
|
||||
statement->statement.letn = statement_create_let ();
|
||||
line = this->priv->stream->get_line (this->priv->stream);
|
||||
|
||||
/* see what variable we're assigning */
|
||||
token = get_token_to_parse ();
|
||||
if (token->get_class (token) != TOKEN_VARIABLE) {
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_INVALID_VARIABLE, line, this->priv->last_label);
|
||||
statement_destroy (statement);
|
||||
token->destroy (token);
|
||||
return NULL;
|
||||
}
|
||||
statement->statement.letn->variable = *token->get_content (token) & 0x1f;
|
||||
|
||||
/* get the "=" */
|
||||
token->destroy (token);
|
||||
token = get_token_to_parse ();
|
||||
if (token->get_class (token) != TOKEN_EQUAL) {
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_INVALID_ASSIGNMENT, line, this->priv->last_label);
|
||||
statement_destroy (statement);
|
||||
token->destroy (token);
|
||||
return NULL;
|
||||
}
|
||||
token->destroy (token);
|
||||
|
||||
/* get the expression */
|
||||
statement->statement.letn->expression = parse_expression ();
|
||||
if (! statement->statement.letn->expression) {
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_INVALID_EXPRESSION, line, this->priv->last_label);
|
||||
statement_destroy (statement);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* return the statement */
|
||||
return statement;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse an IF statement
|
||||
* returns:
|
||||
* StatementNode* The statement to assemble.
|
||||
*/
|
||||
static StatementNode *parse_if_statement (void) {
|
||||
|
||||
/* local variables */
|
||||
Token *token; /* tokens read as part of the statement */
|
||||
StatementNode *statement; /* the IF statement */
|
||||
|
||||
/* initialise the statement */
|
||||
statement = statement_create ();
|
||||
statement->class = STATEMENT_IF;
|
||||
statement->statement.ifn = statement_create_if ();
|
||||
|
||||
/* parse the first expression */
|
||||
statement->statement.ifn->left = parse_expression ();
|
||||
|
||||
/* parse the operator */
|
||||
if (! this->priv->errors->get_code (this->priv->errors)) {
|
||||
token = get_token_to_parse ();
|
||||
switch (token->get_class (token)) {
|
||||
case TOKEN_EQUAL:
|
||||
statement->statement.ifn->op = RELOP_EQUAL;
|
||||
break;
|
||||
case TOKEN_UNEQUAL:
|
||||
statement->statement.ifn->op = RELOP_UNEQUAL;
|
||||
break;
|
||||
case TOKEN_LESSTHAN:
|
||||
statement->statement.ifn->op = RELOP_LESSTHAN;
|
||||
break;
|
||||
case TOKEN_LESSOREQUAL:
|
||||
statement->statement.ifn->op = RELOP_LESSOREQUAL;
|
||||
break;
|
||||
case TOKEN_GREATERTHAN:
|
||||
statement->statement.ifn->op = RELOP_GREATERTHAN;
|
||||
break;
|
||||
case TOKEN_GREATEROREQUAL:
|
||||
statement->statement.ifn->op = RELOP_GREATEROREQUAL;
|
||||
break;
|
||||
default:
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_INVALID_OPERATOR, token->get_line (token),
|
||||
this->priv->last_label);
|
||||
}
|
||||
token->destroy (token);
|
||||
}
|
||||
|
||||
/* parse the second expression */
|
||||
if (! this->priv->errors->get_code (this->priv->errors))
|
||||
statement->statement.ifn->right = parse_expression ();
|
||||
|
||||
/* parse the THEN */
|
||||
if (! this->priv->errors->get_code (this->priv->errors)) {
|
||||
token = get_token_to_parse ();
|
||||
if (token->get_class (token) != TOKEN_THEN)
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_THEN_EXPECTED, token->get_line (token),
|
||||
this->priv->last_label);
|
||||
token->destroy (token);
|
||||
}
|
||||
|
||||
/* parse the conditional statement */
|
||||
if (! this->priv->errors->get_code (this->priv->errors))
|
||||
statement->statement.ifn->statement = parse_statement ();
|
||||
|
||||
/* destroy the half-made statement if errors occurred */
|
||||
if (this->priv->errors->get_code (this->priv->errors)) {
|
||||
statement_destroy (statement);
|
||||
statement = NULL;
|
||||
}
|
||||
|
||||
/* return the statement */
|
||||
return statement;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse a GOTO statement
|
||||
* returns:
|
||||
* StatementNode* the parsed GOTO statement
|
||||
*/
|
||||
static StatementNode *parse_goto_statement (void) {
|
||||
|
||||
/* local variables */
|
||||
StatementNode *statement; /* the IF statement */
|
||||
|
||||
/* initialise the statement */
|
||||
statement = statement_create ();
|
||||
statement->class = STATEMENT_GOTO;
|
||||
statement->statement.goton = statement_create_goto ();
|
||||
|
||||
/* parse the line label expression */
|
||||
if (! (statement->statement.goton->label = parse_expression ())) {
|
||||
statement_destroy (statement);
|
||||
statement = NULL;
|
||||
}
|
||||
|
||||
/* return the new statement */
|
||||
return statement;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse a GOSUB statement
|
||||
* returns:
|
||||
* StatementNode* the parsed GOSUB statement
|
||||
*/
|
||||
static StatementNode *parse_gosub_statement (void) {
|
||||
|
||||
/* local variables */
|
||||
StatementNode *statement; /* the IF statement */
|
||||
|
||||
/* initialise the statement */
|
||||
statement = statement_create ();
|
||||
statement->class = STATEMENT_GOSUB;
|
||||
statement->statement.gosubn = statement_create_gosub ();
|
||||
|
||||
/* parse the line label expression */
|
||||
if (! (statement->statement.gosubn->label = parse_expression ())) {
|
||||
statement_destroy (statement);
|
||||
statement = NULL;
|
||||
}
|
||||
|
||||
/* return the new statement */
|
||||
return statement;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse an RETURN statement
|
||||
* returns:
|
||||
* StatementNode* The statement assembled
|
||||
*/
|
||||
static StatementNode *parse_return_statement (void) {
|
||||
StatementNode *statement; /* the RETURN */
|
||||
statement = statement_create ();
|
||||
statement->class = STATEMENT_RETURN;
|
||||
return statement;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse an END statement
|
||||
* returns:
|
||||
* StatementNode* The statement assembled
|
||||
*/
|
||||
static StatementNode *parse_end_statement (void) {
|
||||
StatementNode *statement = NULL; /* the END */
|
||||
statement = statement_create ();
|
||||
statement->class = STATEMENT_END;
|
||||
return statement;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse a PRINT statement
|
||||
* returns:
|
||||
* StatementNode* The statement assembled
|
||||
*/
|
||||
static StatementNode *parse_print_statement (void) {
|
||||
|
||||
/* local variables */
|
||||
Token *token = NULL; /* tokens read as part of the statement */
|
||||
StatementNode *statement; /* the statement we're building */
|
||||
int line; /* line containing the PRINT token */
|
||||
OutputNode
|
||||
*nextoutput = NULL, /* the next output node we're parsing */
|
||||
*lastoutput = NULL; /* the last output node we parsed */
|
||||
ExpressionNode *expression; /* a parsed expression */
|
||||
|
||||
/* initialise the statement */
|
||||
statement = statement_create ();
|
||||
statement->class = STATEMENT_PRINT;
|
||||
statement->statement.printn = statement_create_print ();
|
||||
line = this->priv->stream->get_line (this->priv->stream);
|
||||
|
||||
/* main loop for parsing the output list */
|
||||
do {
|
||||
|
||||
/* discard a previous comma, and read the next output value */
|
||||
if (token)
|
||||
token->destroy (token);
|
||||
token = get_token_to_parse ();
|
||||
|
||||
/* process a premature end of line */
|
||||
if (token->get_class (token) == TOKEN_EOF
|
||||
|| token->get_class (token) == TOKEN_EOL) {
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_INVALID_PRINT_OUTPUT, line,
|
||||
this->priv->last_label);
|
||||
statement_destroy (statement);
|
||||
statement = NULL;
|
||||
token->destroy (token);
|
||||
}
|
||||
|
||||
/* process a literal string */
|
||||
else if (token->get_class (token) == TOKEN_STRING) {
|
||||
nextoutput = malloc (sizeof (OutputNode));
|
||||
nextoutput->class = OUTPUT_STRING;
|
||||
nextoutput->output.string = malloc
|
||||
(1 + strlen (token->get_content (token)));
|
||||
strcpy (nextoutput->output.string, token->get_content (token));
|
||||
nextoutput->next = NULL;
|
||||
token->destroy (token);
|
||||
}
|
||||
|
||||
/* attempt to process an expression */
|
||||
else {
|
||||
this->priv->stored_token = token;
|
||||
if ((expression = parse_expression ())) {
|
||||
nextoutput = malloc (sizeof (OutputNode));
|
||||
nextoutput->class = OUTPUT_EXPRESSION;
|
||||
nextoutput->output.expression = expression;
|
||||
nextoutput->next = NULL;
|
||||
} else {
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_INVALID_PRINT_OUTPUT, token->get_line (token),
|
||||
this->priv->last_label);
|
||||
statement_destroy (statement);
|
||||
statement = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* add this output item to the statement and look for another */
|
||||
if (! this->priv->errors->get_code (this->priv->errors)) {
|
||||
if (lastoutput)
|
||||
lastoutput->next = nextoutput;
|
||||
else
|
||||
statement->statement.printn->first = nextoutput;
|
||||
lastoutput = nextoutput;
|
||||
token = get_token_to_parse ();
|
||||
}
|
||||
|
||||
/* continue the loop until the statement appears to be finished */
|
||||
} while (! this->priv->errors->get_code (this->priv->errors)
|
||||
&& token->get_class (token) == TOKEN_COMMA);
|
||||
|
||||
/* push back the last token and return the assembled statement */
|
||||
if (! this->priv->errors->get_code (this->priv->errors))
|
||||
this->priv->stored_token = token;
|
||||
return statement;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse an INPUT statement
|
||||
* returns:
|
||||
* StatementNode* The statement assembled
|
||||
*/
|
||||
static StatementNode *parse_input_statement (void) {
|
||||
|
||||
/* local variables */
|
||||
Token *token = NULL; /* tokens read as part of the statement */
|
||||
StatementNode *statement; /* the statement we're building */
|
||||
int line; /* line containing the INPUT token */
|
||||
VariableListNode
|
||||
*nextvar = NULL, /* the next variable node we're parsing */
|
||||
*lastvar = NULL; /* the last variable node we parsed */
|
||||
|
||||
/* initialise the statement */
|
||||
statement = statement_create ();
|
||||
statement->class = STATEMENT_INPUT;
|
||||
statement->statement.inputn = statement_create_input ();
|
||||
line = this->priv->stream->get_line (this->priv->stream);
|
||||
|
||||
/* main loop for parsing the variable list */
|
||||
do {
|
||||
|
||||
/* discard a previous comma, and seek the next variable */
|
||||
if (token) token->destroy (token);
|
||||
token = get_token_to_parse ();
|
||||
|
||||
/* process a premature end of line */
|
||||
if (token->get_class (token) == TOKEN_EOF
|
||||
|| token->get_line (token) != line) {
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_INVALID_VARIABLE, line, this->priv->last_label);
|
||||
statement_destroy (statement);
|
||||
statement = NULL;
|
||||
}
|
||||
|
||||
/* attempt to process an variable name */
|
||||
else if (token->get_class (token) != TOKEN_VARIABLE) {
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_INVALID_VARIABLE, token->get_line (token),
|
||||
this->priv->last_label);
|
||||
statement_destroy (statement);
|
||||
statement = NULL;
|
||||
} else {
|
||||
nextvar = malloc (sizeof (VariableListNode));
|
||||
nextvar->variable = *token->get_content (token) & 0x1f;
|
||||
nextvar->next = NULL;
|
||||
token->destroy (token);
|
||||
}
|
||||
|
||||
/* add this variable to the statement and look for another */
|
||||
if (! this->priv->errors->get_code (this->priv->errors)) {
|
||||
if (lastvar)
|
||||
lastvar->next = nextvar;
|
||||
else
|
||||
statement->statement.inputn->first = nextvar;
|
||||
lastvar = nextvar;
|
||||
token = get_token_to_parse ();
|
||||
}
|
||||
} while (! this->priv->errors->get_code (this->priv->errors)
|
||||
&& token->get_class (token) == TOKEN_COMMA);
|
||||
|
||||
/* return the assembled statement */
|
||||
this->priv->stored_token = token;
|
||||
return statement;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse a statement from the source file
|
||||
* returns:
|
||||
* StatementNode* a fully-assembled statement, hopefully.
|
||||
*/
|
||||
static StatementNode *parse_statement () {
|
||||
|
||||
/* local variables */
|
||||
Token *token; /* token read */
|
||||
StatementNode *statement = NULL; /* the new statement */
|
||||
|
||||
/* get the next token */
|
||||
token = get_token_to_parse ();
|
||||
|
||||
/* check for command */
|
||||
switch (token->get_class (token)) {
|
||||
case TOKEN_EOL:
|
||||
this->priv->stored_token = token;
|
||||
statement = NULL;
|
||||
break;
|
||||
case TOKEN_LET:
|
||||
token->destroy (token);
|
||||
statement = parse_let_statement ();
|
||||
break;
|
||||
case TOKEN_IF:
|
||||
token->destroy (token);
|
||||
statement = parse_if_statement ();
|
||||
break;
|
||||
case TOKEN_GOTO:
|
||||
token->destroy (token);
|
||||
statement = parse_goto_statement ();
|
||||
break;
|
||||
case TOKEN_GOSUB:
|
||||
token->destroy (token);
|
||||
statement = parse_gosub_statement ();
|
||||
break;
|
||||
case TOKEN_RETURN:
|
||||
token->destroy (token);
|
||||
statement = parse_return_statement ();
|
||||
break;
|
||||
case TOKEN_END:
|
||||
token->destroy (token);
|
||||
statement = parse_end_statement ();
|
||||
break;
|
||||
case TOKEN_PRINT:
|
||||
token->destroy (token);
|
||||
statement = parse_print_statement ();
|
||||
break;
|
||||
case TOKEN_INPUT:
|
||||
token->destroy (token);
|
||||
statement = parse_input_statement ();
|
||||
break;
|
||||
default:
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_UNRECOGNISED_COMMAND, token->get_line (token),
|
||||
this->priv->last_label);
|
||||
token->destroy (token);
|
||||
}
|
||||
|
||||
/* return the statement */
|
||||
return statement;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse a line from the source file.
|
||||
* returns:
|
||||
* StatementNode a fully-assembled statement, hopefully.
|
||||
*/
|
||||
static ProgramLineNode *parse_program_line (void) {
|
||||
|
||||
/* local variables */
|
||||
Token *token; /* token read */
|
||||
ProgramLineNode *program_line; /* program line read */
|
||||
int label_encountered = 0; /* 1 if this line has an explicit label */
|
||||
|
||||
/* initialise the program line and get the first token */
|
||||
program_line = program_line_create ();
|
||||
program_line->label = generate_default_label ();
|
||||
token = get_token_to_parse ();
|
||||
|
||||
/* deal with end of file */
|
||||
if (token->get_class (token) == TOKEN_EOF) {
|
||||
token->destroy (token);
|
||||
program_line_destroy (program_line);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* deal with line label, if supplied */
|
||||
if (token->get_class (token) == TOKEN_NUMBER) {
|
||||
program_line->label = atoi (token->get_content (token));
|
||||
label_encountered = 1;
|
||||
token->destroy (token);
|
||||
} else
|
||||
this->priv->stored_token = token;
|
||||
|
||||
/* validate the supplied or implied line label */
|
||||
if (! validate_line_label (program_line->label)) {
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_INVALID_LINE_NUMBER, this->priv->current_line,
|
||||
program_line->label);
|
||||
program_line_destroy (program_line);
|
||||
return NULL;
|
||||
}
|
||||
if (label_encountered)
|
||||
this->priv->last_label = program_line->label;
|
||||
|
||||
/* check for a statement and an EOL */
|
||||
program_line->statement = parse_statement ();
|
||||
if (! this->priv->errors->get_code (this->priv->errors)) {
|
||||
token = get_token_to_parse ();
|
||||
if (token->get_class (token) != TOKEN_EOL
|
||||
&& token->get_class (token) != TOKEN_EOF)
|
||||
this->priv->errors->set_code
|
||||
(this->priv->errors, E_UNEXPECTED_PARAMETER, this->priv->current_line,
|
||||
this->priv->last_label);
|
||||
token->destroy (token);
|
||||
}
|
||||
if (program_line->statement)
|
||||
this->priv->last_label = program_line->label;
|
||||
|
||||
/* return the program line */
|
||||
return program_line;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Public Methods
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Parse the whole program
|
||||
* params:
|
||||
* Parser* parser The parser to use
|
||||
* returns:
|
||||
* ProgramNode* The parsed program
|
||||
*/
|
||||
static ProgramNode *parse (Parser *parser) {
|
||||
|
||||
/* local varables */
|
||||
ProgramNode *program; /* the stored program */
|
||||
ProgramLineNode
|
||||
*previous = NULL, /* the previous line */
|
||||
*current; /* the current line */
|
||||
|
||||
/* initialise the program */
|
||||
this = parser;
|
||||
program = malloc (sizeof (ProgramNode));
|
||||
program->first = NULL;
|
||||
|
||||
/* read lines until reaching an error or end of input */
|
||||
while ((current = parse_program_line ())
|
||||
&& ! this->priv->errors->get_code (this->priv->errors)) {
|
||||
if (previous)
|
||||
previous->next = current;
|
||||
else
|
||||
program->first = current;
|
||||
previous = current;
|
||||
}
|
||||
|
||||
/* return the program */
|
||||
return program;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the current source line we're parsing
|
||||
* params:
|
||||
* Parser* The parser to use
|
||||
* returns:
|
||||
* int the line returned
|
||||
*/
|
||||
static int get_line (Parser *parser) {
|
||||
return parser->priv->current_line;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the label of the source line we're parsing
|
||||
* params:
|
||||
* Parser* parser The parser to use
|
||||
* returns:
|
||||
* int the label returned
|
||||
*/
|
||||
static int get_label (Parser *parser) {
|
||||
return parser->priv->last_label;
|
||||
}
|
||||
|
||||
/*
|
||||
* Destroy this parser object
|
||||
* params:
|
||||
* Parser* parser the doomed parser
|
||||
*/
|
||||
void destroy (Parser *parser) {
|
||||
if (parser) {
|
||||
if (parser->priv) {
|
||||
if (parser->priv->stream)
|
||||
parser->priv->stream->destroy (parser->priv->stream);
|
||||
}
|
||||
free (parser->priv);
|
||||
}
|
||||
free (parser);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Constructors
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Constructor
|
||||
* params:
|
||||
* ErrorHandler* errors the error handler to use
|
||||
* LanguageOptions* options the language options to use
|
||||
* FILE* input the input file
|
||||
* returns:
|
||||
* Parser* the new parser
|
||||
*/
|
||||
Parser *new_Parser (ErrorHandler *errors, LanguageOptions *options,
|
||||
FILE *input) {
|
||||
|
||||
/* allocate memory */
|
||||
this = malloc (sizeof (Parser));
|
||||
this->priv = malloc (sizeof (ParserData));
|
||||
this->priv->stream = new_TokenStream (input);
|
||||
|
||||
/* initialise methods */
|
||||
this->parse = parse;
|
||||
this->get_line = get_line;
|
||||
this->get_label = get_label;
|
||||
this->destroy = destroy;
|
||||
|
||||
/* initialise properties */
|
||||
this->priv->last_label = 0;
|
||||
this->priv->current_line = 0;
|
||||
this->priv->end_of_file = 0;
|
||||
this->priv->stored_token = NULL;
|
||||
this->priv->errors = errors;
|
||||
this->priv->options = options;
|
||||
|
||||
/* return the new object */
|
||||
return this;
|
||||
}
|
393
programs/develop/tinybasic-1.0.4/src/statement.c
Normal file
393
programs/develop/tinybasic-1.0.4/src/statement.c
Normal file
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
* Tiny BASIC
|
||||
* Statement Handling Module
|
||||
*
|
||||
* Released as Public Domain by Damian Gareth Walker 2019
|
||||
* Created: 15-Aug-2019
|
||||
*/
|
||||
|
||||
|
||||
/* included headers */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "statement.h"
|
||||
|
||||
|
||||
/*
|
||||
* LET Statement Functions
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* LET statement constructor
|
||||
* returns:
|
||||
* LetStatementNode* the created LET statement
|
||||
*/
|
||||
LetStatementNode *statement_create_let (void) {
|
||||
|
||||
/* local variables */
|
||||
LetStatementNode *letn; /* the created node */
|
||||
|
||||
/* allocate memory and assign safe defaults */
|
||||
letn = malloc (sizeof (LetStatementNode));
|
||||
letn->variable = 0;
|
||||
letn->expression = NULL;
|
||||
|
||||
/* return the LET statement node */
|
||||
return letn;
|
||||
}
|
||||
|
||||
/*
|
||||
* Destructor for a LET statement
|
||||
* params:
|
||||
* LetStatementNode *letn the doomed LET statement.
|
||||
*/
|
||||
void statement_destroy_let (LetStatementNode *letn) {
|
||||
if (letn->expression)
|
||||
expression_destroy (letn->expression);
|
||||
free (letn);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* IF Statement Functions
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* IF statement constructor
|
||||
* returns:
|
||||
* IfStatementNode* the created IF statement
|
||||
*/
|
||||
IfStatementNode *statement_create_if (void) {
|
||||
|
||||
/* local variables */
|
||||
IfStatementNode *ifn; /* the created node */
|
||||
|
||||
/* allocate memory and assign safe defaults */
|
||||
ifn = malloc (sizeof (IfStatementNode));
|
||||
ifn->left = ifn->right = NULL;
|
||||
ifn->op = RELOP_EQUAL;
|
||||
ifn->statement = NULL;
|
||||
|
||||
/* return the IF statement node */
|
||||
return ifn;
|
||||
}
|
||||
|
||||
/*
|
||||
* IF statement destructor
|
||||
* params:
|
||||
* IfStatementNode* ifn the doomed IF statement
|
||||
*/
|
||||
void statement_destroy_if (IfStatementNode *ifn) {
|
||||
if (ifn->left)
|
||||
expression_destroy (ifn->left);
|
||||
if (ifn->right)
|
||||
expression_destroy (ifn->right);
|
||||
if (ifn->statement)
|
||||
statement_destroy (ifn->statement);
|
||||
free (ifn);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GOTO Statement Functions
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* GOTO Statement Constructor
|
||||
* returns:
|
||||
* GotoStatementNode* the new GOTO statement
|
||||
*/
|
||||
GotoStatementNode *statement_create_goto (void) {
|
||||
|
||||
/* local variables */
|
||||
GotoStatementNode *goton; /* the statement to create */
|
||||
|
||||
/* create and initialise the data */
|
||||
goton = malloc (sizeof (GotoStatementNode));
|
||||
goton->label = NULL;
|
||||
|
||||
/* return the goto statement */
|
||||
return goton;
|
||||
}
|
||||
|
||||
/*
|
||||
* GOTO Statement Destructor
|
||||
* params:
|
||||
* GotoStatementNode* goton the doomed GOTO statement
|
||||
*/
|
||||
void statement_destroy_goto (GotoStatementNode *goton) {
|
||||
if (goton) {
|
||||
if (goton->label)
|
||||
expression_destroy (goton->label);
|
||||
free (goton);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GOSUB Statement Functions
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* GOSUB Statement Constructor
|
||||
* returns:
|
||||
* GosubStatementNode* the new GOSUB statement
|
||||
*/
|
||||
GosubStatementNode *statement_create_gosub (void) {
|
||||
|
||||
/* local variables */
|
||||
GosubStatementNode *gosubn; /* the statement to create */
|
||||
|
||||
/* create and initialise the data */
|
||||
gosubn = malloc (sizeof (GosubStatementNode));
|
||||
gosubn->label = NULL;
|
||||
|
||||
/* return the gosub statement */
|
||||
return gosubn;
|
||||
}
|
||||
|
||||
/*
|
||||
* GOSUB Statement Destructor
|
||||
* params:
|
||||
* GosubStatementNode* gosubn the doomed GOSUB statement
|
||||
*/
|
||||
void statement_destroy_gosub (GosubStatementNode *gosubn) {
|
||||
if (gosubn) {
|
||||
if (gosubn->label)
|
||||
expression_destroy (gosubn->label);
|
||||
free (gosubn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PRINT Statement Functions
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* PRINT statement constructor
|
||||
* returns:
|
||||
* PrintStatementNode* the created PRINT statement
|
||||
*/
|
||||
PrintStatementNode *statement_create_print (void) {
|
||||
|
||||
/* local variables */
|
||||
PrintStatementNode *printn; /* the created node */
|
||||
|
||||
/* allocate memory and assign safe defaults */
|
||||
printn = malloc (sizeof (PrintStatementNode));
|
||||
printn->first = NULL;
|
||||
|
||||
/* return the PRINT statement node */
|
||||
return printn;
|
||||
}
|
||||
|
||||
/*
|
||||
* Destructor for a PRINT statement
|
||||
* params:
|
||||
* PrintStatementNode *printn the doomed PRINT statement.
|
||||
*/
|
||||
void statement_destroy_print (PrintStatementNode *printn) {
|
||||
OutputNode *current, *next;
|
||||
current = printn->first;
|
||||
while (current) {
|
||||
next = current->next;
|
||||
if (current->class == OUTPUT_STRING)
|
||||
free (current->output.string);
|
||||
else if (current->class == OUTPUT_EXPRESSION)
|
||||
expression_destroy (current->output.expression);
|
||||
free (current);
|
||||
current = next;
|
||||
}
|
||||
free (printn);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* INPUT Statement Functions
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* INPUT statement constructor
|
||||
* returns:
|
||||
* InputStatementNode* initialised INPUT statement data
|
||||
*/
|
||||
InputStatementNode *statement_create_input (void) {
|
||||
|
||||
/* local variables */
|
||||
InputStatementNode *inputn; /* the new input statement data */
|
||||
|
||||
/* allocate memory and initalise safely */
|
||||
inputn = malloc (sizeof (InputStatementNode));
|
||||
inputn->first = NULL;
|
||||
|
||||
/* return the created node */
|
||||
return inputn;
|
||||
}
|
||||
|
||||
/*
|
||||
* INPUT statement destructor
|
||||
* params:
|
||||
* InputStatementNode* inputn the doomed INPUT statement node
|
||||
*/
|
||||
void statement_destroy_input (InputStatementNode *inputn) {
|
||||
|
||||
/* local variables */
|
||||
VariableListNode
|
||||
*variable, /* the current variable to destroy */
|
||||
*next; /* the next variable to destroy */
|
||||
|
||||
/* delete the variables from the variable list, then the input node */
|
||||
if (inputn) {
|
||||
variable = inputn->first;
|
||||
while (variable) {
|
||||
next = variable->next;
|
||||
free (variable);
|
||||
variable = next;
|
||||
}
|
||||
free (inputn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Top Level Functions
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Statement constructor
|
||||
* returns:
|
||||
* StatementNode* the newly-created blank statement
|
||||
*/
|
||||
StatementNode *statement_create (void) {
|
||||
|
||||
/* local variables */
|
||||
StatementNode *statement; /* the created statement */
|
||||
|
||||
/* allocate memory and set defaults */
|
||||
statement = malloc (sizeof (StatementNode));
|
||||
statement->class = STATEMENT_NONE;
|
||||
|
||||
/* return the created statement */
|
||||
return statement;
|
||||
}
|
||||
|
||||
/*
|
||||
* Statement destructor
|
||||
* params:
|
||||
* StatementNode* statement the doomed statement
|
||||
*/
|
||||
void statement_destroy (StatementNode *statement) {
|
||||
switch (statement->class) {
|
||||
case STATEMENT_LET:
|
||||
statement_destroy_let (statement->statement.letn);
|
||||
break;
|
||||
case STATEMENT_PRINT:
|
||||
statement_destroy_print (statement->statement.printn);
|
||||
break;
|
||||
case STATEMENT_INPUT:
|
||||
statement_destroy_input (statement->statement.inputn);
|
||||
break;
|
||||
case STATEMENT_IF:
|
||||
statement_destroy_if (statement->statement.ifn);
|
||||
break;
|
||||
case STATEMENT_GOTO:
|
||||
statement_destroy_goto (statement->statement.goton);
|
||||
break;
|
||||
case STATEMENT_GOSUB:
|
||||
statement_destroy_gosub (statement->statement.gosubn);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
free (statement);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Program Line Constructor
|
||||
* returns:
|
||||
* ProgramLineNode* the new program line
|
||||
*/
|
||||
ProgramLineNode *program_line_create (void) {
|
||||
|
||||
/* local variables */
|
||||
ProgramLineNode *program_line; /* the program line to create */
|
||||
|
||||
/* create and initialise the program line */
|
||||
program_line = malloc (sizeof (ProgramLineNode));
|
||||
program_line->label = 0;
|
||||
program_line->statement = NULL;
|
||||
program_line->next = NULL;
|
||||
|
||||
/* return the new program line */
|
||||
return program_line;
|
||||
}
|
||||
|
||||
/*
|
||||
* Program Line Destructor
|
||||
* params:
|
||||
* ProgramLineNode* program_line the doomed program line
|
||||
* params:
|
||||
* ProgramLineNode* the next program line
|
||||
*/
|
||||
ProgramLineNode *program_line_destroy (ProgramLineNode *program_line) {
|
||||
|
||||
/* local variables */
|
||||
ProgramLineNode *next = NULL; /* the next program line */
|
||||
|
||||
/* record the next line and destroy this one */
|
||||
if (program_line) {
|
||||
next = program_line->next;
|
||||
if (program_line->statement)
|
||||
statement_destroy (program_line->statement);
|
||||
free (program_line);
|
||||
}
|
||||
|
||||
/* return the line following */
|
||||
return next;
|
||||
}
|
||||
|
||||
/*
|
||||
* Program Constructor
|
||||
* returns:
|
||||
* ProgramNode* the constructed program
|
||||
*/
|
||||
ProgramNode *program_create (void) {
|
||||
|
||||
/* local variables */
|
||||
ProgramNode *program; /* new program */
|
||||
|
||||
/* create and initialise the program */
|
||||
program = malloc (sizeof (program));
|
||||
program->first = NULL;
|
||||
|
||||
/* return the new program */
|
||||
return program;
|
||||
}
|
||||
|
||||
/*
|
||||
* Program Destructor
|
||||
* params:
|
||||
* ProgramNode* program the doomed program
|
||||
*/
|
||||
void program_destroy (ProgramNode *program) {
|
||||
|
||||
/* local variables */
|
||||
ProgramLineNode *program_line; /* the program line to destroy */
|
||||
|
||||
/* destroy the program lines, then the program itself */
|
||||
program_line = program->first;
|
||||
while (program_line)
|
||||
program_line = program_line_destroy (program_line);
|
||||
free (program);
|
||||
}
|
411
programs/develop/tinybasic-1.0.4/src/tinybasic.c
Normal file
411
programs/develop/tinybasic-1.0.4/src/tinybasic.c
Normal file
@@ -0,0 +1,411 @@
|
||||
/*
|
||||
* Tiny BASIC
|
||||
* Interpreter and Compiler Main Program
|
||||
*
|
||||
* Released as Public Domain by Damian Gareth Walker 2019
|
||||
* Created: 04-Aug-2019
|
||||
*/
|
||||
|
||||
|
||||
/* included headers */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "options.h"
|
||||
#include "errors.h"
|
||||
#include "parser.h"
|
||||
#include "statement.h"
|
||||
#include "interpret.h"
|
||||
#include "formatter.h"
|
||||
#include "generatec.h"
|
||||
|
||||
#ifdef _KOLIBRI
|
||||
#define KTCC_BIN "/kolibrios/develop/tcc/tcc"
|
||||
#define KTCC_FLAGS "%s -o %s -lck"
|
||||
#endif
|
||||
|
||||
/* static variables */
|
||||
static char *input_filename = NULL; /* name of the input file */
|
||||
static enum { /* action to take with parsed program */
|
||||
OUTPUT_INTERPRET, /* interpret the program */
|
||||
OUTPUT_LST, /* output a formatted listing */
|
||||
OUTPUT_C, /* output a C program */
|
||||
OUTPUT_EXE /* output an executable */
|
||||
} output = OUTPUT_INTERPRET;
|
||||
static ErrorHandler *errors; /* universal error handler */
|
||||
static LanguageOptions *loptions; /* language options */
|
||||
|
||||
|
||||
/*
|
||||
* Level 2 Routines
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Set line number option
|
||||
* params:
|
||||
* char* option the option supplied on the command line
|
||||
*/
|
||||
static void set_line_numbers (char *option) {
|
||||
if (! strncmp ("optional", option, strlen (option)))
|
||||
loptions->set_line_numbers (loptions, LINE_NUMBERS_OPTIONAL);
|
||||
else if (! strncmp ("implied", option, strlen (option)))
|
||||
loptions->set_line_numbers (loptions, LINE_NUMBERS_IMPLIED);
|
||||
else if (! strncmp ("mandatory", option, strlen (option)))
|
||||
loptions->set_line_numbers (loptions, LINE_NUMBERS_MANDATORY);
|
||||
else
|
||||
errors->set_code (errors, E_BAD_COMMAND_LINE, 0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set line number limit
|
||||
* params:
|
||||
* char* option the option supplied on the command line
|
||||
*/
|
||||
static void set_line_limit (char *option) {
|
||||
int limit; /* the limit contained in the option */
|
||||
if (sscanf (option, "%d", &limit))
|
||||
loptions->set_line_limit (loptions, limit);
|
||||
else
|
||||
errors->set_code (errors, E_BAD_COMMAND_LINE, 0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set comment option
|
||||
* params:
|
||||
* char* option the option supplied on the command line
|
||||
*/
|
||||
static void set_comments (char *option) {
|
||||
if (! strncmp ("enabled", option, strlen (option)))
|
||||
loptions->set_comments (loptions, COMMENTS_ENABLED);
|
||||
else if (! strncmp ("disabled", option, strlen (option)))
|
||||
loptions->set_comments (loptions, COMMENTS_DISABLED);
|
||||
else
|
||||
errors->set_code (errors, E_BAD_COMMAND_LINE, 0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the output options
|
||||
* params:
|
||||
* char* option the option supplied on the command line
|
||||
*/
|
||||
static void set_output (char *option) {
|
||||
if (! strcmp ("lst", option))
|
||||
output = OUTPUT_LST;
|
||||
else if (! strcmp ("c", option))
|
||||
output = OUTPUT_C;
|
||||
else if (! strcmp ("exe", option))
|
||||
output = OUTPUT_EXE;
|
||||
else
|
||||
errors->set_code (errors, E_BAD_COMMAND_LINE, 0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the GOSUB stack limit option
|
||||
* params:
|
||||
* char* option the option supplied on the command line
|
||||
*/
|
||||
static void set_gosub_limit (char *option) {
|
||||
int limit; /* the limit contained in the option */
|
||||
if (sscanf (option, "%d", &limit))
|
||||
loptions->set_gosub_limit (loptions, limit);
|
||||
else
|
||||
errors->set_code (errors, E_BAD_COMMAND_LINE, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Level 1 Routines
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Process the command line options
|
||||
* params:
|
||||
* int argc number of arguments on the command line
|
||||
* char** argv the arguments
|
||||
*/
|
||||
static void set_options (int argc, char **argv) {
|
||||
|
||||
/* local variables */
|
||||
int argn; /* argument number count */
|
||||
|
||||
/* loop through all parameters */
|
||||
for (argn = 1; argn < argc && ! errors->get_code (errors); ++argn) {
|
||||
|
||||
/* scan for line number options */
|
||||
if (! strncmp (argv[argn], "-n", 2))
|
||||
set_line_numbers (&argv[argn][2]);
|
||||
else if (! strncmp (argv[argn], "--line-numbers=", 15))
|
||||
set_line_numbers (&argv[argn][15]);
|
||||
|
||||
/* scan for line number limit */
|
||||
else if (! strncmp (argv[argn], "-N", 2))
|
||||
set_line_limit (&argv[argn][2]);
|
||||
else if (! strncmp (argv[argn], "--line-limit=", 13))
|
||||
set_line_limit (&argv[argn][13]);
|
||||
|
||||
/* scan for comment option */
|
||||
else if (! strncmp (argv[argn], "-o", 2))
|
||||
set_comments (&argv[argn][2]);
|
||||
else if (! strncmp (argv[argn], "--comments=", 11))
|
||||
set_comments (&argv[argn][11]);
|
||||
|
||||
/* scan for output option */
|
||||
else if (! strncmp (argv[argn], "-O", 2))
|
||||
set_output (&argv[argn][2]);
|
||||
else if (! strncmp (argv[argn], "--output=", 9))
|
||||
set_output (&argv[argn][9]);
|
||||
|
||||
/* scan for gosub stack limit */
|
||||
else if (! strncmp (argv[argn], "-g", 2))
|
||||
set_gosub_limit (&argv[argn][2]);
|
||||
else if (! strncmp (argv[argn], "--gosub-limit=", 14))
|
||||
set_gosub_limit (&argv[argn][14]);
|
||||
|
||||
/* accept filename */
|
||||
else if (! input_filename)
|
||||
input_filename = argv[argn];
|
||||
|
||||
/* raise an error upon illegal option */
|
||||
else
|
||||
errors->set_code (errors, E_BAD_COMMAND_LINE, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Output a formatted program listing
|
||||
* params:
|
||||
* ProgramNode* program the program to output
|
||||
*/
|
||||
static void output_lst (ProgramNode *program) {
|
||||
|
||||
/* local variables */
|
||||
FILE *output; /* the output file */
|
||||
char *output_filename; /* the output filename */
|
||||
Formatter *formatter; /* the formatter object */
|
||||
|
||||
/* ascertain the output filename */
|
||||
output_filename = malloc (strlen (input_filename) + 5);
|
||||
if (output_filename) {
|
||||
|
||||
/* open the output file */
|
||||
sprintf (output_filename, "%s.lst", input_filename);
|
||||
if ((output = fopen (output_filename, "w"))) {
|
||||
|
||||
/* write to the output file */
|
||||
formatter = new_Formatter (errors);
|
||||
if (formatter) {
|
||||
formatter->generate (formatter, program);
|
||||
if (formatter->output)
|
||||
fprintf (output, "%s", formatter->output);
|
||||
formatter->destroy (formatter);
|
||||
}
|
||||
fclose (output);
|
||||
}
|
||||
|
||||
/* deal with errors */
|
||||
else
|
||||
errors->set_code (errors, E_FILE_NOT_FOUND, 0, 0);
|
||||
|
||||
/* free the output filename */
|
||||
free (output_filename);
|
||||
}
|
||||
|
||||
/* deal with out of memory error */
|
||||
else
|
||||
errors->set_code (errors, E_MEMORY, 0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Output a C source file
|
||||
* params:
|
||||
* ProgramNode* program the parsed program
|
||||
*/
|
||||
static void output_c (ProgramNode *program) {
|
||||
|
||||
/* local variables */
|
||||
FILE *output; /* the output file */
|
||||
char *output_filename; /* the output filename */
|
||||
CProgram *c_program; /* the C program */
|
||||
|
||||
/* open the output file */
|
||||
output_filename = malloc (strlen (input_filename) + 5);
|
||||
sprintf (output_filename, "%s.c", input_filename);
|
||||
if ((output = fopen (output_filename, "w"))) {
|
||||
|
||||
/* write to the output file */
|
||||
c_program = new_CProgram (errors, loptions);
|
||||
if (c_program) {
|
||||
c_program->generate (c_program, program);
|
||||
if (c_program->c_output)
|
||||
fprintf (output, "%s", c_program->c_output);
|
||||
c_program->destroy (c_program);
|
||||
}
|
||||
fclose (output);
|
||||
}
|
||||
|
||||
/* deal with errors */
|
||||
else
|
||||
errors->set_code (errors, E_FILE_NOT_FOUND, 0, 0);
|
||||
|
||||
/* clean up allocated memory */
|
||||
free (output_filename);
|
||||
}
|
||||
|
||||
/*
|
||||
* Invoke a compiler to turn a C source file into an executable
|
||||
* params:
|
||||
* char* basic_filename The BASIC program's name
|
||||
*/
|
||||
static void output_exe (char *command, char *basic_filename) {
|
||||
|
||||
/* local variables */
|
||||
char
|
||||
c_filename[256], /* the name of the C source */
|
||||
exe_filename[256], /* the base name of the executable */
|
||||
final_command[1024], /* the constructed compiler command */
|
||||
*ext, /* position of extension character '.' in filename */
|
||||
*src, /* source pointer for string copying */
|
||||
*dst; /* destination pointer for string copying */
|
||||
|
||||
/* work out the C and EXE filenames */
|
||||
sprintf (c_filename, "%s.c", basic_filename);
|
||||
strcpy (exe_filename, basic_filename);
|
||||
if ((ext = strchr (exe_filename, '.')))
|
||||
*ext = '\0';
|
||||
else
|
||||
strcat (exe_filename, ".out");
|
||||
|
||||
#ifndef _KOLIBRI
|
||||
/* build the compiler command */
|
||||
src = command;
|
||||
dst = final_command;
|
||||
while (*src) {
|
||||
if (! strncmp (src, "$(TARGET)", strlen ("$(TARGET)"))) {
|
||||
strcpy (dst, exe_filename);
|
||||
dst += strlen (exe_filename);
|
||||
src += strlen ("$(TARGET)");
|
||||
} else if (! strncmp (src, "$(SOURCE)", strlen ("$(SOURCE)"))) {
|
||||
strcpy (dst, c_filename);
|
||||
dst += strlen (c_filename);
|
||||
src += strlen ("$(SOURCE)");
|
||||
} else
|
||||
*(dst++) = *(src++);
|
||||
}
|
||||
*dst = '\0';
|
||||
|
||||
/* run the compiler command */
|
||||
system (final_command);
|
||||
#else
|
||||
sprintf(final_command, KTCC_FLAGS, c_filename, exe_filename);
|
||||
if(!_ksys_exec(KTCC_BIN, final_command)){
|
||||
printf(final_command);
|
||||
}else{
|
||||
printf("Bad command: %s %s\n", KTCC_BIN, final_command);
|
||||
exit(0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Top Level Routine
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Main Program
|
||||
* params:
|
||||
* int argc number of arguments on the command line
|
||||
* char** argv the arguments
|
||||
* returns:
|
||||
* int any error code from processing/running the program
|
||||
*/
|
||||
int main (int argc, char **argv) {
|
||||
/* local variables */
|
||||
FILE *input; /* input file */
|
||||
ProgramNode *program; /* the parsed program */
|
||||
ErrorCode code; /* error returned */
|
||||
Parser *parser; /* parser object */
|
||||
Interpreter *interpreter; /* interpreter object */
|
||||
char
|
||||
*error_text, /* error text message */
|
||||
*command; /* command for compilation */
|
||||
|
||||
/* interpret the command line arguments */
|
||||
errors = new_ErrorHandler ();
|
||||
loptions = new_LanguageOptions ();
|
||||
set_options (argc, argv);
|
||||
|
||||
/* give usage if filename not given */
|
||||
if (! input_filename) {
|
||||
printf ("Usage: tinybas [OPTIONS] INPUT-FILE\n");
|
||||
errors->destroy (errors);
|
||||
loptions->destroy (loptions);
|
||||
return 0;
|
||||
}
|
||||
/* otherwise attempt to open the file */
|
||||
if (!(input = fopen (input_filename, "r"))) {
|
||||
printf ("Error: cannot open file %s\n", input_filename);
|
||||
errors->destroy (errors);
|
||||
loptions->destroy (loptions);
|
||||
return E_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
/* get the parse tree */
|
||||
parser = new_Parser (errors, loptions, input);
|
||||
program = parser->parse (parser);
|
||||
parser->destroy (parser);
|
||||
fclose (input);
|
||||
|
||||
/* deal with errors */
|
||||
if ((code = errors->get_code (errors))) {
|
||||
error_text = errors->get_text (errors);
|
||||
printf ("Parse error: %s\n", error_text);
|
||||
free (error_text);
|
||||
loptions->destroy (loptions);
|
||||
errors->destroy (errors);
|
||||
return code;
|
||||
}
|
||||
|
||||
/* perform the desired action */
|
||||
switch (output) {
|
||||
case OUTPUT_INTERPRET:
|
||||
interpreter = new_Interpreter (errors, loptions);
|
||||
interpreter->interpret (interpreter, program);
|
||||
interpreter->destroy (interpreter);
|
||||
if ((code = errors->get_code (errors))) {
|
||||
error_text = errors->get_text (errors);
|
||||
printf ("Runtime error: %s\n", error_text);
|
||||
free (error_text);
|
||||
}
|
||||
break;
|
||||
case OUTPUT_LST:
|
||||
output_lst (program);
|
||||
break;
|
||||
case OUTPUT_C:
|
||||
output_c (program);
|
||||
break;
|
||||
case OUTPUT_EXE:
|
||||
|
||||
#ifndef _KOLIBRI
|
||||
if ((command = getenv ("TBEXE"))) {
|
||||
output_c (program);
|
||||
output_exe (command, input_filename);
|
||||
} else {
|
||||
printf ("TBEXE not set.\n");
|
||||
break;
|
||||
}
|
||||
#else
|
||||
output_c (program);
|
||||
output_exe (NULL, input_filename);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
/* clean up and return success */
|
||||
program_destroy (program);
|
||||
loptions->destroy (loptions);
|
||||
errors->destroy (errors);
|
||||
exit(0);
|
||||
}
|
235
programs/develop/tinybasic-1.0.4/src/token.c
Normal file
235
programs/develop/tinybasic-1.0.4/src/token.c
Normal file
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Tiny BASIC
|
||||
* Token handling functions
|
||||
*
|
||||
* Copyright (C) Damian Walker 2019
|
||||
* Created: 15-Aug-2019
|
||||
*/
|
||||
|
||||
|
||||
/* includes */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "token.h"
|
||||
|
||||
|
||||
/*
|
||||
* Internal data structures
|
||||
*/
|
||||
|
||||
|
||||
/* Private data */
|
||||
typedef struct {
|
||||
TokenClass class; /* class of token */
|
||||
int line; /* line on which token was found */
|
||||
int pos; /* position within the line on which token was found */
|
||||
char *content; /* representation of token */
|
||||
} Private;
|
||||
|
||||
|
||||
/*
|
||||
* Internal Data
|
||||
*/
|
||||
|
||||
|
||||
/* Convenience variables */
|
||||
static Token *this; /* the token object */
|
||||
static Private *data; /* the private data */
|
||||
|
||||
|
||||
/*
|
||||
* Public methods
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Return the class of the token
|
||||
* params:
|
||||
* Token* token the token object
|
||||
* returns:
|
||||
* TokenClass the class of the token
|
||||
*/
|
||||
static TokenClass get_class (Token *token) {
|
||||
this = token;
|
||||
data = token->data;
|
||||
return data->class;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the line on which the token begins
|
||||
* params:
|
||||
* Token* token the token object
|
||||
* returns:
|
||||
* int the line on which the token begins
|
||||
*/
|
||||
static int get_line (Token *token) {
|
||||
this = token;
|
||||
data = token->data;
|
||||
return data->line;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the character position on which the token begins
|
||||
* params:
|
||||
* Token* token the token object
|
||||
* returns:
|
||||
* int the position on which the token begins
|
||||
*/
|
||||
static int get_pos (Token *token) {
|
||||
this = token;
|
||||
data = token->data;
|
||||
return data->pos;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the content of the token begins
|
||||
* params:
|
||||
* Token* token the token object
|
||||
* returns:
|
||||
* char* the text content of the token
|
||||
*/
|
||||
static char *get_content (Token *token) {
|
||||
this = token;
|
||||
data = token->data;
|
||||
return data->content;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the token class
|
||||
* params:
|
||||
* Token* token the token to set
|
||||
* TokenClass class the class
|
||||
*/
|
||||
static void set_class (Token *token, TokenClass class) {
|
||||
this = token;
|
||||
data = this->data;
|
||||
data->class = class;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the token line and position
|
||||
* params:
|
||||
* Token* token the token to set
|
||||
* int line the line on which the token began
|
||||
* int pos the position on which the token began
|
||||
*/
|
||||
static void set_line_pos (Token *token, int line, int pos) {
|
||||
this = token;
|
||||
data = this->data;
|
||||
data->line = line;
|
||||
data->pos = pos;;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the token's text content
|
||||
* params:
|
||||
* Token* token the token to alter
|
||||
* char* content the text content to set
|
||||
*/
|
||||
static void set_content (Token *token, char *content) {
|
||||
this = token;
|
||||
data = this->data;
|
||||
if (data->content)
|
||||
free (data->content);
|
||||
data->content = malloc (strlen (content) + 1);
|
||||
strcpy (data->content, content);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set all of the values of an existing token in a single function call.
|
||||
* params:
|
||||
* Token* token the token to update
|
||||
* TokenClass class class of token to initialise
|
||||
* int line line on which the token occurred
|
||||
* int pos character position on which the token occurred
|
||||
* char* content the textual content of the token
|
||||
*/
|
||||
static void initialise (Token *token, TokenClass class, int line, int pos,
|
||||
char *content) {
|
||||
|
||||
/* set convenience variables */
|
||||
this = token;
|
||||
data = this->data;
|
||||
|
||||
/* initialise the easy members */
|
||||
data->class = class ? class : TOKEN_NONE;
|
||||
data->line = line ? line : 0;
|
||||
data->pos = pos ? pos : 0;
|
||||
|
||||
/* initialise the content */
|
||||
if (content)
|
||||
set_content (this, content);
|
||||
}
|
||||
|
||||
/*
|
||||
* Token destructor
|
||||
* params:
|
||||
* Token* token the doomed token
|
||||
*/
|
||||
static void destroy (Token *token) {
|
||||
if ((this = token)) {
|
||||
data = this->data;
|
||||
if (data->content)
|
||||
free (data->content);
|
||||
free (data);
|
||||
free (this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Constructors
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Token constructor without values to initialise
|
||||
* returns:
|
||||
* Token* the created token
|
||||
*/
|
||||
Token *new_Token (void) {
|
||||
|
||||
/* create the structure */
|
||||
this = malloc (sizeof (Token));
|
||||
this->data = data = malloc (sizeof (Private));
|
||||
|
||||
/* initialise properties */
|
||||
data->class = TOKEN_NONE;
|
||||
data->line = data->pos = 0;
|
||||
data->content = NULL;
|
||||
|
||||
/* set up methods */
|
||||
this->initialise = initialise;
|
||||
this->get_class = get_class;
|
||||
this->get_line = get_line;
|
||||
this->get_pos = get_pos;
|
||||
this->get_content = get_content;
|
||||
this->set_class = set_class;
|
||||
this->set_line_pos = set_line_pos;
|
||||
this->set_content = set_content;
|
||||
this->destroy = destroy;
|
||||
|
||||
/* return the created structure */
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* Token constructor with values to initialise
|
||||
* params:
|
||||
* TokenClass class class of token to initialise
|
||||
* int line line on which the token occurred
|
||||
* int pos character position on which the token occurred
|
||||
* char* content the textual content of the token
|
||||
* returns:
|
||||
* Token* the created token
|
||||
*/
|
||||
Token *new_Token_init (TokenClass class, int line, int pos, char *content) {
|
||||
|
||||
/* create a blank token */
|
||||
this = new_Token ();
|
||||
this->initialise (this, class, line, pos, content);
|
||||
|
||||
/* return the new token */
|
||||
return this;
|
||||
}
|
602
programs/develop/tinybasic-1.0.4/src/tokeniser.c
Normal file
602
programs/develop/tinybasic-1.0.4/src/tokeniser.c
Normal file
@@ -0,0 +1,602 @@
|
||||
/*
|
||||
* Tiny BASIC
|
||||
* Tokenisation module
|
||||
*
|
||||
* Copyright (C) Damian Gareth Walker 2019
|
||||
* Created: 04-Aug-2019
|
||||
*/
|
||||
|
||||
|
||||
/* included headers */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "token.h"
|
||||
#include "tokeniser.h"
|
||||
#include "common.h"
|
||||
|
||||
|
||||
/*
|
||||
* Data definitions
|
||||
*/
|
||||
|
||||
|
||||
/* modes of reading */
|
||||
typedef enum {
|
||||
DEFAULT_MODE, /* we have no idea what's coming */
|
||||
COMMENT_MODE, /* reading a comment */
|
||||
WORD_MODE, /* reading an identifier or keyword */
|
||||
NUMBER_MODE, /* reading a numeric constant */
|
||||
LESS_THAN_MODE, /* reading an operator staring with < */
|
||||
GREATER_THAN_MODE, /* reading an operator starting with > */
|
||||
STRING_LITERAL_MODE, /* reading a string literal */
|
||||
UNKNOWN_MODE /* we are lost */
|
||||
} Mode;
|
||||
|
||||
/* current state information */
|
||||
typedef struct {
|
||||
Token *token; /* token to return */
|
||||
Mode mode; /* current reading mode */
|
||||
int ch; /* last-read character */
|
||||
char *content; /* content of token under construction */
|
||||
int max; /* memory reserved for content */
|
||||
} TokeniserState;
|
||||
|
||||
/* Private data */
|
||||
typedef struct {
|
||||
FILE *input; /* the input file */
|
||||
int line, /* current line in the input file */
|
||||
pos, /* current position on the input line */
|
||||
start_line, /* line on which a token started */
|
||||
start_pos; /* position on which a token started */
|
||||
} Private;
|
||||
|
||||
|
||||
/*
|
||||
* File level variables
|
||||
*/
|
||||
|
||||
|
||||
/* convenience variables */
|
||||
static TokenStream *this; /* token stream passed in to public method */
|
||||
static Private *data; /* private data for this */
|
||||
|
||||
|
||||
/*
|
||||
* Level 2 Tokeniser Routines
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Read a character and update the position counter
|
||||
* globals:
|
||||
* int line current line after character read
|
||||
* int pos current character position after character read
|
||||
* params:
|
||||
* TokeniserState* state current state of the tokeniser
|
||||
* returns:
|
||||
* int character just read
|
||||
*/
|
||||
static int read_character (TokeniserState *state) {
|
||||
|
||||
int ch; /* character read from stream */
|
||||
|
||||
/* read the character */
|
||||
ch = fgetc (data->input);
|
||||
|
||||
/* update the position and line counters */
|
||||
if (ch == '\n') {
|
||||
++data->line;
|
||||
data->pos = 0;
|
||||
} else {
|
||||
++data->pos;
|
||||
}
|
||||
|
||||
/* return the character */
|
||||
return ch;
|
||||
}
|
||||
|
||||
/*
|
||||
* Push a character back into the input stream and update position markers
|
||||
* globals:
|
||||
* int line line number rolled back
|
||||
* int pos character position rolled back
|
||||
* params:
|
||||
* TokeniserState* state current state of the tokeniser
|
||||
*/
|
||||
static void unread_character (TokeniserState *state) {
|
||||
ungetc (state->ch, data->input);
|
||||
if (state->ch == '\n')
|
||||
--data->line;
|
||||
else
|
||||
--data->pos;
|
||||
}
|
||||
|
||||
/*
|
||||
* Append the last read character to the token content
|
||||
* params:
|
||||
* TokeniserState* state current state of the tokeniser
|
||||
*/
|
||||
static void store_character (TokeniserState *state) {
|
||||
|
||||
/* variable declarations */
|
||||
char *temp; /* temporary pointer to content */
|
||||
int length; /* current length of token */
|
||||
|
||||
/* allocate more memory for the token content if necessary */
|
||||
if (strlen (state->content) == state->max - 1) {
|
||||
temp = state->content;
|
||||
state->max *= 2;
|
||||
state->content = malloc (state->max);
|
||||
strcpy (state->content, temp);
|
||||
free (temp);
|
||||
}
|
||||
|
||||
/* now add the character to the token */
|
||||
length = strlen (state->content);
|
||||
state->content [length++] = state->ch;
|
||||
state->content [length] = '\0';
|
||||
}
|
||||
|
||||
/*
|
||||
* Identify the various recognised symbols
|
||||
* params:
|
||||
* int ch the character to identify
|
||||
* returns:
|
||||
* TokenClass the token class recognised by the parser
|
||||
*/
|
||||
static TokenClass identify_symbol (int ch) {
|
||||
switch (ch) {
|
||||
case '+':
|
||||
return TOKEN_PLUS;
|
||||
break;
|
||||
case '-':
|
||||
return TOKEN_MINUS;
|
||||
break;
|
||||
case '*':
|
||||
return TOKEN_MULTIPLY;
|
||||
break;
|
||||
case '/':
|
||||
return TOKEN_DIVIDE;
|
||||
break;
|
||||
case '=':
|
||||
return TOKEN_EQUAL;
|
||||
break;
|
||||
case '(':
|
||||
return TOKEN_LEFT_PARENTHESIS;
|
||||
break;
|
||||
case ')':
|
||||
return TOKEN_RIGHT_PARENTHESIS;
|
||||
break;
|
||||
case ',':
|
||||
return TOKEN_COMMA;
|
||||
break;
|
||||
default:
|
||||
return TOKEN_SYMBOL;
|
||||
}
|
||||
}
|
||||
|
||||
static TokenClass identify_word (char *word) {
|
||||
if (strlen (word) == 1)
|
||||
return TOKEN_VARIABLE;
|
||||
else if (! tinybasic_strcmp (word, "LET"))
|
||||
return TOKEN_LET;
|
||||
else if (! tinybasic_strcmp (word, "IF"))
|
||||
return TOKEN_IF;
|
||||
else if (! tinybasic_strcmp (word, "THEN"))
|
||||
return TOKEN_THEN;
|
||||
else if (! tinybasic_strcmp (word, "GOTO"))
|
||||
return TOKEN_GOTO;
|
||||
else if (! tinybasic_strcmp (word, "GOSUB"))
|
||||
return TOKEN_GOSUB;
|
||||
else if (! tinybasic_strcmp (word, "RETURN"))
|
||||
return TOKEN_RETURN;
|
||||
else if (! tinybasic_strcmp (word, "END"))
|
||||
return TOKEN_END;
|
||||
else if (! tinybasic_strcmp (word, "PRINT"))
|
||||
return TOKEN_PRINT;
|
||||
else if (! tinybasic_strcmp (word, "INPUT"))
|
||||
return TOKEN_INPUT;
|
||||
else if (! tinybasic_strcmp (word, "REM"))
|
||||
return TOKEN_REM;
|
||||
else
|
||||
return TOKEN_WORD;
|
||||
}
|
||||
|
||||
/*
|
||||
* Identify compound (multi-character) symbols.
|
||||
* Also identifies some single-character symbols that can form
|
||||
* the start of multi-character symbols.
|
||||
* params:
|
||||
* char* symbol the symbol to identify
|
||||
* returns:
|
||||
* TokenClass the identification
|
||||
*/
|
||||
static TokenClass identify_compound_symbol (char *symbol) {
|
||||
if (! strcmp (symbol, "<>")
|
||||
|| ! strcmp (symbol, "><"))
|
||||
return TOKEN_UNEQUAL;
|
||||
else if (! strcmp (symbol, "<"))
|
||||
return TOKEN_LESSTHAN;
|
||||
else if (! strcmp (symbol, "<="))
|
||||
return TOKEN_LESSOREQUAL;
|
||||
else if (! strcmp (symbol, ">"))
|
||||
return TOKEN_GREATERTHAN;
|
||||
else if (! strcmp (symbol, ">="))
|
||||
return TOKEN_GREATEROREQUAL;
|
||||
else
|
||||
return TOKEN_SYMBOL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Level 1 Tokeniser Routines
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Default mode - deal with character when state is unknown
|
||||
* globals:
|
||||
* int line current line in the source file
|
||||
* int pos current character position in the source
|
||||
* int start_line line on which the current token started
|
||||
* int start_pos char pos on which the current token started
|
||||
* params:
|
||||
* TokeniserState* state current state of the tokeniser
|
||||
*/
|
||||
static void default_mode (TokeniserState *state) {
|
||||
|
||||
/* deal with non-EOL whitespace */
|
||||
if (state->ch == ' ' ||
|
||||
state->ch == '\t') {
|
||||
state->ch = read_character (state);
|
||||
data->start_line = data->line;
|
||||
data->start_pos = data->pos;
|
||||
}
|
||||
|
||||
/* deal with EOL whitespace */
|
||||
else if (state->ch == '\n') {
|
||||
data->start_line = data->line - 1;
|
||||
data->start_pos = data->pos;
|
||||
state->token = new_Token_init
|
||||
(TOKEN_EOL, data->start_line, data->start_pos, state->content);
|
||||
}
|
||||
|
||||
/* alphabetic characters start a word */
|
||||
else if ((state->ch >= 'A' && state->ch <= 'Z') ||
|
||||
(state->ch >= 'a' && state->ch <= 'z')) {
|
||||
data->start_line = data->line;
|
||||
data->start_pos = data->pos;
|
||||
state->mode = WORD_MODE;
|
||||
}
|
||||
|
||||
/* digits start a number */
|
||||
else if (state->ch >= '0' && state->ch <= '9')
|
||||
state->mode = NUMBER_MODE;
|
||||
|
||||
/* check for tokens starting with less-than (<, <=, <>) */
|
||||
else if (state->ch == '<') {
|
||||
data->start_line = data->line;
|
||||
data->start_pos = data->pos;
|
||||
store_character (state);
|
||||
state->ch = read_character (state);
|
||||
state->mode = LESS_THAN_MODE;
|
||||
}
|
||||
|
||||
/* check for tokens starting with greater-than (>, >=) */
|
||||
else if (state->ch == '>') {
|
||||
data->start_line = data->line;
|
||||
data->start_pos = data->pos;
|
||||
store_character (state);
|
||||
state->ch = read_character (state);
|
||||
state->mode = GREATER_THAN_MODE;
|
||||
}
|
||||
|
||||
/* deal with other symbol operators */
|
||||
else if (strchr ("+-*/=(),", state->ch) != NULL) {
|
||||
data->start_line = data->line;
|
||||
data->start_pos = data->pos;
|
||||
store_character (state);
|
||||
state->token = new_Token_init (identify_symbol (state->ch),
|
||||
data->start_line, data->start_pos, state->content);
|
||||
}
|
||||
|
||||
/* double quotes start a string literal */
|
||||
else if (state->ch == '"') {
|
||||
data->start_line = data->line;
|
||||
data->start_pos = data->pos;
|
||||
state->ch = read_character (state);
|
||||
state->mode = STRING_LITERAL_MODE;
|
||||
}
|
||||
|
||||
/* detect end of file */
|
||||
else if (state->ch == EOF) {
|
||||
data->start_line = data->line;
|
||||
data->start_pos = data->pos;
|
||||
state->token = new_Token_init
|
||||
(TOKEN_EOF, data->start_line, data->start_pos, state->content);
|
||||
}
|
||||
|
||||
/* other characters are illegal */
|
||||
else {
|
||||
data->start_line = data->line;
|
||||
data->start_pos = data->pos;
|
||||
store_character (state);
|
||||
state->token = new_Token_init
|
||||
(TOKEN_ILLEGAL, data->start_line, data->start_pos, state->content);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Word mode - deal with character when building a word token
|
||||
* globals:
|
||||
* int start_line line on which the current token started
|
||||
* int start_pos char pos on which the current token started
|
||||
* params:
|
||||
* TokeniserState* state current state of the tokeniser
|
||||
*/
|
||||
static void word_mode (TokeniserState *state) {
|
||||
|
||||
/* local variables */
|
||||
TokenClass class; /* recognised class of keyword */
|
||||
|
||||
/* add letters and digits to the token */
|
||||
if ((state->ch >= 'A' && state->ch <= 'Z') ||
|
||||
(state->ch >= 'a' && state->ch <= 'z')) {
|
||||
store_character (state);
|
||||
state->ch = read_character (state);
|
||||
}
|
||||
|
||||
/* other characters are pushed back for the next token */
|
||||
else {
|
||||
if (state->ch != EOF)
|
||||
unread_character (state);
|
||||
class = identify_word (state->content);
|
||||
if (class == TOKEN_REM) {
|
||||
*state->content = '\0';
|
||||
state->mode = COMMENT_MODE;
|
||||
}
|
||||
else
|
||||
state->token = new_Token_init
|
||||
(class, data->start_line, data->start_pos, state->content);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Comment mode - skip till end of line after a REM
|
||||
* globals:
|
||||
* int start_line line on which the current token started
|
||||
* int start_pos char pos on which the current token started
|
||||
* params:
|
||||
* TokeniserState* state current state of the tokeniser
|
||||
*/
|
||||
static void comment_mode (TokeniserState *state) {
|
||||
if (state->ch == '\n')
|
||||
state->mode = DEFAULT_MODE;
|
||||
else
|
||||
state->ch = read_character (state);
|
||||
}
|
||||
|
||||
/*
|
||||
* Number mode - building a number token (integer only)
|
||||
* globals:
|
||||
* int start_line line on which the current token started
|
||||
* int start_pos char pos on which the current token started
|
||||
* params:
|
||||
* TokeniserState* state current state of the tokeniser
|
||||
*/
|
||||
static void number_mode (TokeniserState *state) {
|
||||
|
||||
/* add digits to the token */
|
||||
if (state->ch >= '0' && state->ch <= '9') {
|
||||
store_character (state);
|
||||
state->ch = read_character (state);
|
||||
}
|
||||
|
||||
/* other characters are pushed back for the next token */
|
||||
else {
|
||||
if (state->ch != EOF)
|
||||
unread_character (state);
|
||||
state->token = new_Token_init
|
||||
(TOKEN_NUMBER, data->start_line, data->start_pos, state->content);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Less than mode - checking for <> and <= operators
|
||||
* globals:
|
||||
* int start_line line on which the current token started
|
||||
* int start_pos char pos on which the current token started
|
||||
* params:
|
||||
* TokeniserState* state current state of the tokeniser
|
||||
*/
|
||||
static void less_than_mode (TokeniserState *state) {
|
||||
if (state->ch == '=' || state->ch == '>')
|
||||
store_character (state);
|
||||
else
|
||||
unread_character (state);
|
||||
state->token = new_Token_init
|
||||
(identify_compound_symbol (state->content), data->start_line,
|
||||
data->start_pos, state->content);
|
||||
}
|
||||
|
||||
/*
|
||||
* Greater than mode - checking for >= and >< operators
|
||||
* globals:
|
||||
* int start_line line on which the current token started
|
||||
* int start_pos char pos on which the current token started
|
||||
* params:
|
||||
* TokeniserState* state current state of the tokeniser
|
||||
*/
|
||||
static void greater_than_mode (TokeniserState *state) {
|
||||
if (state->ch == '=' || state->ch == '<')
|
||||
store_character (state);
|
||||
else
|
||||
ungetc (state->ch, data->input);
|
||||
state->token = new_Token_init
|
||||
(identify_compound_symbol (state->content), data->start_line,
|
||||
data->start_pos, state->content);
|
||||
}
|
||||
|
||||
/*
|
||||
* String literal mode - reading a string
|
||||
* globals:
|
||||
* int start_line line on which the current token started
|
||||
* int start_pos char pos on which the current token started
|
||||
* params:
|
||||
* TokeniserState* state current state of the tokeniser
|
||||
*/
|
||||
static void string_literal_mode (TokeniserState *state) {
|
||||
|
||||
/* a quote terminates the string */
|
||||
if (state->ch == '"')
|
||||
state->token = new_Token_init
|
||||
(TOKEN_STRING, data->start_line, data->start_pos, state->content);
|
||||
|
||||
/* a backslash escapes the next character */
|
||||
else if (state->ch == '\\') {
|
||||
state->ch = read_character (state);
|
||||
store_character (state);
|
||||
state->ch = read_character (state);
|
||||
}
|
||||
|
||||
/* EOF generates an error */
|
||||
else if (state->ch == EOF)
|
||||
state->token = new_Token_init
|
||||
(TOKEN_ILLEGAL, data->start_line, data->start_pos, state->content);
|
||||
|
||||
/* all other characters are part of the string */
|
||||
else {
|
||||
store_character (state);
|
||||
state->ch = read_character (state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Top Level Tokeniser Routines
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Get the next token
|
||||
* params:
|
||||
* TokenStream* token_stream the token stream being processed
|
||||
* returns:
|
||||
* Token* the token built
|
||||
*/
|
||||
static Token *next (TokenStream *token_stream) {
|
||||
|
||||
/* local variables */
|
||||
TokeniserState state; /* current state of reading */
|
||||
Token *return_token; /* token to return */
|
||||
|
||||
/* initialise */
|
||||
this = token_stream;
|
||||
data = this->data;
|
||||
state.token = NULL;
|
||||
state.mode = DEFAULT_MODE;
|
||||
state.max = 1024;
|
||||
state.content = malloc (state.max);
|
||||
*(state.content) = '\0';
|
||||
state.ch = read_character (&state);
|
||||
/* main loop */
|
||||
while (state.token == NULL) {
|
||||
switch (state.mode) {
|
||||
case DEFAULT_MODE:
|
||||
|
||||
default_mode (&state);
|
||||
break;
|
||||
case COMMENT_MODE:
|
||||
comment_mode (&state);
|
||||
break;
|
||||
case WORD_MODE:
|
||||
word_mode (&state);
|
||||
break;
|
||||
case NUMBER_MODE:
|
||||
number_mode (&state);
|
||||
break;
|
||||
case LESS_THAN_MODE:
|
||||
less_than_mode (&state);
|
||||
break;
|
||||
case GREATER_THAN_MODE:
|
||||
greater_than_mode (&state);
|
||||
break;
|
||||
case STRING_LITERAL_MODE:
|
||||
string_literal_mode (&state);
|
||||
break;
|
||||
default:
|
||||
state.token = new_Token_init
|
||||
(TOKEN_EOF, data->start_line, data->start_pos, state.content);
|
||||
state.ch = EOF; /* temporary hack */
|
||||
}
|
||||
}
|
||||
|
||||
/* store token and release state memory */
|
||||
return_token = state.token;
|
||||
free (state.content);
|
||||
|
||||
/* return result */
|
||||
return return_token;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Getter for the current line number
|
||||
* paramss:
|
||||
* TokenStream* token_stream the token stream being processed
|
||||
* returns:
|
||||
* int the current line number returned
|
||||
*/
|
||||
static int get_line (TokenStream *token_stream) {
|
||||
this = token_stream;
|
||||
data = this->data;
|
||||
return data->line;
|
||||
}
|
||||
|
||||
/*
|
||||
* Destructor for a TokenStream
|
||||
* params:
|
||||
* TokenStream* token_stream the doomed token stream
|
||||
*/
|
||||
static void destroy (TokenStream *token_stream) {
|
||||
if (token_stream) {
|
||||
if (token_stream->data)
|
||||
free (token_stream->data);
|
||||
free (token_stream);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Constructors
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Constructor for TokenStream
|
||||
* params:
|
||||
* FILE* input Input file
|
||||
* returns:
|
||||
* TokenStream* The new token stream
|
||||
*/
|
||||
TokenStream *new_TokenStream (FILE *input) {
|
||||
|
||||
/* allocate the memory */
|
||||
this = malloc (sizeof (TokenStream));
|
||||
this->data = data = malloc (sizeof (Private));
|
||||
|
||||
/* initialise methods */
|
||||
this->next = next;
|
||||
this->get_line = get_line;
|
||||
this->destroy = destroy;
|
||||
|
||||
/* initialise data */
|
||||
data->input = input;
|
||||
data->line = data->start_line = 1;
|
||||
data->pos = data->start_pos = 0;
|
||||
|
||||
/* return new token stream */
|
||||
return this;
|
||||
}
|
Reference in New Issue
Block a user