u6a/src/u6ac.c

229 lines
7.8 KiB
C

/*
* u6ac.c - Unlambda compiler CLI
*
* Copyright (C) 2020 CismonX <admin@cismon.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "logging.h"
#include "lexer.h"
#include "parser.h"
#include "codegen.h"
#include <unistd.h>
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#define EC_ERR_OPTIONS 1
#define EC_ERR_LEX 2
#define EC_ERR_PARSE 3
#define EC_ERR_CODEGEN 4
struct arg_options {
struct u6a_codegen_options codegen;
FILE* input_file;
char* input_file_name;
char* output_file_prefix;
bool print_only;
};
static const char* err_toplevel = "error";
static const char* info_toplevel = "info";
static void
arg_options_destroy(struct arg_options* options, bool delete_output_file) {
if (options->input_file && options->input_file != stdin) {
fclose(options->input_file);
}
const bool not_using_stdout = options->codegen.output_stream != stdout;
if (options->codegen.output_stream && not_using_stdout) {
fclose(options->codegen.output_stream);
}
if (delete_output_file && not_using_stdout && options->codegen.file_name) {
remove(options->codegen.file_name);
}
}
static bool
process_options(struct arg_options* options, int argc, char** argv) {
static const struct option long_opts[] = {
{ "out-file", required_argument, NULL, 'o' },
{ "add-prefix", optional_argument, NULL, 'p' },
{ "verbose", no_argument, NULL, 'v' },
{ "syntax-only", no_argument, NULL, 's' },
{ "help", no_argument, NULL, 'H' },
{ "version", no_argument, NULL, 'V' },
{ 0, 0, 0, 0 }
};
options->codegen.optimize_const = false;
bool syntax_only = false;
bool verbose = false;
char optimize_level = '1';
while (true) {
int result = getopt_long(argc, argv, "o:O::SvHV", long_opts, NULL);
if (result == -1) {
break;
}
switch (result) {
case 'o':
if (UNLIKELY(options->codegen.file_name)) {
break;
}
options->codegen.file_name = optarg;
break;
case 'O':
optimize_level = optarg ? optarg[0] : '1';
case 'p':
if (UNLIKELY(options->output_file_prefix)) {
break;
}
options->output_file_prefix = optarg ? optarg : "#!/usr/bin/env u6a\n";
break;
case 'S':
options->codegen.dump_mnemonics = true;
break;
case 'v':
verbose = true;
break;
case 's':
syntax_only = true;
break;
case 'H':
printf("Usage: u6ac [options] source-file\n\n"
"Bytecode compiler for the Unlambda programming language.\n"
"See \"man u6ac\" for details.\n");
options->print_only = true;
break;
case 'V':
printf("%d.%d.%d\n", U6A_VER_MAJOR, U6A_VER_MINOR, U6A_VER_PATCH);
options->print_only = true;
break;
case '?':
return false;
default:
U6A_NOT_REACHED();
}
}
if (UNLIKELY(options->print_only)) {
return true;
}
// Input file
if (UNLIKELY(optind == argc)) {
u6a_err_no_input_file(err_toplevel);
return false;
}
options->input_file_name = argv[optind];
uint32_t file_name_size = strlen(options->input_file_name);
if (file_name_size == 1 && options->input_file_name[0] == '-') {
options->input_file = stdin;
options->input_file_name = "STDIN";
} else if (UNLIKELY(file_name_size > PATH_MAX - 1)) {
u6a_err_path_too_long(err_toplevel, PATH_MAX - 1, file_name_size);
return false;
} else {
options->input_file = fopen(options->input_file_name, "r");
if (options->input_file == NULL) {
u6a_err_cannot_open_file(err_toplevel, options->input_file_name);
return false;
}
}
// Output file
if (syntax_only) {
if (UNLIKELY(options->codegen.file_name)) {
options->codegen.file_name = NULL;
}
} else {
if (options->codegen.file_name == NULL) {
if (options->input_file == stdin) {
goto write_to_stdout;
} else {
if (UNLIKELY(file_name_size + 8 > PATH_MAX - 1)) {
u6a_err_path_too_long(err_toplevel, PATH_MAX - 1, file_name_size + 8);
return false;
}
options->codegen.file_name = malloc((file_name_size + 9) * sizeof(char));
strcpy(options->codegen.file_name, options->input_file_name);
strcpy(options->codegen.file_name + file_name_size,
options->codegen.dump_mnemonics ? ".bc.dump\0" : ".bc\0");
}
} else if (strlen(options->codegen.file_name) == 1 && options->codegen.file_name[0] == '-') {
write_to_stdout:
if (verbose) {
u6a_err_custom(err_toplevel, "cannot write to STDOUT on verbose mode");
return false;
}
options->codegen.output_stream = stdout;
options->codegen.file_name = "STDOUT";
}
if (options->codegen.output_stream == NULL) {
options->codegen.output_stream = fopen(options->codegen.file_name, "w");
if (options->codegen.output_stream == NULL) {
u6a_err_cannot_open_file(err_toplevel, options->codegen.file_name);
return false;
}
}
}
if (optimize_level > '0') {
options->codegen.optimize_const = true;
}
u6a_logging_verbose(verbose);
return true;
}
int
main(int argc, char** argv) {
struct arg_options options = { 0 };
struct u6a_token* token_arr = 0;
struct u6a_ast_node* ast_arr = 0;
int exit_code = 0;
u6a_logging_init(argv[0]);
if (UNLIKELY(!process_options(&options, argc, argv))) {
exit_code = EC_ERR_OPTIONS;
goto terminate;
}
if (UNLIKELY(options.print_only)) {
goto terminate;
}
u6a_info_verbose(info_toplevel, "reading source code from %s", options.input_file_name);
uint32_t token_len;
if (UNLIKELY(!u6a_lex(options.input_file, &token_arr, &token_len))) {
exit_code = EC_ERR_LEX;
goto terminate;
}
if (UNLIKELY(!u6a_parse(token_arr, token_len, &ast_arr))) {
exit_code = EC_ERR_PARSE;
goto terminate;
}
if (UNLIKELY(options.codegen.output_stream == NULL)) {
goto terminate;
}
u6a_info_verbose(info_toplevel, "writing to %s", options.codegen.file_name);
if (UNLIKELY(!u6a_write_prefix(&options.codegen, options.output_file_prefix))) {
exit_code = EC_ERR_CODEGEN;
goto terminate;
}
if (UNLIKELY(!u6a_codegen(&options.codegen, ast_arr, token_len + 2))) {
exit_code = EC_ERR_CODEGEN;
goto terminate;
}
terminate:
arg_options_destroy(&options, exit_code);
free(token_arr);
free(ast_arr);
return exit_code;
}