/* * Compiler command-line interface. * Copyright (c) 2023-2024, Quinn Stephens and the OSMORA team. * Provided under the BSD 3-Clause license. */ #include #include #include #include #include #include "debug.h" #include "hashmap.h" #include "parser/type.h" #include "parser.h" #define HASHMAP_ROWS 16 #define OID_O 0x00 struct option { char *name; char *value; }; static char *argv0; static char *in_fname; static struct option options[] = { [OID_O] = { "o", NULL } }; __attribute__((noreturn)) static void cmd_fatal(const char *fmt, ...) { va_list ap; fprintf(stderr, "\033[1m%s: \033[31mfatal error:\033[0m ", argv0); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "compilation terminated.\n"); exit(EXIT_FAILURE); } static void cmd_error(const char *fmt, ...) { va_list ap; fprintf(stderr, "\033[1m%s: \033[31merror:\033[0m ", argv0); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); } static char * load_text_file(char *filename) { FILE *fp; size_t size; char *buf; fp = fopen(filename, "rb"); if (fp == NULL) { cmd_error("%s: %s\n", filename, strerror(errno)); return NULL; } /* Get file size */ fseek(fp, 0, SEEK_END); size = (size_t)ftell(fp); fseek(fp, 0, SEEK_SET); if (size < sizeof(char)) { cmd_error("%s: empty file\n", filename); fclose(fp); return NULL; } /* Read entire file */ buf = malloc(size + sizeof(char)); if (fread(buf, 1, size, fp) != size) { cmd_error("%s: %s\n", filename, strerror(errno)); free(buf); fclose(fp); return NULL; } fclose(fp); /* Terminate string */ buf[size] = '\0'; return buf; } static struct option * find_option(char *name) { for (int o = 0; o < (int)(sizeof(options) / sizeof(struct option)); o++) { if (strcmp(options[o].name, name) == 0) { return &options[0]; } } return NULL; } static void set_out_fname(void) { size_t len; char *buf; char *dot; /* Skip if set already */ if (options[OID_O].value != NULL) { return; } len = strlen(in_fname); buf = malloc(len + 4); strcpy(buf, in_fname); /* Append extension after last '.' or at end */ dot = strrchr(buf, '.'); if (dot == NULL) { strcpy(buf + len, ".txt"); } else { strcpy(buf + (dot - buf), ".txt"); } options[OID_O].value = buf; } static void parse_args(int argc, char **argv) { struct option *opt; in_fname = NULL; for (int a = 1; a < argc; a++) { /* Set input filename */ if (argv[a][0] != '-') { if (in_fname != NULL) { cmd_error("multiple input files specified\n"); } else { in_fname = argv[a]; } continue; } /* Identify option */ opt = find_option(argv[a] + 1); if (opt == NULL) { cmd_error("unrecognized command-line option '%s'\n", argv[a]); continue; } /* Set option value */ a++; if (a >= argc) { cmd_error("missing value after '%s'\n", argv[a - 1]); } opt->value = argv[a]; } if (in_fname == NULL) { cmd_fatal("no input files\n"); } set_out_fname(); } static void print_ptrs(int n_ptrs) { for (int n = 0; n < n_ptrs; n++) { putchar('*'); } } static void print_alias(struct type *typ) { printf("alias (%lu bytes, %d pointer(s));\n", typ->size, typ->n_ptrs); } static void print_enum(struct type *typ) { struct enum_member *mem; printf("enum {\n"); for (size_t r = 0; r < typ->members.n_rows; r++) { mem = (struct enum_member*)typ->members.rows[r].head; if (mem == (struct enum_member*)&typ->members.rows[r]) { continue; } do { printf(" %.*s,\n", (int)mem->name_len, mem->name); mem = (struct enum_member*)mem->hashmap_entry.list_entry.next; } while (mem != (struct enum_member*)&typ->members.rows[r]); } printf("}\n"); } static void print_struct(struct type *typ) { struct struct_member *mem; printf("struct (%lu bytes) {\n", typ->size); for (size_t r = 0; r < typ->members.n_rows; r++) { mem = (struct struct_member*)typ->members.rows[r].head; if (mem == (struct struct_member*)&typ->members.rows[r]) { continue; } do { printf(" %.*s", (int)mem->typ->name_len, mem->typ->name); print_ptrs(mem->n_ptrs); printf(" %.*s;\n", (int)mem->name_len, mem->name); mem = (struct struct_member*)mem->hashmap_entry.list_entry.next; } while (mem != (struct struct_member*)&typ->members.rows[r]); } printf("}\n"); } static void print_type(struct type *typ) { printf("type %.*s: ", (int)typ->name_len, typ->name); switch (typ->kind) { case TYK_ALIAS: print_alias(typ); break; case TYK_ENUM: print_enum(typ); break; case TYK_STRUCT: print_struct(typ); break; default: printf("(unknown);\n"); break; } } int main(int argc, char **argv) { char *input; struct parser parser; struct list *types_rows, *procs_rows; struct hashmap types, procs; struct type *typ; argv0 = argv[0]; if (argc < 2) { cmd_fatal("no input files\n"); exit(EXIT_FAILURE); } /* Parse command-line arguments */ parse_args(argc, argv); /* Load input file */ input = load_text_file(in_fname); if (input == NULL) { exit(EXIT_FAILURE); } /* Initialize hashmaps */ types_rows = malloc(HASHMAP_ROWS * sizeof(struct list)); types.rows = types_rows; types.n_rows = HASHMAP_ROWS; hashmap_init(&types); procs_rows = malloc(HASHMAP_ROWS * sizeof(struct list)); procs.rows = procs_rows; procs.n_rows = HASHMAP_ROWS; hashmap_init(&procs); /* Parse input */ parser.tok.fname = in_fname; parser.types = &types; parser.procs = &procs; parser_init(&parser, input); parser_parse(&parser); /* Print parsed types */ for (size_t r = 0; r < types.n_rows; r++) { typ = (struct type*)types.rows[r].head; if (typ == (struct type*)&types.rows[r]) { continue; } do { print_type(typ); typ = (struct type*)typ->hashmap_entry.list_entry.next; } while (typ != (struct type*)&types.rows[r]); } free(procs_rows); free(types_rows); free(input); return 0; }