about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--boot/catboot.h2
-rw-r--r--boot/visit/ir.c1032
-rw-r--r--boot/visit/tree.c11
-rw-r--r--boot/visit/visit.c20
5 files changed, 1055 insertions, 12 deletions
diff --git a/Makefile b/Makefile
index 002966b..7e1f989 100644
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,7 @@ LDFLAGS ?= -ltcc
 
 .DEFAULT_GOAL := all
 
-BOOTSTRAP_SOURCES = boot/catboot.c boot/catboot.h boot/common.c boot/lex.c boot/tree.c boot/visit/tree.c boot/ir.c boot/parse.c boot/transpile.c boot/build.c
+BOOTSTRAP_SOURCES = boot/catboot.c boot/catboot.h boot/common.c boot/lex.c boot/tree.c boot/visit/visit.c boot/visit/tree.c boot/ir.c boot/visit/ir.c boot/parse.c boot/transpile.c boot/build.c
 BOOTSTRAP_TEST_SOURCES = boot/tests/test.c
 SOURCES = src/catskill.csk
 
diff --git a/boot/catboot.h b/boot/catboot.h
index e5ffe5e..71c2366 100644
--- a/boot/catboot.h
+++ b/boot/catboot.h
@@ -17,7 +17,9 @@
 #include "lex.c"
 #include "tree.c"
 #include "parse.c"
+#include "visit/visit.c"
 #include "visit/tree.c"
 #include "ir.c"
+#include "visit/ir.c"
 #include "transpile.c"
 #include "build.c"
diff --git a/boot/visit/ir.c b/boot/visit/ir.c
new file mode 100644
index 0000000..13ea529
--- /dev/null
+++ b/boot/visit/ir.c
@@ -0,0 +1,1032 @@
+/*
+ * visit system for the ir,
+ * implementing a default traversal mechanism,
+ * allowing for selective operations on it.
+ * includes a simple ir printer as the primary example of usage.
+ *
+ * Copyright (c) 2026, Mel G. <mel@rnrd.eu>
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+#pragma once
+
+#include "../catboot.h"
+
+struct Visit
+{
+    struct Visit_Table* table;
+    void* user_data;
+};
+
+struct Visit_Table
+{
+    void (*visit_unit)(struct Visit* visitor, struct Unit* unit);
+
+    void (*visit_type)(struct Visit* visitor, struct Type* type);
+    void (*visit_type_primitive)(struct Visit* visitor, struct Type* type);
+    void (*visit_type_alias)(struct Visit* visitor, struct Type* type);
+    void (*visit_type_structure)(struct Visit* visitor, struct Type* type);
+    void (*visit_type_variant)(struct Visit* visitor, struct Type* type);
+    void (*visit_type_function)(struct Visit* visitor, struct Type* type);
+
+    void (*visit_function)(struct Visit* visitor, struct Function* function);
+
+    void (*visit_statement)(struct Visit* visitor, struct Statement* stmt);
+    void (*visit_statement_declaration)(struct Visit* visitor, struct Statement* stmt);
+    void (*visit_statement_assign)(struct Visit* visitor, struct Statement* stmt);
+    void (*visit_statement_expression)(struct Visit* visitor, struct Statement* stmt);
+    void (*visit_statement_conditional)(struct Visit* visitor, struct Statement* stmt);
+    void (*visit_statement_loop)(struct Visit* visitor, struct Statement* stmt);
+    void (*visit_statement_return)(struct Visit* visitor, struct Statement* stmt);
+    void (*visit_statement_break)(struct Visit* visitor, struct Statement* stmt);
+    void (*visit_statement_continue)(struct Visit* visitor, struct Statement* stmt);
+    void (*visit_statement_block)(struct Visit* visitor, struct Statement* stmt);
+    void (*visit_statement_label)(struct Visit* visitor, struct Statement* stmt);
+    void (*visit_statement_goto)(struct Visit* visitor, struct Statement* stmt);
+
+    void (*visit_expression)(struct Visit* visitor, struct Expression* expr);
+    void (*visit_expression_integer_literal)(struct Visit* visitor, struct Expression* expr);
+    void (*visit_expression_float_literal)(struct Visit* visitor, struct Expression* expr);
+    void (*visit_expression_string_literal)(struct Visit* visitor, struct Expression* expr);
+    void (*visit_expression_boolean_literal)(struct Visit* visitor, struct Expression* expr);
+    void (*visit_expression_name)(struct Visit* visitor, struct Expression* expr);
+    void (*visit_expression_unary_operation)(struct Visit* visitor, struct Expression* expr);
+    void (*visit_expression_binary_operation)(struct Visit* visitor, struct Expression* expr);
+    void (*visit_expression_sizeof_operation)(struct Visit* visitor, struct Expression* expr);
+    void (*visit_expression_call)(struct Visit* visitor, struct Expression* expr);
+    void (*visit_expression_member)(struct Visit* visitor, struct Expression* expr);
+    void (*visit_expression_subscript)(struct Visit* visitor, struct Expression* expr);
+    void (*visit_expression_cast)(struct Visit* visitor, struct Expression* expr);
+    void (*visit_expression_construct)(struct Visit* visitor, struct Expression* expr);
+
+    void (*visit_block)(struct Visit* visitor, struct Block* block);
+    void (*visit_type_ref)(struct Visit* visitor, struct Type_Ref* ref);
+    void (*visit_import)(struct Visit* visitor, struct Import* import);
+    void (*visit_diagnostic)(struct Visit* visitor, struct Diagnostic* diagnostic);
+};
+
+void
+walk(struct Visit* visit, struct Unit* unit)
+{
+    VISIT(visit_unit, unit);
+}
+
+void
+walk_unit(struct Visit* visit, struct Unit* unit)
+{
+    FOR_EACH_ARRAY(struct Type*, type, &unit->types.entries, {
+        VISIT(visit_type, type);
+    })
+    FOR_EACH_ARRAY(struct Function*, function, &unit->functions.entries, {
+        VISIT(visit_function, function);
+    })
+    for (uint i = 0; i < array_length(&unit->imports); ++i) {
+        VISIT(visit_import, array_at(struct Import, &unit->imports, i));
+    }
+    for (uint i = 0; i < array_length(&unit->diagnostics); ++i) {
+        VISIT(visit_diagnostic, array_at(struct Diagnostic, &unit->diagnostics, i));
+    }
+}
+
+void
+walk_type(struct Visit* visit, struct Type* type)
+{
+    switch (type->kind) {
+    case TYPE_PRIMITIVE:
+        VISIT(visit_type_primitive, type);
+        break;
+    case TYPE_ALIAS:
+        VISIT(visit_type_alias, type);
+        break;
+    case TYPE_STRUCTURE:
+        VISIT(visit_type_structure, type);
+        break;
+    case TYPE_VARIANT:
+        VISIT(visit_type_variant, type);
+        break;
+    case TYPE_FUNCTION:
+        VISIT(visit_type_function, type);
+        break;
+    case TYPE_NONE:
+        break;
+    default:
+        failure("unexpected type kind in `walk_type`");
+    }
+}
+
+void walk_type_primitive(struct Visit* visit, struct Type* type) {}
+void walk_type_alias(struct Visit* visit, struct Type* type) {}
+
+void
+walk_type_structure(struct Visit* visit, struct Type* type)
+{
+    for (uint i = 0; i < array_length(&type->value.structure.fields); ++i) {
+        struct Field* field = array_at(struct Field, &type->value.structure.fields, i);
+        VISIT(visit_type_ref, &field->type);
+    }
+}
+
+void
+walk_type_variant(struct Visit* visit, struct Type* type)
+{
+    for (uint i = 0; i < array_length(&type->value.variant.cases); ++i) {
+        struct Variant_Case* one_case = array_at(struct Variant_Case, &type->value.variant.cases, i);
+        if (one_case->has_payload) VISIT(visit_type_ref, &one_case->payload);
+    }
+}
+
+void
+walk_type_function(struct Visit* visit, struct Type* type)
+{
+    VISIT(visit_type_ref, &type->value.function.return_type);
+    for (uint i = 0; i < array_length(&type->value.function.params); ++i) {
+        struct Type_Ref* param = array_at(struct Type_Ref, &type->value.function.params, i);
+        VISIT(visit_type_ref, param);
+    }
+}
+
+void
+walk_function(struct Visit* visit, struct Function* function)
+{
+    VISIT(visit_type_ref, &function->return_type);
+    for (uint i = 0; i < array_length(&function->params); ++i) {
+        struct Param* param = array_at(struct Param, &function->params, i);
+        VISIT(visit_type_ref, &param->type);
+    }
+    VISIT_MAYBE(visit_block, function->body);
+}
+
+void
+walk_statement(struct Visit* visit, struct Statement* stmt)
+{
+    switch (stmt->kind) {
+    case STATEMENT_DECLARATION:
+        VISIT(visit_statement_declaration, stmt);
+        break;
+    case STATEMENT_ASSIGN:
+        VISIT(visit_statement_assign, stmt);
+        break;
+    case STATEMENT_EXPRESSION:
+        VISIT(visit_statement_expression, stmt);
+        break;
+    case STATEMENT_CONDITIONAL:
+        VISIT(visit_statement_conditional, stmt);
+        break;
+    case STATEMENT_LOOP:
+        VISIT(visit_statement_loop, stmt);
+        break;
+    case STATEMENT_RETURN:
+        VISIT(visit_statement_return, stmt);
+        break;
+    case STATEMENT_BREAK:
+        VISIT(visit_statement_break, stmt);
+        break;
+    case STATEMENT_CONTINUE:
+        VISIT(visit_statement_continue, stmt);
+        break;
+    case STATEMENT_BLOCK:
+        VISIT(visit_statement_block, stmt);
+        break;
+    case STATEMENT_LABEL:
+        VISIT(visit_statement_label, stmt);
+        break;
+    case STATEMENT_GOTO:
+        VISIT(visit_statement_goto, stmt);
+        break;
+    case STATEMENT_NONE:
+        break;
+    default:
+        failure("unexpected statement kind in `walk_statement`");
+    }
+}
+
+void
+walk_statement_declaration(struct Visit* visit, struct Statement* stmt)
+{
+    VISIT(visit_type_ref, &stmt->value.declaration.type);
+    VISIT_MAYBE(visit_expression, stmt->value.declaration.initializer);
+}
+
+void
+walk_statement_assign(struct Visit* visit, struct Statement* stmt)
+{
+    VISIT(visit_expression, stmt->value.assign.lhs);
+    VISIT(visit_expression, stmt->value.assign.rhs);
+}
+
+void
+walk_statement_expression(struct Visit* visit, struct Statement* stmt)
+{
+    VISIT(visit_expression, stmt->value.expression.inner);
+}
+
+void
+walk_statement_conditional(struct Visit* visit, struct Statement* stmt)
+{
+    for (uint i = 0; i < array_length(&stmt->value.conditional.branches); ++i) {
+        struct If_Branch* branch = array_at(struct If_Branch, &stmt->value.conditional.branches, i);
+        VISIT_MAYBE(visit_expression, branch->condition);
+        VISIT(visit_block, branch->body);
+    }
+}
+
+void
+walk_statement_loop(struct Visit* visit, struct Statement* stmt)
+{
+    VISIT(visit_expression, stmt->value.loop.condition);
+    VISIT(visit_block, stmt->value.loop.body);
+}
+
+void
+walk_statement_return(struct Visit* visit, struct Statement* stmt)
+{
+    VISIT_MAYBE(visit_expression, stmt->value.return_value.value);
+}
+
+void walk_statement_break(struct Visit* visit, struct Statement* stmt) {}
+void walk_statement_continue(struct Visit* visit, struct Statement* stmt) {}
+void walk_statement_label(struct Visit* visit, struct Statement* stmt) {}
+void walk_statement_goto(struct Visit* visit, struct Statement* stmt) {}
+
+void
+walk_statement_block(struct Visit* visit, struct Statement* stmt)
+{
+    VISIT(visit_block, stmt->value.block.inner);
+}
+
+void
+walk_expression(struct Visit* visit, struct Expression* expr)
+{
+    switch (expr->kind) {
+    case EXPRESSION_INTEGER_LITERAL:
+        VISIT(visit_expression_integer_literal, expr);
+        break;
+    case EXPRESSION_FLOAT_LITERAL:
+        VISIT(visit_expression_float_literal, expr);
+        break;
+    case EXPRESSION_STRING_LITERAL:
+        VISIT(visit_expression_string_literal, expr);
+        break;
+    case EXPRESSION_BOOLEAN_LITERAL:
+        VISIT(visit_expression_boolean_literal, expr);
+        break;
+    case EXPRESSION_NAME:
+        VISIT(visit_expression_name, expr);
+        break;
+    case EXPRESSION_UNARY_OPERATION:
+        VISIT(visit_expression_unary_operation, expr);
+        break;
+    case EXPRESSION_BINARY_OPERATION:
+        VISIT(visit_expression_binary_operation, expr);
+        break;
+    case EXPRESSION_SIZEOF_OPERATION:
+        VISIT(visit_expression_sizeof_operation, expr);
+        break;
+    case EXPRESSION_CALL:
+        VISIT(visit_expression_call, expr);
+        break;
+    case EXPRESSION_MEMBER:
+        VISIT(visit_expression_member, expr);
+        break;
+    case EXPRESSION_SUBSCRIPT:
+        VISIT(visit_expression_subscript, expr);
+        break;
+    case EXPRESSION_CAST:
+        VISIT(visit_expression_cast, expr);
+        break;
+    case EXPRESSION_CONSTRUCT:
+        VISIT(visit_expression_construct, expr);
+        break;
+    case EXPRESSION_NONE:
+        break;
+    default:
+        failure("unexpected expression kind in `walk_expression`");
+    }
+}
+
+void walk_expression_integer_literal(struct Visit* visit, struct Expression* expr) {}
+void walk_expression_float_literal(struct Visit* visit, struct Expression* expr) {}
+void walk_expression_string_literal(struct Visit* visit, struct Expression* expr) {}
+void walk_expression_boolean_literal(struct Visit* visit, struct Expression* expr) {}
+void walk_expression_name(struct Visit* visit, struct Expression* expr) {}
+
+void
+walk_expression_unary_operation(struct Visit* visit, struct Expression* expr)
+{
+    VISIT(visit_expression, expr->value.unary_operator.operand);
+}
+
+void
+walk_expression_binary_operation(struct Visit* visit, struct Expression* expr)
+{
+    VISIT(visit_expression, expr->value.binary_operator.left_operand);
+    VISIT(visit_expression, expr->value.binary_operator.right_operand);
+}
+
+void
+walk_expression_sizeof_operation(struct Visit* visit, struct Expression* expr)
+{
+    VISIT(visit_type_ref, &expr->value.sizeof_operator.target);
+}
+
+void
+walk_expression_call(struct Visit* visit, struct Expression* expr)
+{
+    VISIT(visit_expression, expr->value.call.subject);
+    FOR_EACH_ARRAY(struct Expression*, argument, &expr->value.call.arguments, {
+        VISIT(visit_expression, argument);
+    })
+}
+
+void
+walk_expression_member(struct Visit* visit, struct Expression* expr)
+{
+    VISIT(visit_expression, expr->value.member.subject);
+}
+
+void
+walk_expression_subscript(struct Visit* visit, struct Expression* expr)
+{
+    VISIT(visit_expression, expr->value.subscript.subject);
+    VISIT(visit_expression, expr->value.subscript.index);
+}
+
+void
+walk_expression_cast(struct Visit* visit, struct Expression* expr)
+{
+    VISIT(visit_type_ref, &expr->value.cast.target);
+    VISIT(visit_expression, expr->value.cast.operand);
+}
+
+void
+walk_expression_construct(struct Visit* visit, struct Expression* expr)
+{
+    for (uint i = 0; i < array_length(&expr->value.construct.fields); ++i) {
+        struct Construct_Field* field = array_at(struct Construct_Field, &expr->value.construct.fields, i);
+        VISIT(visit_expression, field->value);
+    }
+}
+
+void
+walk_block(struct Visit* visit, struct Block* block)
+{
+    FOR_EACH_ARRAY(struct Statement*, statement, &block->statements, {
+        VISIT(visit_statement, statement);
+    })
+}
+
+void walk_type_ref(struct Visit* visit, struct Type_Ref* ref) {}
+void walk_import(struct Visit* visit, struct Import* import) {}
+void walk_diagnostic(struct Visit* visit, struct Diagnostic* diagnostic) {}
+
+struct Visit_Table walk_functions = {
+    .visit_unit = walk_unit,
+
+    .visit_type = walk_type,
+    .visit_type_primitive = walk_type_primitive,
+    .visit_type_alias = walk_type_alias,
+    .visit_type_structure = walk_type_structure,
+    .visit_type_variant = walk_type_variant,
+    .visit_type_function = walk_type_function,
+
+    .visit_function = walk_function,
+
+    .visit_statement = walk_statement,
+    .visit_statement_declaration = walk_statement_declaration,
+    .visit_statement_assign = walk_statement_assign,
+    .visit_statement_expression = walk_statement_expression,
+    .visit_statement_conditional = walk_statement_conditional,
+    .visit_statement_loop = walk_statement_loop,
+    .visit_statement_return = walk_statement_return,
+    .visit_statement_break = walk_statement_break,
+    .visit_statement_continue = walk_statement_continue,
+    .visit_statement_block = walk_statement_block,
+    .visit_statement_label = walk_statement_label,
+    .visit_statement_goto = walk_statement_goto,
+
+    .visit_expression = walk_expression,
+    .visit_expression_integer_literal = walk_expression_integer_literal,
+    .visit_expression_float_literal = walk_expression_float_literal,
+    .visit_expression_string_literal = walk_expression_string_literal,
+    .visit_expression_boolean_literal = walk_expression_boolean_literal,
+    .visit_expression_name = walk_expression_name,
+    .visit_expression_unary_operation = walk_expression_unary_operation,
+    .visit_expression_binary_operation = walk_expression_binary_operation,
+    .visit_expression_sizeof_operation = walk_expression_sizeof_operation,
+    .visit_expression_call = walk_expression_call,
+    .visit_expression_member = walk_expression_member,
+    .visit_expression_subscript = walk_expression_subscript,
+    .visit_expression_cast = walk_expression_cast,
+    .visit_expression_construct = walk_expression_construct,
+
+    .visit_block = walk_block,
+    .visit_type_ref = walk_type_ref,
+    .visit_import = walk_import,
+    .visit_diagnostic = walk_diagnostic,
+};
+
+void
+visit_table_fill_defaults(struct Visit_Table* table)
+{
+    const uint function_count = sizeof(struct Visit_Table) / sizeof(void (*)());
+    typedef void (*Function_Ptr)();
+
+    for (uint fi = 0; fi < function_count; ++fi) {
+        Function_Ptr* function_slot = &((Function_Ptr*)table)[fi];
+        Function_Ptr* default_slot = &((Function_Ptr*)&walk_functions)[fi];
+        if (!*function_slot) *function_slot = *default_slot;
+    }
+}
+
+struct Printer
+{
+    uint indentation_level;
+    FILE* output;
+    bool did_print_last_visit;
+    struct Unit* unit;
+};
+
+#define PRINTER_PREAMBLE                  \
+    DATA_FOR_VISIT(struct Printer, p);    \
+    p->did_print_last_visit = false;
+
+void
+printer_out(struct Printer* p, const ascii* format, ...)
+{
+    va_list args;
+    va_start(args, format);
+    vfprintf(p->output, format, args);
+    va_end(args);
+    p->did_print_last_visit = true;
+}
+
+#define PRINT(...) printer_out(p, __VA_ARGS__)
+
+void
+printer_indent(struct Printer* p)
+{
+    for (uint i = 0; i < p->indentation_level; ++i) PRINT("\t");
+}
+
+struct Type*
+printer_type_at(struct Printer* p, Type_Id id)
+{
+    if (id >= array_length(&p->unit->types.entries)) return nil;
+    return *array_at(struct Type*, &p->unit->types.entries, id);
+}
+
+void
+printer_visit_unit(struct Visit* visit, struct Unit* unit)
+{
+    PRINTER_PREAMBLE
+
+    PRINT("(unit");
+    p->indentation_level++;
+
+    if (array_length(&unit->types.entries) > 0) {
+        PRINT("\n");
+        printer_indent(p);
+        PRINT("(types");
+        p->indentation_level++;
+        FOR_EACH_ARRAY(struct Type*, type, &unit->types.entries, {
+            PRINT("\n");
+            printer_indent(p);
+            VISIT(visit_type, type);
+        })
+        p->indentation_level--;
+        PRINT(")");
+    }
+
+    if (array_length(&unit->functions.entries) > 0) {
+        PRINT("\n");
+        printer_indent(p);
+        PRINT("(functions");
+        p->indentation_level++;
+        FOR_EACH_ARRAY(struct Function*, function, &unit->functions.entries, {
+            PRINT("\n");
+            printer_indent(p);
+            VISIT(visit_function, function);
+        })
+        p->indentation_level--;
+        PRINT(")");
+    }
+
+    if (array_length(&unit->imports) > 0) {
+        PRINT("\n");
+        printer_indent(p);
+        PRINT("(imports");
+        for (uint i = 0; i < array_length(&unit->imports); ++i) {
+            PRINT(" ");
+            VISIT(visit_import, array_at(struct Import, &unit->imports, i));
+        }
+        PRINT(")");
+    }
+
+    if (array_length(&unit->type_emission_order) > 0) {
+        PRINT("\n");
+        printer_indent(p);
+        PRINT("(emission_order");
+        for (uint i = 0; i < array_length(&unit->type_emission_order); ++i) {
+            PRINT(" %lu", *array_at(Type_Id, &unit->type_emission_order, i));
+        }
+        PRINT(")");
+    }
+
+    if (array_length(&unit->diagnostics) > 0) {
+        PRINT("\n");
+        printer_indent(p);
+        PRINT("(diagnostics");
+        p->indentation_level++;
+        for (uint i = 0; i < array_length(&unit->diagnostics); ++i) {
+            PRINT("\n");
+            printer_indent(p);
+            VISIT(visit_diagnostic, array_at(struct Diagnostic, &unit->diagnostics, i));
+        }
+        p->indentation_level--;
+        PRINT(")");
+    }
+
+    p->indentation_level--;
+    PRINT(")\n");
+}
+
+void
+printer_visit_type_ref(struct Visit* visit, struct Type_Ref* ref)
+{
+    PRINTER_PREAMBLE
+
+    PRINT("(ref");
+    for (uint i = 0; i < array_length(&ref->mods); ++i) {
+        enum Type_Modifier mod = *array_at(enum Type_Modifier, &ref->mods, i);
+        if (mod == TYPE_MOD_REFERENCE) PRINT(" &");
+    }
+    struct Type* target = printer_type_at(p, ref->type_id);
+    if (target) PRINT(" %s", string_c_str(target->name));
+    else PRINT(" #%lu", ref->type_id);
+    PRINT(")");
+}
+
+void
+printer_visit_type(struct Visit* visit, struct Type* type)
+{
+    PRINTER_PREAMBLE
+
+    PRINT("(type %lu %s", type->id, string_c_str(type->name));
+    if (type->synthetic) PRINT(" synthetic");
+    walk_type(visit, type);
+    PRINT(")");
+}
+
+void
+printer_visit_type_primitive(struct Visit* visit, struct Type* type)
+{
+    PRINTER_PREAMBLE
+    PRINT(" primitive");
+}
+
+void
+printer_visit_type_alias(struct Visit* visit, struct Type* type)
+{
+    PRINTER_PREAMBLE
+
+    PRINT(" alias");
+    struct Type* target = printer_type_at(p, type->value.alias.target_id);
+    if (target) PRINT(" %s", string_c_str(target->name));
+    else PRINT(" #%lu", type->value.alias.target_id);
+}
+
+void
+printer_visit_type_structure(struct Visit* visit, struct Type* type)
+{
+    PRINTER_PREAMBLE
+
+    PRINT(" structure");
+    for (uint i = 0; i < array_length(&type->value.structure.fields); ++i) {
+        struct Field* field = array_at(struct Field, &type->value.structure.fields, i);
+        PRINT(" (field %s ", string_c_str(field->name));
+        VISIT(visit_type_ref, &field->type);
+        PRINT(")");
+    }
+}
+
+void
+printer_visit_type_variant(struct Visit* visit, struct Type* type)
+{
+    PRINTER_PREAMBLE
+
+    PRINT(" variant");
+    for (uint i = 0; i < array_length(&type->value.variant.cases); ++i) {
+        struct Variant_Case* one_case = array_at(struct Variant_Case, &type->value.variant.cases, i);
+        PRINT(" (case %s tag=%u", string_c_str(one_case->name), one_case->tag);
+        if (one_case->has_payload) {
+            PRINT(" ");
+            VISIT(visit_type_ref, &one_case->payload);
+        }
+        PRINT(")");
+    }
+}
+
+void
+printer_visit_type_function(struct Visit* visit, struct Type* type)
+{
+    PRINTER_PREAMBLE
+
+    PRINT(" function (returns ");
+    VISIT(visit_type_ref, &type->value.function.return_type);
+    PRINT(")");
+    for (uint i = 0; i < array_length(&type->value.function.params); ++i) {
+        struct Type_Ref* param = array_at(struct Type_Ref, &type->value.function.params, i);
+        PRINT(" (param ");
+        VISIT(visit_type_ref, param);
+        PRINT(")");
+    }
+    if (type->value.function.variadic) PRINT(" variadic");
+}
+
+void
+printer_visit_function(struct Visit* visit, struct Function* function)
+{
+    PRINTER_PREAMBLE
+
+    PRINT("(function %lu %s", function->id, string_c_str(function->name));
+    if (function->is_main) PRINT(" main");
+    if (function->synthetic) PRINT(" synthetic");
+    PRINT(" (returns ");
+    VISIT(visit_type_ref, &function->return_type);
+    PRINT(")");
+    for (uint i = 0; i < array_length(&function->params); ++i) {
+        struct Param* param = array_at(struct Param, &function->params, i);
+        PRINT(" (param %s ", string_c_str(param->name));
+        VISIT(visit_type_ref, &param->type);
+        PRINT(")");
+    }
+    if (function->variadic) PRINT(" variadic");
+
+    if (function->body) {
+        PRINT(" ");
+        VISIT(visit_block, function->body);
+    }
+    PRINT(")");
+}
+
+void
+printer_visit_statement(struct Visit* visit, struct Statement* stmt)
+{
+    PRINTER_PREAMBLE
+    printer_indent(p);
+    walk_statement(visit, stmt);
+}
+
+void
+printer_visit_statement_declaration(struct Visit* visit, struct Statement* stmt)
+{
+    PRINTER_PREAMBLE
+
+    PRINT("(declaration %s ", string_c_str(stmt->value.declaration.name));
+    VISIT(visit_type_ref, &stmt->value.declaration.type);
+    if (stmt->value.declaration.initializer) {
+        PRINT(" (initializer ");
+        VISIT(visit_expression, stmt->value.declaration.initializer);
+        PRINT(")");
+    }
+    PRINT(")");
+}
+
+void
+printer_visit_statement_assign(struct Visit* visit, struct Statement* stmt)
+{
+    PRINTER_PREAMBLE
+
+    PRINT("(assign ");
+    VISIT(visit_expression, stmt->value.assign.lhs);
+    PRINT(" ");
+    VISIT(visit_expression, stmt->value.assign.rhs);
+    PRINT(")");
+}
+
+void
+printer_visit_statement_expression(struct Visit* visit, struct Statement* stmt)
+{
+    PRINTER_PREAMBLE
+
+    PRINT("(expression-stmt ");
+    VISIT(visit_expression, stmt->value.expression.inner);
+    PRINT(")");
+}
+
+void
+printer_visit_statement_conditional(struct Visit* visit, struct Statement* stmt)
+{
+    PRINTER_PREAMBLE
+
+    PRINT("(conditional");
+    for (uint i = 0; i < array_length(&stmt->value.conditional.branches); ++i) {
+        struct If_Branch* branch = array_at(struct If_Branch, &stmt->value.conditional.branches, i);
+        PRINT(" ");
+        if (branch->condition) {
+            PRINT("(when ");
+            VISIT(visit_expression, branch->condition);
+            PRINT(") ");
+        } else {
+            PRINT("(else) ");
+        }
+        VISIT(visit_block, branch->body);
+    }
+    PRINT(")");
+}
+
+void
+printer_visit_statement_loop(struct Visit* visit, struct Statement* stmt)
+{
+    PRINTER_PREAMBLE
+
+    PRINT("(loop (condition ");
+    VISIT(visit_expression, stmt->value.loop.condition);
+    PRINT(") ");
+    VISIT(visit_block, stmt->value.loop.body);
+    PRINT(")");
+}
+
+void
+printer_visit_statement_return(struct Visit* visit, struct Statement* stmt)
+{
+    PRINTER_PREAMBLE
+
+    PRINT("(return");
+    if (stmt->value.return_value.value) {
+        PRINT(" ");
+        VISIT(visit_expression, stmt->value.return_value.value);
+    }
+    PRINT(")");
+}
+
+void
+printer_visit_statement_break(struct Visit* visit, struct Statement* stmt)
+{
+    PRINTER_PREAMBLE
+    PRINT("(break)");
+}
+
+void
+printer_visit_statement_continue(struct Visit* visit, struct Statement* stmt)
+{
+    PRINTER_PREAMBLE
+    PRINT("(continue)");
+}
+
+void
+printer_visit_statement_block(struct Visit* visit, struct Statement* stmt)
+{
+    PRINTER_PREAMBLE
+    VISIT(visit_block, stmt->value.block.inner);
+}
+
+void
+printer_visit_statement_label(struct Visit* visit, struct Statement* stmt)
+{
+    PRINTER_PREAMBLE
+    PRINT("(label %s)", string_c_str(stmt->value.label.name));
+}
+
+void
+printer_visit_statement_goto(struct Visit* visit, struct Statement* stmt)
+{
+    PRINTER_PREAMBLE
+    PRINT("(goto %s)", string_c_str(stmt->value.goto_target.target));
+}
+
+void
+printer_visit_expression(struct Visit* visit, struct Expression* expr)
+{
+    PRINTER_PREAMBLE
+    PRINT("(expr ");
+    walk_expression(visit, expr);
+    PRINT(")");
+}
+
+void
+printer_visit_expression_integer_literal(struct Visit* visit, struct Expression* expr)
+{
+    PRINTER_PREAMBLE
+    PRINT("%ld", expr->value.integer_literal.value);
+}
+
+void
+printer_visit_expression_float_literal(struct Visit* visit, struct Expression* expr)
+{
+    PRINTER_PREAMBLE
+    PRINT("%lf", expr->value.float_literal.value);
+}
+
+void
+printer_visit_expression_string_literal(struct Visit* visit, struct Expression* expr)
+{
+    PRINTER_PREAMBLE
+    PRINT("\"%s\"", string_c_str(expr->value.string_literal.value));
+}
+
+void
+printer_visit_expression_boolean_literal(struct Visit* visit, struct Expression* expr)
+{
+    PRINTER_PREAMBLE
+    PRINT("%s", expr->value.bool_literal.value ? "true" : "false");
+}
+
+void
+printer_visit_expression_name(struct Visit* visit, struct Expression* expr)
+{
+    PRINTER_PREAMBLE
+    PRINT("(name %s)", string_c_str(expr->value.name.name));
+}
+
+void
+printer_visit_expression_unary_operation(struct Visit* visit, struct Expression* expr)
+{
+    PRINTER_PREAMBLE
+    PRINT("(unary %s ", unary_operation_to_string(expr->value.unary_operator.operation));
+    VISIT(visit_expression, expr->value.unary_operator.operand);
+    PRINT(")");
+}
+
+void
+printer_visit_expression_binary_operation(struct Visit* visit, struct Expression* expr)
+{
+    PRINTER_PREAMBLE
+    PRINT("(binary %s ", binary_operation_to_string(expr->value.binary_operator.operation));
+    VISIT(visit_expression, expr->value.binary_operator.left_operand);
+    PRINT(" ");
+    VISIT(visit_expression, expr->value.binary_operator.right_operand);
+    PRINT(")");
+}
+
+void
+printer_visit_expression_sizeof_operation(struct Visit* visit, struct Expression* expr)
+{
+    PRINTER_PREAMBLE
+    PRINT("(sizeof ");
+    VISIT(visit_type_ref, &expr->value.sizeof_operator.target);
+    PRINT(")");
+}
+
+void
+printer_visit_expression_call(struct Visit* visit, struct Expression* expr)
+{
+    PRINTER_PREAMBLE
+    PRINT("(call ");
+    VISIT(visit_expression, expr->value.call.subject);
+    FOR_EACH_ARRAY(struct Expression*, argument, &expr->value.call.arguments, {
+        PRINT(" ");
+        VISIT(visit_expression, argument);
+    })
+    PRINT(")");
+}
+
+void
+printer_visit_expression_member(struct Visit* visit, struct Expression* expr)
+{
+    PRINTER_PREAMBLE
+    PRINT("(member ");
+    VISIT(visit_expression, expr->value.member.subject);
+    PRINT(" %s)", string_c_str(expr->value.member.name));
+}
+
+void
+printer_visit_expression_subscript(struct Visit* visit, struct Expression* expr)
+{
+    PRINTER_PREAMBLE
+    PRINT("(subscript ");
+    VISIT(visit_expression, expr->value.subscript.subject);
+    PRINT(" ");
+    VISIT(visit_expression, expr->value.subscript.index);
+    PRINT(")");
+}
+
+void
+printer_visit_expression_cast(struct Visit* visit, struct Expression* expr)
+{
+    PRINTER_PREAMBLE
+    PRINT("(cast ");
+    VISIT(visit_type_ref, &expr->value.cast.target);
+    PRINT(" ");
+    VISIT(visit_expression, expr->value.cast.operand);
+    PRINT(")");
+}
+
+void
+printer_visit_expression_construct(struct Visit* visit, struct Expression* expr)
+{
+    PRINTER_PREAMBLE
+
+    PRINT("(construct");
+    struct Type* type = printer_type_at(p, expr->value.construct.type_id);
+    if (type) PRINT(" %s", string_c_str(type->name));
+    else PRINT(" #%lu", expr->value.construct.type_id);
+    for (uint i = 0; i < array_length(&expr->value.construct.fields); ++i) {
+        struct Construct_Field* field = array_at(struct Construct_Field, &expr->value.construct.fields, i);
+        if (field->name.length > 0) PRINT(" (field %s ", string_c_str(field->name));
+        else PRINT(" (positional ");
+        VISIT(visit_expression, field->value);
+        PRINT(")");
+    }
+    PRINT(")");
+}
+
+void
+printer_visit_block(struct Visit* visit, struct Block* block)
+{
+    PRINTER_PREAMBLE
+
+    PRINT("(block");
+    if (array_length(&block->statements) > 0) {
+        PRINT("\n");
+        p->indentation_level++;
+        FOR_EACH_ARRAY(struct Statement*, statement, &block->statements, {
+            VISIT(visit_statement, statement);
+            PRINT("\n");
+        })
+        p->indentation_level--;
+        printer_indent(p);
+    }
+    PRINT(")");
+}
+
+void
+printer_visit_import(struct Visit* visit, struct Import* import)
+{
+    PRINTER_PREAMBLE
+    PRINT("(import \"%s\")", string_c_str(import->path));
+}
+
+void
+printer_visit_diagnostic(struct Visit* visit, struct Diagnostic* diagnostic)
+{
+    PRINTER_PREAMBLE
+    const ascii* sev = "note";
+    if (diagnostic->severity == DIAGNOSTIC_WARNING) sev = "warning";
+    else if (diagnostic->severity == DIAGNOSTIC_ERROR) sev = "error";
+    PRINT("(%s \"%s\")", sev, string_c_str(diagnostic->message));
+}
+
+struct Visit_Table printer_visit_functions = {
+    .visit_unit = printer_visit_unit,
+
+    .visit_type = printer_visit_type,
+    .visit_type_primitive = printer_visit_type_primitive,
+    .visit_type_alias = printer_visit_type_alias,
+    .visit_type_structure = printer_visit_type_structure,
+    .visit_type_variant = printer_visit_type_variant,
+    .visit_type_function = printer_visit_type_function,
+
+    .visit_function = printer_visit_function,
+
+    .visit_statement = printer_visit_statement,
+    .visit_statement_declaration = printer_visit_statement_declaration,
+    .visit_statement_assign = printer_visit_statement_assign,
+    .visit_statement_expression = printer_visit_statement_expression,
+    .visit_statement_conditional = printer_visit_statement_conditional,
+    .visit_statement_loop = printer_visit_statement_loop,
+    .visit_statement_return = printer_visit_statement_return,
+    .visit_statement_break = printer_visit_statement_break,
+    .visit_statement_continue = printer_visit_statement_continue,
+    .visit_statement_block = printer_visit_statement_block,
+    .visit_statement_label = printer_visit_statement_label,
+    .visit_statement_goto = printer_visit_statement_goto,
+
+    .visit_expression = printer_visit_expression,
+    .visit_expression_integer_literal = printer_visit_expression_integer_literal,
+    .visit_expression_float_literal = printer_visit_expression_float_literal,
+    .visit_expression_string_literal = printer_visit_expression_string_literal,
+    .visit_expression_boolean_literal = printer_visit_expression_boolean_literal,
+    .visit_expression_name = printer_visit_expression_name,
+    .visit_expression_unary_operation = printer_visit_expression_unary_operation,
+    .visit_expression_binary_operation = printer_visit_expression_binary_operation,
+    .visit_expression_sizeof_operation = printer_visit_expression_sizeof_operation,
+    .visit_expression_call = printer_visit_expression_call,
+    .visit_expression_member = printer_visit_expression_member,
+    .visit_expression_subscript = printer_visit_expression_subscript,
+    .visit_expression_cast = printer_visit_expression_cast,
+    .visit_expression_construct = printer_visit_expression_construct,
+
+    .visit_block = printer_visit_block,
+    .visit_type_ref = printer_visit_type_ref,
+    .visit_import = printer_visit_import,
+    .visit_diagnostic = printer_visit_diagnostic,
+};
+
+void
+printer(struct Unit* unit)
+{
+    struct Printer p = {
+        .indentation_level = 0,
+        .output = stdout,
+        .did_print_last_visit = false,
+        .unit = unit,
+    };
+    struct Visit visit = { .table = &printer_visit_functions, .user_data = &p };
+
+    walk(&visit, unit);
+}
+
+#undef PRINT
+#undef PRINTER_PREAMBLE
diff --git a/boot/visit/tree.c b/boot/visit/tree.c
index 001cd36..48db391 100644
--- a/boot/visit/tree.c
+++ b/boot/visit/tree.c
@@ -69,17 +69,6 @@ struct Tree_Visit_Table
     void (*visit_pragma_node)(struct Tree_Visit* visitor, struct Tree_Pragma* node);
 };
 
-#define VISIT(visit_function, node) visit->table->visit_function(visit, node);
-
-#define VISIT_MAYBE(visit_function, node) \
-    if (node) visit->table->visit_function(visit, node)
-
-#define DATA_FOR_VISIT(type, name)        \
-    type* name = (type*)visit->user_data; \
-    if (!name) { failure("visit user data is NULL for " #name); }
-
-// tree_walk functions are the default traversal mechanism
-
 // define a tree_walk function which goes nowhere.
 #define TREE_WALK_LEAF_FUNCTION(name, type) \
     void name(struct Tree_Visit* visit, type node) {}
diff --git a/boot/visit/visit.c b/boot/visit/visit.c
new file mode 100644
index 0000000..8bc6cf7
--- /dev/null
+++ b/boot/visit/visit.c
@@ -0,0 +1,20 @@
+/*
+ * useful shared visit system helpers.
+ *
+ * Copyright (c) 2026, Mel G. <mel@rnrd.eu>
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+#pragma once
+
+#include "../catboot.h"
+
+#define VISIT(visit_function, node) visit->table->visit_function(visit, node);
+
+#define VISIT_MAYBE(visit_function, node) \
+    if (node) visit->table->visit_function(visit, node)
+
+#define DATA_FOR_VISIT(type, name)        \
+    type* name = (type*)visit->user_data; \
+    if (!name) { failure("visit user data is NULL for " #name); }