/* Copyright (C) 2019, 2020 Leo Tenenbaum. This file is part of toc. toc is distributed under version 3 of the GNU General Public License, without any warranty whatsoever. You should have received a copy of the GNU General Public License along with toc. If not, see . */ /* see development.md for development information */ /* @TODO: if we do #include "foo.toc", bar; and foo.toc fails, bar should be declared as TYPE_UNKNOWN (right now it's undeclared) fix #foreign not at global scope - right now the cgen'd definition doesn't use the proper type figure out how printf is gonna work improve type_to_str: Foo ::= struct(t::Type) {} type_to_str(Foo(int)) switch - #fallthrough enums unions bitwise operations --- #compile_only for functions only used at compile time warn about x : u8 = 1283; switch to / add as an alternative: libffi - better yet, inline assembly don't bother generating ret_ if nothing's deferred X ::= newtype(int); or something any odd number of "s for a string give each warning a number; #no_warn(warning), #no_warn_throughout_this_file(warning) test various parse errors; see if they can be improved, e.g. if else { 3; } use point #except x; optional -Wshadow format errors so that vim/emacs can jump to them show include stack--especially for redeclarations with #include #force stuff like __builtin_sqrt --- make sure that floating point literals are as exact as possible have some way of doing Infinity and s/qNaN (you can have them be in std/math.toc) once you have a bunch of test code: - analyze memory usage by secretly passing __FILE__, __LINE__ to allocr_m/c/realloc - try making more Expression members pointers - should in_decls be off the allocator? maybe macros are just inline functions - passing untyped expressions to macros @OPTIM: figure out how much stack space each block uses (make sure you only do the calculation one time for each block), and allocate it at the start of the block, potentially with alloca (reduce number of mallocs) */ #if defined __unix__ || (defined __APPLE__ && defined __MACH__) #define _POSIX_C_SOURCE 200112L #include #define UNISTD_AVAILABLE 1 #endif #include "toc.c" #if defined TOC_DEBUG && defined __GNU_LIBRARY__ && defined UNISTD_AVAILABLE && !defined BACKTRACE #define BACKTRACE 1 #endif #if BACKTRACE #include #include static char *program_name; static void signal_handler(int num) { switch (num) { case SIGABRT: fprintf(stderr, "Aborted.\n"); break; case SIGSEGV: fprintf(stderr, "Segmentation fault.\n"); break; case SIGFPE: fprintf(stderr, "Floating point exception.\n"); break; case SIGINT: fprintf(stderr, "Interrupted.\n"); break; case SIGTERM: fprintf(stderr, "Terminated.\n"); break; case SIGILL: fprintf(stderr, "Illegal instruction.\n"); break; default: fprintf(stderr, "Terminated for unknown reason (signal %d).\n", num); break; } fprintf(stderr, "Stack trace:\n"); static void *addrs[300]; int naddrs = (int)(sizeof addrs / sizeof *addrs); naddrs = backtrace(addrs, naddrs); /* char **syms = backtrace_symbols(addrs, naddrs); */ char command[2048] = "addr2line -p -f -a -e "; strcat(command, program_name); strcat(command, " "); for (int i = 4; i < naddrs; ++i) { snprintf(command + strlen(command), sizeof command - strlen(command), "%p ", addrs[i]); } system(command); /* free(syms); */ signal(SIGABRT, SIG_DFL); abort(); } #endif static const char *replace_extension(Allocator *a, const char *filename, const char *new_extension, const char *dflt) { char *new_filename = allocr_malloc(a, strlen(filename) + strlen(new_extension) + 1); strcpy(new_filename, filename); char *dot = strchr(new_filename, '.'); if (dot) { strcpy(dot, new_extension); return new_filename; } else { return dflt; } } #ifdef MALLOC_TRACKER static int compare_entries(const void *av, const void *bv) { const AllocTrackerEntry *a = av, *b = bv; if (a->amount > b->amount) return -1; else if (b->amount > a->amount) return +1; else return 0; } #endif int main(int argc, char **argv) { #if BACKTRACE program_name = argv[0]; signal(SIGABRT, signal_handler); signal(SIGSEGV, signal_handler); signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); signal(SIGILL, signal_handler); signal(SIGFPE, signal_handler); #endif #if RUN_TESTS printf("running tests...\n"); test_all(); #endif const char *in_filename = NULL; const char *out_filename = NULL, *c_filename = NULL; const char *c_compiler = "cc"; char *env_CC = getenv("CC"); if (env_CC) c_compiler = env_CC; bool verbose = false; bool debug_build = true; bool compile_c = true; ErrCtx err_ctx = {0}; err_ctx.enabled = true; bool default_color_enabled; #if UNISTD_AVAILABLE #if defined _POSIX_VERSION && _POSIX_VERSION >= 200112L /* isatty available */ default_color_enabled = (bool)isatty(2); /* is /dev/stderr a tty? */ #else default_color_enabled = false; /* old posix version */ #endif #else default_color_enabled = false; /* probably windows */ #endif err_ctx.color_enabled = default_color_enabled; for (int i = 1; i < argc; ++i) { char *arg = argv[i]; if (streq(arg, "-no-color")) { err_ctx.color_enabled = false; } else if (streq(arg, "-color")) { err_ctx.color_enabled = true; } else if (streq(arg, "-o")) { if (i == argc-1) { fprintf(stderr, "-o cannot be the last argument to toc.\n"); return EXIT_FAILURE; } out_filename = argv[i+1]; ++i; } else if (streq(arg, "-O")) { if (i == argc-1) { fprintf(stderr, "-O cannot be the last argument to toc.\n"); return EXIT_FAILURE; } c_filename = argv[i+1]; ++i; } else if (streq(arg, "-v") || streq(arg, "-verbose")) { printf("Verbose mode enabled\n"); verbose = true; } else if (streq(arg, "-d") || streq(arg, "-debug")) { debug_build = true; } else if (streq(arg, "-r") || streq(arg, "-release")) { debug_build = false; } else if (streq(arg, "-cc")) { if (i == argc-1) { fprintf(stderr, "-cc cannot be the last argument to toc.\n"); return EXIT_FAILURE; } c_compiler = argv[i+1]; ++i; } else if (streq(arg, "-c")) { compile_c = false; } else { if (arg[0] == '-') { fprintf(stderr, "Unrecognized option: %s.\n", argv[i]); return EXIT_FAILURE; } else { in_filename = arg; } } } if (!in_filename) { #ifdef TOC_DEBUG in_filename = "test.toc"; #else fprintf(stderr, "Please specify an input file.\n"); return EXIT_FAILURE; #endif } Allocator main_allocr; allocr_create(&main_allocr); if (c_filename && out_filename && !compile_c) { fprintf(stderr, "You can't set both -o and -O unless you're compiling the outputted C code."); return EXIT_FAILURE; } if (!c_filename) { if (out_filename && !compile_c) { c_filename = out_filename; } else { c_filename = replace_extension(&main_allocr, in_filename, ".c", "out.c"); } } if (!out_filename) { out_filename = replace_extension(&main_allocr, in_filename, "", "a.out"); } File file = {0}; file.filename = in_filename; Location file_where = {0}; file_where.file = &file; file.ctx = &err_ctx; if (verbose) printf("Reading contents of %s...\n", in_filename); char *contents = read_file_contents(&main_allocr, in_filename, file_where); if (!contents) return EXIT_FAILURE; Identifiers globals; idents_create(&globals, &main_allocr, NULL); Tokenizer t; file.contents = contents; if (verbose) printf("Tokenizing...\n"); tokr_create(&t, &err_ctx, &main_allocr); if (!tokenize_file(&t, &file)) { err_text_important(&err_ctx, "Errors occured during preprocessing.\n"); allocr_free_all(&main_allocr); return EXIT_FAILURE; } if (verbose) { printf("Tokens:\n"); arr_foreach(t.tokens, Token, token) { if (token != t.tokens) printf(" "); fprint_token(stdout, token); } printf("\n-----\n"); } if (verbose) printf("Parsing...\n"); GlobalCtx global_ctx = {0}; str_hash_table_create(&global_ctx.included_files, sizeof(IncludedFile), &main_allocr); global_ctx.main_file = &file; global_ctx.err_ctx = &err_ctx; global_ctx.debug_build = debug_build; Parser p; parser_create(&p, &globals, &t, &main_allocr, &global_ctx); ParsedFile f; if (!parse_file(&p, &f)) { err_text_important(&err_ctx, "Errors occured during parsing.\n"); allocr_free_all(&main_allocr); return EXIT_FAILURE; } if (verbose) { printf("Parsed file:\n"); fprint_parsed_file(stdout, &f); printf("\n\n-----\n\n"); } if (verbose) printf("Determining types...\n"); Typer tr; Evaluator ev; evalr_create(&ev, &tr, &main_allocr); typer_create(&tr, &ev, &main_allocr, &globals, &global_ctx); if (!types_file(&tr, &f)) { err_text_important(&err_ctx, "Errors occured while determining types.\n"); allocr_free_all(&main_allocr); return EXIT_FAILURE; } if (verbose) { printf("Typed file:\n"); fprint_parsed_file(stdout, &f); printf("\n\n-----\n\n"); } if (verbose) printf("Opening output file %s...\n", c_filename); FILE *out = fopen(c_filename, "w"); if (!out) { err_text_important(&err_ctx, "Could not open output file: "); err_fprint(&err_ctx, "%s\n", c_filename); allocr_free_all(&main_allocr); return EXIT_FAILURE; } if (verbose) printf("Generating C code...\n"); CGenerator g; cgen_create(&g, out, &globals, &main_allocr); cgen_file(&g, &f, &tr); fclose(out); if (compile_c) { if (verbose) printf("Compiling C code...\n"); char cmd[2048] = {0}; char const *cflags = debug_build ? "-O0 -g" : "-O3 -s"; snprintf(cmd, sizeof cmd, "%s -w %s %s -o %s", c_compiler, cflags, c_filename, out_filename); int ret = system(cmd); if (ret) { fprintf(stderr, "Uh oh... C compiler failed...\n"); return EXIT_FAILURE; } } if (verbose) printf("Cleaning up...\n"); allocr_free_all(&main_allocr); #ifdef MALLOC_TRACKER qsort(eval_c, sizeof eval_c / sizeof *eval_c, sizeof *eval_c, compare_entries); size_t total = 0; for (size_t i = 0; i < sizeof eval_c / sizeof *eval_c; ++i) { total += eval_c[i].amount; } printf("A total of %lu bytes were allocated by eval.c.\n", total); for (size_t i = 0; i < 10; ++i) { printf("Line %4d has %10lu bytes over %9lu allocations\n", eval_c[i].line, (unsigned long)eval_c[i].amount, (unsigned long)eval_c[i].nallocs); } #endif return 0; }