#include #include #include #include #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 "" #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(""); } 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; }