Commit b6354ddd5a

Andrew Kelley <superjoe30@gmail.com>
2016-01-28 05:10:38
move AST rendering code to separate file
1 parent c281533
doc/targets.md
@@ -11,3 +11,6 @@ for the target when an exported or external function has a byvalue struct.
 Write the target-specific code in std.zig.
 
 Update the C integer types to be the correct size for the target.
+
+Add the conditional compilation code for the page size global. It is hardcoded
+for each target.
src/all_types.hpp
@@ -894,6 +894,7 @@ struct ImportTableEntry {
     ZigList<int> *line_offsets;
     BlockContext *block_context;
     ZigList<ImporterInfo> importers;
+    bool is_c_import;
 
     // reminder: hash tables must be initialized before use
     HashMap<Buf *, FnTableEntry *, buf_hash, buf_eql_buf> fn_table;
src/analyze.cpp
@@ -1039,15 +1039,15 @@ static void resolve_error_value_decl(CodeGen *g, ImportTableEntry *import, AstNo
     }
 }
 
-static void resolve_c_import_decl(CodeGen *g, ImportTableEntry *import, AstNode *node) {
+static void resolve_c_import_decl(CodeGen *g, ImportTableEntry *parent_import, AstNode *node) {
     assert(node->type == NodeTypeCImport);
 
     AstNode *block_node = node->data.c_import.block;
 
-    BlockContext *child_context = new_block_context(node, import->block_context);
+    BlockContext *child_context = new_block_context(node, parent_import->block_context);
     child_context->c_import_buf = buf_alloc();
 
-    TypeTableEntry *resolved_type = analyze_block_expr(g, import, child_context,
+    TypeTableEntry *resolved_type = analyze_block_expr(g, parent_import, child_context,
             g->builtin_types.entry_void, block_node);
 
     if (resolved_type->id == TypeTableEntryIdInvalid) {
@@ -1055,23 +1055,27 @@ static void resolve_c_import_decl(CodeGen *g, ImportTableEntry *import, AstNode
     }
 
     find_libc_path(g);
+
+    ImportTableEntry child_import = {0};
+    ZigList<ErrorMsg *> errors = {0};
+
     int err;
-    ParseH parse_h = {{0}};
-    if ((err = parse_h_buf(&parse_h, child_context->c_import_buf, g->clang_argv, g->clang_argv_len,
+    if ((err = parse_h_buf(&child_import, &errors, child_context->c_import_buf, g->clang_argv, g->clang_argv_len,
                     buf_ptr(g->libc_include_path))))
     {
         zig_panic("unable to parse h file: %s\n", err_str(err));
     }
 
-    if (parse_h.errors.length > 0) {
+    if (errors.length > 0) {
         ErrorMsg *parent_err_msg = add_node_error(g, node, buf_sprintf("C import failed"));
-        for (int i = 0; i < parse_h.errors.length; i += 1) {
-            ErrorMsg *err_msg = parse_h.errors.at(i);
+        for (int i = 0; i < errors.length; i += 1) {
+            ErrorMsg *err_msg = errors.at(i);
             err_msg_add_note(parent_err_msg, err_msg);
         }
-    } else {
-        zig_panic("TODO integrate the parsed AST");
+        return;
     }
+
+    zig_panic("TODO integrate the AST");
 }
 
 static void resolve_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode *node) {
src/ast_render.cpp
@@ -0,0 +1,682 @@
+#include "ast_render.hpp"
+
+#include <stdio.h>
+
+static const char *bin_op_str(BinOpType bin_op) {
+    switch (bin_op) {
+        case BinOpTypeInvalid:             return "(invalid)";
+        case BinOpTypeBoolOr:              return "||";
+        case BinOpTypeBoolAnd:             return "&&";
+        case BinOpTypeCmpEq:               return "==";
+        case BinOpTypeCmpNotEq:            return "!=";
+        case BinOpTypeCmpLessThan:         return "<";
+        case BinOpTypeCmpGreaterThan:      return ">";
+        case BinOpTypeCmpLessOrEq:         return "<=";
+        case BinOpTypeCmpGreaterOrEq:      return ">=";
+        case BinOpTypeBinOr:               return "|";
+        case BinOpTypeBinXor:              return "^";
+        case BinOpTypeBinAnd:              return "&";
+        case BinOpTypeBitShiftLeft:        return "<<";
+        case BinOpTypeBitShiftRight:       return ">>";
+        case BinOpTypeAdd:                 return "+";
+        case BinOpTypeSub:                 return "-";
+        case BinOpTypeMult:                return "*";
+        case BinOpTypeDiv:                 return "/";
+        case BinOpTypeMod:                 return "%";
+        case BinOpTypeAssign:              return "=";
+        case BinOpTypeAssignTimes:         return "*=";
+        case BinOpTypeAssignDiv:           return "/=";
+        case BinOpTypeAssignMod:           return "%=";
+        case BinOpTypeAssignPlus:          return "+=";
+        case BinOpTypeAssignMinus:         return "-=";
+        case BinOpTypeAssignBitShiftLeft:  return "<<=";
+        case BinOpTypeAssignBitShiftRight: return ">>=";
+        case BinOpTypeAssignBitAnd:        return "&=";
+        case BinOpTypeAssignBitXor:        return "^=";
+        case BinOpTypeAssignBitOr:         return "|=";
+        case BinOpTypeAssignBoolAnd:       return "&&=";
+        case BinOpTypeAssignBoolOr:        return "||=";
+        case BinOpTypeUnwrapMaybe:         return "??";
+        case BinOpTypeStrCat:              return "++";
+    }
+}
+
+static const char *prefix_op_str(PrefixOp prefix_op) {
+    switch (prefix_op) {
+        case PrefixOpInvalid: return "(invalid)";
+        case PrefixOpNegation: return "-";
+        case PrefixOpBoolNot: return "!";
+        case PrefixOpBinNot: return "~";
+        case PrefixOpAddressOf: return "&";
+        case PrefixOpConstAddressOf: return "&const ";
+        case PrefixOpDereference: return "*";
+        case PrefixOpMaybe: return "?";
+        case PrefixOpError: return "%";
+        case PrefixOpUnwrapError: return "%%";
+    }
+}
+
+static const char *return_prefix_str(ReturnKind kind) {
+    switch (kind) {
+        case ReturnKindError: return "%";
+        case ReturnKindMaybe: return "?";
+        case ReturnKindUnconditional: return "";
+    }
+}
+
+static const char *visib_mod_string(VisibMod mod) {
+    switch (mod) {
+        case VisibModPub: return "pub ";
+        case VisibModPrivate: return "";
+        case VisibModExport: return "export ";
+    }
+}
+
+static const char *extern_string(bool is_extern) {
+    return is_extern ? "export " : "";
+}
+
+static const char *const_or_var_string(bool is_const) {
+    return is_const ? "const" : "var";
+}
+
+static const char *node_type_str(NodeType node_type) {
+    switch (node_type) {
+        case NodeTypeRoot:
+            return "Root";
+        case NodeTypeRootExportDecl:
+            return "RootExportDecl";
+        case NodeTypeFnDef:
+            return "FnDef";
+        case NodeTypeFnDecl:
+            return "FnDecl";
+        case NodeTypeFnProto:
+            return "FnProto";
+        case NodeTypeParamDecl:
+            return "ParamDecl";
+        case NodeTypeBlock:
+            return "Block";
+        case NodeTypeBinOpExpr:
+            return "BinOpExpr";
+        case NodeTypeUnwrapErrorExpr:
+            return "UnwrapErrorExpr";
+        case NodeTypeFnCallExpr:
+            return "FnCallExpr";
+        case NodeTypeArrayAccessExpr:
+            return "ArrayAccessExpr";
+        case NodeTypeSliceExpr:
+            return "SliceExpr";
+        case NodeTypeDirective:
+            return "Directive";
+        case NodeTypeReturnExpr:
+            return "ReturnExpr";
+        case NodeTypeVariableDeclaration:
+            return "VariableDeclaration";
+        case NodeTypeErrorValueDecl:
+            return "ErrorValueDecl";
+        case NodeTypeNumberLiteral:
+            return "NumberLiteral";
+        case NodeTypeStringLiteral:
+            return "StringLiteral";
+        case NodeTypeCharLiteral:
+            return "CharLiteral";
+        case NodeTypeSymbol:
+            return "Symbol";
+        case NodeTypePrefixOpExpr:
+            return "PrefixOpExpr";
+        case NodeTypeImport:
+            return "Import";
+        case NodeTypeCImport:
+            return "CImport";
+        case NodeTypeBoolLiteral:
+            return "BoolLiteral";
+        case NodeTypeNullLiteral:
+            return "NullLiteral";
+        case NodeTypeUndefinedLiteral:
+            return "UndefinedLiteral";
+        case NodeTypeIfBoolExpr:
+            return "IfBoolExpr";
+        case NodeTypeIfVarExpr:
+            return "IfVarExpr";
+        case NodeTypeWhileExpr:
+            return "WhileExpr";
+        case NodeTypeForExpr:
+            return "ForExpr";
+        case NodeTypeSwitchExpr:
+            return "SwitchExpr";
+        case NodeTypeSwitchProng:
+            return "SwitchProng";
+        case NodeTypeSwitchRange:
+            return "SwitchRange";
+        case NodeTypeLabel:
+            return "Label";
+        case NodeTypeGoto:
+            return "Goto";
+        case NodeTypeBreak:
+            return "Break";
+        case NodeTypeContinue:
+            return "Continue";
+        case NodeTypeAsmExpr:
+            return "AsmExpr";
+        case NodeTypeFieldAccessExpr:
+            return "FieldAccessExpr";
+        case NodeTypeStructDecl:
+            return "StructDecl";
+        case NodeTypeStructField:
+            return "StructField";
+        case NodeTypeStructValueField:
+            return "StructValueField";
+        case NodeTypeContainerInitExpr:
+            return "ContainerInitExpr";
+        case NodeTypeArrayType:
+            return "ArrayType";
+        case NodeTypeErrorType:
+            return "ErrorType";
+    }
+}
+
+
+void ast_print(FILE *f, AstNode *node, int indent) {
+    for (int i = 0; i < indent; i += 1) {
+        fprintf(f, " ");
+    }
+    assert(node->type == NodeTypeRoot || *node->parent_field == node);
+
+    switch (node->type) {
+        case NodeTypeRoot:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            for (int i = 0; i < node->data.root.top_level_decls.length; i += 1) {
+                AstNode *child = node->data.root.top_level_decls.at(i);
+                ast_print(f, child, indent + 2);
+            }
+            break;
+        case NodeTypeRootExportDecl:
+            fprintf(f, "%s %s '%s'\n", node_type_str(node->type),
+                    buf_ptr(&node->data.root_export_decl.type),
+                    buf_ptr(&node->data.root_export_decl.name));
+            break;
+        case NodeTypeFnDef:
+            {
+                fprintf(f, "%s\n", node_type_str(node->type));
+                AstNode *child = node->data.fn_def.fn_proto;
+                ast_print(f, child, indent + 2);
+                ast_print(f, node->data.fn_def.body, indent + 2);
+                break;
+            }
+        case NodeTypeFnProto:
+            {
+                Buf *name_buf = &node->data.fn_proto.name;
+                fprintf(f, "%s '%s'\n", node_type_str(node->type), buf_ptr(name_buf));
+
+                for (int i = 0; i < node->data.fn_proto.params.length; i += 1) {
+                    AstNode *child = node->data.fn_proto.params.at(i);
+                    ast_print(f, child, indent + 2);
+                }
+
+                ast_print(f, node->data.fn_proto.return_type, indent + 2);
+
+                break;
+            }
+        case NodeTypeBlock:
+            {
+                fprintf(f, "%s\n", node_type_str(node->type));
+                for (int i = 0; i < node->data.block.statements.length; i += 1) {
+                    AstNode *child = node->data.block.statements.at(i);
+                    ast_print(f, child, indent + 2);
+                }
+                break;
+            }
+        case NodeTypeParamDecl:
+            {
+                Buf *name_buf = &node->data.param_decl.name;
+                fprintf(f, "%s '%s'\n", node_type_str(node->type), buf_ptr(name_buf));
+
+                ast_print(f, node->data.param_decl.type, indent + 2);
+
+                break;
+            }
+        case NodeTypeReturnExpr:
+            {
+                const char *prefix_str = return_prefix_str(node->data.return_expr.kind);
+                fprintf(f, "%s%s\n", prefix_str, node_type_str(node->type));
+                if (node->data.return_expr.expr)
+                    ast_print(f, node->data.return_expr.expr, indent + 2);
+                break;
+            }
+        case NodeTypeVariableDeclaration:
+            {
+                Buf *name_buf = &node->data.variable_declaration.symbol;
+                fprintf(f, "%s '%s'\n", node_type_str(node->type), buf_ptr(name_buf));
+                if (node->data.variable_declaration.type)
+                    ast_print(f, node->data.variable_declaration.type, indent + 2);
+                if (node->data.variable_declaration.expr)
+                    ast_print(f, node->data.variable_declaration.expr, indent + 2);
+                break;
+            }
+        case NodeTypeErrorValueDecl:
+            {
+                Buf *name_buf = &node->data.error_value_decl.name;
+                fprintf(f, "%s '%s'\n", node_type_str(node->type), buf_ptr(name_buf));
+                break;
+            }
+        case NodeTypeFnDecl:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            ast_print(f, node->data.fn_decl.fn_proto, indent + 2);
+            break;
+        case NodeTypeBinOpExpr:
+            fprintf(f, "%s %s\n", node_type_str(node->type),
+                    bin_op_str(node->data.bin_op_expr.bin_op));
+            ast_print(f, node->data.bin_op_expr.op1, indent + 2);
+            ast_print(f, node->data.bin_op_expr.op2, indent + 2);
+            break;
+        case NodeTypeUnwrapErrorExpr:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            ast_print(f, node->data.unwrap_err_expr.op1, indent + 2);
+            if (node->data.unwrap_err_expr.symbol) {
+                ast_print(f, node->data.unwrap_err_expr.symbol, indent + 2);
+            }
+            ast_print(f, node->data.unwrap_err_expr.op2, indent + 2);
+            break;
+        case NodeTypeFnCallExpr:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            ast_print(f, node->data.fn_call_expr.fn_ref_expr, indent + 2);
+            for (int i = 0; i < node->data.fn_call_expr.params.length; i += 1) {
+                AstNode *child = node->data.fn_call_expr.params.at(i);
+                ast_print(f, child, indent + 2);
+            }
+            break;
+        case NodeTypeArrayAccessExpr:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            ast_print(f, node->data.array_access_expr.array_ref_expr, indent + 2);
+            ast_print(f, node->data.array_access_expr.subscript, indent + 2);
+            break;
+        case NodeTypeSliceExpr:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            ast_print(f, node->data.slice_expr.array_ref_expr, indent + 2);
+            ast_print(f, node->data.slice_expr.start, indent + 2);
+            if (node->data.slice_expr.end) {
+                ast_print(f, node->data.slice_expr.end, indent + 2);
+            }
+            break;
+        case NodeTypeDirective:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            break;
+        case NodeTypePrefixOpExpr:
+            fprintf(f, "%s %s\n", node_type_str(node->type),
+                    prefix_op_str(node->data.prefix_op_expr.prefix_op));
+            ast_print(f, node->data.prefix_op_expr.primary_expr, indent + 2);
+            break;
+        case NodeTypeNumberLiteral:
+            {
+                NumLit kind = node->data.number_literal.kind;
+                const char *name = node_type_str(node->type);
+                if (kind == NumLitUInt) {
+                    fprintf(f, "%s uint %" PRIu64 "\n", name, node->data.number_literal.data.x_uint);
+                } else {
+                    fprintf(f, "%s float %f\n", name, node->data.number_literal.data.x_float);
+                }
+                break;
+            }
+        case NodeTypeStringLiteral:
+            {
+                const char *c = node->data.string_literal.c ? "c" : "";
+                fprintf(f, "StringLiteral %s'%s'\n", c,
+                        buf_ptr(&node->data.string_literal.buf));
+                break;
+            }
+        case NodeTypeCharLiteral:
+            {
+                fprintf(f, "%s '%c'\n", node_type_str(node->type), node->data.char_literal.value);
+                break;
+            }
+        case NodeTypeSymbol:
+            fprintf(f, "Symbol %s\n", buf_ptr(&node->data.symbol_expr.symbol));
+            break;
+        case NodeTypeImport:
+            fprintf(f, "%s '%s'\n", node_type_str(node->type), buf_ptr(&node->data.import.path));
+            break;
+        case NodeTypeCImport:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            ast_print(f, node->data.c_import.block, indent + 2);
+            break;
+        case NodeTypeBoolLiteral:
+            fprintf(f, "%s '%s'\n", node_type_str(node->type),
+                    node->data.bool_literal.value ? "true" : "false");
+            break;
+        case NodeTypeNullLiteral:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            break;
+        case NodeTypeIfBoolExpr:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            if (node->data.if_bool_expr.condition)
+                ast_print(f, node->data.if_bool_expr.condition, indent + 2);
+            ast_print(f, node->data.if_bool_expr.then_block, indent + 2);
+            if (node->data.if_bool_expr.else_node)
+                ast_print(f, node->data.if_bool_expr.else_node, indent + 2);
+            break;
+        case NodeTypeIfVarExpr:
+            {
+                Buf *name_buf = &node->data.if_var_expr.var_decl.symbol;
+                fprintf(f, "%s '%s'\n", node_type_str(node->type), buf_ptr(name_buf));
+                if (node->data.if_var_expr.var_decl.type)
+                    ast_print(f, node->data.if_var_expr.var_decl.type, indent + 2);
+                if (node->data.if_var_expr.var_decl.expr)
+                    ast_print(f, node->data.if_var_expr.var_decl.expr, indent + 2);
+                ast_print(f, node->data.if_var_expr.then_block, indent + 2);
+                if (node->data.if_var_expr.else_node)
+                    ast_print(f, node->data.if_var_expr.else_node, indent + 2);
+                break;
+            }
+        case NodeTypeWhileExpr:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            ast_print(f, node->data.while_expr.condition, indent + 2);
+            ast_print(f, node->data.while_expr.body, indent + 2);
+            break;
+        case NodeTypeForExpr:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            ast_print(f, node->data.for_expr.elem_node, indent + 2);
+            ast_print(f, node->data.for_expr.array_expr, indent + 2);
+            if (node->data.for_expr.index_node) {
+                ast_print(f, node->data.for_expr.index_node, indent + 2);
+            }
+            ast_print(f, node->data.for_expr.body, indent + 2);
+            break;
+        case NodeTypeSwitchExpr:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            ast_print(f, node->data.switch_expr.expr, indent + 2);
+            for (int i = 0; i < node->data.switch_expr.prongs.length; i += 1) {
+                AstNode *child_node = node->data.switch_expr.prongs.at(i);
+                ast_print(f, child_node, indent + 2);
+            }
+            break;
+        case NodeTypeSwitchProng:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            for (int i = 0; i < node->data.switch_prong.items.length; i += 1) {
+                AstNode *child_node = node->data.switch_prong.items.at(i);
+                ast_print(f, child_node, indent + 2);
+            }
+            if (node->data.switch_prong.var_symbol) {
+                ast_print(f, node->data.switch_prong.var_symbol, indent + 2);
+            }
+            ast_print(f, node->data.switch_prong.expr, indent + 2);
+            break;
+        case NodeTypeSwitchRange:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            ast_print(f, node->data.switch_range.start, indent + 2);
+            ast_print(f, node->data.switch_range.end, indent + 2);
+            break;
+        case NodeTypeLabel:
+            fprintf(f, "%s '%s'\n", node_type_str(node->type), buf_ptr(&node->data.label.name));
+            break;
+        case NodeTypeGoto:
+            fprintf(f, "%s '%s'\n", node_type_str(node->type), buf_ptr(&node->data.goto_expr.name));
+            break;
+        case NodeTypeBreak:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            break;
+        case NodeTypeContinue:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            break;
+        case NodeTypeUndefinedLiteral:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            break;
+        case NodeTypeAsmExpr:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            break;
+        case NodeTypeFieldAccessExpr:
+            fprintf(f, "%s '%s'\n", node_type_str(node->type),
+                    buf_ptr(&node->data.field_access_expr.field_name));
+            ast_print(f, node->data.field_access_expr.struct_expr, indent + 2);
+            break;
+        case NodeTypeStructDecl:
+            fprintf(f, "%s '%s'\n",
+                    node_type_str(node->type), buf_ptr(&node->data.struct_decl.name));
+            for (int i = 0; i < node->data.struct_decl.fields.length; i += 1) {
+                AstNode *child = node->data.struct_decl.fields.at(i);
+                ast_print(f, child, indent + 2);
+            }
+            for (int i = 0; i < node->data.struct_decl.fns.length; i += 1) {
+                AstNode *child = node->data.struct_decl.fns.at(i);
+                ast_print(f, child, indent + 2);
+            }
+            break;
+        case NodeTypeStructField:
+            fprintf(f, "%s '%s'\n", node_type_str(node->type), buf_ptr(&node->data.struct_field.name));
+            if (node->data.struct_field.type) {
+                ast_print(f, node->data.struct_field.type, indent + 2);
+            }
+            break;
+        case NodeTypeStructValueField:
+            fprintf(f, "%s '%s'\n", node_type_str(node->type), buf_ptr(&node->data.struct_val_field.name));
+            ast_print(f, node->data.struct_val_field.expr, indent + 2);
+            break;
+        case NodeTypeContainerInitExpr:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            ast_print(f, node->data.container_init_expr.type, indent + 2);
+            for (int i = 0; i < node->data.container_init_expr.entries.length; i += 1) {
+                AstNode *child = node->data.container_init_expr.entries.at(i);
+                ast_print(f, child, indent + 2);
+            }
+            break;
+        case NodeTypeArrayType:
+            {
+                const char *const_str = node->data.array_type.is_const ? "const" : "var";
+                fprintf(f, "%s %s\n", node_type_str(node->type), const_str);
+                if (node->data.array_type.size) {
+                    ast_print(f, node->data.array_type.size, indent + 2);
+                }
+                ast_print(f, node->data.array_type.child_type, indent + 2);
+                break;
+            }
+        case NodeTypeErrorType:
+            fprintf(f, "%s\n", node_type_str(node->type));
+            break;
+    }
+}
+
+struct AstRender {
+    int indent;
+    int indent_size;
+    FILE *f;
+};
+
+static void print_indent(AstRender *ar) {
+    for (int i = 0; i < ar->indent; i += 1) {
+        fprintf(ar->f, " ");
+    }
+}
+
+static void render_node(AstRender *ar, AstNode *node) {
+    assert(node->type == NodeTypeRoot || *node->parent_field == node);
+
+    switch (node->type) {
+        case NodeTypeRoot:
+            for (int i = 0; i < node->data.root.top_level_decls.length; i += 1) {
+                AstNode *child = node->data.root.top_level_decls.at(i);
+                print_indent(ar);
+                render_node(ar, child);
+
+                if (child->type == NodeTypeImport ||
+                    child->type == NodeTypeVariableDeclaration ||
+                    child->type == NodeTypeErrorValueDecl)
+                {
+                    fprintf(ar->f, ";");
+                }
+                fprintf(ar->f, "\n");
+            }
+            break;
+        case NodeTypeRootExportDecl:
+            zig_panic("TODO");
+        case NodeTypeFnProto:
+            {
+                const char *fn_name = buf_ptr(&node->data.fn_proto.name);
+                const char *pub_str = visib_mod_string(node->data.fn_proto.visib_mod);
+                const char *extern_str = extern_string(node->data.fn_proto.is_extern);
+                fprintf(ar->f, "%s%sfn %s(", pub_str, extern_str, fn_name);
+                int arg_count = node->data.fn_proto.params.length;
+                bool is_var_args = node->data.fn_proto.is_var_args;
+                for (int arg_i = 0; arg_i < arg_count; arg_i += 1) {
+                    AstNode *param_decl = node->data.fn_proto.params.at(arg_i);
+                    assert(param_decl->type == NodeTypeParamDecl);
+                    const char *arg_name = buf_ptr(&param_decl->data.param_decl.name);
+                    const char *noalias_str = param_decl->data.param_decl.is_noalias ? "noalias " : "";
+                    fprintf(ar->f, "%s%s: ", noalias_str, arg_name);
+                    render_node(ar, param_decl->data.param_decl.type);
+
+                    if (arg_i + 1 < arg_count || is_var_args) {
+                        fprintf(ar->f, ", ");
+                    }
+                }
+                if (is_var_args) {
+                    fprintf(ar->f, "...");
+                }
+                fprintf(ar->f, ")");
+
+                AstNode *return_type_node = node->data.fn_proto.return_type;
+                bool is_void = return_type_node->type != NodeTypeSymbol &&
+                    buf_eql_str(&return_type_node->data.symbol_expr.symbol, "void");
+                if (!is_void) {
+                    fprintf(ar->f, " -> ");
+                    render_node(ar, return_type_node);
+                }
+                fprintf(ar->f, ";");
+                break;
+            }
+        case NodeTypeFnDef:
+            zig_panic("TODO");
+        case NodeTypeFnDecl:
+            zig_panic("TODO");
+        case NodeTypeParamDecl:
+            zig_panic("TODO");
+        case NodeTypeBlock:
+            zig_panic("TODO");
+        case NodeTypeDirective:
+            zig_panic("TODO");
+        case NodeTypeReturnExpr:
+            zig_panic("TODO");
+        case NodeTypeVariableDeclaration:
+            {
+                const char *pub_str = visib_mod_string(node->data.variable_declaration.visib_mod);
+                const char *extern_str = extern_string(node->data.variable_declaration.is_extern);
+                const char *var_name = buf_ptr(&node->data.variable_declaration.symbol);
+                const char *const_or_var = const_or_var_string(node->data.variable_declaration.is_const);
+                fprintf(ar->f, "%s%s%s %s", pub_str, extern_str, const_or_var, var_name);
+                if (node->data.variable_declaration.type) {
+                    fprintf(ar->f, ": ");
+                    render_node(ar, node->data.variable_declaration.type);
+                }
+                if (node->data.variable_declaration.expr) {
+                    fprintf(ar->f, " = ");
+                    render_node(ar, node->data.variable_declaration.expr);
+                }
+                break;
+            }
+        case NodeTypeErrorValueDecl:
+            zig_panic("TODO");
+        case NodeTypeBinOpExpr:
+            zig_panic("TODO");
+        case NodeTypeUnwrapErrorExpr:
+            zig_panic("TODO");
+        case NodeTypeNumberLiteral:
+            zig_panic("TODO");
+        case NodeTypeStringLiteral:
+            zig_panic("TODO");
+        case NodeTypeCharLiteral:
+            zig_panic("TODO");
+        case NodeTypeSymbol:
+            fprintf(ar->f, "%s", buf_ptr(&node->data.symbol_expr.symbol));
+            break;
+        case NodeTypePrefixOpExpr:
+            {
+                PrefixOp op = node->data.prefix_op_expr.prefix_op;
+                fprintf(ar->f, "%s", prefix_op_str(op));
+
+                render_node(ar, node->data.prefix_op_expr.primary_expr);
+                break;
+            }
+        case NodeTypeFnCallExpr:
+            zig_panic("TODO");
+        case NodeTypeArrayAccessExpr:
+            zig_panic("TODO");
+        case NodeTypeSliceExpr:
+            zig_panic("TODO");
+        case NodeTypeFieldAccessExpr:
+            zig_panic("TODO");
+        case NodeTypeImport:
+            zig_panic("TODO");
+        case NodeTypeCImport:
+            zig_panic("TODO");
+        case NodeTypeBoolLiteral:
+            zig_panic("TODO");
+        case NodeTypeNullLiteral:
+            zig_panic("TODO");
+        case NodeTypeUndefinedLiteral:
+            zig_panic("TODO");
+        case NodeTypeIfBoolExpr:
+            zig_panic("TODO");
+        case NodeTypeIfVarExpr:
+            zig_panic("TODO");
+        case NodeTypeWhileExpr:
+            zig_panic("TODO");
+        case NodeTypeForExpr:
+            zig_panic("TODO");
+        case NodeTypeSwitchExpr:
+            zig_panic("TODO");
+        case NodeTypeSwitchProng:
+            zig_panic("TODO");
+        case NodeTypeSwitchRange:
+            zig_panic("TODO");
+        case NodeTypeLabel:
+            zig_panic("TODO");
+        case NodeTypeGoto:
+            zig_panic("TODO");
+        case NodeTypeBreak:
+            zig_panic("TODO");
+        case NodeTypeContinue:
+            zig_panic("TODO");
+        case NodeTypeAsmExpr:
+            zig_panic("TODO");
+        case NodeTypeStructDecl:
+            {
+                const char *struct_name = buf_ptr(&node->data.struct_decl.name);
+                const char *pub_str = visib_mod_string(node->data.struct_decl.visib_mod);
+                fprintf(ar->f, "%sstruct %s {\n", pub_str, struct_name);
+                ar->indent += ar->indent_size;
+                for (int field_i = 0; field_i < node->data.struct_decl.fields.length; field_i += 1) {
+                    AstNode *field_node = node->data.struct_decl.fields.at(field_i);
+                    assert(field_node->type == NodeTypeStructField);
+                    const char *field_name = buf_ptr(&field_node->data.struct_field.name);
+                    print_indent(ar);
+                    fprintf(ar->f, "%s: ", field_name);
+                    render_node(ar, field_node->data.struct_field.type);
+                    fprintf(ar->f, ",\n");
+                }
+
+                ar->indent -= ar->indent_size;
+                fprintf(ar->f, "}\n");
+                break;
+            }
+        case NodeTypeStructField:
+            zig_panic("TODO");
+        case NodeTypeContainerInitExpr:
+            zig_panic("TODO");
+        case NodeTypeStructValueField:
+            zig_panic("TODO");
+        case NodeTypeArrayType:
+            zig_panic("TODO");
+        case NodeTypeErrorType:
+            zig_panic("TODO");
+    }
+}
+
+
+void ast_render(FILE *f, AstNode *node, int indent_size) {
+    AstRender ar = {0};
+    ar.f = f;
+    ar.indent_size = indent_size;
+    ar.indent = 0;
+
+    assert(node->type == NodeTypeRoot);
+
+    render_node(&ar, node);
+}
src/ast_render.hpp
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2015 Andrew Kelley
+ *
+ * This file is part of zig, which is MIT licensed.
+ * See http://opensource.org/licenses/MIT
+ */
+
+#ifndef ZIG_AST_RENDER_HPP
+#define ZIG_AST_RENDER_HPP
+
+#include "all_types.hpp"
+
+#include <stdio.h>
+
+void ast_print(FILE *f, AstNode *node, int indent);
+
+void ast_render(FILE *f, AstNode *node, int indent_size);
+
+#endif
+
src/codegen.cpp
@@ -13,6 +13,7 @@
 #include "error.hpp"
 #include "analyze.hpp"
 #include "errmsg.hpp"
+#include "ast_render.hpp"
 
 #include <stdio.h>
 #include <errno.h>
@@ -3117,7 +3118,7 @@ static ImportTableEntry *codegen_add_code(CodeGen *g, Buf *abs_full_path,
             &g->next_node_index);
     assert(import_entry->root);
     if (g->verbose) {
-        ast_print(import_entry->root, 0);
+        ast_print(stderr, import_entry->root, 0);
     }
 
     import_entry->di_file = LLVMZigCreateFile(g->dbuilder, buf_ptr(src_basename), buf_ptr(src_dirname));
src/main.cpp
@@ -11,6 +11,7 @@
 #include "os.hpp"
 #include "error.hpp"
 #include "parseh.hpp"
+#include "ast_render.hpp"
 
 #include <stdio.h>
 
@@ -162,41 +163,10 @@ static int build(const char *arg0, int argc, char **argv) {
     return 0;
 }
 
-struct ParseHPrint {
-    ParseH parse_h;
-    FILE *f;
-    int cur_indent;
-};
-
-static const int indent_size = 4;
-
-static void print_indent(ParseHPrint *p) {
-    for (int i = 0; i < p->cur_indent; i += 1) {
-        fprintf(p->f, " ");
-    }
-}
-
-static Buf *node_to_buf(AstNode *node) {
-    if (node->type == NodeTypeSymbol) {
-        return &node->data.symbol_expr.symbol;
-    } else if (node->type == NodeTypePrefixOpExpr) {
-        PrefixOp op = node->data.prefix_op_expr.prefix_op;
-        const char *child_type_str = buf_ptr(node_to_buf(node->data.prefix_op_expr.primary_expr));
-        if (op == PrefixOpAddressOf) {
-            return buf_sprintf("&%s", child_type_str);
-        } else if (op == PrefixOpConstAddressOf) {
-            return buf_sprintf("&const %s", child_type_str);
-        } else {
-            zig_unreachable();
-        }
-    } else {
-        zig_unreachable();
-    }
-}
-
 static int parseh(const char *arg0, int argc, char **argv) {
     char *in_file = nullptr;
     ZigList<const char *> clang_argv = {0};
+    ErrColor color = ErrColorAuto;
     for (int i = 0; i < argc; i += 1) {
         char *arg = argv[i];
         if (arg[0] == '-') {
@@ -206,10 +176,24 @@ static int parseh(const char *arg0, int argc, char **argv) {
                 if (i + 1 >= argc) {
                     return usage(arg0);
                 }
+                i += 1;
                 clang_argv.append("-isystem");
-                clang_argv.append(argv[i + 1]);
+                clang_argv.append(argv[i]);
+            } else if (strcmp(arg, "--color") == 0) {
+                if (i + 1 >= argc) {
+                    return usage(arg0);
+                }
                 i += 1;
-            } else {
+                if (strcmp(argv[i], "auto") == 0) {
+                    color = ErrColorAuto;
+                } else if (strcmp(argv[i], "on") == 0) {
+                    color = ErrColorOn;
+                } else if (strcmp(argv[i], "off") == 0) {
+                    color = ErrColorOff;
+                } else {
+                    return usage(arg0);
+                }
+        } else {
                 fprintf(stderr, "unrecognized argument: %s", arg);
                 return usage(arg0);
             }
@@ -231,91 +215,24 @@ static int parseh(const char *arg0, int argc, char **argv) {
     clang_argv.append("-isystem");
     clang_argv.append(buf_ptr(libc_include_path));
 
-    ParseHPrint parse_h_print = {{{0}}};
-    ParseHPrint *p = &parse_h_print;
-    p->f = stdout;
-    p->cur_indent = 0;
-
-    parse_h_file(&p->parse_h, &clang_argv);
+    ImportTableEntry import = {0};
+    ZigList<ErrorMsg *> errors = {0};
+    int err = parse_h_file(&import, &errors, &clang_argv);
 
-    if (p->parse_h.errors.length > 0) {
-        for (int i = 0; i < p->parse_h.errors.length; i += 1) {
-            ErrorMsg *err_msg = p->parse_h.errors.at(i);
-            // TODO respect --color arg
-            print_err_msg(err_msg, ErrColorAuto);
-        }
+    if (err) {
+        fprintf(stderr, "unable to parse .h file: %s\n", err_str(err));
         return EXIT_FAILURE;
     }
 
-    for (int var_i = 0; var_i < p->parse_h.var_list.length; var_i += 1) {
-        AstNode *var_decl = p->parse_h.var_list.at(var_i);
-        assert(var_decl->type == NodeTypeVariableDeclaration);
-        const char *pub_str = (var_decl->data.variable_declaration.visib_mod == VisibModPub) ? "pub " : "";
-        const char *extern_str = var_decl->data.variable_declaration.is_extern ? "extern " : "";
-        const char *var_name = buf_ptr(&var_decl->data.variable_declaration.symbol);
-        const char *const_or_var = var_decl->data.variable_declaration.is_const ? "const" : "var";
-        print_indent(p);
-        fprintf(p->f, "%s%s%s %s", pub_str, extern_str, const_or_var, var_name);
-        if (var_decl->data.variable_declaration.type) {
-            fprintf(p->f, ": %s", buf_ptr(node_to_buf(var_decl->data.variable_declaration.type)));
-        }
-        if (var_decl->data.variable_declaration.expr) {
-            fprintf(p->f, " = %s", buf_ptr(node_to_buf(var_decl->data.variable_declaration.expr)));
+    if (errors.length > 0) {
+        for (int i = 0; i < errors.length; i += 1) {
+            ErrorMsg *err_msg = errors.at(i);
+            print_err_msg(err_msg, color);
         }
-        fprintf(p->f, ";\n");
-    }
-
-    for (int struct_i = 0; struct_i < p->parse_h.struct_list.length; struct_i += 1) {
-        AstNode *struct_decl = p->parse_h.struct_list.at(struct_i);
-        assert(struct_decl->type == NodeTypeStructDecl);
-        const char *struct_name = buf_ptr(&struct_decl->data.struct_decl.name);
-        print_indent(p);
-        fprintf(p->f, "struct %s {\n", struct_name);
-        p->cur_indent += indent_size;
-        for (int field_i = 0; field_i < struct_decl->data.struct_decl.fields.length; field_i += 1) {
-            AstNode *field_node = struct_decl->data.struct_decl.fields.at(field_i);
-            assert(field_node->type == NodeTypeStructField);
-            const char *field_name = buf_ptr(&field_node->data.struct_field.name);
-            Buf *type_name = node_to_buf(field_node->data.struct_field.type);
-            print_indent(p);
-            fprintf(p->f, "%s: %s,\n", field_name, buf_ptr(type_name));
-        }
-
-        p->cur_indent -= indent_size;
-        fprintf(p->f, "}\n\n");
+        return EXIT_FAILURE;
     }
 
-    for (int fn_i = 0; fn_i < p->parse_h.fn_list.length; fn_i += 1) {
-        AstNode *fn_proto = p->parse_h.fn_list.at(fn_i);
-        assert(fn_proto->type == NodeTypeFnProto);
-        print_indent(p);
-        const char *fn_name = buf_ptr(&fn_proto->data.fn_proto.name);
-        const char *pub_str = (fn_proto->data.fn_proto.visib_mod == VisibModPub) ? "pub " : "";
-        const char *extern_str = fn_proto->data.fn_proto.is_extern ? "extern " : "";
-        fprintf(p->f, "%s%sfn %s(", pub_str, extern_str, fn_name);
-        int arg_count = fn_proto->data.fn_proto.params.length;
-        bool is_var_args = fn_proto->data.fn_proto.is_var_args;
-        for (int arg_i = 0; arg_i < arg_count; arg_i += 1) {
-            AstNode *param_decl = fn_proto->data.fn_proto.params.at(arg_i);
-            assert(param_decl->type == NodeTypeParamDecl);
-            const char *arg_name = buf_ptr(&param_decl->data.param_decl.name);
-            Buf *arg_type = node_to_buf(param_decl->data.param_decl.type);
-            const char *noalias_str = param_decl->data.param_decl.is_noalias ? "noalias " : "";
-            fprintf(p->f, "%s%s: %s", noalias_str, arg_name, buf_ptr(arg_type));
-            if (arg_i + 1 < arg_count || is_var_args) {
-                fprintf(p->f, ", ");
-            }
-        }
-        if (is_var_args) {
-            fprintf(p->f, "...");
-        }
-        fprintf(p->f, ")");
-        Buf *return_type_name = node_to_buf(fn_proto->data.fn_proto.return_type);
-        if (!buf_eql_str(return_type_name, "void")) {
-            fprintf(p->f, " -> %s", buf_ptr(return_type_name));
-        }
-        fprintf(p->f, ";\n");
-    }
+    ast_render(stdout, import.root, 4);
 
     return 0;
 }
src/parseh.cpp
@@ -9,6 +9,8 @@
 #include "config.h"
 #include "os.hpp"
 #include "error.hpp"
+#include "parser.hpp"
+#include "all_types.hpp"
 
 #include <clang/Frontend/ASTUnit.h>
 #include <clang/Frontend/CompilerInstance.h>
@@ -18,10 +20,12 @@
 using namespace clang;
 
 struct Context {
-    ParseH *parse_h;
+    ImportTableEntry *import;
+    ZigList<ErrorMsg *> *errors;
     bool warnings_on;
     VisibMod visib_mod;
     AstNode *c_void_decl_node;
+    AstNode *root;
     HashMap<Buf *, bool, buf_hash, buf_eql_buf> type_table;
 };
 
@@ -30,6 +34,7 @@ static AstNode *make_qual_type_node(Context *c, QualType qt);
 static AstNode *create_node(Context *c, NodeType type) {
     AstNode *node = allocate<AstNode>(1);
     node->type = type;
+    node->owner = c->import;
     return node;
 }
 
@@ -44,6 +49,10 @@ static const char *decl_name(const Decl *decl) {
     return (const char *)named_decl->getName().bytes_begin();
 }
 
+static ZigList<AstNode *> *create_empty_directives(Context *c) {
+    return allocate<ZigList<AstNode*>>(1);
+}
+
 static AstNode *create_typedef_node(Context *c, Buf *new_name, AstNode *target_node) {
     if (!target_node) {
         return nullptr;
@@ -53,7 +62,10 @@ static AstNode *create_typedef_node(Context *c, Buf *new_name, AstNode *target_n
     node->data.variable_declaration.is_const = true;
     node->data.variable_declaration.visib_mod = c->visib_mod;
     node->data.variable_declaration.expr = target_node;
-    c->parse_h->var_list.append(node);
+    node->data.variable_declaration.directives = create_empty_directives(c);
+    normalize_parent_ptrs(node);
+
+    c->root->data.root.top_level_decls.append(node);
     return node;
 }
 
@@ -79,6 +91,7 @@ static AstNode *pointer_to_type(Context *c, AstNode *type_node, bool is_const) {
     AstNode *node = create_node(c, NodeTypePrefixOpExpr);
     node->data.prefix_op_expr.prefix_op = is_const ? PrefixOpConstAddressOf : PrefixOpAddressOf;
     node->data.prefix_op_expr.primary_expr = convert_to_c_void(c, type_node);
+    normalize_parent_ptrs(node);
     return node;
 }
 
@@ -255,6 +268,7 @@ static void visit_fn_decl(Context *c, const FunctionDecl *fn_decl) {
     AstNode *node = create_node(c, NodeTypeFnProto);
     node->data.fn_proto.is_extern = true;
     node->data.fn_proto.visib_mod = c->visib_mod;
+    node->data.fn_proto.directives = create_empty_directives(c);
     node->data.fn_proto.is_var_args = fn_decl->isVariadic();
     buf_init_from_str(&node->data.fn_proto.name, decl_name(fn_decl));
 
@@ -276,6 +290,7 @@ static void visit_fn_decl(Context *c, const FunctionDecl *fn_decl) {
             break;
         }
 
+        normalize_parent_ptrs(param_decl_node);
         node->data.fn_proto.params.append(param_decl_node);
     }
 
@@ -296,8 +311,9 @@ static void visit_fn_decl(Context *c, const FunctionDecl *fn_decl) {
         return;
     }
 
-    c->parse_h->fn_list.append(node);
+    normalize_parent_ptrs(node);
 
+    c->root->data.root.top_level_decls.append(node);
 }
 
 static void visit_typedef_decl(Context *c, const TypedefNameDecl *typedef_decl) {
@@ -322,6 +338,7 @@ static void visit_typedef_decl(Context *c, const TypedefNameDecl *typedef_decl)
     AstNode *node = create_typedef_node(c, type_name, make_qual_type_node(c, child_qt));
 
     if (node) {
+        normalize_parent_ptrs(node);
         c->type_table.put(type_name, true);
     }
 }
@@ -345,7 +362,9 @@ static bool decl_visitor(void *context, const Decl *decl) {
     return true;
 }
 
-int parse_h_buf(ParseH *parse_h, Buf *source, const char **args, int args_len, const char *libc_include_path) {
+int parse_h_buf(ImportTableEntry *import, ZigList<ErrorMsg *> *errors, Buf *source,
+        const char **args, int args_len, const char *libc_include_path)
+{
     int err;
     Buf tmp_file_path = BUF_INIT;
     if ((err = os_buf_to_tmp_file(source, buf_create_from_str(".h"), &tmp_file_path))) {
@@ -361,17 +380,18 @@ int parse_h_buf(ParseH *parse_h, Buf *source, const char **args, int args_len, c
         clang_argv.append(args[i]);
     }
 
-    err = parse_h_file(parse_h, &clang_argv);
+    err = parse_h_file(import, errors, &clang_argv);
 
     os_delete_file(&tmp_file_path);
 
     return err;
 }
 
-int parse_h_file(ParseH *parse_h, ZigList<const char *> *clang_argv) {
+int parse_h_file(ImportTableEntry *import, ZigList<ErrorMsg *> *errors, ZigList<const char *> *clang_argv) {
     Context context = {0};
     Context *c = &context;
-    c->parse_h = parse_h;
+    c->import = import;
+    c->errors = errors;
     c->type_table.init(64);
 
     char *ZIG_PARSEH_CFLAGS = getenv("ZIG_PARSEH_CFLAGS");
@@ -455,13 +475,18 @@ int parse_h_file(ParseH *parse_h, ZigList<const char *> *clang_argv) {
 
             ErrorMsg *err_msg = err_msg_create_with_offset(path, line, column, offset, source, msg);
 
-            parse_h->errors.append(err_msg);
+            c->errors->append(err_msg);
         }
 
         return 0;
     }
 
+    c->root = create_node(c, NodeTypeRoot);
     ast_unit->visitLocalTopLevelDecls(c, decl_visitor);
+    normalize_parent_ptrs(c->root);
+
+    import->root = c->root;
+    import->is_c_import = true;
 
     return 0;
 }
src/parseh.hpp
@@ -11,7 +11,9 @@
 
 #include "all_types.hpp"
 
-int parse_h_file(ParseH *parse_h, ZigList<const char *> *clang_argv);
-int parse_h_buf(ParseH *parse_h, Buf *source, const char **args, int args_len, const char *libc_include_path);
+int parse_h_file(ImportTableEntry *out_import, ZigList<ErrorMsg *> *out_errs,
+        ZigList<const char *> *clang_argv);
+int parse_h_buf(ImportTableEntry *out_import, ZigList<ErrorMsg *> *out_errs,
+        Buf *source, const char **args, int args_len, const char *libc_include_path);
 
 #endif
src/parser.cpp
@@ -14,465 +14,6 @@
 #include <limits.h>
 #include <errno.h>
 
-static const char *bin_op_str(BinOpType bin_op) {
-    switch (bin_op) {
-        case BinOpTypeInvalid:             return "(invalid)";
-        case BinOpTypeBoolOr:              return "||";
-        case BinOpTypeBoolAnd:             return "&&";
-        case BinOpTypeCmpEq:               return "==";
-        case BinOpTypeCmpNotEq:            return "!=";
-        case BinOpTypeCmpLessThan:         return "<";
-        case BinOpTypeCmpGreaterThan:      return ">";
-        case BinOpTypeCmpLessOrEq:         return "<=";
-        case BinOpTypeCmpGreaterOrEq:      return ">=";
-        case BinOpTypeBinOr:               return "|";
-        case BinOpTypeBinXor:              return "^";
-        case BinOpTypeBinAnd:              return "&";
-        case BinOpTypeBitShiftLeft:        return "<<";
-        case BinOpTypeBitShiftRight:       return ">>";
-        case BinOpTypeAdd:                 return "+";
-        case BinOpTypeSub:                 return "-";
-        case BinOpTypeMult:                return "*";
-        case BinOpTypeDiv:                 return "/";
-        case BinOpTypeMod:                 return "%";
-        case BinOpTypeAssign:              return "=";
-        case BinOpTypeAssignTimes:         return "*=";
-        case BinOpTypeAssignDiv:           return "/=";
-        case BinOpTypeAssignMod:           return "%=";
-        case BinOpTypeAssignPlus:          return "+=";
-        case BinOpTypeAssignMinus:         return "-=";
-        case BinOpTypeAssignBitShiftLeft:  return "<<=";
-        case BinOpTypeAssignBitShiftRight: return ">>=";
-        case BinOpTypeAssignBitAnd:        return "&=";
-        case BinOpTypeAssignBitXor:        return "^=";
-        case BinOpTypeAssignBitOr:         return "|=";
-        case BinOpTypeAssignBoolAnd:       return "&&=";
-        case BinOpTypeAssignBoolOr:        return "||=";
-        case BinOpTypeUnwrapMaybe:         return "??";
-        case BinOpTypeStrCat:              return "++";
-    }
-    zig_unreachable();
-}
-
-static const char *prefix_op_str(PrefixOp prefix_op) {
-    switch (prefix_op) {
-        case PrefixOpInvalid: return "(invalid)";
-        case PrefixOpNegation: return "-";
-        case PrefixOpBoolNot: return "!";
-        case PrefixOpBinNot: return "~";
-        case PrefixOpAddressOf: return "&";
-        case PrefixOpConstAddressOf: return "&const";
-        case PrefixOpDereference: return "*";
-        case PrefixOpMaybe: return "?";
-        case PrefixOpError: return "%";
-        case PrefixOpUnwrapError: return "%%";
-    }
-    zig_unreachable();
-}
-
-static const char *return_prefix_str(ReturnKind kind) {
-    switch (kind) {
-        case ReturnKindError: return "%";
-        case ReturnKindMaybe: return "?";
-        case ReturnKindUnconditional: return "";
-    }
-    zig_unreachable();
-}
-
-const char *node_type_str(NodeType node_type) {
-    switch (node_type) {
-        case NodeTypeRoot:
-            return "Root";
-        case NodeTypeRootExportDecl:
-            return "RootExportDecl";
-        case NodeTypeFnDef:
-            return "FnDef";
-        case NodeTypeFnDecl:
-            return "FnDecl";
-        case NodeTypeFnProto:
-            return "FnProto";
-        case NodeTypeParamDecl:
-            return "ParamDecl";
-        case NodeTypeBlock:
-            return "Block";
-        case NodeTypeBinOpExpr:
-            return "BinOpExpr";
-        case NodeTypeUnwrapErrorExpr:
-            return "UnwrapErrorExpr";
-        case NodeTypeFnCallExpr:
-            return "FnCallExpr";
-        case NodeTypeArrayAccessExpr:
-            return "ArrayAccessExpr";
-        case NodeTypeSliceExpr:
-            return "SliceExpr";
-        case NodeTypeDirective:
-            return "Directive";
-        case NodeTypeReturnExpr:
-            return "ReturnExpr";
-        case NodeTypeVariableDeclaration:
-            return "VariableDeclaration";
-        case NodeTypeErrorValueDecl:
-            return "ErrorValueDecl";
-        case NodeTypeNumberLiteral:
-            return "NumberLiteral";
-        case NodeTypeStringLiteral:
-            return "StringLiteral";
-        case NodeTypeCharLiteral:
-            return "CharLiteral";
-        case NodeTypeSymbol:
-            return "Symbol";
-        case NodeTypePrefixOpExpr:
-            return "PrefixOpExpr";
-        case NodeTypeImport:
-            return "Import";
-        case NodeTypeCImport:
-            return "CImport";
-        case NodeTypeBoolLiteral:
-            return "BoolLiteral";
-        case NodeTypeNullLiteral:
-            return "NullLiteral";
-        case NodeTypeUndefinedLiteral:
-            return "UndefinedLiteral";
-        case NodeTypeIfBoolExpr:
-            return "IfBoolExpr";
-        case NodeTypeIfVarExpr:
-            return "IfVarExpr";
-        case NodeTypeWhileExpr:
-            return "WhileExpr";
-        case NodeTypeForExpr:
-            return "ForExpr";
-        case NodeTypeSwitchExpr:
-            return "SwitchExpr";
-        case NodeTypeSwitchProng:
-            return "SwitchProng";
-        case NodeTypeSwitchRange:
-            return "SwitchRange";
-        case NodeTypeLabel:
-            return "Label";
-        case NodeTypeGoto:
-            return "Goto";
-        case NodeTypeBreak:
-            return "Break";
-        case NodeTypeContinue:
-            return "Continue";
-        case NodeTypeAsmExpr:
-            return "AsmExpr";
-        case NodeTypeFieldAccessExpr:
-            return "FieldAccessExpr";
-        case NodeTypeStructDecl:
-            return "StructDecl";
-        case NodeTypeStructField:
-            return "StructField";
-        case NodeTypeStructValueField:
-            return "StructValueField";
-        case NodeTypeContainerInitExpr:
-            return "ContainerInitExpr";
-        case NodeTypeArrayType:
-            return "ArrayType";
-        case NodeTypeErrorType:
-            return "ErrorType";
-    }
-    zig_unreachable();
-}
-
-void ast_print(AstNode *node, int indent) {
-    for (int i = 0; i < indent; i += 1) {
-        fprintf(stderr, " ");
-    }
-    assert(node->type == NodeTypeRoot || *node->parent_field == node);
-
-    switch (node->type) {
-        case NodeTypeRoot:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            for (int i = 0; i < node->data.root.top_level_decls.length; i += 1) {
-                AstNode *child = node->data.root.top_level_decls.at(i);
-                ast_print(child, indent + 2);
-            }
-            break;
-        case NodeTypeRootExportDecl:
-            fprintf(stderr, "%s %s '%s'\n", node_type_str(node->type),
-                    buf_ptr(&node->data.root_export_decl.type),
-                    buf_ptr(&node->data.root_export_decl.name));
-            break;
-        case NodeTypeFnDef:
-            {
-                fprintf(stderr, "%s\n", node_type_str(node->type));
-                AstNode *child = node->data.fn_def.fn_proto;
-                ast_print(child, indent + 2);
-                ast_print(node->data.fn_def.body, indent + 2);
-                break;
-            }
-        case NodeTypeFnProto:
-            {
-                Buf *name_buf = &node->data.fn_proto.name;
-                fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(name_buf));
-
-                for (int i = 0; i < node->data.fn_proto.params.length; i += 1) {
-                    AstNode *child = node->data.fn_proto.params.at(i);
-                    ast_print(child, indent + 2);
-                }
-
-                ast_print(node->data.fn_proto.return_type, indent + 2);
-
-                break;
-            }
-        case NodeTypeBlock:
-            {
-                fprintf(stderr, "%s\n", node_type_str(node->type));
-                for (int i = 0; i < node->data.block.statements.length; i += 1) {
-                    AstNode *child = node->data.block.statements.at(i);
-                    ast_print(child, indent + 2);
-                }
-                break;
-            }
-        case NodeTypeParamDecl:
-            {
-                Buf *name_buf = &node->data.param_decl.name;
-                fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(name_buf));
-
-                ast_print(node->data.param_decl.type, indent + 2);
-
-                break;
-            }
-        case NodeTypeReturnExpr:
-            {
-                const char *prefix_str = return_prefix_str(node->data.return_expr.kind);
-                fprintf(stderr, "%s%s\n", prefix_str, node_type_str(node->type));
-                if (node->data.return_expr.expr)
-                    ast_print(node->data.return_expr.expr, indent + 2);
-                break;
-            }
-        case NodeTypeVariableDeclaration:
-            {
-                Buf *name_buf = &node->data.variable_declaration.symbol;
-                fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(name_buf));
-                if (node->data.variable_declaration.type)
-                    ast_print(node->data.variable_declaration.type, indent + 2);
-                if (node->data.variable_declaration.expr)
-                    ast_print(node->data.variable_declaration.expr, indent + 2);
-                break;
-            }
-        case NodeTypeErrorValueDecl:
-            {
-                Buf *name_buf = &node->data.error_value_decl.name;
-                fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(name_buf));
-                break;
-            }
-        case NodeTypeFnDecl:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            ast_print(node->data.fn_decl.fn_proto, indent + 2);
-            break;
-        case NodeTypeBinOpExpr:
-            fprintf(stderr, "%s %s\n", node_type_str(node->type),
-                    bin_op_str(node->data.bin_op_expr.bin_op));
-            ast_print(node->data.bin_op_expr.op1, indent + 2);
-            ast_print(node->data.bin_op_expr.op2, indent + 2);
-            break;
-        case NodeTypeUnwrapErrorExpr:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            ast_print(node->data.unwrap_err_expr.op1, indent + 2);
-            if (node->data.unwrap_err_expr.symbol) {
-                ast_print(node->data.unwrap_err_expr.symbol, indent + 2);
-            }
-            ast_print(node->data.unwrap_err_expr.op2, indent + 2);
-            break;
-        case NodeTypeFnCallExpr:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            ast_print(node->data.fn_call_expr.fn_ref_expr, indent + 2);
-            for (int i = 0; i < node->data.fn_call_expr.params.length; i += 1) {
-                AstNode *child = node->data.fn_call_expr.params.at(i);
-                ast_print(child, indent + 2);
-            }
-            break;
-        case NodeTypeArrayAccessExpr:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            ast_print(node->data.array_access_expr.array_ref_expr, indent + 2);
-            ast_print(node->data.array_access_expr.subscript, indent + 2);
-            break;
-        case NodeTypeSliceExpr:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            ast_print(node->data.slice_expr.array_ref_expr, indent + 2);
-            ast_print(node->data.slice_expr.start, indent + 2);
-            if (node->data.slice_expr.end) {
-                ast_print(node->data.slice_expr.end, indent + 2);
-            }
-            break;
-        case NodeTypeDirective:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            break;
-        case NodeTypePrefixOpExpr:
-            fprintf(stderr, "%s %s\n", node_type_str(node->type),
-                    prefix_op_str(node->data.prefix_op_expr.prefix_op));
-            ast_print(node->data.prefix_op_expr.primary_expr, indent + 2);
-            break;
-        case NodeTypeNumberLiteral:
-            {
-                NumLit kind = node->data.number_literal.kind;
-                const char *name = node_type_str(node->type);
-                if (kind == NumLitUInt) {
-                    fprintf(stderr, "%s uint %" PRIu64 "\n", name, node->data.number_literal.data.x_uint);
-                } else {
-                    fprintf(stderr, "%s float %f\n", name, node->data.number_literal.data.x_float);
-                }
-                break;
-            }
-        case NodeTypeStringLiteral:
-            {
-                const char *c = node->data.string_literal.c ? "c" : "";
-                fprintf(stderr, "StringLiteral %s'%s'\n", c,
-                        buf_ptr(&node->data.string_literal.buf));
-                break;
-            }
-        case NodeTypeCharLiteral:
-            {
-                fprintf(stderr, "%s '%c'\n", node_type_str(node->type), node->data.char_literal.value);
-                break;
-            }
-        case NodeTypeSymbol:
-            fprintf(stderr, "Symbol %s\n", buf_ptr(&node->data.symbol_expr.symbol));
-            break;
-        case NodeTypeImport:
-            fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(&node->data.import.path));
-            break;
-        case NodeTypeCImport:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            ast_print(node->data.c_import.block, indent + 2);
-            break;
-        case NodeTypeBoolLiteral:
-            fprintf(stderr, "%s '%s'\n", node_type_str(node->type),
-                    node->data.bool_literal.value ? "true" : "false");
-            break;
-        case NodeTypeNullLiteral:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            break;
-        case NodeTypeIfBoolExpr:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            if (node->data.if_bool_expr.condition)
-                ast_print(node->data.if_bool_expr.condition, indent + 2);
-            ast_print(node->data.if_bool_expr.then_block, indent + 2);
-            if (node->data.if_bool_expr.else_node)
-                ast_print(node->data.if_bool_expr.else_node, indent + 2);
-            break;
-        case NodeTypeIfVarExpr:
-            {
-                Buf *name_buf = &node->data.if_var_expr.var_decl.symbol;
-                fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(name_buf));
-                if (node->data.if_var_expr.var_decl.type)
-                    ast_print(node->data.if_var_expr.var_decl.type, indent + 2);
-                if (node->data.if_var_expr.var_decl.expr)
-                    ast_print(node->data.if_var_expr.var_decl.expr, indent + 2);
-                ast_print(node->data.if_var_expr.then_block, indent + 2);
-                if (node->data.if_var_expr.else_node)
-                    ast_print(node->data.if_var_expr.else_node, indent + 2);
-                break;
-            }
-        case NodeTypeWhileExpr:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            ast_print(node->data.while_expr.condition, indent + 2);
-            ast_print(node->data.while_expr.body, indent + 2);
-            break;
-        case NodeTypeForExpr:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            ast_print(node->data.for_expr.elem_node, indent + 2);
-            ast_print(node->data.for_expr.array_expr, indent + 2);
-            if (node->data.for_expr.index_node) {
-                ast_print(node->data.for_expr.index_node, indent + 2);
-            }
-            ast_print(node->data.for_expr.body, indent + 2);
-            break;
-        case NodeTypeSwitchExpr:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            ast_print(node->data.switch_expr.expr, indent + 2);
-            for (int i = 0; i < node->data.switch_expr.prongs.length; i += 1) {
-                AstNode *child_node = node->data.switch_expr.prongs.at(i);
-                ast_print(child_node, indent + 2);
-            }
-            break;
-        case NodeTypeSwitchProng:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            for (int i = 0; i < node->data.switch_prong.items.length; i += 1) {
-                AstNode *child_node = node->data.switch_prong.items.at(i);
-                ast_print(child_node, indent + 2);
-            }
-            if (node->data.switch_prong.var_symbol) {
-                ast_print(node->data.switch_prong.var_symbol, indent + 2);
-            }
-            ast_print(node->data.switch_prong.expr, indent + 2);
-            break;
-        case NodeTypeSwitchRange:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            ast_print(node->data.switch_range.start, indent + 2);
-            ast_print(node->data.switch_range.end, indent + 2);
-            break;
-        case NodeTypeLabel:
-            fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(&node->data.label.name));
-            break;
-        case NodeTypeGoto:
-            fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(&node->data.goto_expr.name));
-            break;
-        case NodeTypeBreak:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            break;
-        case NodeTypeContinue:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            break;
-        case NodeTypeUndefinedLiteral:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            break;
-        case NodeTypeAsmExpr:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            break;
-        case NodeTypeFieldAccessExpr:
-            fprintf(stderr, "%s '%s'\n", node_type_str(node->type),
-                    buf_ptr(&node->data.field_access_expr.field_name));
-            ast_print(node->data.field_access_expr.struct_expr, indent + 2);
-            break;
-        case NodeTypeStructDecl:
-            fprintf(stderr, "%s '%s'\n",
-                    node_type_str(node->type), buf_ptr(&node->data.struct_decl.name));
-            for (int i = 0; i < node->data.struct_decl.fields.length; i += 1) {
-                AstNode *child = node->data.struct_decl.fields.at(i);
-                ast_print(child, indent + 2);
-            }
-            for (int i = 0; i < node->data.struct_decl.fns.length; i += 1) {
-                AstNode *child = node->data.struct_decl.fns.at(i);
-                ast_print(child, indent + 2);
-            }
-            break;
-        case NodeTypeStructField:
-            fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(&node->data.struct_field.name));
-            if (node->data.struct_field.type) {
-                ast_print(node->data.struct_field.type, indent + 2);
-            }
-            break;
-        case NodeTypeStructValueField:
-            fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(&node->data.struct_val_field.name));
-            ast_print(node->data.struct_val_field.expr, indent + 2);
-            break;
-        case NodeTypeContainerInitExpr:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            ast_print(node->data.container_init_expr.type, indent + 2);
-            for (int i = 0; i < node->data.container_init_expr.entries.length; i += 1) {
-                AstNode *child = node->data.container_init_expr.entries.at(i);
-                ast_print(child, indent + 2);
-            }
-            break;
-        case NodeTypeArrayType:
-            {
-                const char *const_str = node->data.array_type.is_const ? "const" : "var";
-                fprintf(stderr, "%s %s\n", node_type_str(node->type), const_str);
-                if (node->data.array_type.size) {
-                    ast_print(node->data.array_type.size, indent + 2);
-                }
-                ast_print(node->data.array_type.child_type, indent + 2);
-                break;
-            }
-        case NodeTypeErrorType:
-            fprintf(stderr, "%s\n", node_type_str(node->type));
-            break;
-    }
-}
-
 struct ParseContext {
     Buf *buf;
     AstNode *root;
CMakeLists.txt
@@ -25,6 +25,7 @@ include_directories(
 )
 
 set(ZIG_SOURCES
+    "${CMAKE_SOURCE_DIR}/src/ast_render.cpp"
     "${CMAKE_SOURCE_DIR}/src/bignum.cpp"
     "${CMAKE_SOURCE_DIR}/src/tokenizer.cpp"
     "${CMAKE_SOURCE_DIR}/src/parser.cpp"