aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKimplul <kimi.h.kuparinen@gmail.com>2024-04-27 00:22:13 +0300
committerKimplul <kimi.h.kuparinen@gmail.com>2024-04-27 00:22:13 +0300
commit449ca1e570aa421992bbe98c6928def1ba8896fd (patch)
treee64321b0c26da220c804403edc644079a81621dc
parent515ad657ec6ef685c9c91479540518a0091f1516 (diff)
downloadposthaste-449ca1e570aa421992bbe98c6928def1ba8896fd.tar.gz
posthaste-449ca1e570aa421992bbe98c6928def1ba8896fd.zip
forgot to exclude include in gitignore
-rw-r--r--.gitignore1
-rw-r--r--include/posthaste/ast.h343
-rw-r--r--include/posthaste/check.h10
-rw-r--r--include/posthaste/compile.h8
-rw-r--r--include/posthaste/core.h7
-rw-r--r--include/posthaste/date.h23
-rw-r--r--include/posthaste/debug.h38
-rw-r--r--include/posthaste/execute.h8
-rw-r--r--include/posthaste/interpret.h8
-rw-r--r--include/posthaste/lower.h107
-rw-r--r--include/posthaste/parser.h57
-rw-r--r--include/posthaste/scope.h31
-rw-r--r--include/posthaste/utils.h14
-rw-r--r--include/posthaste/vec.h45
14 files changed, 700 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 1fbcc14..991c3a2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ build
deps.mk
docs/output
posthaste
+!include/posthaste
diff --git a/include/posthaste/ast.h b/include/posthaste/ast.h
new file mode 100644
index 0000000..c47e28c
--- /dev/null
+++ b/include/posthaste/ast.h
@@ -0,0 +1,343 @@
+#ifndef AST_H
+#define AST_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+/* used to detect semantic loops.
+ * When entering a node during type checking, AST_FLAG_INIT is set for the node,
+ * and when exiting, AST_FLAG_CHECKED is set.
+ * If we enter a node and AST_FLAG_INIT is set without AST_FLAG_CHECKED, we're
+ * in a loop (for example, two auto functions that just contains calls to each other)
+ * and type checking has failed.
+ */
+enum ast_flags {
+ AST_FLAG_INIT = (1 << 0),
+ AST_FLAG_CHECKED = (1 << 1),
+};
+
+enum ast_kind {
+ /* reserve 0 for uninitialized nodes */
+ AST_PROC_DEF = 1,
+ AST_FUNC_DEF,
+ AST_VAR_DEF,
+ AST_FORMAL_DEF,
+ AST_CONST_INT,
+ AST_CONST_DATE,
+ AST_RETURN,
+ AST_ASSIGN,
+ AST_ID,
+ AST_DOT, /* write attribute */
+ AST_ATTR, /* read attribute */
+ AST_PRINT,
+ AST_CONST_STRING,
+ AST_PROC_CALL,
+ AST_FUNC_CALL,
+ AST_UNTIL,
+ AST_UNLESS,
+ AST_UNLESS_EXPR,
+ AST_EQ,
+ AST_LT,
+ AST_ADD,
+ AST_SUB,
+ AST_MUL,
+ AST_DIV,
+ AST_POS, /* + INT */
+ AST_NEG, /* - INT */
+ /* hidden, internal nodes */
+ AST_DATE_ADD,
+ AST_DATE_SUB,
+ AST_DATE_DIFF,
+ AST_PRINT_STRING,
+ AST_PRINT_DATE,
+ AST_PRINT_BOOL,
+ AST_PRINT_INT,
+ AST_BUILTIN_CALL,
+};
+
+enum type_kind {
+ TYPE_DATE = 1, TYPE_INT,
+ /* internal */
+ TYPE_STRING, TYPE_BOOL, TYPE_VOID,
+ /* placeholder for auto functions before their return type is deduced */
+ TYPE_AUTO,
+};
+
+/* used by lower.c, defined here to avoid circular dependencies */
+struct loc {
+ /* is this a local location? 1 local, 0 global */
+ uint32_t l : 1;
+ /* offset within either local stack or global array */
+ uint32_t o : 31;
+};
+
+/* where a node was generated from in the source file */
+struct src_loc {
+ int first_line;
+ int last_line;
+ int first_col;
+ int last_col;
+};
+
+struct ast {
+ enum ast_kind k;
+ enum type_kind t;
+
+ /* holds name of variables/funcs/procs */
+ char *id;
+
+ /* hold name of return type in func/proc defs */
+ char *type;
+
+ /* holds constant value, both ints and dates */
+ int64_t v;
+
+ /* references to other ast nodes, exact usage depends on the kind of
+ * node this is. See macros below. */
+ struct ast *a0;
+ struct ast *a1;
+ struct ast *a2;
+
+ /* next ast node in list */
+ struct ast *n;
+
+ struct src_loc loc;
+
+ /* scope that this node belongs to */
+ struct scope *scope;
+
+ enum ast_flags flags;
+
+ /* used by lower.c as a kind of cache for mapping variable definitions
+ * to locations */
+ struct loc l;
+};
+
+static inline unsigned ast_flags(struct ast *n, enum ast_flags flags)
+{
+ return n->flags & flags;
+}
+
+static inline void ast_set_flags(struct ast *n, enum ast_flags flags)
+{
+ n->flags |= flags;
+}
+
+struct ast *gen_ast(enum ast_kind kind,
+ struct ast *a0,
+ struct ast *a1,
+ struct ast *a2,
+ char *id,
+ char *type,
+ int64_t v,
+ struct src_loc loc);
+
+/* nifty way to check that all ASTs are of the kind we think they are.
+ * Generally all accesses to ast members a0 - a2 should go through these macros.
+ * id is also usually best to access via this macro, but a simple null check
+ * works as a 'polymorphic' check. Wrappers for each ast node kind follow. */
+#define get_a0(x, kind) *({assert((x)->k == kind); &(x)->a0;})
+#define get_a1(x, kind) *({assert((x)->k == kind); &(x)->a1;})
+#define get_a2(x, kind) *({assert((x)->k == kind); &(x)->a2;})
+#define get_id(x, kind) *({assert((x)->k == kind); &(x)->id;})
+
+#define gen_id(id, loc) \
+ gen_ast(AST_ID, NULL, NULL, NULL, id, NULL, 0, loc)
+
+#define var_id(x) get_id(x, AST_VAR_DEF)
+#define var_expr(x) get_a0(x, AST_VAR_DEF)
+#define gen_var_def(id, expr, loc) \
+ gen_ast(AST_VAR_DEF, expr, NULL, NULL, id, NULL, 0, loc)
+
+#define formal_id(x) get_id(x, AST_FORMAL_DEF)
+#define gen_formal_def(id, type, loc) \
+ gen_ast(AST_FORMAL_DEF, NULL, NULL, NULL, id, type, 0, loc)
+
+#define proc_call_id(x) get_id(x, AST_PROC_CALL)
+#define proc_call_args(x) get_a0(x, AST_PROC_CALL)
+#define gen_proc_call(id, args, loc) \
+ gen_ast(AST_PROC_CALL, args, NULL, NULL, id, NULL, 0, loc)
+
+#define func_call_id(x) get_id(x, AST_FUNC_CALL)
+#define func_call_args(x) get_a0(x, AST_FUNC_CALL)
+#define gen_func_call(id, args, loc) \
+ gen_ast(AST_FUNC_CALL, args, NULL, NULL, id, NULL, 0, loc)
+
+#define assign_l(x) get_a0(x, AST_ASSIGN)
+#define assign_r(x) get_a1(x, AST_ASSIGN)
+#define gen_assign(l, r, loc) \
+ gen_ast(AST_ASSIGN, l, r, NULL, NULL, NULL, 0, loc)
+
+#define dot_base(x) get_a0(x, AST_DOT)
+#define gen_dot(base, prop, loc) \
+ gen_ast(AST_DOT, base, NULL, NULL, prop, NULL, 0, loc)
+
+#define print_items(x) get_a0(x, AST_PRINT)
+#define gen_print(items, loc) \
+ gen_ast(AST_PRINT, items, NULL, NULL, NULL, NULL, 0, loc)
+
+#define gen_const_string(s, loc) \
+ gen_ast(AST_CONST_STRING, NULL, NULL, NULL, s, NULL, 0, loc)
+
+#define return_expr(x) get_a0(x, AST_RETURN)
+#define gen_return(expr, loc) \
+ gen_ast(AST_RETURN, expr, NULL, NULL, NULL, NULL, 0, loc)
+
+#define until_body(x) get_a0(x, AST_UNTIL)
+#define until_cond(x) get_a1(x, AST_UNTIL)
+#define gen_until(body, cond, loc) \
+ gen_ast(AST_UNTIL, body, cond, NULL, NULL, NULL, 0, loc)
+
+#define eq_l(x) get_a0(x, AST_EQ)
+#define eq_r(x) get_a1(x, AST_EQ)
+#define gen_eq(l, r, loc) \
+ gen_ast(AST_EQ, l, r, NULL, NULL, NULL, 0, loc)
+
+#define lt_l(x) get_a0(x, AST_LT)
+#define lt_r(x) get_a1(x, AST_LT)
+#define gen_lt(l, r, loc) \
+ gen_ast(AST_LT, l, r, NULL, NULL, NULL, 0, loc)
+
+#define add_l(x) get_a0(x, AST_ADD)
+#define add_r(x) get_a1(x, AST_ADD)
+#define gen_add(l, r, loc) \
+ gen_ast(AST_ADD, l, r, NULL, NULL, NULL, 0, loc)
+
+#define sub_l(x) get_a0(x, AST_SUB)
+#define sub_r(x) get_a1(x, AST_SUB)
+#define gen_sub(l, r, loc) \
+ gen_ast(AST_SUB, l, r, NULL, NULL, NULL, 0, loc)
+
+#define mul_l(x) get_a0(x, AST_MUL)
+#define mul_r(x) get_a1(x, AST_MUL)
+#define gen_mul(l, r, loc) \
+ gen_ast(AST_MUL, l, r, NULL, NULL, NULL, 0, loc)
+
+#define div_l(x) get_a0(x, AST_DIV)
+#define div_r(x) get_a1(x, AST_DIV)
+#define gen_div(l, r, loc) \
+ gen_ast(AST_DIV, l, r, NULL, NULL, NULL, 0, loc)
+
+#define pos_expr(x) get_a0(x, AST_POS)
+#define gen_pos(expr, loc) \
+ gen_ast(AST_POS, expr, NULL, NULL, NULL, NULL, 0, loc)
+
+#define neg_expr(x) get_a0(x, AST_NEG)
+#define gen_neg(expr, loc) \
+ gen_ast(AST_NEG, expr, NULL, NULL, NULL, NULL, 0, loc)
+
+#define attr_base(x) get_a0(x, AST_ATTR)
+#define gen_attr(base, attr, loc) \
+ gen_ast(AST_ATTR, base, NULL, NULL, attr, NULL, 0, loc)
+
+#define gen_const_int(v, loc) \
+ gen_ast(AST_CONST_INT, NULL, NULL, NULL, NULL, NULL, v, loc)
+
+#define gen_const_date(d, loc) \
+ gen_ast(AST_CONST_DATE, NULL, NULL, NULL, NULL, NULL, d, loc)
+
+#define unless_body(x) get_a0(x, AST_UNLESS)
+#define unless_cond(x) get_a1(x, AST_UNLESS)
+#define unless_otherwise(x) get_a2(x, AST_UNLESS)
+#define gen_unless(body, cond, otherwise, loc) \
+ gen_ast(AST_UNLESS, body, cond, otherwise, NULL, NULL, 0, loc)
+
+#define unless_expr_body(x) get_a0(x, AST_UNLESS_EXPR)
+#define unless_expr_cond(x) get_a1(x, AST_UNLESS_EXPR)
+#define unless_expr_otherwise(x) get_a2(x, AST_UNLESS_EXPR)
+#define gen_unless_expr(body, cond, otherwise, loc) \
+ gen_ast(AST_UNLESS_EXPR, body, cond, otherwise, NULL, NULL, 0, loc)
+
+#define proc_id(x) get_id(x, AST_PROC_DEF)
+#define proc_formals(x) get_a0(x, AST_PROC_DEF)
+#define proc_vars(x) get_a1(x, AST_PROC_DEF)
+#define proc_body(x) get_a2(x, AST_PROC_DEF)
+#define gen_proc_def(id, rtype, formals, vars, body, loc) \
+ gen_ast(AST_PROC_DEF, formals, vars, body, id, rtype, 0, loc)
+
+#define func_id(x) get_id(x, AST_FUNC_DEF)
+#define func_formals(x) get_a0(x, AST_FUNC_DEF)
+#define func_vars(x) get_a1(x, AST_FUNC_DEF)
+#define func_body(x) get_a2(x, AST_FUNC_DEF)
+#define gen_func_def(id, rtype, formals, vars, body, loc) \
+ gen_ast(AST_FUNC_DEF, formals, vars, body, id, rtype, 0, loc)
+
+#define date_diff_l(x) get_a0(x, AST_DATE_DIFF)
+#define date_diff_r(x) get_a1(x, AST_DATE_DIFF)
+
+#define date_add_l(x) get_a0(x, AST_DATE_ADD)
+#define date_add_r(x) get_a1(x, AST_DATE_ADD)
+
+#define date_sub_l(x) get_a0(x, AST_DATE_SUB)
+#define date_sub_r(x) get_a1(x, AST_DATE_SUB)
+
+/* used by fixup_print_item() below */
+static inline enum ast_kind print_item(enum type_kind k)
+{
+ switch (k) {
+ case TYPE_STRING: return AST_PRINT_STRING;
+ case TYPE_INT: return AST_PRINT_INT;
+ case TYPE_DATE: return AST_PRINT_DATE;
+ case TYPE_BOOL: return AST_PRINT_BOOL;
+ default:
+ }
+
+ abort();
+}
+
+/* convert a print item to an explicit AST node for printing that specific type,
+ * makes lowering ast to bytecode a little bit easier */
+#define fixup_print_item(expr) \
+ gen_ast(print_item(expr->t), expr, NULL, NULL, NULL, NULL, 0, expr->loc)
+
+#define print_date_expr(x) get_a0(x, AST_PRINT_DATE)
+#define print_int_expr(x) get_a0(x, AST_PRINT_INT)
+#define print_string_expr(x) get_a0(x, AST_PRINT_STRING)
+#define print_bool_expr(x) get_a0(x, AST_PRINT_BOOL)
+
+#define builtin_call_id(x) get_id(x, AST_BUILTIN_CALL)
+
+/* get last ast node in a list */
+struct ast *ast_last(struct ast *n);
+
+#ifdef DEBUG
+void ast_dump(int depth, struct ast *root);
+void ast_dump_list(int depth, struct ast *root);
+#endif /* DEBUG */
+
+/* free memory owned by ast nodes */
+void destroy_ast_nodes();
+
+typedef int (*ast_callback_t)(struct ast *, 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);
+
+size_t ast_list_len(struct ast *l);
+
+#define foreach_node(iter, nodes) \
+ for (struct ast *iter = nodes; iter; iter = iter->n)
+
+/* convert type name to a string representation */
+static inline const char *type_str(enum type_kind k)
+{
+ switch (k) {
+ case TYPE_INT: return "INT";
+ case TYPE_DATE: return "DATE";
+ case TYPE_STRING: return "STRING";
+ case TYPE_BOOL: return "BOOL";
+ case TYPE_VOID: return "VOID";
+ /* purposefully skip TYPE_AUTO as it just means we
+ * haven't assigned a type to it yet */
+ default: break;
+ }
+
+ return "NO TYPE";
+}
+
+#endif /* AST_H */
diff --git a/include/posthaste/check.h b/include/posthaste/check.h
new file mode 100644
index 0000000..a796fc7
--- /dev/null
+++ b/include/posthaste/check.h
@@ -0,0 +1,10 @@
+#ifndef POSTHASTE_CHECK_H
+#define POSTHASTE_CHECK_H
+
+#include <posthaste/scope.h>
+
+/* stuff related to semantic checking */
+
+int check(struct scope *scope, struct ast *tree);
+
+#endif /* POSTHASTE_CHECK_H */
diff --git a/include/posthaste/compile.h b/include/posthaste/compile.h
new file mode 100644
index 0000000..e149ac6
--- /dev/null
+++ b/include/posthaste/compile.h
@@ -0,0 +1,8 @@
+#ifndef POSTHASTE_COMPILE_H
+#define POSTHASTE_COMPILE_H
+
+/* stuff related to compiling bytecode to machine code */
+
+void compile();
+
+#endif /* POSTHASTE_COMPILE_H */
diff --git a/include/posthaste/core.h b/include/posthaste/core.h
new file mode 100644
index 0000000..ba52525
--- /dev/null
+++ b/include/posthaste/core.h
@@ -0,0 +1,7 @@
+#ifndef CORE_H
+#define CORE_H
+
+/* handles parsing, type checking and execution of fname */
+int run(const char *fname);
+
+#endif /* CORE_H */
diff --git a/include/posthaste/date.h b/include/posthaste/date.h
new file mode 100644
index 0000000..d091d79
--- /dev/null
+++ b/include/posthaste/date.h
@@ -0,0 +1,23 @@
+#ifndef DATE_H
+#define DATE_H
+
+/* stuff related to posthaste date handling */
+
+#include <stdint.h>
+#include <time.h>
+#include <stdbool.h>
+
+/* store dates as regular integers, with lowest five bits being the day, the
+ * next four the month, and the rest the year */
+typedef uint64_t ph_date_t;
+
+ph_date_t date_from_string(const char str[static 11]);
+ph_date_t date_from_numbers(unsigned year, unsigned month, unsigned day);
+void date_to_string(char str[static 11], ph_date_t date);
+void date_split(ph_date_t date, unsigned *year, unsigned *month, unsigned *day);
+bool date_valid(ph_date_t date);
+struct tm tm_from_date(ph_date_t date);
+ph_date_t date_from_tm(struct tm time);
+ph_date_t current_date();
+
+#endif /* DATE_H */
diff --git a/include/posthaste/debug.h b/include/posthaste/debug.h
new file mode 100644
index 0000000..fa3d2b9
--- /dev/null
+++ b/include/posthaste/debug.h
@@ -0,0 +1,38 @@
+#ifndef POSTHASTE_DEBUG_H
+#define POSTHASTE_DEBUG_H
+
+/* stuff related to printing out issues in code to the user */
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#include <posthaste/scope.h>
+#include <posthaste/ast.h>
+
+struct src_issue {
+ const char *fname;
+ const char *buf;
+ struct src_loc loc;
+};
+
+void src_issue(struct src_issue issue, const char *msg, ...);
+void vsrc_issue(struct src_issue issue, const char *msg, va_list args);
+
+/* warn on incompatible types in format string */
+__attribute__((format (printf, 3, 4)))
+static inline void semantic_error(struct scope *scope, struct ast *n,
+ const char *msg, ...)
+{
+ va_list args;
+ va_start(args, msg);
+ struct src_issue issue;
+ issue.loc = n->loc;
+ /* technically we could also just iterate upwards through parent scopes
+ * until we reach a file scope, but this works as well */
+ issue.fname = scope->fname;
+ issue.buf = scope->buf;
+ vsrc_issue(issue, msg, args);
+ va_end(args);
+}
+
+#endif /* POSTHASTE_DEBUG_H */
diff --git a/include/posthaste/execute.h b/include/posthaste/execute.h
new file mode 100644
index 0000000..ef51549
--- /dev/null
+++ b/include/posthaste/execute.h
@@ -0,0 +1,8 @@
+#ifndef POSTHASTE_EXECUTE_H
+#define POSTHASTE_EXECUTE_H
+
+/* stuff related to executing compiled machine code */
+
+void execute();
+
+#endif /* POSTHASTE_EXECUTE_H */
diff --git a/include/posthaste/interpret.h b/include/posthaste/interpret.h
new file mode 100644
index 0000000..50bdbf3
--- /dev/null
+++ b/include/posthaste/interpret.h
@@ -0,0 +1,8 @@
+#ifndef POSTHASTE_INTERPRET_H
+#define POSTHASTE_INTERPRET_H
+
+/* stuff related to interpreting our bytecode */
+
+void interpret();
+
+#endif /* POSTHASTE_INTERPRET_H */
diff --git a/include/posthaste/lower.h b/include/posthaste/lower.h
new file mode 100644
index 0000000..d5b1d7d
--- /dev/null
+++ b/include/posthaste/lower.h
@@ -0,0 +1,107 @@
+#ifndef POSTHASTE_LOWER_H
+#define POSTHASTE_LOWER_H
+
+/* stuff related to lowering AST to bytecode */
+
+#include <posthaste/ast.h>
+#include <posthaste/vec.h>
+
+enum insn_kind {
+ LABEL, /* no-op during interpretation,
+ used by JIT engine to keep track of jump branch destinations.
+ Each branch/jump must have a corresponding label!*/
+ CALL,
+ MOVE,
+ ADD,
+ SUB,
+ MUL,
+ DIV,
+ ARG, /* push location to arg stack */
+ RETVAL, /* move return value to location */
+ PRINT_DATE,
+ PRINT_STRING,
+ PRINT_INT,
+ PRINT_BOOL,
+ PRINT_NEWLINE,
+ PRINT_SPACE,
+ DATE_ADD,
+ DATE_SUB,
+ DATE_DIFF,
+ STORE_DAY, /* x.day etc */
+ STORE_MONTH,
+ STORE_YEAR,
+ LOAD_DAY, /* x'day etc */
+ LOAD_MONTH,
+ LOAD_YEAR,
+ LOAD_WEEKDAY,
+ LOAD_WEEKNUM,
+ TODAY, /* the builtin Today() is lowered to a single instruction */
+ RET, /* return with value */
+ STOP, /* return without value */
+ CONST, /* push a constant value to loc */
+ EQ,
+ LT,
+ NEG, /* negation, -x */
+ B, /* branch if non-zero */
+ BZ, /* branch if zero */
+ J, /* jump */
+};
+
+struct insn {
+ enum insn_kind k;
+ /* output location */
+ struct loc o;
+
+ /* input locations */
+ struct loc i0;
+ struct loc i1;
+
+ /* potential constant value */
+ union {
+ int64_t v;
+ char *s;
+ };
+};
+
+/* after lowering, there's no real difference between posthaste 'functions' and
+ * 'procedures', so I just use fn to refer to either or */
+struct fn {
+ char *name;
+
+ size_t idx;
+
+ /* virtual stack pointer used during lowering to choose where to place
+ * values */
+ size_t sp;
+
+ /* maximum stack pointer value to know how much stack space to allocate */
+ size_t max_sp;
+
+ /* instruction buffer */
+ struct vec insns;
+
+ /* used by jit */
+ /* how many formal parameters */
+ size_t params;
+
+ /* machine code buffer, for whatever reason seems to commonly be called 'arena'
+ * in JIT compilers, just following the convention here*/
+ void *arena;
+
+ /* size of arena */
+ size_t size;
+};
+
+int lower_ast(struct ast *tree);
+
+struct fn *find_fn(size_t idx);
+size_t num_globals();
+
+void destroy_lowering();
+
+static inline bool is_null_loc(struct loc l)
+{
+ return l.l == 0 && l.o == 1;
+}
+
+#endif /* POSTHASTE_LOWER_H */
diff --git a/include/posthaste/parser.h b/include/posthaste/parser.h
new file mode 100644
index 0000000..d3a3e6b
--- /dev/null
+++ b/include/posthaste/parser.h
@@ -0,0 +1,57 @@
+#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 <posthaste/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 Contexts of \p fname.
+ */
+void parse(struct parser *p, const char *fname, const char *buf);
+
+#endif /* PARSER_H */
diff --git a/include/posthaste/scope.h b/include/posthaste/scope.h
new file mode 100644
index 0000000..8c9dbf9
--- /dev/null
+++ b/include/posthaste/scope.h
@@ -0,0 +1,31 @@
+#ifndef POSTHASTE_SCOPE_H
+#define POSTHASTE_SCOPE_H
+
+#include <posthaste/ast.h>
+#include <posthaste/vec.h>
+
+struct scope {
+ size_t id;
+ struct scope *parent;
+
+ const char *fname;
+ const char *buf;
+
+ struct vec visible;
+};
+
+struct scope *create_scope();
+void destroy_scopes();
+
+void scope_add_scope(struct scope *parent, struct scope *scope);
+void scope_set_file(struct scope *scope, const char *fname, const char *buf);
+
+struct ast *scope_find(struct scope *scope, char *id);
+struct ast *file_scope_find(struct scope *scope, char *id);
+
+int scope_add_var(struct scope *scope, struct ast *var);
+int scope_add_formal(struct scope *scope, struct ast *var);
+int scope_add_proc(struct scope *scope, struct ast *proc);
+int scope_add_func(struct scope *scope, struct ast *func);
+
+#endif /* POSTHASTE_SCOPE_H */
diff --git a/include/posthaste/utils.h b/include/posthaste/utils.h
new file mode 100644
index 0000000..603455a
--- /dev/null
+++ b/include/posthaste/utils.h
@@ -0,0 +1,14 @@
+#ifndef POSTHASTE_UTILS_H
+#define POSTHASTE_UTILS_H
+
+/* random things not strictly related to anything else */
+
+static inline bool same_id(const char *a, const char *b)
+{
+ /* I always forget to compare to zero */
+ return strcmp(a, b) == 0;
+}
+
+#define UNUSED(x) (void)x
+
+#endif /* POSTHASTE_UTILS_H */
diff --git a/include/posthaste/vec.h b/include/posthaste/vec.h
new file mode 100644
index 0000000..2ffa80c
--- /dev/null
+++ b/include/posthaste/vec.h
@@ -0,0 +1,45 @@
+#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);
+void vec_reserve(struct vec *v, size_t ns);
+
+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 */