rutile/rutilec.c
2025-01-21 14:08:46 -03:00

199 lines
5.3 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "compiler/pre.h"
#include "compiler/lex.h"
#include "compiler/parse.h"
#include "compiler/sema.h"
#include "compiler/state.h"
#include "compiler/codegen.h"
#include "compiler/messages.h"
#include "compiler/libs/optparse.h"
#include "compiler/libs/stb_ds.h"
#ifndef GIT_HASH
# define GIT_HASH "<no hash>"
#endif
#ifndef BUG_REPORT_URL
# define BUG_REPORT_URL "https://codeberg.org/tocariimaa/rutile"
#endif
#ifndef TARGET_EXE_EXT /* without prefix dot! */
# define TARGET_EXE_EXT Sl("")
#endif
static const char *HelpMessage = \
"Summary of common options:\n" \
" -c\tCompile only. Don't link, output an object file instead.\n" \
" -d\tDefine a constant with the specified value.\n" \
" -h\tPrint this help message.\n" \
" -o\tSet output file name of the executable/object file.\n" \
" -v\tPrint the version of this compiler, plus other relevant information.\n" \
" -S\tEmit intermediate code.\n" \
" -R\tSet the code generation mode, 'release' for an optimized build,\n" \
" \t'debug' for a debug build.\n" \
;
/* Creates the output binary file name, changing the extension to the current platform
* executable file extension, or it simply removes the original extension if the platform
* has no binary extension, (i.e UNIX-likes OSes). */
static Str
make_binary_filename(Compiler *cm, Str src_filename, const Str exe_ext)
{
bool exe_has_ext = exe_ext.len > 0;
const size_t ss = src_filename.len;
Assert(ss != 0);
char *buf = malloc(ss + 2 + (exe_has_ext ? exe_ext.len : 0));
memcpy(buf, src_filename.s, ss);
buf[ss] = '\0';
char *p = buf + (ss - 1);
while (p != buf && *p != '.') /* Search for the first '.' backwards */
--p;
/* No extension in filename, egde case really */
if (p == buf) {
if (!exe_has_ext)
fatal(cm, nil, "output file name required in this case (host OS binary format lacks extension)");
/* append extension then */
p = buf + ss;
*p = '.';
}
if (exe_has_ext) {
memcpy(++p, exe_ext.s, exe_ext.len);
p += 3;
}
*p = '\0';
return Str_from_c(buf);
}
static enum CodegenBackends
backend_from_str(Compiler *cm, Str s)
{
if (s.len == 1 && (s.s[0] == 'c' || s.s[0] == 'C'))
return CgBackendC;
else if (Str_equal(s, Sl("gcc")))
return CgBackendLibGccJit;
fatal(cm, nil, "unknown backend '%s'", s.s);
unreachable();
}
static Str
cli_boilerplate(char **argv, Compiler *cm)
{
const struct optparse_long longopts[] = {
{"backend", 'b', OPTPARSE_REQUIRED},
{"compile-only", 'c', OPTPARSE_NONE},
{"define", 'd', OPTPARSE_REQUIRED},
{"max-errors", 'E', OPTPARSE_REQUIRED},
{"release", 'R', OPTPARSE_REQUIRED},
{"emit-ir", 'S', OPTPARSE_OPTIONAL},
{"exe", 'o', OPTPARSE_REQUIRED},
{"version", 'v', OPTPARSE_NONE},
{"help", 'h', OPTPARSE_NONE},
{0},
};
const char *no_fun_env = getenv("NO_COLOR");
cm->opts.color = isatty(STDERR_FILENO) && !(no_fun_env != nil && *no_fun_env != '\0');
struct optparse opts;
optparse_init(&opts, argv);
i8 opt;
while ((opt = optparse_long(&opts, longopts, nil)) != -1) {
switch (opt) {
case 'b':
cm->opts.backend = backend_from_str(cm, Str_from_c(opts.optarg));
break;
case 'c':
cm->opts.compile_only = true;
break;
case 'd':
trace("define: %s\n", opts.optarg);
arrput(cm->opts.defines, Str_from_c(opts.optarg));
break;
case 'h':
printf("Usage: %s [options...] files...\n\n%s\n", *argv, HelpMessage);
exit(0);
case 'E':
cm->opts.max_errors = atoi(opts.optarg); /* XXX: atoi LOL */
break;
case 'R':
trace("release: %s\n", opts.optarg);
cm->opts.release_mode = Str_from_c(opts.optarg);
break;
case 'S':
break;
case 'o':
cm->opts.exe_out = Str_from_c(opts.optarg);
break;
case 'v':
printf("Rutile compiler v0.0.1\n");
printf("git commit: %s\nReport bugs here: %s\n", GIT_HASH, BUG_REPORT_URL);
exit(0);
case '?':
fatal(cm, nil, "%s: %s", *argv, opts.errmsg);
}
}
const char *src_filename = optparse_arg(&opts);
if (src_filename == nil)
fatal(cm, nil, "no input files specified");
return Str_from_c(src_filename);
}
int
main(int argc, char **argv)
{
(void)argc;
Compiler cm = {
.opts = {
.backend = CgBackendC,
.max_errors = 20,
}
};
Str src_filename = cli_boilerplate(argv, &cm);
FILE *src_in = nil;
if (src_filename.s[0] == '-' && src_filename.s[1] == '\0') {
src_in = stdin;
src_filename = Sl("<stdin>");
} else {
if ((src_in = fopen((char *)src_filename.s, "rb")) == nil) {
fatal(&cm, nil, "can't open: %s", src_filename.s);
}
}
cm.current_filename = src_filename;
if (cm.opts.exe_out.len == 0)
cm.opts.exe_out = make_binary_filename(&cm, src_filename, TARGET_EXE_EXT);
if (Str_equal(cm.opts.exe_out, cm.current_filename)) {
fatal(&cm, nil, "input source file and output file are the same");
}
/* Compiler pipeline */
LexState *ls = lex_new(&cm, src_in, src_filename, 4);
ParserState *ps = parse_new(&cm, ls);
SemaCtx *ss = sema_new(&cm);
Ast *program = parse(ps);
if (!ps->ok)
goto err;
sema(ss, program);
if (!ss->ok)
goto err;
CodegenCtx *cgctx = codegen_new(&cm, cm.opts.backend);
codegen(cgctx, program);
codegen_destroy(cgctx);
err:
sema_destroy(ss);
parse_destroy(ps);
lex_destroy(ls);
fclose(src_in);
return 0;
}