diff options
author | Kimplul <kimi.h.kuparinen@gmail.com> | 2024-12-03 22:04:38 +0200 |
---|---|---|
committer | Kimplul <kimi.h.kuparinen@gmail.com> | 2024-12-03 22:04:38 +0200 |
commit | 2253da61e9b3dd5408bed182ea08e5270156c17e (patch) | |
tree | 298bb06e681ec5366faa539906cae6e805fe5862 /src | |
download | fwd-2253da61e9b3dd5408bed182ea08e5270156c17e.tar.gz fwd-2253da61e9b3dd5408bed182ea08e5270156c17e.zip |
initial commit
+ Lots of code copied from ek, so didn't have to start from scratch, but
might mean there are some quirks here and there that made sense in ek
but not necessarily here.
Diffstat (limited to 'src')
-rw-r--r-- | src/analyze.c | 15 | ||||
-rw-r--r-- | src/ast.c | 483 | ||||
-rw-r--r-- | src/compiler.c | 138 | ||||
-rw-r--r-- | src/debug.c | 193 | ||||
-rw-r--r-- | src/lexer.l | 166 | ||||
-rw-r--r-- | src/lower.c | 347 | ||||
-rw-r--r-- | src/main.c | 75 | ||||
-rw-r--r-- | src/parser.y | 460 | ||||
-rw-r--r-- | src/scope.c | 217 | ||||
-rw-r--r-- | src/source.mk | 2 | ||||
-rw-r--r-- | src/vec.c | 68 |
11 files changed, 2164 insertions, 0 deletions
diff --git a/src/analyze.c b/src/analyze.c new file mode 100644 index 0000000..6efd60f --- /dev/null +++ b/src/analyze.c @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +#include <fwd/analyze.h> +#include <assert.h> + +int analyze_root(struct scope *scope, struct ast *root) +{ + foreach_node(node, root) { + assert(node->k == AST_PROC_DEF); + scope_add_proc(scope, node); + } + + return 0; +} diff --git a/src/ast.c b/src/ast.c new file mode 100644 index 0000000..102dff5 --- /dev/null +++ b/src/ast.c @@ -0,0 +1,483 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +/** + * @file ast.c + * + * Abstract syntax tree handling implementations. + */ + +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> +#include <assert.h> +#include <math.h> + +#include <fwd/ast.h> +#include <fwd/vec.h> +#include <fwd/scope.h> + +static struct vec nodes = {0}; +static struct vec types = {0}; + +static void destroy_ast_node(struct ast *n) +{ + if (!n) + return; + + if (n->s) + free(n->s); + + free(n); +} + +static void destroy_type(struct type *n) +{ + if (!n) + return; + + if (n->id) + free(n->id); + + free(n); +} + +static void destroy_ast_nodes() +{ + foreach_vec(ni, nodes) { + struct ast *n = vect_at(struct ast *, nodes, ni); + destroy_ast_node(n); + } + + vec_destroy(&nodes); +} + +static void destroy_types() +{ + foreach_vec(ti, types) { + struct type *t = vect_at(struct type *, types, ti); + destroy_type(t); + } + + vec_destroy(&types); +} + +void destroy_allocs() +{ + destroy_ast_nodes(); + destroy_types(); +} + +static struct ast *create_empty_ast() +{ + if (vec_uninit(nodes)) { + nodes = vec_create(sizeof(struct ast *)); + } + + struct ast *n = calloc(1, sizeof(struct ast)); + /* just to be safe */ + n->k = AST_EMPTY; + vect_append(struct ast *, nodes, &n); + return n; +} + +static struct type *create_empty_type() +{ + if (vec_uninit(types)) { + types = vec_create(sizeof(struct type *)); + } + + struct type *n = calloc(1, sizeof(struct type)); + vect_append(struct ast *, types, &n); + return n; +} + +struct ast *gen_ast(enum ast_kind kind, + struct ast *a0, + struct ast *a1, + struct ast *a2, + struct ast *a3, + struct type *t2, + char *s, + long long v, + double d, + struct src_loc loc) +{ + struct ast *n = create_empty_ast(); + n->k = kind; + n->a0 = a0; + n->a1 = a1; + n->a2 = a2; + n->a3 = a3; + n->t2 = t2; + n->s = s; + n->v = v; + n->d = d; + n->loc = loc; + return n; +} + +struct type *tgen_type(enum type_kind kind, + struct type *t0, + char *id, + struct src_loc loc) +{ + struct type *n = create_empty_type(); + n->k = kind; + n->t0 = t0; + n->id = id; + n->loc = loc; + return n; +} + +void ast_append(struct ast **list, struct ast *elem) +{ + struct ast *cur = *list; + if (!cur) { + *list = elem; + return; + } + + while (cur->n) + cur = cur->n; + + cur->n = elem; +} + +struct ast *ast_prepend(struct ast *list, struct ast *elem) +{ + elem->n = list; + return elem; +} + +static void dump(int depth, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + printf("//"); + for (int i = 0; i < depth; ++i) + printf(" "); + + vprintf(fmt, args); + + va_end(args); +} + +void ast_dump(int depth, struct ast *n) +{ + if (!n) { + dump(depth, "{NULL}\n"); + return; + } + +#define DUMP(x) case x: dump(depth, #x); break; + switch (n->k) { + DUMP(AST_CLOSURE); + DUMP(AST_LET); + DUMP(AST_INIT); + DUMP(AST_CALL); + DUMP(AST_PROC_DEF); + DUMP(AST_VAR_DEF); + DUMP(AST_DOT); + DUMP(AST_BLOCK); + DUMP(AST_ID); + DUMP(AST_EMPTY); + DUMP(AST_ADD); + DUMP(AST_SUB); + DUMP(AST_MUL); + DUMP(AST_DIV); + DUMP(AST_REM); + DUMP(AST_LAND); + DUMP(AST_LOR); + DUMP(AST_LSHIFT); + DUMP(AST_RSHIFT); + DUMP(AST_LT); + DUMP(AST_GT); + DUMP(AST_LE); + DUMP(AST_GE); + DUMP(AST_NE); + DUMP(AST_EQ); + DUMP(AST_NEG); + DUMP(AST_LNOT); + DUMP(AST_NOT); + DUMP(AST_CONST_INT); + DUMP(AST_CONST_CHAR); + DUMP(AST_CONST_BOOL); + DUMP(AST_CONST_FLOAT); + DUMP(AST_CONST_STR); + } +#undef DUMP + + depth++; + + if (n->scope) + printf(" {%llu}", (unsigned long long)n->scope->number); + + printf("\n"); + + if (n->s) + dump(depth, "%s\n", n->s); + + if (is_const(n)) + dump(depth, "%lli\n", n->v); + + if (n->a0) + ast_dump_list(depth, n->a0); + + if (n->a1) + ast_dump_list(depth, n->a1); + + if (n->a2) + ast_dump_list(depth, n->a2); + + if (n->a3) + ast_dump_list(depth, n->a3); +} + +void ast_dump_list(int depth, struct ast *root) +{ + if (!root) { + dump(depth, "{NULL}\n"); + return; + } + + foreach_node(n, root) { + ast_dump(depth, n); + } +} + +struct ast *clone_ast(struct ast *n) +{ + if (!n) + return NULL; + + assert(n->k); + struct ast *new = create_empty_ast(); + new->scope = n->scope; + new->loc = n->loc; + new->k = n->k; + new->v = n->v; + new->d = n->d; + + if (n->s) + new->s = strdup(n->s); + + if (n->a0) + new->a0 = clone_ast_list(n->a0); + + if (n->a1) + new->a1 = clone_ast_list(n->a1); + + if (n->a2) + new->a2 = clone_ast_list(n->a2); + + if (n->a3) + new->a3 = clone_ast_list(n->a3); + + return new; +} + +struct ast *clone_ast_list(struct ast *root) +{ + struct ast *n = root, *new_root = NULL, *prev = NULL; + while (n) { + struct ast *new = clone_ast(n); + + if (prev) prev->n = new; + else new_root = new; + + prev = new; + n = n->n; + } + + return new_root; +} + +int ast_visit(ast_callback_t before, ast_callback_t after, struct ast *n, + void *d) +{ + int ret = 0; + if (!n) + return ret; + + if (before && (ret = before(n, d))) + return ret; + + if (n->a0 && (ret = ast_visit_list(before, after, n->a0, d))) + return ret; + + if (n->a1 && (ret = ast_visit_list(before, after, n->a1, d))) + return ret; + + if (n->a2 && (ret = ast_visit_list(before, after, n->a2, d))) + return ret; + + if (n->a3 && (ret = ast_visit_list(before, after, n->a3, d))) + return ret; + + if (after && (ret = after(n, d))) + return ret; + + return ret; +} + +int ast_visit_list(ast_callback_t before, ast_callback_t after, struct ast *l, + void *d) +{ + int ret = 0; + foreach_node(n, l) { + if ((ret = ast_visit(before, after, n, d))) + return ret; + } + + return ret; +} + +size_t ast_list_len(struct ast *node) +{ + size_t count = 0; + while (node) { + count++; + node = node->n; + } + + return count; +} + +struct ast *ast_last(struct ast *list) +{ + if (!list) + return NULL; + + while (list->n) + list = list->n; + + return list; +} + +struct ast *ast_block_last(struct ast *block) +{ + struct ast *b = ast_last(block); + if (b && b->k == AST_BLOCK) + return ast_block_last(block_body(b)); + + return b; +} + +int same_id(char *id1, char *id2) +{ + return strcmp(id1, id2) == 0; +} + +int equiv_nodes(struct ast *n1, struct ast *n2) +{ + if (n1 && !n2) + return 0; + + if (!n1 && n2) + return 0; + + if (!n1 && !n2) + return 1; + + if (n1->k != n2->k) + return 0; + + if (n1->s && strcmp(n1->s, n2->s) != 0) + return 0; + + if (n1->a0 && !equiv_node_lists(n1->a0, n2->a0)) + return 0; + + if (n1->a1 && !equiv_node_lists(n1->a1, n2->a1)) + return 0; + + if (n1->a2 && !equiv_node_lists(n1->a2, n2->a2)) + return 0; + + if (n1->a3 && !equiv_node_lists(n1->a3, n2->a3)) + return 0; + + return 1; +} + +int equiv_node_lists(struct ast *c1, struct ast *c2) +{ + do { + if (!equiv_nodes(c1, c2)) + return 0; + + c1 = c1->n; + c2 = c2->n; + + } while (c1 && c2); + + return 1; +} + +size_t align3k(size_t o) +{ + size_t rem = o % 3; + if (rem) + o += 3 - rem; + + return o; +} + +struct ast *reverse_ast_list(struct ast *root) +{ + struct ast *new_root = NULL; + while (root) { + struct ast *next = root->n; + root->n = new_root; + new_root = root; + root = next; + } + + return new_root; +} + +struct type *reverse_type_list(struct type *root) +{ + struct type *new_root = NULL; + while (root) { + struct type *next = root->n; + root->n = new_root; + new_root = root; + root = next; + } + + return new_root; +} + +void fix_closures(struct ast *root) +{ + while (root) { + if (root->k != AST_CALL) { + root = root->n; + continue; + } + struct ast *arg = ast_last(call_args(root)); + if (!arg) { + root = root->n; + continue; + } + + if (arg->k != AST_CLOSURE) { + root = root->n; + continue; + } + + if (closure_body(arg) != NULL) { + root = root->n; + continue; + } + + struct ast *next = root->n; + struct ast *block = gen_block(next, next->loc); + closure_body(arg) = block; + root->n = NULL; + root = next; + } +} diff --git a/src/compiler.c b/src/compiler.c new file mode 100644 index 0000000..d18e767 --- /dev/null +++ b/src/compiler.c @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +/** + * @file compiler.c + * + * Top level compiler implementation. + */ + +#include <stdbool.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <stdlib.h> +#include <limits.h> +#include <errno.h> + +#include <fwd/compiler.h> +#include <fwd/analyze.h> +#include <fwd/parser.h> +#include <fwd/debug.h> +#include <fwd/scope.h> +#include <fwd/lower.h> + +/** + * 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, 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; + } + + scope->fctx.fbuf = buf; + scope->fctx.fname = strdup(file); + if (*parent) + scope_add_scope(*parent, scope); + else + *parent = scope; + + if (analyze_root(scope, tree)) + return -1; + + return 0; +} + +int compile(const char *input) { + int ret = -1; + struct scope *root = NULL; + if (process(&root, input)) { + destroy_scope(root); + destroy_allocs(); + error("processing of %s stopped due to errors", input); + return ret; + } + + if ((ret = lower(root))) { + destroy_scope(root); + destroy_allocs(); + error("lowering of %s stopped due to errors", input); + return ret; + } + + destroy_scope(root); + destroy_allocs(); + return 0; +} diff --git a/src/debug.c b/src/debug.c new file mode 100644 index 0000000..f47a022 --- /dev/null +++ b/src/debug.c @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +/** + * @file debug.c + * + * Debugging and information printing helper implementations. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <stdio.h> +#include <string.h> +#include <stdarg.h> + +#include <fwd/debug.h> + +/** + * Get string representation of issue_level. + * + * @param level issue_level to get string representation for. + * @return \p level as a string. + */ +const char *issue_level_str(enum issue_level level) +{ + switch (level) { + case SRC_INFO: return "info"; + case SRC_WARN: return "warn"; + case SRC_ERROR: return "error"; + } + + return "unknown"; +} + +/** + * Find position in file buffer where line number \p no + * starts. Lines are assumed to be one-indexed, with + * \p no = \c 0 and \p no = \c 1 both considered the first line. + * + * @param buf Buffer to look in. + * @param no Line number whose start to look for. + * @return Pointer to location in buffer where line number \p no + * starts. + */ +static const char *find_lineno(const char *buf, size_t no) +{ + if (no == 0 || no == 1) + return buf; + + char c; + while ((c = *buf)) { + buf++; + + if (c == '\n') + no--; + + if (no == 1) + break; + } + + return buf; +} + +/** + * Helper for printing out an issue. + * + * @param issue Issue context. + * @param fmt Format string. Follows standard printf() formatting. + * @param args Arguments for \p fmt. + */ +static void _issue(struct src_issue issue, const char *fmt, va_list args) +{ + /* get start and end of current line in buffer */ + const char *line_start = find_lineno(issue.fctx.fbuf, + (size_t)issue.loc.first_line); + const char *line_end = strchr(line_start, '\n'); + if (!line_end) + line_end = strchr(line_start, 0); + + const int line_len = (int)(line_end - line_start); + + fprintf(stderr, "%s:%i:%i: %s: ", issue.fctx.fname, + issue.loc.first_line, + issue.loc.first_col, + issue_level_str(issue.level)); + + vfprintf(stderr, fmt, args); + fputc('\n', stderr); + + int lineno_len = snprintf(NULL, 0, "%i", issue.loc.first_line); + fputc(' ', stderr); + fprintf(stderr, "%i | ", issue.loc.first_line); + + fprintf(stderr, "%.*s\n", line_len, line_start); + + for (int i = 0; i < lineno_len + 2; ++i) + fputc(' ', stderr); + + fprintf(stderr, "| "); + + for (int i = 0; i < issue.loc.first_col - 1; ++i) + fputc(line_start[i] == '\t' ? '\t' : ' ', stderr); + + for (int i = issue.loc.first_col; i < issue.loc.last_col; ++i) { + if (i == issue.loc.first_col) + fputc('^', stderr); + else + fputc('~', stderr); + } + + fputc('\n', stderr); +} + +void src_issue(struct src_issue issue, const char *err_msg, ...) +{ + va_list args; + va_start(args, err_msg); + _issue(issue, err_msg, args); + va_end(args); +} + +void semantic_error(struct file_ctx fctx, struct ast *node, + const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + struct src_issue issue; + issue.level = SRC_ERROR; + issue.loc = node->loc; + issue.fctx = fctx; + _issue(issue, fmt, args); + va_end(args); +} + +void loc_error(struct file_ctx fctx, struct src_loc loc, + const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + struct src_issue issue; + issue.level = SRC_ERROR; + issue.loc = loc; + issue.fctx = fctx; + _issue(issue, fmt, args); + va_end(args); +} + +void semantic_warn(struct file_ctx fctx, struct ast *node, const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + struct src_issue issue; + issue.level = SRC_WARN; + issue.loc = node->loc; + issue.fctx = fctx; + _issue(issue, fmt, args); + va_end(args); +} + +void semantic_info(struct file_ctx fctx, struct ast *node, const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + struct src_issue issue; + issue.level = SRC_INFO; + issue.loc = node->loc; + issue.fctx = fctx; + _issue(issue, fmt, args); + va_end(args); +} + +void internal_error(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + fprintf(stderr, "internal error: "); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); + va_end(args); +} + +void internal_warn(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + fprintf(stderr, "internal warning: "); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); + va_end(args); +} diff --git a/src/lexer.l b/src/lexer.l new file mode 100644 index 0000000..fe748e2 --- /dev/null +++ b/src/lexer.l @@ -0,0 +1,166 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +%option reentrant noyywrap nounput noinput nodefault +%{ +#define FROM_LEXER +#include <fwd/parser.h> +#include <fwd/debug.h> + +static void update_yylloc(struct parser *parser, YYLTYPE *lloc, const char *text) +{ + (void)parser; + + lloc->first_line = lloc->last_line; + lloc->first_column = lloc->last_column; + + for (size_t i = 0; text[i] != 0; ++i) { + if (text[i] == '\n') { + lloc->last_line++; + /* flex uses 1 based indexing */ + lloc->last_column = 1; + } else { + lloc->last_column++; + } + } +} + +#define YY_USER_ACTION update_yylloc(parser, yylloc, yytext); +%} + +HEX 0[xX][0-9a-fA-F]+ +DEC -?[0-9]+ +OCT 0[0-8]+ +BIN 0b[0-1]+ + +INT {HEX}|{DEC}|{OCT}|{BIN} + +HEXF [+-]?0[xX][0-9a-fA-F]+([pP][+-]?[0-9]+) +DECF [+-]?[0-9]+[.]([eE]?[+-]?[0-9]+)?[fF]? + +ID [_a-zA-Z][_a-zA-Z0-9]* +APPLY {ID}! + +STRING \"(\\.|[^"\\])*\" + +%x SC_COMMENT + +%% +"//".* {/* skip line comments */} + +"/*" {BEGIN(SC_COMMENT);} +<SC_COMMENT>{ + "/*" {++parser->comment_nesting;} + "*"+"/" { + if (parser->comment_nesting) + --parser->comment_nesting; + else + BEGIN(INITIAL); + } + + "*"+ {} + [^/*\n]+ {} + [/] {} + \n {} +} + +"::" {return SCOPE;} +"(" {return LPAREN;} +")" {return RPAREN;} +"{" {return LBRACE;} +"}" {return RBRACE;} +"[" {return LBRACKET;} +"]" {return RBRACKET;} +"." {return DOT;} +"," {return COMMA;} +";" {return SEMICOLON;} +":" {return COLON;} +"!" {return BANG;} + +"+" {return PLUS;} +"-" {return MINUS;} +"*" {return STAR;} +"/" {return DIV;} +"%" {return REM;} +"^" {return XOR;} + +"true" { + yylval->integer = 1; + return BOOL; +} + +"false" { + yylval->integer = 0; + return BOOL; +} + +'[^'\\]' { + /* regular character constant, 'a' */ + yylval->integer = yytext[1]; + return CHAR; +} + +'\\.' { + /* escaped character constant */ + yylval->integer = match_escape(yytext[2]); + return CHAR; +} + +"?" {return QUESTION;} +"'" {return SQUOTE;} + +"&" {return AND;} + +"~" {return TILDE;} +"=" {return TO;} +"<" {return LT;} +">" {return GT;} +"<=" {return LE;} +">=" {return GE;} +"!=" {return NE;} +"==" {return EQ;} + +"=>" {return FATARROW;} + +"<<" {return LSHIFT;} +">>" {return RSHIFT;} + +{STRING} { + /* seems risky, I know, but letting the parser choose when to allocate a + * new string seems to help with syntax error cleanup */ + yylval->str = strdup(yytext); + return STRING; +} + +{INT} { + yylval->integer = strtoll(yytext, 0, 0); + return INT; +} + +{ID} { + yylval->str = strdup(yytext); + return ID; +} + +{APPLY} { + /* strip trailing '!' */ + char *s = yytext + strlen(yytext); + s[-1] = '\0'; + + yylval->str = strdup(yytext); + return APPLY; +} + + +[[:space:]]+ {/* skip whitespace */} + +. { + struct src_issue issue; + issue.level = SRC_ERROR; + issue.loc = src_loc(*yylloc); + issue.fctx.fbuf = parser->buf; + issue.fctx.fname = parser->fname; + src_issue(issue, "Unexpected token: %s", yytext); + parser->failed = true; +} +%% diff --git a/src/lower.c b/src/lower.c new file mode 100644 index 0000000..ad3d1b9 --- /dev/null +++ b/src/lower.c @@ -0,0 +1,347 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <assert.h> + +#include <fwd/lower.h> +#include <fwd/scope.h> +#include <fwd/vec.h> + +struct state { + long indent; +}; + +static void increase_indent(struct state *state) +{ + state->indent++; +} + +static void decrease_indent(struct state *state) +{ + state->indent--; +} + +static void indent(struct state *state) +{ + for (long i = 0; i < state->indent; ++i) + putchar(' '); +} + +static int lower_expr(struct state *state, struct ast *expr); +static int lower_block(struct state *state, struct ast *block); +static int lower_closure(struct state *state, struct ast *closure); + +static int lower_types(struct type *types); + +static int lower_binop(struct state *state, struct ast *binop) +{ + printf("("); + if (lower_expr(state, binop_left(binop))) + return -1; + + switch (binop->k) { + default: + internal_error("missing binop lowering"); + return -1; + } + + if (lower_expr(state, binop_right(binop))) + return -1; + + printf(")"); + return 0; +} + +static int lower_comparison(struct state *state, struct ast *comp) +{ + printf("("); + if (lower_expr(state, comparison_left(comp))) + return -1; + + switch (comp->k) { + default: + internal_error("missing comparison lowering"); + return -1; + } + + if (lower_expr(state, comparison_right(comp))) + return -1; + + printf(")"); + return 0; +} + +static int lower_exprs(struct state *state, struct ast *exprs) +{ + if (!exprs) + return 0; + + if (lower_expr(state, exprs)) + return -1; + + foreach_node(expr, exprs->n) { + printf(", "); + if (lower_expr(state, expr)) + return -1; + } + + return 0; +} + +static int lower_type(struct type *type) +{ + if (type->k == TYPE_ID) { + printf("%s", tid_str(type)); + return 0; + } + + assert(type->k == TYPE_CONSTRUCT); + printf("%s", tconstruct_id(type)); + printf("<"); + + if (lower_types(tconstruct_args(type))) + return -1; + + printf(">"); + return 0; +} + +static int lower_types(struct type *types) +{ + if (!types) + return 0; + + if (lower_type(types)) + return -1; + + foreach_type(type, types->n) { + printf(", "); + if (lower_type(type)) + return -1; + } + + return 0; +} + +static int lower_init(struct state *state, struct ast *init) +{ + printf("%s", init_id(init)); + + if (init_args(init)) { + printf("<"); + if (lower_types(init_args(init))) + return -1; + + printf(">"); + } + + printf("{"); + + if (lower_exprs(state, init_body(init))) + return -1; + + printf("}"); + return 0; +} + +static int lower_expr(struct state *state, struct ast *expr) +{ + if (is_binop(expr)) + return lower_binop(state, expr); + + if (is_comparison(expr)) + return lower_comparison(state, expr); + + switch (expr->k) { + case AST_ID: printf("%s", id_str(expr)); break; + case AST_CONST_INT: printf("%lld", int_val(expr)); break; + case AST_CONST_FLOAT: printf("%f", float_val(expr)); break; + case AST_CONST_BOOL: printf("%s", bool_val(expr) ? "true" : "false"); + break; + case AST_CONST_STR: printf("\"%s\"", str_val(expr)); break; + case AST_CONST_CHAR: printf("'%c'", (char)char_val(expr)); break; + case AST_INIT: return lower_init(state, expr); + case AST_CLOSURE: return lower_closure(state, expr); + default: + internal_error("missing expr lowering"); + return -1; + } + + return 0; +} + +static int lower_move(struct state *state, struct ast *move) +{ + if (move->k == AST_ID) { + /** @todo once I start messing about with references, moves + * should only be outputted for parameters that take ownership + */ + printf("move(%s)", id_str(move)); + return 0; + } + + return lower_expr(state, move); +} + +static int lower_moves(struct state *state, struct ast *moves) +{ + if (!moves) + return 0; + + if (lower_move(state, moves)) + return -1; + + foreach_node(move, moves->n) { + printf(", "); + if (lower_move(state, move)) + return -1; + } + + return 0; +} + +static int lower_call(struct state *state, struct ast *call) +{ + if (lower_expr(state, call_expr(call))) + return -1; + + printf("("); + + if (lower_moves(state, call_args(call))) + return -1; + + printf(");\n"); + return 0; +} + +static int lower_let(struct state *state, struct ast *let) +{ + printf("auto %s = ", let_id(let)); + if (lower_expr(state, let_expr(let))) + return -1; + + printf(";\n"); + return 0; +} + +static int lower_statement(struct state *state, struct ast *stmt) +{ + switch (stmt->k) { + case AST_LET: return lower_let(state, stmt); + case AST_CALL: return lower_call(state, stmt); + default: + internal_error("missing statement lowering"); + return -1; + } + + return 0; +} + +static int lower_block(struct state *state, struct ast *block) +{ + printf("{\n"); + increase_indent(state); + + foreach_node(stmt, block_body(block)) { + indent(state); + + if (lower_statement(state, stmt)) + return -1; + } + + decrease_indent(state); + + indent(state); + printf("}"); + return 0; +} + +static int lower_params(struct ast *params) +{ + if (!params) + return 0; + + printf("auto %s", var_id(params)); + + foreach_node(param, params->n) { + printf(", auto %s", var_id(param)); + } + + return 0; +} + +static int lower_closure(struct state *state, struct ast *closure) +{ + printf("[&]("); + if (lower_params(closure_bindings(closure))) + return -1; + + printf(")"); + + if (lower_block(state, closure_body(closure))) + return -1; + + return 0; +} + +static int lower_proto(struct ast *proc) +{ + if (strcmp("main", proc_id(proc)) == 0) + printf("int "); + else + printf("void "); + + printf("%s(", proc_id(proc)); + + if (lower_params(proc_params(proc))) + return -1; + + printf(");\n\n"); + return 0; +} + +static int lower_proc(struct ast *proc) +{ + if (strcmp("main", proc_id(proc)) == 0) + printf("int "); + else + printf("void "); + + printf("%s(", proc_id(proc)); + + if (lower_params(proc_params(proc))) + return -1; + + printf(")\n"); + + struct state state = {0}; + if (lower_block(&state, proc_body(proc))) + return -1; + + printf("\n\n"); + return 0; +} + +int lower(struct scope *root) +{ + printf("#include <fwdlib.hpp>\n"); + + foreach_visible(visible, root->symbols) { + struct ast *proc = visible->node; + assert(proc->k == AST_PROC_DEF); + if (lower_proto(proc)) + return -1; + } + + foreach_visible(visible, root->symbols) { + struct ast *proc = visible->node; + if (lower_proc(proc)) + return -1; + } + + return 0; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..204a920 --- /dev/null +++ b/src/main.c @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +/** + * @file main.c + * + * Compiler main file, controls compilation and command line + * handling. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <fwd/debug.h> +#include <fwd/compiler.h> + +/** + * String describing compiler usage. + * @todo I suspect backends might want more flags, come up with + * some way to make flag handling more generic + */ +static const char *cmdline_usage = + "fwd frontend usage:\n" + " fwd infile\n" + " -h Show usage (this)\n" + " infile Top file(s) to compile\n" +; + +/** Print usage of compiler. */ +static void usage() +{ + fputs(cmdline_usage, stderr); +} + +/** + * Main entry to compiler. + * Checks command line parameters and drives the rest of the compiler. + * Feels kind of weird documenting main, but doxygen warns about not + * doing it so whatever. + * + * @param argc Number of command line arguments. + * @param argv Array of command line arguments. + * @return \c 0 when succesful, non-zero otherwise. + */ +int main(int argc, char *argv[]) +{ + int opt; + while ((opt = getopt(argc, argv, "hI:")) != -1) { + switch (opt) { + case 'h': + usage(); + exit(EXIT_SUCCESS); + + default: + usage(); + exit(EXIT_FAILURE); + } + } + + if (optind >= argc) { + error("no input file"); + usage(); + exit(EXIT_FAILURE); + } + + if (optind != argc - 1) { + error("too many arguments"); + usage(); + exit(EXIT_FAILURE); + } + + const char *input = argv[optind]; + return compile(input); +} diff --git a/src/parser.y b/src/parser.y new file mode 100644 index 0000000..ccff211 --- /dev/null +++ b/src/parser.y @@ -0,0 +1,460 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +%{ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include <fwd/parser.h> + +%} + +%locations + +%define parse.trace +%define parse.error verbose +%define api.pure full +%define lr.type ielr + +%lex-param {void *scanner} {struct parser *parser} +%parse-param {void *scanner} {struct parser* parser} + +%union { + struct ast *node; + struct type *type; + double dbl; + long long integer; + char *str; +}; + +%token <integer> INT +%token <integer> CHAR +%token <integer> BOOL +%token <dbl> FLOAT +%token <str> STRING +%token <str> ID +%token <str> APPLY + +%token QUESTION "?" +%token SQUOTE "'" +%token TO "=" +%token ELLIPSIS "..." +%token SEMICOLON ";" +%token COLON ":" +%token BANG "!" +%token SIZEOF "sizeof" +%token STAR "*" +%token DIV "/" +%token REM "%" +%token MINUS "-" +%token PLUS "+" +%token XOR "^" +%token AND "&" +%token TILDE "~" +%token LT "<" +%token GT ">" +%token LE "<=" +%token GE ">=" +%token NE "!=" +%token EQ "==" +%token LSHIFT "<<" +%token RSHIFT ">>" +%token COMMA "," +%token LPAREN "(" +%token RPAREN ")" +%token LBRACE "{" +%token RBRACE "}" +%token LBRACKET "[" +%token RBRACKET "]" +%token AS "as" +%token DOT "." +%token SCOPE "::" +%token FATARROW "=>" + +%right "[" "]" +/* precedence */ +%left "," +%right "=" "+=" "-=" "*=" "/=" "%=" "<<=" ">>=" +%left "==" "!=" +%left "<" ">" "<=" ">=" +%left "^" +%left "&" +%left "<<" ">>" +%left "+" "-" +%left "*" "/" "%" +%left "as" "sizeof" +%right "'" "!" "~" +%left "." "=>" "(" ")" +%left "::" + +%nterm <node> top unit proc call closure var expr statement body +%nterm <node> vars exprs statements closures +%nterm <node> opt_vars opt_exprs opt_statements +%nterm <node> rev_vars rev_exprs rev_closures rev_statements +%nterm <node> let unop binop construct + +%nterm <type> type rev_types types opt_types + + +%{ + +/** Modifies the signature of yylex to fit our parser better. */ +#define YY_DECL int yylex(YYSTYPE *yylval, YYLTYPE *yylloc, \ + void *yyscanner, struct parser *parser) + +/** + * Declare yylex. + * + * @param yylval Bison current value. + * @param yylloc Bison location info. + * @param yyscanner Flex scanner. + * @param parser Current parser state. + * @return \c 0 when succesful, \c 1 otherwise. + * More info on yylex() can be found in the flex manual. + */ +YY_DECL; + +/** + * Gobble tokens until we reach the next interesting feature. + * Interesting features are generally new statements. + * Mainly intended for trying to get to a sensible + * location to continue parser after an error has occured. + * + * @param yylval Current parser value. + * @param yylloc Parser location info. + * @param scanner Lex scanner. + * @param parser Current parser. + * @return \c 0 on success, non-zero otherwise. + */ +static int next_interesting_feature(YYSTYPE *yylval, YYLTYPE *yylloc, + void *scanner, struct parser *parser); + +/** + * Convert bison location info to our own source location info. + * + * @param yylloc Bison location info. + * @return Internal location info. + */ +static struct src_loc src_loc(YYLTYPE yylloc); + +/** + * Print parsing error. + * Automatically called by bison. + * + * @param yylloc Location of error. + * @param lexer Lexer. + * @param parser Parser state. + * @param msg Message to print. + */ +static void yyerror(YYLTYPE *yylloc, void *lexer, + struct parser *parser, const char *msg); + +/** + * Try to convert escape code to its actual value. + * I.e. '\n' -> 0x0a. + * + * @param c Escape character without backslash. + * @return Corresponding value. + */ +static char match_escape(char c); + +/** + * Similar to strdup() but skips quotation marks that would + * otherwise be included. + * I.e. "something" -> something. + * + * @param s String to clone, with quotation marks surrounding it. + * @return Identical string but without quotation marks around it. + */ +static char *strip(const char *s); + +%} + +%start input; +%% +var + : ID { $$ = gen_var($1, src_loc(@$)); } + +rev_vars + : rev_vars "," var { $$ = $3; $$->n = $1; } + | var + +vars + : rev_vars { $$ = reverse_ast_list($1); } + | rev_vars "," { $$ = reverse_ast_list($1); } + +opt_vars + : vars + | {$$ = NULL;} + +proc + : ID "(" opt_vars ")" body { + $$ = gen_proc($[ID], $[opt_vars], NULL, $[body], src_loc(@$)); + } + +binop + : expr "+" expr { $$ = gen_binop(AST_ADD, $1, $3, src_loc(@$)); } + | expr "-" expr { $$ = gen_binop(AST_SUB, $1, $3, src_loc(@$)); } + | expr "*" expr { $$ = gen_binop(AST_MUL, $1, $3, src_loc(@$)); } + | expr "/" expr { $$ = gen_binop(AST_DIV, $1, $3, src_loc(@$)); } + | expr "%" expr { $$ = gen_binop(AST_REM, $1, $3, src_loc(@$)); } + | expr "<" expr { $$ = gen_comparison(AST_LT, $1, $3, src_loc(@$)); } + | expr ">" expr { $$ = gen_comparison(AST_GT, $1, $3, src_loc(@$)); } + | expr "<<" expr { $$ = gen_binop(AST_LSHIFT, $1, $3, src_loc(@$)); } + | expr ">>" expr { $$ = gen_binop(AST_RSHIFT, $1, $3, src_loc(@$)); } + | expr "<=" expr { $$ = gen_comparison(AST_LE, $1, $3, src_loc(@$)); } + | expr ">=" expr { $$ = gen_comparison(AST_GE, $1, $3, src_loc(@$)); } + | expr "!=" expr { $$ = gen_comparison(AST_NE, $1, $3, src_loc(@$)); } + | expr "==" expr { $$ = gen_comparison(AST_EQ, $1, $3, src_loc(@$)); } + +unop + : "-" expr { $$ = gen_unop(AST_NEG, $2, src_loc(@$)); } + | "!" expr { $$ = gen_unop(AST_LNOT, $2, src_loc(@$)); } + +type + : ID { $$ = tgen_id($[ID], src_loc(@$)); } + | APPLY "[" opt_types "]" { + $$ = tgen_construct($[APPLY], $[opt_types], src_loc(@$)); + } + +rev_types + : rev_types "," type { $$ = $3; $3->n = $$; } + | type + +types + : rev_types { $$ = reverse_type_list($1); } + +opt_types + : types + | { $$ = NULL; } + +construct + : APPLY "{" opt_exprs "}" { + $$ = gen_init($[APPLY], NULL, $[opt_exprs], src_loc(@$)); + } + | APPLY "[" opt_types "]" "{" opt_exprs "}" { + $$ = gen_init($[APPLY], $[opt_types], $[opt_exprs], src_loc(@$)); + } + +expr + : expr "." ID { $$ = gen_dot($3, $1, src_loc(@$)); } + | STRING { $$ = gen_const_str(strip($1), src_loc(@$)); } + | FLOAT { $$ = gen_const_float($1, src_loc(@$)); } + | BOOL { $$ = gen_const_bool($1, src_loc(@$)); } + | CHAR { $$ = gen_const_char($1, src_loc(@$)); } + | INT { $$ = gen_const_int($1, src_loc(@$)); } + | ID { $$ = gen_id($1, src_loc(@$)); } + | "(" expr ")" { $$ = $2; } + | construct + | binop + | unop + +rev_exprs + : rev_exprs "," expr { $$ = $3; $3->n = $1; } + | expr + +exprs + : rev_exprs { $$ = reverse_ast_list($1); } + +opt_exprs + : exprs + | { $$ = NULL; } + +closure + : "=>" opt_vars body { $$ = gen_closure($[opt_vars], $[body], src_loc(@$)); } + +rev_closures + : rev_closures closure { $$ = $2; $2->n = $1; } + | closure + +closures + : rev_closures { $$ = reverse_ast_list($1); } + +call + : expr "(" opt_exprs ")" ";" { + $$ = gen_call($1, $[opt_exprs], src_loc(@$)); + } + | expr "(" opt_exprs ")" "=>" opt_vars ";" { + /* the rest of the function body is our closure, gets fixed up + * later */ + struct ast *closure = gen_closure($[opt_vars], NULL, src_loc(@$)); + ast_append(&$[opt_exprs], closure); + $$ = gen_call($[expr], $[opt_exprs], src_loc(@$)); + } + | expr "(" opt_exprs ")" closures { + ast_append(&$[opt_exprs], $[closures]); + $$ = gen_call($[expr], $[opt_exprs], src_loc(@$)); + } + +let + : expr "=>" ID ";" { $$ = gen_let($3, $1, src_loc(@$)); } + +statement + : call + | let + | ";" { $$ = gen_empty(src_loc(@$)); } + +rev_statements + : rev_statements statement { $$ = $2; $2->n = $1; } + | statement + +statements + : rev_statements { $$ = reverse_ast_list($1); fix_closures($$); } + +opt_statements + : statements + | { $$ = NULL; } + +body + : "{" opt_statements "}" { $$ = gen_block($2, src_loc(@$)); } + +top + : proc + | error { + $$ = gen_empty(src_loc(@$)); + parser->failed = true; + /* ignore any content inside a top level thing and just move onto + * the next one */ + if (next_interesting_feature(&yylval, &yylloc, scanner, parser)) { + YYABORT; + } + yyclearin; + yyerrok; + } + +unit + : top { $$ = $1; } + | unit top { $$ = $2; $2->n = $1; } + +input + : unit { parser->tree = reverse_ast_list($1); } + | /* empty */ + +%% + +#include "gen_lexer.inc" + +/* I'm not convinced this is foolproof quite yet, more testing would be nice. */ +static int next_interesting_feature(YYSTYPE *yylval, YYLTYPE *yylloc, + void *scanner, struct parser *parser) +{ + size_t depth = 0; + while (1) { + int ret = yylex(yylval, yylloc, scanner, parser); + if (ret == LBRACE) { + depth++; + continue; + } + + if (ret == RBRACE && depth > 0) + depth--; + + if (ret == RBRACE && depth == 0) + return 0; + + if (ret == SEMICOLON && depth == 0) + return 0; + + /* return fatal error and parser should abort */ + if (ret == YYEOF) + /* some error for unmatched braces would be cool I think */ + return 1; + } +} + + +static struct src_loc src_loc(YYLTYPE yylloc) +{ + struct src_loc loc; + loc.first_line = yylloc.first_line; + loc.last_line = yylloc.last_line; + loc.first_col = yylloc.first_column; + loc.last_col = yylloc.last_column; + return loc; +} + +static void yyerror(YYLTYPE *yylloc, void *lexer, + struct parser *parser, const char *msg) +{ + (void)lexer; + + struct src_issue issue; + issue.level = SRC_ERROR; + issue.loc = src_loc(*yylloc); + issue.fctx.fbuf = parser->buf; + issue.fctx.fname = parser->fname; + src_issue(issue, msg); +} + +static char match_escape(char c) +{ + switch (c) { + case '\'': return '\''; + case '\\': return '\\'; + case 'a': return '\a'; + case 'b': return '\b'; + case 'f': return '\f'; + case 'n': return '\n'; + case 'r': return '\r'; + case 't': return '\t'; + case 'v': return '\v'; + } + + return c; +} + +static char *strip(const char *str) +{ + const size_t len = strlen(str) + 1; + char *buf = malloc(len); + if (!buf) { + /* should probably try to handle the error in some way... */ + internal_error("failed allocating buffer for string clone"); + free((void *)str); + return NULL; + } + + /* skip quotation marks */ + size_t j = 0; + for (size_t i = 1; i < len - 2; ++i) { + char c = str[i]; + + if (c == '\\') + c = match_escape(str[++i]); + + buf[j++] = c; + } + + buf[j] = 0; + free((void *)str); + return buf; + +} + +struct parser *create_parser() +{ + return calloc(1, sizeof(struct parser)); +} + +void destroy_parser(struct parser *p) +{ + yylex_destroy(p->lexer); + free(p); +} + +void parse(struct parser *p, const char *fname, const char *buf) +{ + p->fname = fname; + p->buf = buf; + + p->comment_nesting = 0; + + p->failed = false; + + yylex_init(&p->lexer); + yy_scan_string(buf, p->lexer); + yyparse(p->lexer, p); +} diff --git a/src/scope.c b/src/scope.c new file mode 100644 index 0000000..869e0d6 --- /dev/null +++ b/src/scope.c @@ -0,0 +1,217 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +/** @file scope.c + * + * Implementations for scope handling stuff. + */ + +#include <stddef.h> +#include <string.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <assert.h> + +#include <fwd/debug.h> +#include <fwd/scope.h> + +struct scope *create_scope() +{ + /* if I ever try making the parser multithreaded, this should be atomic. */ + static size_t counter = 0; + + struct scope *scope = calloc(1, sizeof(struct scope)); + if (!scope) { + internal_error("ran out of memory allocating scope"); + return NULL; + } + + scope->number = counter++; + return scope; +} + +static void destroy_visible(struct visible *visible) +{ + struct visible *prev = visible, *cur; + if (prev) + do { + cur = prev->next; + free(prev); + } while ((prev = cur)); +} + +void destroy_scope(struct scope *scope) +{ + if (!scope) + return; + + if (!scope->parent) { + free((void *)scope->fctx.fbuf); + free((void *)scope->fctx.fname); + } + + destroy_visible(scope->symbols); + + struct scope *prev = scope->children, *cur; + if (prev) + do { + cur = prev->next; + destroy_scope(prev); + } while ((prev = cur)); + + free(scope); +} + +static struct visible *create_visible(char *id, struct ast *node) +{ + struct visible *visible = calloc(1, sizeof(struct visible)); + if (!visible) + return NULL; + + visible->id = id; + visible->node = node; + return visible; +} + +struct visible *create_var(struct scope *scope, char *id, struct ast *var) +{ + struct visible *n = create_visible(id, var); + if (!n) + return NULL; + + n->next = scope->symbols; + scope->symbols = n; + + return n; +} + +struct visible *create_proc(struct scope *scope, char *id, struct ast *proc) +{ + struct visible *n = create_visible(id, proc); + if (!n) + return NULL; + + n->next = scope->symbols; + scope->symbols = n; + + return n; +} + +int scope_add_var(struct scope *scope, struct ast *var) +{ + struct ast *exists = scope_find_symbol(scope, var_id(var)); + if (exists) { + semantic_error(scope->fctx, var, "var redefined"); + semantic_info(scope->fctx, exists, "previously here"); + return -1; + } + + create_var(scope, var_id(var), var); + return 0; +} + +int scope_add_proc(struct scope *scope, struct ast *proc) +{ + assert(proc->k == AST_PROC_DEF); + struct ast *exists = file_scope_find_symbol(scope, proc_id(proc)); + if (exists) { + semantic_error(scope->fctx, proc, "proc redefined"); + semantic_info(scope->fctx, exists, "previously here"); + return -1; + } + + /* always add to scope, do resolve checking later */ + create_proc(scope, proc_id(proc), proc); + return 0; +} + +static struct ast *scope_find_visible(struct visible *v, char *id) +{ + if (!v) + return NULL; + + foreach_visible(n, v) { + struct ast *node = n->node; + if (same_id(node->s, id)) + return node; + } + + return NULL; +} + +struct ast *scope_find_proc(struct scope *scope, char *id) +{ + struct ast *n = scope_find_visible(scope->symbols, id); + if (!n) + return NULL; + + if (n->k != AST_PROC_DEF) + return NULL; + + return n; +} + +struct ast *file_scope_find_proc(struct scope *scope, char *id) +{ + struct ast *n = file_scope_find_symbol(scope, id); + if (!n) + return NULL; + + if (n->k != AST_PROC_DEF) + return NULL; + + return n; +} + +struct ast *scope_find_symbol(struct scope *scope, char *id) +{ + return scope_find_visible(scope->symbols, id); +} + +struct ast *file_scope_find_symbol(struct scope *scope, char *id) +{ + if (!scope) + return NULL; + + struct ast *found = scope_find_symbol(scope, id); + if (found) + return found; + + return file_scope_find_symbol(scope->parent, id); +} + +struct ast *scope_find_var(struct scope *scope, char *id) +{ + struct ast *n = scope_find_visible(scope->symbols, id); + if (!n) + return NULL; + + if (n->k != AST_VAR_DEF) + return NULL; + + return n; +} + +struct ast *file_scope_find_var(struct scope *scope, char *id) +{ + if (!scope) + return NULL; + + struct ast *found = scope_find_var(scope, id); + if (found) + return found; + + return file_scope_find_var(scope->parent, id); +} + +void scope_add_scope(struct scope *parent, struct scope *child) +{ + assert(parent); + assert(child); + + child->fctx = parent->fctx; + child->parent = parent; + child->next = parent->children; + parent->children = child; +} diff --git a/src/source.mk b/src/source.mk new file mode 100644 index 0000000..fa6ffc1 --- /dev/null +++ b/src/source.mk @@ -0,0 +1,2 @@ +SRC_LOCAL != echo src/*.c +FWD_SOURCES := $(FWD_SOURCES) $(SRC_LOCAL) diff --git a/src/vec.c b/src/vec.c new file mode 100644 index 0000000..be413a0 --- /dev/null +++ b/src/vec.c @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +#include <stdlib.h> +#include <assert.h> +#include <string.h> + +#include <fwd/vec.h> + +struct vec vec_create(size_t ns) +{ + return (struct vec) { + .n = 0, + .s = 1, + .ns = ns, + .buf = malloc(ns), + }; +} + +size_t vec_len(struct vec *v) +{ + return v->n; +} + +void *vec_at(struct vec *v, size_t i) +{ + assert(i < v->n && "out of vector bounds"); + return v->buf + i * v->ns; +} + +void *vec_back(struct vec *v) +{ + assert(v->n); + return v->buf + (v->n - 1) * v->ns; +} + +void *vec_pop(struct vec *v) +{ + assert(v->n && "attempting to pop empty vector"); + v->n--; + return v->buf + v->n * v->ns; +} + +void vec_append(struct vec *v, void *n) +{ + v->n++; + if (v->n >= v->s) { + v->s *= 2; + v->buf = realloc(v->buf, v->s * v->ns); + } + + void *p = vec_at(v, v->n - 1); + memcpy(p, n, v->ns); +} + +void vec_reset(struct vec *v) +{ + v->n = 0; +} + +void vec_destroy(struct vec *v) { + free(v->buf); +} + +void vec_sort(struct vec *v, vec_comp_t comp) +{ + qsort(v->buf, v->n, v->ns, comp); +} |