aboutsummaryrefslogtreecommitdiff
path: root/include
diff options
context:
space:
mode:
Diffstat (limited to 'include')
-rw-r--r--include/fwd/analyze.h12
-rw-r--r--include/fwd/ast.h393
-rw-r--r--include/fwd/compiler.h25
-rw-r--r--include/fwd/debug.h138
-rw-r--r--include/fwd/lower.h12
-rw-r--r--include/fwd/parser.h59
-rw-r--r--include/fwd/scope.h194
-rw-r--r--include/fwd/vec.h47
8 files changed, 880 insertions, 0 deletions
diff --git a/include/fwd/analyze.h b/include/fwd/analyze.h
new file mode 100644
index 0000000..bdbcdb2
--- /dev/null
+++ b/include/fwd/analyze.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+#ifndef FWD_ANALYZE
+#define FWD_ANALYZE
+
+#include <fwd/ast.h>
+#include <fwd/scope.h>
+
+int analyze_root(struct scope *scope, struct ast *root);
+
+#endif /* FWD_ANALYZE */
diff --git a/include/fwd/ast.h b/include/fwd/ast.h
new file mode 100644
index 0000000..909137d
--- /dev/null
+++ b/include/fwd/ast.h
@@ -0,0 +1,393 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+#ifndef AST_H
+#define AST_H
+
+#include <stddef.h>
+#include <stdbool.h>
+
+/**
+ * @file ast.h
+ *
+ * Abstract syntax tree handling.
+ */
+
+#define NULL_LOC() ((struct src_loc){0, 0, 0, 0})
+
+/** Represents a source location, spanning over some bit of code. */
+struct src_loc {
+ /** First line of interesting text. */
+ int first_line;
+ /** Last line of interesting text. */
+ int last_line;
+ /** First column in first line of interesting text. */
+ int first_col;
+ /** Last column in last line of interesting text. */
+ int last_col;
+};
+
+struct ast;
+
+enum type_kind {
+ TYPE_ID = 1, TYPE_CONSTRUCT
+};
+
+struct type {
+ enum type_kind k;
+
+ /* type arg */
+ struct type *t0;
+
+ /* id */
+ char *id;
+
+ /* next */
+ struct type *n;
+
+ struct src_loc loc;
+};
+
+/** Possible AST node types. We reserve node 0 as an illegal value. */
+enum ast_kind {
+ /** Creating new variable. */
+ AST_LET = 1,
+ /** Call procedure. */
+ AST_CALL,
+ /** Procedure definition. */
+ AST_PROC_DEF,
+ /** Variable declaration/definition. */
+ AST_VAR_DEF,
+ /** Dot. \c a.b; */
+ AST_DOT,
+ /* Closure */
+ AST_CLOSURE,
+ /** Construct new type, something!{} */
+ AST_INIT,
+ /** Block. E.g. \c {} */
+ AST_BLOCK,
+ /** Any ID, variable or whatever. */
+ AST_ID,
+ /** Empty. Essentially noop. */
+ AST_EMPTY,
+ /** Add, \c + */
+ AST_ADD,
+ /** Subtract, \c - */
+ AST_SUB,
+ /** Multiply, \ * */
+ AST_MUL,
+ /** Divide, \c \ */
+ AST_DIV,
+ /** Remainder, \c % */
+ AST_REM,
+ /** Logical and, \c && */
+ AST_LAND,
+ /** Logical or, \c ||*/
+ AST_LOR,
+ /** Left shift (logical), \c << @todo add arithmetic shifts? */
+ AST_LSHIFT,
+ /** Right shift (logical), \c >> */
+ AST_RSHIFT,
+ /** Less than, \c < */
+ AST_LT,
+ /** Greater than, \c > */
+ AST_GT,
+ /** Less than or equal, \c <= */
+ AST_LE,
+ /** Greater than or equal, \c >= */
+ AST_GE,
+ /** Not equal, \c != */
+ AST_NE,
+ /** Equal, \c == */
+ AST_EQ,
+ /** Negation, \c - */
+ AST_NEG,
+ /** Logical negation, \c ! */
+ AST_LNOT,
+ /** Bitwise negation, \c ~ */
+ AST_NOT,
+ AST_CONST_INT,
+ AST_CONST_FLOAT,
+ AST_CONST_CHAR,
+ AST_CONST_BOOL,
+ AST_CONST_STR,
+};
+
+struct ast {
+ enum ast_kind k;
+ double d;
+ long long v;
+ char *s;
+ struct ast *a0;
+ struct ast *a1;
+ struct ast *a2;
+ struct ast *a3;
+ struct type *t2;
+
+ struct ast *n;
+ struct src_loc loc;
+ struct scope *scope;
+};
+
+struct ast *gen_ast(enum ast_kind kind,
+ struct ast *a0,
+ struct ast *a1,
+ struct ast *a2,
+ struct ast *a3,
+ struct type *t1,
+ char *s,
+ long long v,
+ double d,
+ struct src_loc loc);
+
+struct type *tgen_type(enum type_kind kind,
+ struct type *t0,
+ char *id,
+ struct src_loc loc);
+
+static inline bool is_binop(struct ast *x)
+{
+ switch (x->k) {
+ case AST_ADD:
+ case AST_SUB:
+ case AST_MUL:
+ case AST_DIV:
+ case AST_REM:
+ case AST_LSHIFT:
+ case AST_RSHIFT:
+ return true;
+ default:
+ break;
+ };
+
+ return false;
+}
+
+static inline bool is_unop(struct ast *x)
+{
+ switch (x->k) {
+ case AST_NOT:
+ case AST_NEG:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static inline bool is_comparison(struct ast *x)
+{
+ switch (x->k) {
+ case AST_LT:
+ case AST_GT:
+ case AST_LE:
+ case AST_GE:
+ case AST_NE:
+ case AST_EQ:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static inline bool is_const(struct ast *x)
+{
+ /* note that const strings are sort of their own entity */
+ switch (x->k) {
+ case AST_CONST_INT:
+ case AST_CONST_CHAR:
+ case AST_CONST_BOOL:
+ case AST_CONST_FLOAT:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+#define gen_str_type1(k, s, t, a, loc) gen_ast(k, a, NULL, NULL, NULL, \
+ t, s, -1, 0., loc)
+#define gen_str_type(k, s, t, loc) gen_str_type1(k, s, t, NULL, loc)
+#define gen_str3(k, s, a, b, c, loc) gen_ast(k, a, b, c, NULL, NULL, s, -1, 0., \
+ loc)
+#define gen_str2(k, s, a, b, loc) gen_str3(k, s, a, b, NULL, loc)
+#define gen_str1(k, s, a, loc) gen_str2(k, s, a, NULL, loc)
+#define gen_str(k, s, loc) gen_ast(k, NULL, NULL, NULL, NULL, NULL, s, -1, 0., \
+ loc)
+
+
+#define gen4(k, a, b, c, d, loc) gen_ast(k, a, b, c, d, NULL, NULL, -1, 0., loc)
+#define gen3(k, a, b, c, loc) gen4(k, a, b, c, NULL, loc)
+#define gen2(k, a, b, loc) gen3(k, a, b, NULL, loc)
+#define gen1(k, a, loc) gen2(k, a, NULL, loc)
+
+
+/* kind of hacky but I guess it works, and allows us to check that the type is
+ * correct every time */
+#define return_s(x, kind) *({assert((x)->k == kind); &(x)->s;})
+#define return_a0(x, kind) *({assert((x)->k == kind); &(x)->a0;})
+#define return_a1(x, kind) *({assert((x)->k == kind); &(x)->a1;})
+#define return_a2(x, kind) *({assert((x)->k == kind); &(x)->a2;})
+#define return_a3(x, kind) *({assert((x)->k == kind); &(x)->a3;})
+#define return_t2(x, kind) *({assert((x)->k == kind); &(x)->t2;})
+
+#define return_id(x, kind) *({assert((x)->k == kind); &(x)->id;})
+#define return_t0(x, kind) *({assert((x)->k == kind); &(x)->t0;})
+
+#define tid_str(x) return_id(x, TYPE_ID)
+#define tgen_id(id, loc) \
+ tgen_type(TYPE_ID, NULL, id, loc)
+
+#define tconstruct_id(x) return_id(x, TYPE_CONSTRUCT)
+#define tconstruct_args(x) return_t0(x, TYPE_CONSTRUCT)
+#define tgen_construct(id, args, loc) \
+ tgen_type(TYPE_CONSTRUCT, args, id, loc)
+
+
+#define closure_bindings(x) return_a0(x, AST_CLOSURE)
+#define closure_body(x) return_a1(x, AST_CLOSURE)
+#define gen_closure(bindings, body, loc) \
+ gen2(AST_CLOSURE, bindings, body, loc)
+
+#define binop_left(x) ({assert(is_binop(x)); x->a0;})
+#define binop_right(x) ({assert(is_binop(x)); x->a1;})
+#define gen_binop(op, left, right, loc) \
+ gen2(op, left, right, loc)
+
+#define comparison_left(x) ({assert(is_comparison(x)); x->a0;})
+#define comparison_right(x) ({assert(is_comparison(x)); x->a1;})
+#define gen_comparison(op, left, right, loc) \
+ gen2(op, left, right, loc)
+
+#define unop_expr(x) ({assert(is_unop(x)); x->a0;})
+#define gen_unop(op, expr, loc) \
+ gen1(op, expr, loc)
+
+#define call_expr(x) return_a0(x, AST_CALL)
+#define call_args(x) return_a1(x, AST_CALL)
+#define gen_call(expr, args, loc) \
+ gen2(AST_CALL, expr, args, loc)
+
+#define proc_id(x) return_s(x, AST_PROC_DEF)
+#define proc_params(x) return_a0(x, AST_PROC_DEF)
+#define proc_rtype(x) return_t2(x, AST_PROC_DEF)
+#define proc_body(x) return_a1(x, AST_PROC_DEF)
+#define gen_proc(id, params, rtype, body, loc) \
+ gen_ast(AST_PROC_DEF, params, body, NULL, NULL, rtype, id, 0, 0., loc)
+
+#define dot_id(x) return_s(x, AST_DOT)
+#define dot_expr(x) return_a0(x, AST_DOT)
+#define gen_dot(id, expr, loc) \
+ gen_str1(AST_DOT, id, expr, loc)
+
+#define var_id(x) return_s(x, AST_VAR_DEF)
+#define var_type(x) return_t2(x, AST_VAR_DEF)
+#define var_init(x) return_a0(x, AST_VAR_DEF)
+#define gen_var(id, loc) \
+ gen_ast(AST_VAR_DEF, NULL, NULL, NULL, NULL, NULL, id, 0, 0., loc)
+
+#define block_body(x) return_a0(x, AST_BLOCK)
+#define gen_block(body, loc) \
+ gen1(AST_BLOCK, body, loc)
+
+#define str_val(x) return_s(x, AST_CONST_STR)
+#define gen_const_str(s, loc) \
+ gen_ast(AST_CONST_STR, NULL, NULL, NULL, NULL, NULL, s, 0, 0., loc)
+
+#define int_val(x) *({assert(x->k == AST_CONST_INT); &x->v;})
+#define gen_const_int(i, loc) \
+ gen_ast(AST_CONST_INT, NULL, NULL, NULL, NULL, NULL, NULL, i, 0., loc)
+
+#define float_val(x) *({assert(x->k == AST_CONST_FLOAT); &x->d;})
+#define gen_const_float(d, loc) \
+ gen_ast(AST_CONST_FLOAT, NULL, NULL, NULL, NULL, NULL, NULL, 0, d, loc)
+
+#define char_val(x) *({assert(x->k == AST_CONST_CHAR); &x->v;})
+#define gen_const_char(i, loc) \
+ gen_ast(AST_CONST_CHAR, NULL, NULL, NULL, NULL, NULL, NULL, i, 0., loc)
+
+#define bool_val(x) *({assert(x->k == AST_CONST_CHAR); &x->v;})
+#define gen_const_bool(i, loc) \
+ gen_ast(AST_CONST_BOOL, NULL, NULL, NULL, NULL, NULL, NULL, i, 0., loc)
+
+#define let_id(x) return_s(x, AST_LET)
+#define let_expr(x) return_a0(x, AST_LET)
+#define gen_let(id, from, loc) \
+ gen_str1(AST_LET, id, from, loc)
+
+#define init_args(x) return_t2(x, AST_INIT)
+#define init_body(x) return_a0(x, AST_INIT)
+#define init_id(x) return_s(x, AST_INIT)
+#define gen_init(id, targs, body, loc) \
+ gen_str_type1(AST_INIT, id, targs, body, loc)
+
+#define id_str(x) return_s(x, AST_ID)
+#define gen_id(id, loc) \
+ gen_str(AST_ID, id, loc)
+
+#define gen_empty(loc) \
+ gen1(AST_EMPTY, NULL, loc)
+
+struct ast *clone_ast(struct ast *n);
+struct ast *clone_ast_list(struct ast *l);
+
+void ast_dump_list(int depth, struct ast *root);
+void ast_dump(int depth, struct ast *node);
+void ast_append(struct ast **list, struct ast *elem);
+
+struct ast *ast_prepend(struct ast *list, struct ast *elem);
+
+typedef int (*ast_callback_t)(struct ast *, void *);
+typedef int (*type_callback_t)(struct type *, void *);
+int ast_visit(ast_callback_t before, ast_callback_t after, struct ast *node,
+ void *data);
+int ast_visit_list(ast_callback_t before, ast_callback_t after,
+ struct ast *node, void *data);
+
+/**
+ * Number of elements in AST list.
+ *
+ * @param list List whose elements to count.
+ * @return Number of elements in \p list.
+ */
+size_t ast_list_len(struct ast *list);
+
+/**
+ * Get last nose in ASt list.
+ *
+ * @param list List whose last element to get.
+ * @return Last node in \p list.
+ */
+struct ast *ast_last(struct ast *list);
+
+/**
+ * Get last element in block.
+ *
+ * @param block Block whose last element to get.
+ * @return Last node in block.
+ */
+struct ast *ast_block_last(struct ast *block);
+
+void destroy_allocs();
+const char *primitive_str(struct type *kind);
+
+int same_id(char *id1, char *id2);
+int equiv_nodes(struct ast *n1, struct ast *n2);
+int equiv_node_lists(struct ast *c1, struct ast *c2);
+
+struct ast *reverse_ast_list(struct ast *root);
+struct type *reverse_type_list(struct type *root);
+
+void fix_closures(struct ast *root);
+
+#define foreach_node(iter, nodes) \
+ for (struct ast *iter = nodes; iter; iter = iter->n)
+
+#define foreach_type(iter, types) \
+ for (struct type *iter = types; iter; iter = iter->n)
+
+#endif /* AST_H */
diff --git a/include/fwd/compiler.h b/include/fwd/compiler.h
new file mode 100644
index 0000000..b7f1569
--- /dev/null
+++ b/include/fwd/compiler.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+#ifndef FWD_COMPILER_H
+#define FWD_COMPILER_H
+
+/**
+ * @file compiler.h
+ *
+ * Top level compiler.
+ */
+
+#include <fwd/scope.h>
+
+/**
+ * Compile a root file.
+ * A root file is a file given on the command line, and is assumed to
+ * create a file tree that eventually results in a binary.
+ *
+ * @param file Root file to compile.
+ * @return \c 0 if compilation was succesful, otherwise some non-zero value.
+ */
+int compile(const char *input);
+
+#endif /* FWD_COMPILER_H */
diff --git a/include/fwd/debug.h b/include/fwd/debug.h
new file mode 100644
index 0000000..85ed9db
--- /dev/null
+++ b/include/fwd/debug.h
@@ -0,0 +1,138 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+#ifndef FWD_DEBUG_H
+#define FWD_DEBUG_H
+
+/**
+ * @file debug.h
+ *
+ * Debugging and general information printing helpers.
+ */
+
+#include <stdio.h>
+
+#include <fwd/ast.h>
+
+#if DEBUG
+/**
+ * Print debugging message. Only active if \c DEBUG is defined,
+ *
+ * @param x Format string. Follows standard printf() formatting.
+ */
+#define debug(x, ...) \
+ do {fprintf(stderr, "debug: " x "\n",##__VA_ARGS__);} while(0)
+#else
+#define debug(x, ...)
+#endif
+
+/**
+ * Print error message.
+ *
+ * @param x Format string. Follows standard printf() formatting.
+ */
+#define error(x, ...) \
+ do {fprintf(stderr, "error: " x "\n",##__VA_ARGS__);} while(0)
+
+/**
+ * Print warning message.
+ *
+ * @param x Format string. Follows standard printf() formatting.
+ */
+#define warn(x, ...) \
+ do {fprintf(stderr, "warn: " x "\n",##__VA_ARGS__);} while(0)
+
+/**
+ * Print info message.
+ *
+ * @param x Format string. Follows standard printf() formatting.
+ */
+#define info(x, ...) \
+ do {fprintf(stderr, "info: " x "\n",##__VA_ARGS__);} while(0)
+
+
+/** Keeps track of file name and file buffer. */
+struct file_ctx {
+ /** File name. */
+ const char *fname;
+ /** File buffer. */
+ const char *fbuf;
+};
+
+/**
+ * Print info that relates to a specific AST node.
+ * Recommended for situations where it may be useful to clarify some
+ * previous error or warning.
+ *
+ * @param ctx File context \p node was generated from.
+ * @param node AST node to print message with.
+ * @param fmt Format string. Follows standard printf() formatting.
+ */
+void semantic_info(struct file_ctx ctx, struct ast *node, const char *fmt, ...);
+
+/**
+ * Print warning that relates to a specific AST node.
+ * Recommended for situations where the user wrote some shady code
+ * and it might be unclear what was meant.
+ *
+ * The language itself tries to avoid such situations, and as such warnings
+ * should probably be avoided in favor of errors. Still, I can imagine that
+ * there are situations where warnings can be useful, so it's provided.
+ *
+ * @param ctx File context \p node was generated from.
+ * @param node AST node to print message with.
+ * @param fmt Format string. Follows standard printf() formatting.
+ */
+void semantic_warn(struct file_ctx ctx, struct ast *node, const char *fmt,
+ ...);
+
+/**
+ * Print warning that relates to a specific AST node.
+ * Recommended for situations where the user messed up.
+ *
+ * @param ctx File context \p node was generated from.
+ * @param node AST node to print message with.
+ * @param fmt Format string. Follows standard printf() formatting.
+ */
+void semantic_error(struct file_ctx ctx, struct ast *node, const char *fmt,
+ ...);
+void loc_error(struct file_ctx ctx, struct src_loc loc, const char *fmt, ...);
+
+/**
+ * Print internal error.
+ * Recommended for situations where the developer (probably me) messed up.
+ *
+ * @param fmt Format string. Follows standard printf() formatting.
+ */
+void internal_error(const char *fmt, ...);
+void internal_warn(const char *fmt, ...);
+
+/** Issue categorization. */
+enum issue_level {
+ /** Information. */
+ SRC_INFO,
+ /** Warning. */
+ SRC_WARN,
+ /** Error. */
+ SRC_ERROR
+};
+
+/** Context for issue in user code. */
+struct src_issue {
+ /** How bad the issue is. */
+ enum issue_level level;
+ /** Where the issue happened relative to file buffer. */
+ struct src_loc loc;
+ /** File context issue happened in. */
+ struct file_ctx fctx;
+};
+
+/**
+ * Print a source issue.
+ *
+ * @param issue Context for issue.
+ * @param err_msg Format string. Follows standard printf() formatting.
+ */
+void src_issue(struct src_issue issue, const char *err_msg, ...);
+
+#endif /* FWD_DEBUG_H */
diff --git a/include/fwd/lower.h b/include/fwd/lower.h
new file mode 100644
index 0000000..1aace53
--- /dev/null
+++ b/include/fwd/lower.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+#ifndef FWD_LOWER_H
+#define FWD_LOWER_H
+
+#include <fwd/ast.h>
+#include <stdio.h>
+
+int lower(struct scope *root);
+
+#endif /* FWD_LOWER_H */
diff --git a/include/fwd/parser.h b/include/fwd/parser.h
new file mode 100644
index 0000000..93017d8
--- /dev/null
+++ b/include/fwd/parser.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+#ifndef PARSER_H
+#define PARSER_H
+
+/**
+ * @file parser.h
+ *
+ * Glue file to get lexer and parser to play nice.
+ */
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <fwd/ast.h>
+
+/** Stuff the parser needs to do its job. */
+struct parser {
+ /** Whether parsing failed or succeeded. */
+ bool failed;
+ /** Lexer. Parser owns the lexer and is responsible for initializing
+ * and destroyint the lexer.
+ */
+ void *lexer;
+
+ /** File content in memory. */
+ const char *buf;
+ /** Filename. */
+ const char *fname;
+ /** How deeply we've nested comments. */
+ size_t comment_nesting;
+ /** Raw AST. */
+ struct ast *tree;
+};
+
+/**
+ * Create new parser.
+ *
+ * @return Created parser.
+ */
+struct parser *create_parser();
+
+/**
+ * Destroy parser.
+ *
+ * @param p Parser to destroy.
+ */
+void destroy_parser(struct parser *p);
+
+/**
+ * Run parser on buffer \p buf with name \p fname.
+ *
+ * @param p Parser to run.
+ * @param fname Name of file \p buf was read from.
+ * @param buf Contents of \p fname.
+ */
+void parse(struct parser *p, const char *fname, const char *buf);
+
+#endif /* PARSER_H */
diff --git a/include/fwd/scope.h b/include/fwd/scope.h
new file mode 100644
index 0000000..6a78793
--- /dev/null
+++ b/include/fwd/scope.h
@@ -0,0 +1,194 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+#ifndef SCOPE_H
+#define SCOPE_H
+
+/**
+ * @file scope.h
+ *
+ * Scope handling stuff.
+ */
+
+#include "ast.h"
+#include "debug.h"
+
+/**
+ * An AST node visible to the scope we're in.
+ * The same AST node can be referenced by multiple visible nodes,
+ * but only the owning scope is allowed to destroy the node.
+ * Check that \p owner is identical to the scope the visible node
+ * belongs to.
+ *
+ * Basic linked list for now, can probably be optimized into some kind of hash
+ * table later.
+ */
+struct visible {
+ /** Name of the visible node. */
+ char *id;
+ /** AST node that is visible. */
+ struct ast *node;
+ /** Next visible object in the scope we're in. */
+ struct visible *next;
+};
+
+/**
+ * Scope.
+ * Responsible for keeping track of visibilities and
+ * file context.
+ */
+struct scope {
+ /** Parent scope, NULL if top scope. */
+ struct scope *parent;
+
+ /** Unique scope ID. Mostly used for debugging. */
+ size_t number;
+
+ /**
+ * File context of scope. Accurate debugging output relies
+ * on keeping track of which file and buffer a context is in.
+ */
+ struct file_ctx fctx;
+
+ /**
+ * Next child node.
+ * Used by the parent scope to keep track of all its children.
+ */
+ struct scope *next;
+
+ /** List of child scopes. */
+ struct scope *children;
+
+ struct visible *symbols;
+};
+
+/**
+ * Create scope.
+ *
+ * @return New, empty scope.
+ */
+struct scope *create_scope();
+
+/**
+ * Destroy scope.
+ * Destroys all lists the scope owns and frees the scope.
+ *
+ * @param scope Scope to destroy.
+ */
+void destroy_scope(struct scope *scope);
+
+/**
+ * Add default stuff to scope, mainly builtin types.
+ *
+ * @param root Scope to add default stuff to.
+ * @note Only has to be called on the file scope, all child scopes will
+ * look up stuff from the file scope anyway.
+ * @return \c 0 if succesful, non-zero otherwise.
+ */
+int scope_add_defaults(struct scope *root);
+
+/**
+ * Destroy defaults in scope that might otherwise not be freed.
+ *
+ * @param root Scope to destroy added defaults in.
+ * @todo not sure if this should be private.
+ */
+void scope_destroy_defaults(struct scope *root);
+
+/**
+ * Add a scratch AST node.
+ *
+ * @param scope Scope to add scratch AST node to.
+ * @param scratch Scratch node to add to \p scope.
+ * @return \c 0 when successful, non-zero otherwise.
+ */
+int scope_add_scratch(struct scope *scope, struct ast *scratch);
+
+/**
+ * Add child scope to \p parent.
+ *
+ * @param parent Scope to add scope to.
+ * @param child Scope to add to scope.
+ */
+void scope_add_scope(struct scope *parent, struct scope *child);
+
+/**
+ * Add variable to scope.
+ * Propagates public variables up the file scope chain as references.
+ *
+ * @param scope Scope to add variable to.
+ * @param var Variable to add to scope.
+ * @return \c 0 when succesful, non-zero otherwise.
+ */
+int scope_add_var(struct scope *scope, struct ast *var);
+
+/**
+ * Add type to scope.
+ * Propagates public types up the file scope chain as references.
+ *
+ * @param scope Scope to add type to.
+ * @param type Type to add to scope.
+ * @return \c 0 when succesful, non-zero otherwise.
+ */
+int scope_add_type(struct scope *scope, char *id, struct ast *type);
+
+/**
+ * Add procedure to scope.
+ * Propagates public procedures up the file scope chain as references.
+ *
+ * @param scope Scope to add procedure to.
+ * @param proc Procedure to add to scope.
+ * @return \c 0 when succesful, non-zero otherwise.
+ */
+int scope_add_proc(struct scope *scope, struct ast *proc);
+
+/**
+ * Find a variable with ID in \p scope.
+ * @note Only looks in the current scope, so doesn't see anything outside
+ * of it. See file_scope_find_var().
+ *
+ * @param scope Scope to look in.
+ * @param id ID of variable to find.
+ * @return Pointer to the AST node corresponding to \p id if found,
+ * otherwise \c NULL.
+ */
+struct ast *scope_find_var(struct scope *scope, char *id);
+struct ast *scope_find_symbol(struct scope *scope, char *id);
+
+/**
+ * Find a procedure with ID in \p scope.
+ * @note Only looks in the current scope, so doesn't see anything outside
+ * of it. See file_scope_find_proc().
+ *
+ * @param scope Scope to look in.
+ * @param id ID of procedure to find.
+ * @return Pointer to the AST node corresponding to \p id if found,
+ * otherwise \c NULL.
+ */
+struct ast *scope_find_proc(struct scope *scope, char *id);
+
+/**
+ * Find a variable with ID visible to \p scope.
+ *
+ * @param scope Scope to look in.
+ * @param id ID of variable to find.
+ * @return Pointer to the AST node corresponding to \p id if found,
+ * otherwise \c NULL.
+ */
+struct ast *file_scope_find_var(struct scope *scope, char *id);
+struct ast *file_scope_find_symbol(struct scope *scope, char *id);
+
+/**
+ * Find a procedure with ID visible to \p scope.
+ *
+ * @param scope Scope to look in.
+ * @param id ID of procedure to find.
+ * @return Pointer to the AST node corresponding to \p id if found,
+ * otherwise \c NULL.
+ */
+struct ast *file_scope_find_proc(struct scope *scope, char *id);
+
+#define foreach_visible(iter, init) \
+ for (struct visible *iter = init; iter; iter = iter->next)
+
+#endif /* SCOPE_H */
diff --git a/include/fwd/vec.h b/include/fwd/vec.h
new file mode 100644
index 0000000..1b78c59
--- /dev/null
+++ b/include/fwd/vec.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+#ifndef VEC_H
+#define VEC_H
+
+#include <stddef.h>
+
+struct vec {
+ size_t n;
+ size_t s;
+ size_t ns;
+ void *buf;
+};
+
+struct vec vec_create(size_t s);
+void vec_destroy(struct vec *v);
+void vec_reset(struct vec *v);
+
+size_t vec_len(struct vec *v);
+void *vec_at(struct vec *v, size_t i);
+void *vec_back(struct vec *v);
+void *vec_pop(struct vec *v);
+void vec_append(struct vec *v, void *n);
+
+typedef int (*vec_comp_t)(const void *, const void *);
+void vec_sort(struct vec *v, vec_comp_t comp);
+
+#define foreach_vec(iter, v) \
+ for (size_t iter = 0; iter < vec_len(&v); ++iter)
+
+#define vect_at(type, v, i) \
+ *(type *)vec_at(&v, i)
+
+#define vect_append(type, v, e) \
+ vec_append(&v, (type *)(e))
+
+#define vect_back(type, v) \
+ *(type *)vec_back(&v)
+
+#define vect_pop(type, v) \
+ *(type *)vec_pop(&v)
+
+#define vec_uninit(v) \
+ (v.buf == NULL)
+
+#endif /* VEC_H */