/* SPDX-License-Identifier: copyleft-next-0.3.1 */ /* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ /** * @file compiler.c * * Top level compiler implementation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * Read whole file into a buffer and return pointer to buffer. * Possibly kind of silly to have both \p file and \p f. * Apparently there's no standardized way to get the file name of a * file pointer. * * @param file Name of file to read. * @param f File pointer. * @return Pointer to buffer with file contents. */ static char *read_file(const char *file, FILE *f) { fseek(f, 0, SEEK_END); /** @todo check how well standardized this actually is */ long s = ftell(f); if (s == LONG_MAX) { error("%s might be a directory", file); return NULL; } fseek(f, 0, SEEK_SET); char *buf = malloc((size_t)(s + 1)); if (!buf) return NULL; fread(buf, (size_t)(s + 1), 1, f); /* remember terminating null */ buf[s] = 0; return buf; } /** * Helper for process_file(), actually processes the file * after process_file() has done path lookups and working directory * changes and whatnot. * * @param parent Parent file context. \c NULL if root file. * @param public \c 1 if file is being imported publicly, \c 0 otherwise. * @param file File name to process. * @return \c 0 if processing was succesful, non-zero value otherwise. */ static int process(struct scope **parent, int public, const char *file) { FILE *f = fopen(file, "rb"); if (!f) { error("failed opening %s: %s\n", file, strerror(errno)); return -1; } const char *buf = read_file(file, f); fclose(f); if (!buf) return -1; struct parser *p = create_parser(); if (!p) { free((void *)buf); return -1; } parse(p, file, buf); struct ast *tree = p->tree; bool failed = p->failed; destroy_parser(p); if (failed) { free((void*)buf); return -1; } ast_dump_list(0, tree); struct scope *scope = create_scope(); if (!scope) { free((void *)buf); return -1; } if (public) scope_set_flags(scope, SCOPE_PUBLIC); scope->fctx.fbuf = buf; scope->fctx.fname = strdup(file); scope_set_flags(scope, SCOPE_FILE); if (*parent) scope_add_scope(*parent, scope); else *parent = scope; if (analyze_root(scope, tree)) return -1; return 0; } #define MAP_KEY char * #define MAP_TYPE struct scope * #define MAP_CMP(a, b) strcmp((a), (b)) #define MAP_NAME scopes #include "ek/map.h" static int copy_scope(struct scope *to, struct scope *from) { /** @todo handle duplicates */ foreach_visible(n, from->symbols) { struct ast *def = n->node; if (!ast_flags(def, AST_FLAG_PUBLIC)) continue; switch (def->k) { case AST_PROC_DEF: if (scope_add_proc(to, def)) return -1; break; case AST_VAR_DEF: if (scope_add_var(to, def)) return -1; break; default: abort(); } } return 0; } /* ugly global for now */ static struct scopes scopes; static void destroy_scopes() { if (scopes_len(&scopes)) foreach(scopes, n, &scopes) { free(n->key); } scopes_destroy(&scopes); } int process_file(struct scope **scope, int public, const char *file) { int res = -1; /** todo report failure allocating stuff maybe? */ struct res *r = res_create(); if (!r) return -1; const char *base = ek_basename(file); res_add(r, (void *)base); if (!base) goto out; const char *dir = ek_dirname(file); res_add(r, (void *)dir); if (!dir) goto out; /* TODO: iterate through include paths? */ const char *cwd = ek_cwdname(); res_add(r, (void *)cwd); if (!cwd) goto out; if (*dir != 0 && chdir(dir)) { error("couldn't change to directory %s: %s", dir, strerror( errno)); goto out; } char *real = realpath(base, NULL); assert(real); struct scope **exists = scopes_find(&scopes, real); if (exists) { if (copy_scope(*scope, *exists)) goto out; } else { if (process(scope, public, base)) goto out; scopes_insert(&scopes, real, *scope); } if (chdir(cwd)) { error("couldn't change back to directory %s: %s\n", cwd, strerror( errno)); goto out; } res = 0; out: res_destroy(r); return res; } int compile(const char *input) { scopes = scopes_create(); int ret = -1; struct scope *root = NULL; if (process_file(&root, 0, input)) { destroy_scope(root); destroy_allocs(); destroy_scopes(); error("compilation of %s stopped due to errors", input); return ret; } if ((ret = lower(root))) { destroy_scope(root); destroy_allocs(); destroy_scopes(); error("compilation of %s stopped due to errors", input); return ret; } destroy_scope(root); destroy_allocs(); destroy_scopes(); return 0; }