/*
* 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:
sprintf (factor_text
, "%c", factor
->data.
variable + 'A' - 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:
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) {
sprintf (factor_buffer
, "-%s", 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);
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))) {
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;
}
/*
* 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);
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
,
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;
}
/*
* 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
, "LET %c=%s", 'A' - 1 + letn
->variable
, 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 */
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 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) {
sprintf (goto_text
, "GOTO %s", 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
, "GOSUB %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 */
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
*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 */
/* add the output items */
if ((output = printn->first)) {
do {
/* add the separator */
strcat (print_text
, output
== printn
->first
? " " : ",");
/* format the output item */
switch (output->class) {
case OUTPUT_STRING:
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 */
strcat (print_text
, 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 */
/* add the output items */
if ((variable = inputn->first)) {
do {
(variable == inputn->first) ? ' ' : ',',
variable->variable + 'A' - 1);
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:
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
/* build the statement itself */
output = output_statement (program_line->statement);
/* if this wasn't a comment, add it to the program */
if (output) {
sprintf (line_text
, "%s%s\n", label_text
, output
);
this
->output
= realloc (this
->output
,
strcat (this
->output
, 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)
}
}
/*
* 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;
}