/*
* 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:
sprintf (factor_text
, "%c", factor
->data.
variable + 'a' - 1);
data->vars_used |= 1 << (factor->data.variable - 1);
break;
case FACTOR_VALUE:
sprintf (factor_text
, "%d", factor
->data.
value);
break;
case FACTOR_EXPRESSION:
if ((expression_text = output_expression (factor->data.expression))) {
sprintf (factor_text
, "(%s)", 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) {
sprintf (factor_buffer
, "-%s", 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);
term_text = NULL;
}
/* get the factor that follows the operator */
if (! errors->get_code (errors)
&& (factor_text = output_factor (rhfactor->factor))) {
sprintf (term_text
, "%s%c%s", term_text
, operator_char
, 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);
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
,
sprintf (expression_text
, "%s%c%s", expression_text
, operator_char
,
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) {
sprintf (let_text
, "%c=%s;", 'a' - 1 + letn
->variable
, 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 */
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) {
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) {
sprintf (goto_text
, "label=%s; goto goto_block;", 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) {
sprintf (gosub_text
, "bas_exec(%s);", 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 */
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 */
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 = '\0';
*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
,
strcat (format_text
, output
->output.
string);
break;
case OUTPUT_EXPRESSION:
output_text = output_expression (output->output.expression);
output_list
= realloc (output_list
,
strcat (output_list
, output_text
);
break;
}
/* look for the next output item */
} while ((output = output->next));
}
/* assemble the whole print text and return it */
sprintf (print_text
, "printf(\"%s\\n\"%s);", format_text
, 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 = '\0';
if ((variable = inputn->first)) {
do {
sprintf (var_text
, "%s%c = bas_input();",
(variable == inputn->first) ? "" : "\n",
variable->variable + 'a' - 1);
strcat (input_text
, 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:
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
);
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) {
strcat (data
->code
, 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
)
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) {
this
->c_output
= realloc (this
->c_output
, strlen (this
->c_output
)
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 */
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);
strcat (goto_block
, goto_line
);
label = label->next;
}
strcat (goto_block
, "lbl_start:\n");
/* put the function together */
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
);
}
/*
* 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;
}
if (data->code)
}
/* destroy the generated output */
if (this->c_output)
/* destroy the containing structure */
}
/*
* 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 = '\0';
*this->c_output = '\0';
/* return the created structure */
return this;
}