forked from KolibriOS/kolibrios
(An improved version in conjunction with ktcc can generate executable files.) git-svn-id: svn://kolibrios.org@8733 a494cfbc-eb01-0410-851d-a64ba20cac60
620 lines
16 KiB
C
620 lines
16 KiB
C
/*
|
|
* 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;
|
|
}
|