forked from KolibriOS/kolibrios
aa8c354057
git-svn-id: svn://kolibrios.org@9649 a494cfbc-eb01-0410-851d-a64ba20cac60
410 lines
11 KiB
C
410 lines
11 KiB
C
/*
|
|
* 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
|
|
#include <sys/ksys.h>
|
|
#define KTCC_BIN "/kolibrios/develop/tcc/tcc"
|
|
#define KTCC_FLAGS "-nobss %s -o %s"
|
|
#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("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);
|
|
}
|