diff options
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); +} |