diff --git a/Makefile b/Makefile
index c35b6ab..77a68ad 100644
--- a/Makefile
+++ b/Makefile
@@ -34,7 +34,7 @@ endif
 
 HEADERS = $(shell find . -name *.h)
 
-ASM_SOURCES = $(shell find asm/ -name *.c)
+ASM_SOURCES = $(shell find asm/ -name *.c -not -name main.c)
 ASM_OBJECTS = $(patsubst %.c,build/%.o,$(ASM_SOURCES))
 
 VM_SOURCES = $(shell find vm/ -name *.c)
@@ -43,7 +43,7 @@ VM_OBJECTS = $(patsubst %.c,build/%.o,$(VM_SOURCES))
 KERN_SOURCES = $(shell find kern/ -name *.c)
 KERN_OBJECTS = $(patsubst %.c,build/%.o,$(KERN_SOURCES))
 
-all: bin/vm bin/build_disk_image
+all: bin/vm bin/build_disk_image bin/asm
 
 bin/vm: $(VM_OBJECTS) $(ASM_OBJECTS) build/image
 	@mkdir -p $(dir $@)
@@ -57,6 +57,10 @@ bin/build_disk_image: $(KERN_OBJECTS) $(ASM_OBJECTS)
 	@mkdir -p $(dir $@)
 	$(CC) $^ -o $@ $(F_FLAGS) $(OPTIMIZATION) $(L_FLAGS)
 
+bin/asm: $(ASM_OBJECTS) build/asm/main.o
+	@mkdir -p $(dir $@)
+	$(CC) $^ -o $@ $(F_FLAGS) $(OPTIMIZATION) $(L_FLAGS)
+
 build/%.o: %.c $(HEADERS)
 	@mkdir -p $(dir $@)
 	$(CC) $< -c -o $@ $(C_FLAGS) $(OPTIMIZATION) $(F_FLAGS)
diff --git a/asm/main.c b/asm/main.c
new file mode 100644
index 0000000..452bde1
--- /dev/null
+++ b/asm/main.c
@@ -0,0 +1,874 @@
+#include "common/arch.h"
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static inline bool str_includes(const char* str, char ch)
+{
+    for (size_t i = 0; str[i] != '\0'; ++i) {
+        if (str[i] == ch) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static inline char* asm_strndup(const char* str, size_t len)
+{
+    char* val = calloc(len + 1, sizeof(char));
+    strncpy(val, str, len);
+    return val;
+}
+
+typedef struct {
+    size_t idx;
+    int line;
+    int col;
+} Loc;
+
+typedef enum {
+    TT_Err,
+    TT_Eof,
+    TT_Ident,
+    TT_Int,
+    TT_Binary,
+    TT_Hex,
+    TT_Newline = '\n',
+    TT_DoubleLt,
+    TT_DoubleGt,
+    TT_Pipe = '|',
+    TT_Hat = '^',
+    TT_Ampersand = '&',
+    TT_Plus = '+',
+    TT_Minus = '-',
+    TT_Asterisk = '*',
+    TT_Slash = '/',
+    TT_Percent = '%',
+    TT_LParen = '(',
+    TT_RParen = ')',
+    TT_LBracket = '[',
+    TT_RBracket = ']',
+    TT_Dot = '.',
+    TT_Comma = ',',
+    TT_Colon = ':',
+    TT_Exclamation = '!',
+} TokTy;
+
+typedef struct {
+    TokTy ty;
+    Loc loc;
+    size_t len;
+} Tok;
+
+typedef struct {
+    const char* filename;
+    const char* text;
+    size_t text_len;
+    size_t idx;
+    int line;
+    int col;
+    char ch;
+    bool error_occured;
+} Lexer;
+
+void lexer_construct(Lexer* lexer, const char* filename, const char* text)
+{
+    *lexer = (Lexer) {
+        .filename = filename,
+        .text = text,
+        .text_len = strlen(text),
+        .idx = 0,
+        .line = 1,
+        .col = 1,
+        .ch = text[0],
+        .error_occured = false,
+    };
+}
+
+static inline bool lexer_done(const Lexer* lexer)
+{
+    return lexer->idx >= lexer->text_len;
+}
+
+static inline void lexer_step(Lexer* lexer)
+{
+    if (lexer_done(lexer)) {
+        return;
+    }
+    if (lexer->ch == '\n') {
+        lexer->line += 1;
+        lexer->col = 1;
+    } else {
+        lexer->col += 1;
+    }
+    lexer->idx += 1;
+    lexer->ch = lexer->text[lexer->idx];
+}
+
+static inline Loc lexer_loc(const Lexer* lexer)
+{
+    return (Loc) { .idx = lexer->idx, .line = lexer->line, .col = lexer->col };
+}
+
+static inline Tok lexer_tok(const Lexer* lexer, TokTy ty, Loc loc)
+{
+    return (Tok) { .ty = ty, .loc = loc, .len = lexer->idx - loc.idx };
+}
+
+static inline void lexer_report(Lexer* lexer, const char* msg, Loc loc)
+{
+    lexer->error_occured = true;
+
+    size_t line_start = loc.idx;
+    while (line_start > 0 && lexer->text[line_start] != '\n') {
+        line_start -= 1;
+    }
+    if (lexer->text[line_start] == '\n') {
+        line_start += 1;
+    }
+    size_t line_end = loc.idx + 1;
+    while (line_end < lexer->text_len && lexer->text[line_end] != '\n') {
+        line_end += 1;
+    }
+    const char* line = &lexer->text[line_start];
+    int line_len = (int)line_end - (int)line_start;
+
+    fprintf(stderr,
+        "error: %s\n  --> %s:%d:%d\n     |\n%5d|%.*s\n     |%*c^\n",
+        msg,
+        lexer->filename,
+        loc.line,
+        loc.col,
+        loc.line,
+        line_len,
+        line,
+        loc.col - 2,
+        ' ');
+}
+
+Tok lexer_next(Lexer* lexer)
+{
+    const char* ident_chars = "abcdefghijklmnopqrstuvwxyz"
+                              "ABCDEFGHIJKLMNOPQRSTUVWXYZ_";
+    const char* int_chars = "1234567890";
+    const char* hex_chars = "01234567889abcdefABCDEF";
+
+    Loc loc = lexer_loc(lexer);
+    if (lexer_done(lexer)) {
+        return lexer_tok(lexer, TT_Eof, loc);
+    }
+    if (lexer->ch == '\n') {
+        lexer_step(lexer);
+        return lexer_tok(lexer, '\n', loc);
+    } else if (str_includes(" \t", lexer->ch)) {
+        while (!lexer_done(lexer) && str_includes(" \t", lexer->ch)) {
+            lexer_step(lexer);
+        }
+        return lexer_next(lexer);
+    } else if (str_includes(ident_chars, lexer->ch)) {
+        while (!lexer_done(lexer)
+            && (str_includes(ident_chars, lexer->ch)
+                || str_includes(int_chars, lexer->ch))) {
+            lexer_step(lexer);
+        }
+        return lexer_tok(lexer, TT_Ident, loc);
+    } else if (str_includes(int_chars, lexer->ch) && lexer->ch != '0') {
+        while (!lexer_done(lexer) && (str_includes(int_chars, lexer->ch))) {
+            lexer_step(lexer);
+        }
+        return lexer_tok(lexer, TT_Int, loc);
+    } else if (lexer->ch == ';') {
+        while (!lexer_done(lexer) && lexer->ch != '\n') {
+            lexer_step(lexer);
+        }
+        return lexer_next(lexer);
+    } else if (lexer->ch == '0') {
+        lexer_step(lexer);
+        if (lexer->ch == 'b') {
+            lexer_step(lexer);
+            if (lexer_done(lexer) || !str_includes("01", lexer->ch)) {
+                lexer_report(lexer, "malformed binary literal", loc);
+                return lexer_tok(lexer, TT_Err, loc);
+            }
+            while (!lexer_done(lexer) && str_includes("01", lexer->ch)) {
+                lexer_step(lexer);
+            }
+            return lexer_tok(lexer, TT_Binary, loc);
+        } else if (lexer->ch == 'x') {
+            lexer_step(lexer);
+            if (lexer_done(lexer) || !str_includes(hex_chars, lexer->ch)) {
+                lexer_report(lexer, "malformed hex literal", loc);
+                return lexer_tok(lexer, TT_Err, loc);
+            }
+            while (!lexer_done(lexer) && str_includes(hex_chars, lexer->ch)) {
+                lexer_step(lexer);
+            }
+            return lexer_tok(lexer, TT_Hex, loc);
+
+        } else {
+            return lexer_tok(lexer, TT_Int, loc);
+        }
+    } else if (lexer->ch == '<') {
+        lexer_step(lexer);
+        if (!lexer_done(lexer) && lexer->ch == '<') {
+            lexer_step(lexer);
+            return lexer_tok(lexer, TT_DoubleLt, loc);
+        } else {
+            lexer_report(lexer, "expected '<'", loc);
+            return lexer_tok(lexer, TT_Err, loc);
+        }
+    } else if (lexer->ch == '>') {
+        lexer_step(lexer);
+        if (!lexer_done(lexer) && lexer->ch == '>') {
+            lexer_step(lexer);
+            return lexer_tok(lexer, TT_DoubleGt, loc);
+        } else {
+            lexer_report(lexer, "expected '>'", loc);
+            return lexer_tok(lexer, TT_Err, loc);
+        }
+    } else if (str_includes("|^&+-*/%()[].,:!", lexer->ch)) {
+        char ch = lexer->ch;
+        lexer_step(lexer);
+        return lexer_tok(lexer, (TokTy)ch, loc);
+    } else {
+        lexer_report(lexer, "illegal character '%c'", loc);
+        lexer_step(lexer);
+        return lexer_next(lexer);
+    }
+}
+
+// typedef enum {
+//     M_err,
+//     M_d8,
+//     M_d16,
+//     M_nop,
+//     M_hlt,
+//     M_jmp,
+//     M_jmpf,
+//     M_jnz,
+//     M_cmp,
+//     M_mov,
+//     M_in,
+//     M_out,
+//     M_call,
+//     M_callf,
+//     M_ret,
+//     M_retf,
+//     M_lit,
+//     M_int,
+//     M_iret,
+// } Mnemonic;
+
+typedef struct PLabel PLabel;
+
+struct PLabel {
+    PLabel* next;
+    char* ident;
+    Loc loc;
+    bool sub_label;
+};
+
+PLabel* plabel_new(PLabel* next, char* ident, bool sub_label, Loc loc)
+{
+    PLabel* label = malloc(sizeof(PLabel));
+    *label = (PLabel) { next, ident, loc, sub_label };
+    return label;
+}
+void plabel_free(PLabel* label)
+{
+    if (!label) {
+        return;
+    }
+    plabel_free(label->next);
+    free(label->ident);
+    free(label);
+}
+
+typedef enum {
+    PoTy_Reg,
+    PoTy_Imm,
+    PoTy_Ident,
+    PoTy_SubLabel,
+    PoTy_MemU8,
+    PoTy_MemU16,
+    PoTy_Not,
+    PoTy_Negate,
+    PoTy_Or,
+    PoTy_Xor,
+    PoTy_And,
+    PoTy_Shl,
+    PoTy_Shr,
+    PoTy_Add,
+    PoTy_Sub,
+    PoTy_Mul,
+    PoTy_Div,
+    PoTy_Mod,
+} POperandTy;
+
+typedef struct POperand POperand;
+
+struct POperand {
+    POperandTy ty;
+    Loc loc;
+    union {
+        Reg reg;
+        uint16_t imm;
+        char* ident;
+        POperand* operand;
+        struct {
+            POperand* left;
+            POperand* right;
+        };
+    };
+};
+
+POperand* poperand_new_reg(Reg reg, Loc loc)
+{
+    POperand* operand = malloc(sizeof(POperand));
+    *operand = (POperand) { .ty = PoTy_Reg, .loc = loc, .reg = reg };
+    return operand;
+}
+POperand* poperand_new_imm(uint16_t imm, Loc loc)
+{
+    POperand* operand = malloc(sizeof(POperand));
+    *operand = (POperand) { .ty = PoTy_Imm, .loc = loc, .imm = imm };
+    return operand;
+}
+POperand* poperand_new_ident(POperandTy ty, char* ident, Loc loc)
+{
+    POperand* operand = malloc(sizeof(POperand));
+    *operand = (POperand) { .ty = ty, .loc = loc, .ident = ident };
+    return operand;
+}
+POperand* poperand_new_unary(POperandTy ty, POperand* inner, Loc loc)
+{
+    POperand* operand = malloc(sizeof(POperand));
+    *operand = (POperand) { .ty = ty, .loc = loc, .operand = inner };
+    return operand;
+}
+POperand* poperand_new_binary(
+    POperandTy ty, POperand* left, POperand* right, Loc loc)
+{
+    POperand* operand = malloc(sizeof(POperand));
+    *operand
+        = (POperand) { .ty = ty, .loc = loc, .left = left, .right = right };
+    return operand;
+}
+void poperand_free(POperand* operand)
+{
+    switch (operand->ty) {
+        case PoTy_Reg:
+        case PoTy_Imm:
+            break;
+        case PoTy_Ident:
+        case PoTy_SubLabel:
+            free(operand->ident);
+            break;
+        case PoTy_MemU8:
+        case PoTy_MemU16:
+        case PoTy_Not:
+        case PoTy_Negate:
+            poperand_free(operand->operand);
+            break;
+        case PoTy_Or:
+        case PoTy_Xor:
+        case PoTy_And:
+        case PoTy_Shl:
+        case PoTy_Shr:
+        case PoTy_Add:
+        case PoTy_Sub:
+        case PoTy_Mul:
+        case PoTy_Div:
+        case PoTy_Mod:
+            poperand_free(operand->left);
+            poperand_free(operand->right);
+            break;
+    }
+    free(operand);
+}
+
+typedef struct {
+    PLabel* labels;
+    char* op;
+    Loc loc;
+    size_t ops_size;
+    POperand* ops[];
+} PLine;
+
+PLine* pline_new_0(char* op, PLabel* labels, Loc loc)
+{
+    PLine* line = malloc(sizeof(PLine) + sizeof(POperand*) * 0);
+    *line = (PLine) { .labels = labels, .op = op, .loc = loc, .ops_size = 0 };
+    return line;
+}
+PLine* pline_new_1(char* op, PLabel* labels, Loc loc, POperand* op0)
+{
+    PLine* line = malloc(sizeof(PLine) + sizeof(POperand*) * 1);
+    *line = (PLine) { .labels = labels, .op = op, .loc = loc, .ops_size = 1 };
+    line->ops[0] = op0;
+    return line;
+}
+PLine* pline_new_2(
+    char* op, PLabel* labels, Loc loc, POperand* op0, POperand* op1)
+{
+    PLine* line = malloc(sizeof(PLine) + sizeof(POperand*) * 2);
+    *line = (PLine) { .labels = labels, .op = op, .loc = loc, .ops_size = 2 };
+    line->ops[0] = op0;
+    line->ops[1] = op1;
+    return line;
+}
+PLine* pline_new_3(char* op,
+    PLabel* labels,
+    Loc loc,
+    POperand* op0,
+    POperand* op1,
+    POperand* op2)
+{
+    PLine* line = malloc(sizeof(PLine) + sizeof(POperand*) * 3);
+    *line = (PLine) { .labels = labels, .op = op, .loc = loc, .ops_size = 3 };
+    line->ops[0] = op0;
+    line->ops[1] = op1;
+    line->ops[2] = op2;
+    return line;
+}
+void pline_free(PLine* pline)
+{
+    plabel_free(pline->labels);
+    free(pline->op);
+    for (size_t i = 0; i < pline->ops_size; ++i) {
+        poperand_free(pline->ops[i]);
+    }
+    free(pline);
+}
+
+typedef struct {
+    Lexer lexer;
+    Tok tok;
+    Tok eaten;
+    bool error_occured;
+} Parser;
+
+void parser_construct(Parser* parser, const char* filename, const char* text)
+{
+    Lexer lexer;
+    lexer_construct(&lexer, filename, text);
+
+    *parser = (Parser) {
+        .lexer = lexer,
+        .tok = lexer_next(&lexer),
+        .eaten = (Tok) { 0 },
+        .error_occured = false,
+    };
+}
+
+bool parser_done(const Parser* parser)
+{
+    return parser->tok.ty == TT_Eof;
+}
+bool parser_error_occured(const Parser* parser)
+{
+    return parser->error_occured || parser->lexer.error_occured;
+}
+
+static inline void parser_step(Parser* parser)
+{
+    parser->tok = lexer_next(&parser->lexer);
+}
+static inline bool parser_test(const Parser* parser, TokTy ty)
+{
+    return parser->tok.ty == ty;
+}
+static inline bool parser_eat(Parser* parser, TokTy ty)
+{
+    if (parser_test(parser, ty)) {
+        parser->eaten = parser->tok;
+        parser_step(parser);
+        return true;
+    }
+    return false;
+}
+static inline char* parser_ident_val(const Parser* parser, Tok tok)
+{
+    return asm_strndup(&parser->lexer.text[tok.loc.idx], tok.len);
+}
+static inline void parser_report(Parser* parser, const char* msg, Loc loc)
+{
+    parser->error_occured = true;
+
+    size_t line_start = loc.idx;
+    while (line_start > 0 && parser->lexer.text[line_start] != '\n') {
+        line_start -= 1;
+    }
+    if (parser->lexer.text[line_start] == '\n') {
+        line_start += 1;
+    }
+    size_t line_end = loc.idx + 1;
+    while (line_end < parser->lexer.text_len
+        && parser->lexer.text[line_end] != '\n') {
+        line_end += 1;
+    }
+    const char* line = &parser->lexer.text[line_start];
+    int line_len = (int)line_end - (int)line_start;
+
+    fprintf(stderr,
+        "error: %s\n  --> %s:%d:%d\n     |\n%5d|%.*s\n     |%*c^\n",
+        msg,
+        parser->lexer.filename,
+        loc.line,
+        loc.col,
+        loc.line,
+        line_len,
+        line,
+        loc.col - 2,
+        ' ');
+}
+
+static inline void parser_skip_newlines(Parser* parser)
+{
+    while (parser_eat(parser, '\n')) { }
+}
+
+static inline PLabel* parser_parse_labels(
+    Parser* parser, char** ident, Loc* ident_loc)
+{
+    *ident = NULL;
+    PLabel* labels = NULL;
+    while (parser->tok.ty != TT_Eof && *ident == NULL) {
+        parser_skip_newlines(parser);
+        Loc loc = parser->tok.loc;
+        if (parser_eat(parser, '.')) {
+            if (!parser_eat(parser, TT_Ident)) {
+                parser_report(parser, "expected identifier", parser->tok.loc);
+                plabel_free(labels);
+                return NULL;
+            }
+            char* label_ident = parser_ident_val(parser, parser->eaten);
+            if (!parser_eat(parser, ':')) {
+                parser_report(parser, "expected ':'", parser->tok.loc);
+                plabel_free(labels);
+                free(label_ident);
+                return NULL;
+            }
+            labels = plabel_new(labels, label_ident, true, loc);
+        } else if (parser_eat(parser, TT_Ident)) {
+            *ident = parser_ident_val(parser, parser->eaten);
+            *ident_loc = loc;
+            if (!parser_eat(parser, ':')) {
+                break;
+            }
+            labels = plabel_new(labels, *ident, false, loc);
+            *ident = NULL;
+        } else {
+            parser_report(
+                parser, "expected identifier or ':'", parser->tok.loc);
+            plabel_free(labels);
+            return NULL;
+        }
+    }
+    return labels;
+}
+
+static const int parser_binary_prec = 6;
+static inline POperand* parser_parse_operand_2(Parser* parser, int prec);
+
+static inline POperand* parser_parse_operand_0(Parser* parser)
+{
+    Loc loc = parser->tok.loc;
+    if (parser_eat(parser, TT_Ident)) {
+        char* ident = parser_ident_val(parser, parser->eaten);
+        const char* reg_key[10] = {
+            "r0", "r1", "r2", "r3", "r4", "rbp", "rsp", "rfl", "rcs", "rip"
+        };
+        Reg reg_val[10] = { R0, R1, R2, R3, R4, Rbp, Rsp, Rfl, Rcs, Rip };
+        for (size_t i = 0; i < 10; ++i) {
+            if (strcmp(reg_key[i], ident) == 0) {
+                free(ident);
+                return poperand_new_reg(reg_val[i], loc);
+            }
+        }
+        return poperand_new_ident(PoTy_Ident, ident, loc);
+    } else if (parser_eat(parser, TT_Int)) {
+        char* str = parser_ident_val(parser, parser->eaten);
+        uint16_t imm = (uint16_t)strtoul(str, NULL, 10);
+        free(str);
+        return poperand_new_imm(imm, loc);
+    } else if (parser_eat(parser, TT_Binary)) {
+        char* str = parser_ident_val(parser, parser->eaten);
+        uint16_t imm = (uint16_t)strtoul(&str[2], NULL, 2);
+        free(str);
+        return poperand_new_imm(imm, loc);
+    } else if (parser_eat(parser, TT_Hex)) {
+        char* str = parser_ident_val(parser, parser->eaten);
+        uint16_t imm = (uint16_t)strtoul(&str[2], NULL, 16);
+        free(str);
+        return poperand_new_imm(imm, loc);
+    } else if (parser_eat(parser, '.')) {
+        if (!parser_eat(parser, TT_Ident)) {
+            parser_report(parser, "expected identifier", parser->tok.loc);
+            return NULL;
+        }
+        char* ident = parser_ident_val(parser, parser->eaten);
+        return poperand_new_ident(PoTy_SubLabel, ident, loc);
+    } else if (parser_eat(parser, '(')) {
+        POperand* operand = parser_parse_operand_2(parser, parser_binary_prec);
+        if (!parser_eat(parser, ')')) {
+            parser_report(parser, "expected ')'", parser->tok.loc);
+            poperand_free(operand);
+            return NULL;
+        }
+        return operand;
+    } else {
+        parser_report(parser, "expected operand", parser->tok.loc);
+        return NULL;
+    }
+}
+
+static inline POperand* parser_parse_operand_1(Parser* parser)
+{
+
+    Loc loc = parser->tok.loc;
+    if (parser_eat(parser, '-')) {
+        POperand* operand = parser_parse_operand_1(parser);
+        return poperand_new_unary(PoTy_Negate, operand, loc);
+    } else if (parser_eat(parser, '!')) {
+        POperand* operand = parser_parse_operand_1(parser);
+        return poperand_new_unary(PoTy_Not, operand, loc);
+    } else {
+        return parser_parse_operand_0(parser);
+    }
+}
+
+static inline POperand* parser_parse_operand_2(Parser* parser, int prec)
+{
+    const POperandTy op_tys[] = {
+        PoTy_Or,
+        PoTy_Xor,
+        PoTy_And,
+        PoTy_Shl,
+        PoTy_Shr,
+        PoTy_Add,
+        PoTy_Sub,
+        PoTy_Mul,
+        PoTy_Div,
+        PoTy_Mod,
+    };
+    const TokTy op_tts[] = {
+        '|',
+        '^',
+        '&',
+        TT_DoubleGt,
+        TT_DoubleLt,
+        '+',
+        '-',
+        '*',
+        '/',
+        '%',
+    };
+    const int op_precs[] = { 6, 5, 4, 3, 3, 2, 2, 1, 1, 1 };
+    static_assert(sizeof(op_tys) / sizeof(op_tys[0])
+            == sizeof(op_tts) / sizeof(op_tts[0]),
+        "misaligned");
+    static_assert(sizeof(op_tys) / sizeof(op_tys[0])
+            == sizeof(op_precs) / sizeof(op_precs[0]),
+        "misaligned");
+
+    if (prec == 0) {
+        return parser_parse_operand_1(parser);
+    }
+    POperand* left = parser_parse_operand_2(parser, prec - 1);
+    bool should_continue = true;
+    while (should_continue) {
+        should_continue = false;
+        for (size_t i = 0; i < sizeof(op_tys) / sizeof(op_tys[0]); ++i) {
+            if (prec >= op_precs[i] && parser_eat(parser, op_tts[i])) {
+                POperand* right = parser_parse_operand_2(parser, prec - 1);
+                left = poperand_new_binary(op_tys[0], left, right, left->loc);
+                should_continue = true;
+                break;
+            }
+        }
+    }
+    return left;
+}
+
+static inline POperand* parser_parse_operand_3(Parser* parser)
+{
+    if (!parser_test(parser, TT_Ident)) {
+        return parser_parse_operand_2(parser, parser_binary_prec);
+    }
+    Loc loc = parser->tok.loc;
+    char* ident = parser_ident_val(parser, parser->eaten);
+    if (strcmp(ident, "u8") == 0) {
+        free(ident);
+        if (!parser_eat(parser, '[')) {
+            parser_report(parser, "expected '['", parser->tok.loc);
+            return NULL;
+        }
+        POperand* operand = parser_parse_operand_2(parser, parser_binary_prec);
+        if (!parser_eat(parser, ']')) {
+            parser_report(parser, "expected ']'", parser->tok.loc);
+            poperand_free(operand);
+            return NULL;
+        }
+        return poperand_new_unary(PoTy_MemU8, operand, loc);
+    } else if (strcmp(ident, "u16") == 0) {
+        free(ident);
+        if (!parser_eat(parser, '[')) {
+            parser_report(parser, "expected '['", parser->tok.loc);
+            return NULL;
+        }
+        POperand* operand = parser_parse_operand_2(parser, parser_binary_prec);
+        if (!parser_eat(parser, ']')) {
+            parser_report(parser, "expected ']'", parser->tok.loc);
+            poperand_free(operand);
+            return NULL;
+        }
+        return poperand_new_unary(PoTy_MemU16, operand, loc);
+    } else {
+        free(ident);
+        return parser_parse_operand_2(parser, parser_binary_prec);
+    }
+}
+
+PLine* parser_next(Parser* parser)
+{
+    char* ident;
+    Loc loc;
+    PLabel* labels = parser_parse_labels(parser, &ident, &loc);
+
+    POperand* ops[3];
+    size_t ops_size = 0;
+
+    if (!parser_test(parser, TT_Eof) && !parser_test(parser, '\n')) {
+        POperand* operand = parser_parse_operand_3(parser);
+        if (!operand) {
+            goto error_free_ops;
+        }
+        ops[ops_size++] = operand;
+        while (!parser_test(parser, TT_Eof) && !parser_test(parser, '\n')
+            && ops_size < 3) {
+            if (!parser_eat(parser, ',')) {
+                parser_report(parser, "expected ','", parser->tok.loc);
+                goto error_free_ops;
+            }
+            POperand* operand = parser_parse_operand_3(parser);
+            if (!operand) {
+                goto error_free_ops;
+            }
+            ops[ops_size++] = operand;
+        }
+    }
+    if (!parser_eat(parser, '\n') && !parser_test(parser, TT_Eof)) {
+        parser_report(parser, "expected newline", parser->tok.loc);
+        goto error_free_ops;
+    }
+    parser_skip_newlines(parser);
+
+    switch (ops_size) {
+        case 0:
+            return pline_new_0(ident, labels, loc);
+        case 1:
+            return pline_new_1(ident, labels, loc, ops[0]);
+        case 2:
+            return pline_new_2(ident, labels, loc, ops[0], ops[1]);
+        case 3:
+        default:
+            return pline_new_3(ident, labels, loc, ops[0], ops[1], ops[2]);
+    }
+
+error_free_ops:
+    for (size_t i = 0; i < ops_size; ++i)
+        if (ops[i])
+            poperand_free(ops[i]);
+    plabel_free(labels);
+    free(ident);
+    return NULL;
+}
+
+typedef struct {
+    const char* input_file;
+    const char* output_file;
+} Args;
+
+static inline Args parse_args(int argc, char** argv);
+
+int main(int argc, char** argv)
+{
+    Args args = parse_args(argc, argv);
+
+    FILE* input_fp = fopen(args.input_file, "r");
+    if (!input_fp) {
+        fprintf(stderr,
+            "error: could not open input file '%s': %s\n",
+            args.input_file,
+            strerror(errno));
+        return -1;
+    }
+    fseek(input_fp, 0L, SEEK_END);
+    size_t file_size = (size_t)ftell(input_fp);
+    rewind(input_fp);
+
+    char* input_text = calloc(file_size + 1, sizeof(char));
+    size_t bytes_read = fread(input_text, sizeof(char), file_size, input_fp);
+    fclose(input_fp);
+    if (bytes_read != file_size) {
+        fprintf(stderr,
+            "error: could not read input file '%s': %s\n",
+            args.input_file,
+            strerror(errno));
+        return -1;
+    }
+
+    Parser parser;
+    parser_construct(&parser, args.input_file, input_text);
+
+    while (!parser_done(&parser)) {
+        PLine* line = parser_next(&parser);
+        if (!line) {
+            break;
+        }
+        pline_free(line);
+        if (parser_error_occured(&parser)) {
+            break;
+        }
+    }
+
+    free(input_text);
+}
+
+static inline Args parse_args(int argc, char** argv)
+{
+    const char* input_file = NULL;
+    const char* output_file = NULL;
+    for (int i = 1; i < argc; ++i) {
+        if (strcmp(argv[i], "-o") == 0) {
+            i += 1;
+            if (i >= argc) {
+                fprintf(stderr, "error: no filename given to -o\n");
+                exit(1);
+            }
+            output_file = argv[i];
+        } else {
+            if (input_file != NULL) {
+                fprintf(stderr, "error: multiple input files specified\n");
+                exit(1);
+            }
+            input_file = argv[i];
+        }
+    }
+    if (input_file == NULL) {
+        fprintf(stderr, "error: no input file\n");
+        exit(1);
+    }
+    if (output_file == NULL) {
+        output_file = "out.o";
+    }
+    return (Args) {
+        input_file,
+        output_file,
+    };
+}
diff --git a/kern/main.asm b/kern/main.asm
new file mode 100644
index 0000000..b5f601b
--- /dev/null
+++ b/kern/main.asm
@@ -0,0 +1,140 @@
+
+start:
+    ; rsp points *at* the top element
+    mov Rbp, 2048
+    mov rsp, 2048 - 2
+
+    lit interrupt_table
+    or rfl, rfl, 1 << Fl_Int
+
+    or rfl, Rfl, 1 << Fl_Vcd
+
+    mov16 r0, 512
+    mov16 r1, 1
+    int Int_DiskRead
+
+main_loop:
+    hlt
+    jmp main_loop
+
+interrupt_table:
+    ; size
+    d16 1 
+    data keyboard_interrupt
+    nop
+
+keyboard_interrupt:
+    and rfl, rfl, !(1 << Fl_Int)
+    push rbp
+    mov rbp, Rsp
+    push r0
+    push r1
+    push r2
+    push r3
+
+    in r0, Device_Keyboard
+
+    cmp r0, 44
+    mov r1, rfl
+    and r1, r1, 1 << Fl_Eq
+    jnz r1, .L0
+
+    cmp r0, 42
+    mov16 r1, rfl
+    and r1, r1, 1 << Fl_Eq
+    jnz r1, .L1
+
+    cmp r0, 40
+    mov r1, rfl
+    and r1, r1, 1 << Fl_Eq
+    jnz r1, .L2
+
+    jmp .L3
+
+.L0:
+    mov R0, 32 ; ' '
+    call put_char
+    jmp .L4
+
+.L1:
+    mov r1, screen_x
+    cmp r1, 0
+    mov r2, rfl
+    and r2, r2, 1 << Fl_Eq
+    jnz r2, .L4
+    sub r1, r1, 1
+    mov screen_x, R1
+    ; mov r0, ' '
+    call put_char
+    mov16 r1, screen_x
+    sub r1, r1, 1
+    mov screen_x, R1
+    jmp .L4
+
+.L2:
+    mov r1, screen_y
+    add r1, r1, 1
+    mov screen_y, R1
+    mov r1, 0
+    mov screen_x, R1
+    jmp .L4
+
+.L3:
+    ; add r0, r0, 'A' - 4
+    call put_char
+    jmp .L4
+
+.L4:
+
+    pop r3
+    pop r2
+    pop r1
+    pop r0
+    mov rsp, rbp
+    pop rbp
+    or rfl, rfl, 1 << Fl_Int
+    iret
+
+put_char:
+    push rbp
+    mov rbp, rsp
+    push r1
+    push r2
+
+    mov r2, screen_y
+    mul r2, r2, vcd_width_in_ch
+    mov r1, screen_x
+    add r1, r1, 0x0c00
+    add r1, r1, r2
+    mov r1, r0
+
+    mov r1, screen_x
+    add r1, r1, 1
+    mov screen_x, r1
+
+    cmp r1, vcd_width_in_ch
+    mov r2, rfl
+    and r2, r2, 1 << Fl_Eq
+    jnz r2, .L0
+    jmp .L1
+
+.L0:
+    mov r1, screen_y
+    add r1, r1, 1
+    mov screen_y, r1
+    mov r1, 0
+    mov screen_x, r1
+
+.L1:
+    pop r1
+    pop r2
+    mov rsp, rbp
+    pop rbp
+    ret
+
+screen_x:
+    d16 0
+screen_y:
+    d16 0
+
+; vim: syntax=nasm