200 lines
5.2 KiB
C
200 lines
5.2 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "pre.h"
|
|
#include "lex.h"
|
|
#include "parse.h"
|
|
#include "sema.h"
|
|
#include "state.h"
|
|
#include "codegen.h"
|
|
#include "messages.h"
|
|
|
|
#include "libs/optparse.h"
|
|
#include "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;
|
|
}
|