aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2026-05-03 19:54:15 +0200
committerMel <mel@rnrd.eu>2026-05-03 19:54:15 +0200
commit74ffd7a95c0894a96ab984e2c4d2cd9c75539af0 (patch)
treeef8db3b3432dc1203b12079247c982892c7d21d1
parent5211970448a05205b67d7604ad5e4e3925e4ddef (diff)
downloadcatskill-74ffd7a95c0894a96ab984e2c4d2cd9c75539af0.tar.zst
catskill-74ffd7a95c0894a96ab984e2c4d2cd9c75539af0.zip
IR visit system and pretty printer
Signed-off-by: Mel <mel@rnrd.eu>
-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); }