/* * transpiler from a catskill intermediate translation unit, * to the c programming language, as the main translation target. * * walks a fully-lowered translation unit and emits (slightly ugly) * c source code. * * Copyright (c) 2025-2026, Mel G. * * SPDX-License-Identifier: MPL-2.0 */ #pragma once #include "catboot.h" // the output of the transpilation pass. // used for both file output and string buffer output. struct Transpile_Output { FILE* file; struct String_Buffer string; }; struct Transpile_Output transpile_output_from_file(FILE* file) { check(file != nil, "transpile output file is nil"); return (struct Transpile_Output){ .file = file, .string = string_buffer_empty(), }; } #define TRANSPILE_OUTPUT_MAX_LENGTH 262144 // 256 KiB struct Transpile_Output transpile_output_from_string(void) { return (struct Transpile_Output){ .file = nil, .string = string_buffer_new(TRANSPILE_OUTPUT_MAX_LENGTH), }; } #define TRANSPILE_OUTPUT_MAX_WRITE_LENGTH 4096 void transpile_output_write(struct Transpile_Output* output, const ascii* format, ...) { va_list args; va_start(args, format); if (output->file) { vfprintf(output->file, format, args); } else { ascii buffer[TRANSPILE_OUTPUT_MAX_WRITE_LENGTH]; int written = vsnprintf(buffer, TRANSPILE_OUTPUT_MAX_WRITE_LENGTH, format, args); check(written >= 0 && (uint)written < TRANSPILE_OUTPUT_MAX_LENGTH, "transpile output string buffer overflow"); string_buffer_append_c_str(&output->string, buffer); } va_end(args); } struct String transpile_output_string(struct Transpile_Output* output) { check(output->file == nil, "transpile output is not a string"); return string_buffer_to_string(&output->string); } FILE* transpile_output_file(struct Transpile_Output* output) { check(output->file != nil, "transpile output is not a file"); return output->file; } struct Transpiler { struct Transpile_Output output; struct Unit* unit; // current nesting depth, used to indent statement bodies. // mostly for decoration and easier observability! uint indent_level; // minimum precedence level of child expressions. // expressions use this to decide whether to wrap themselves in parentheses // in the case where their parent expression is stronger than them. uint expression_parent_precedence; }; void transpiler_new(struct Transpiler* transpiler, struct Transpile_Output output) { *transpiler = (struct Transpiler){ .output = output, }; } #define TRANSPILE_PREAMBLE DATA_FOR_VISIT(struct Transpiler, transpiler); #define TRANSPILE_WRITE(...) transpile_output_write(&transpiler->output, __VA_ARGS__) // look up a type by id in the active translation unit. // nil if the identifier is unknown. struct Type* transpile_type_at(struct Transpiler* transpiler, Type_Id id) { if (id >= array_length(&transpiler->unit->types.entries)) return nil; return *array_at(struct Type*, &transpiler->unit->types.entries, id); } // the c-side form of a primitive catskill type. // nil if the given id is not one of the primitives. const ascii* transpile_primitive_c_name(struct Transpiler* transpiler, Type_Id id) { // TODO: far-out, eventually we want to wrap these types not only in an // empty "primitive" type shape, but actually define these concretely // as real types with functions in the catskill standard library! struct Type_Table* t = &transpiler->unit->types; if (id == t->primitive_int_id) return "integer"; if (id == t->primitive_uint_id) return "uint"; if (id == t->primitive_bool_id) return "bool"; if (id == t->primitive_string_id) return "struct String"; if (id == t->primitive_float_id) return "real"; if (id == t->primitive_byte_id) return "byte"; if (id == t->primitive_ascii_id) return "ascii"; if (id == t->primitive_void_id) return "void"; return nil; } // emit the c form of a use of a type. void transpile_visit_type_ref(struct Visit* visit, struct Type_Ref* ref) { TRANSPILE_PREAMBLE struct Type* target = transpile_type_at(transpiler, ref->type_id); const ascii* primitive = transpile_primitive_c_name(transpiler, ref->type_id); if (primitive) { TRANSPILE_WRITE("%s", primitive); } else if (target && (target->kind == TYPE_STRUCTURE || target->kind == TYPE_VARIANT)) { TRANSPILE_WRITE("struct %s", string_c_str(target->name)); } else if (target) { TRANSPILE_WRITE("%s", string_c_str(target->name)); } else { TRANSPILE_WRITE("/* unknown type %lu */", ref->type_id); } // TODO: the other mods! FOR_EACH_ARRAY (enum Type_Modifier, mod, &ref->mods) { if (*mod == TYPE_MOD_REFERENCE) TRANSPILE_WRITE("*"); } } void transpile_visit_type_forward_structure(struct Visit* visit, struct Type* type) { TRANSPILE_PREAMBLE TRANSPILE_WRITE("struct %s;\n", string_c_str(type->name)); } void transpile_visit_type_forward_variant(struct Visit* visit, struct Type* type) { TRANSPILE_PREAMBLE TRANSPILE_WRITE("struct %s;\n", string_c_str(type->name)); } void transpile_visit_type_forward_function(struct Visit* visit, struct Type* type) { TRANSPILE_PREAMBLE // referenced function types are emitted as a typedef TRANSPILE_WRITE("typedef "); VISIT(visit_type_ref, &type->value.function.return_type); TRANSPILE_WRITE(" (*%s)(", string_c_str(type->name)); if (array_length(&type->value.function.params) == 0) { TRANSPILE_WRITE("void"); } else { bool first = true; FOR_EACH_ARRAY (struct Type_Ref, param, &type->value.function.params) { if (!first) TRANSPILE_WRITE(", "); VISIT(visit_type_ref, param); first = false; } if (type->value.function.variadic) TRANSPILE_WRITE(", ..."); } TRANSPILE_WRITE(");\n"); } void transpile_visit_type_definition_alias(struct Visit* visit, struct Type* type) { TRANSPILE_PREAMBLE // aliases map to typedefs // TODO: maybe aliases are too much sugar for the lowered representation, // technically any reference to an alias could directly map to the underlying type. struct Type_Ref bare = type_ref_bare(type->value.alias.target_id); TRANSPILE_WRITE("typedef "); VISIT(visit_type_ref, &bare); TRANSPILE_WRITE(" %s;\n", string_c_str(type->name)); } void transpile_visit_type_definition_structure(struct Visit* visit, struct Type* type) { TRANSPILE_PREAMBLE TRANSPILE_WRITE("struct %s {\n", string_c_str(type->name)); FOR_EACH_ARRAY (struct Field, field, &type->value.structure.fields) { TRANSPILE_WRITE(" "); VISIT(visit_type_ref, &field->type); TRANSPILE_WRITE(" %s;\n", string_c_str(field->name)); } TRANSPILE_WRITE("};\n"); } void transpile_visit_type_definition_variant(struct Visit* visit, struct Type* type) { TRANSPILE_PREAMBLE TRANSPILE_WRITE("struct %s {\n", string_c_str(type->name)); TRANSPILE_WRITE(" uint32 tag;\n"); bool any_payload = false; FOR_EACH_ARRAY (struct Variant_Case, one_case, &type->value.variant.cases) { if (one_case->has_payload) { any_payload = true; break; } } if (any_payload) { TRANSPILE_WRITE(" union {\n"); FOR_EACH_ARRAY (struct Variant_Case, one_case, &type->value.variant.cases) { if (!one_case->has_payload) continue; TRANSPILE_WRITE(" "); VISIT(visit_type_ref, &one_case->payload); TRANSPILE_WRITE(" %s;\n", string_c_str(one_case->name)); } TRANSPILE_WRITE(" } as;\n"); } TRANSPILE_WRITE("};\n"); } // transpilation pass 1 visit table // emits forward declarations of types struct Visit_Table transpile_forward_visit_functions = { .visit_type_ref = transpile_visit_type_ref, .visit_type_structure = transpile_visit_type_forward_structure, .visit_type_variant = transpile_visit_type_forward_variant, .visit_type_function = transpile_visit_type_forward_function, }; // transpilation pass 2 visit table // emits full definitions of types struct Visit_Table transpile_definition_visit_functions = { .visit_type_ref = transpile_visit_type_ref, .visit_type_alias = transpile_visit_type_definition_alias, .visit_type_structure = transpile_visit_type_definition_structure, .visit_type_variant = transpile_visit_type_definition_variant, .visit_type_function = walk_type_primitive, // no-op, already done in pass 1 }; // the c-side name a function is emitted under. // `main` becomes `catskill_main` so the runtime trailer can wrap it. const ascii* transpile_function_c_name(struct Function* function) { if (function->is_main) return "catskill_main"; return string_c_str(function->name); } // emits c-side format of a function header. // shared by the signature and body passes. void transpile_function_emit_signature(struct Visit* visit, struct Function* function) { TRANSPILE_PREAMBLE VISIT(visit_type_ref, &function->return_type); TRANSPILE_WRITE(" %s(", transpile_function_c_name(function)); if (array_length(&function->params) == 0) { TRANSPILE_WRITE("void"); } else { bool first = true; FOR_EACH_ARRAY (struct Param, param, &function->params) { if (!first) TRANSPILE_WRITE(", "); VISIT(visit_type_ref, ¶m->type); TRANSPILE_WRITE(" %s", string_c_str(param->name)); first = false; } if (function->variadic) TRANSPILE_WRITE(", ..."); } TRANSPILE_WRITE(")"); } void transpile_visit_function_signature(struct Visit* visit, struct Function* function) { TRANSPILE_PREAMBLE transpile_function_emit_signature(visit, function); TRANSPILE_WRITE(";\n"); } void transpile_visit_function_body(struct Visit* visit, struct Function* function) { TRANSPILE_PREAMBLE transpile_function_emit_signature(visit, function); TRANSPILE_WRITE(" "); if (function->body) { VISIT(visit_block, function->body); } else { TRANSPILE_WRITE("{\n}"); } TRANSPILE_WRITE("\n"); } void transpile_emit_indent(struct Transpiler* transpiler) { for (uint i = 0; i < transpiler->indent_level; ++i) TRANSPILE_WRITE(" "); } // dispatch on an expression with a parent-precedence value supplied by the caller. void transpile_emit_expression( struct Visit* visit, struct Expression* expression, uint parent_precedence) { TRANSPILE_PREAMBLE transpiler->expression_parent_precedence = parent_precedence; VISIT(visit_expression, expression); } // precedence levels for non-binary operators! higher means tighter. #define TRANSPILE_PRECEDENCE_UNARY 14 #define TRANSPILE_PRECEDENCE_POSTFIX 15 #define TRANSPILE_PRECEDENCE_PRIMARY 16 void transpile_visit_expression_integer_literal(struct Visit* visit, struct Expression* expression) { TRANSPILE_PREAMBLE TRANSPILE_WRITE("%ld", expression->value.integer_literal.value); } void transpile_visit_expression_float_literal(struct Visit* visit, struct Expression* expression) { TRANSPILE_PREAMBLE TRANSPILE_WRITE("%f", expression->value.float_literal.value); } void transpile_visit_expression_string_literal(struct Visit* visit, struct Expression* expression) { TRANSPILE_PREAMBLE // TODO: emit a real, typed catskill string object once the standard // library has one and it can be wired in as a primitive. TRANSPILE_WRITE("\"%s\"", string_c_str(expression->value.string_literal.value)); } void transpile_visit_expression_boolean_literal(struct Visit* visit, struct Expression* expression) { TRANSPILE_PREAMBLE TRANSPILE_WRITE("%s", expression->value.bool_literal.value ? "true" : "false"); } void transpile_visit_expression_name(struct Visit* visit, struct Expression* expression) { TRANSPILE_PREAMBLE TRANSPILE_WRITE("%s", string_c_str(expression->value.name.name)); } void transpile_visit_expression_unary_operation(struct Visit* visit, struct Expression* expression) { TRANSPILE_PREAMBLE uint parent = transpiler->expression_parent_precedence; bool needs_parens = TRANSPILE_PRECEDENCE_UNARY < parent; if (needs_parens) TRANSPILE_WRITE("("); TRANSPILE_WRITE("%s", unary_operation_to_string(expression->value.unary_operator.operation)); transpile_emit_expression( visit, expression->value.unary_operator.operand, TRANSPILE_PRECEDENCE_UNARY); if (needs_parens) TRANSPILE_WRITE(")"); } void transpile_visit_expression_binary_operation(struct Visit* visit, struct Expression* expression) { TRANSPILE_PREAMBLE enum Binary_Operation op = expression->value.binary_operator.operation; // c has no built-in power operator. // we route the operation via our own runtime library `pow_integer` function, // which performs fast exponentiation by squaring. if (op == BINARY_POWER) { TRANSPILE_WRITE("pow_integer("); transpile_emit_expression(visit, expression->value.binary_operator.left_operand, 0); TRANSPILE_WRITE(", "); transpile_emit_expression(visit, expression->value.binary_operator.right_operand, 0); TRANSPILE_WRITE(")"); return; } uint my_precedence = binary_operation_precedence(op); uint parent = transpiler->expression_parent_precedence; bool needs_parens = my_precedence < parent; enum Binary_Operation_Associativity assoc = binary_operation_associativity(op); uint left_parent = (assoc == BINARY_ASSOCIATIVITY_RIGHT) ? my_precedence + 1 : my_precedence; uint right_parent = (assoc == BINARY_ASSOCIATIVITY_LEFT) ? my_precedence + 1 : my_precedence; if (needs_parens) TRANSPILE_WRITE("("); transpile_emit_expression(visit, expression->value.binary_operator.left_operand, left_parent); TRANSPILE_WRITE(" %s ", binary_operation_to_string(op)); transpile_emit_expression(visit, expression->value.binary_operator.right_operand, right_parent); if (needs_parens) TRANSPILE_WRITE(")"); } void transpile_visit_expression_sizeof_operation(struct Visit* visit, struct Expression* expression) { TRANSPILE_PREAMBLE TRANSPILE_WRITE("sizeof("); VISIT(visit_type_ref, &expression->value.sizeof_operator.target); TRANSPILE_WRITE(")"); } void transpile_visit_expression_call(struct Visit* visit, struct Expression* expression) { TRANSPILE_PREAMBLE transpile_emit_expression(visit, expression->value.call.subject, TRANSPILE_PRECEDENCE_POSTFIX); TRANSPILE_WRITE("("); Array(struct Call_Argument) args = expression->value.call.arguments; uint max_slot = 0; FOR_EACH_ARRAY (struct Call_Argument, a, &args) { if (a->slot > max_slot) max_slot = a->slot; } bool first = true; for (uint s = 0; s <= max_slot; ++s) { FOR_EACH_ARRAY (struct Call_Argument, a, &args) { if (a->slot != s) continue; if (!first) TRANSPILE_WRITE(", "); transpile_emit_expression(visit, a->value, 0); first = false; } } TRANSPILE_WRITE(")"); } void transpile_visit_expression_member(struct Visit* visit, struct Expression* expression) { TRANSPILE_PREAMBLE transpile_emit_expression( visit, expression->value.member.subject, TRANSPILE_PRECEDENCE_POSTFIX); // TODO: this only produces direct member references, however when the // subject is behind a pointer c forces us to use `x->y` to refer to them. fix! TRANSPILE_WRITE(".%s", string_c_str(expression->value.member.name)); } void transpile_visit_expression_subscript(struct Visit* visit, struct Expression* expression) { TRANSPILE_PREAMBLE transpile_emit_expression( visit, expression->value.subscript.subject, TRANSPILE_PRECEDENCE_POSTFIX); TRANSPILE_WRITE("["); transpile_emit_expression(visit, expression->value.subscript.index, 0); TRANSPILE_WRITE("]"); } void transpile_visit_expression_cast(struct Visit* visit, struct Expression* expression) { TRANSPILE_PREAMBLE TRANSPILE_WRITE("("); VISIT(visit_type_ref, &expression->value.cast.target); TRANSPILE_WRITE(")"); transpile_emit_expression(visit, expression->value.cast.operand, TRANSPILE_PRECEDENCE_UNARY); } void transpile_visit_expression_construct(struct Visit* visit, struct Expression* expression) { TRANSPILE_PREAMBLE struct Type_Ref bare = type_ref_bare(expression->value.construct.type_id); TRANSPILE_WRITE("("); VISIT(visit_type_ref, &bare); TRANSPILE_WRITE("){"); bool first = true; FOR_EACH_ARRAY (struct Construct_Field, field, &expression->value.construct.fields) { if (!first) TRANSPILE_WRITE(", "); if (field->name.length > 0) TRANSPILE_WRITE(".%s = ", string_c_str(field->name)); transpile_emit_expression(visit, field->value, 0); first = false; } TRANSPILE_WRITE("}"); } void transpile_visit_expression_increment_decrement(struct Visit* visit, struct Expression* expression) { TRANSPILE_PREAMBLE const ascii* op = expression->value.increment_decrement.operation == INCREMENT_DECREMENT_INCREMENT ? "++" : "--"; if (expression->value.increment_decrement.prefix) { TRANSPILE_WRITE("%s", op); transpile_emit_expression( visit, expression->value.increment_decrement.subject, TRANSPILE_PRECEDENCE_UNARY); } else { transpile_emit_expression( visit, expression->value.increment_decrement.subject, TRANSPILE_PRECEDENCE_POSTFIX); TRANSPILE_WRITE("%s", op); } } // statements always emit their own indentation and trailing newline. // the dispatcher writes the indentation then routes via normal statement apparatus. void transpile_visit_statement(struct Visit* visit, struct Statement* stmt) { TRANSPILE_PREAMBLE transpile_emit_indent(transpiler); walk_statement(visit, stmt); } void transpile_visit_statement_declaration(struct Visit* visit, struct Statement* stmt) { TRANSPILE_PREAMBLE VISIT(visit_type_ref, &stmt->value.declaration.type); TRANSPILE_WRITE(" %s", string_c_str(stmt->value.declaration.name)); if (stmt->value.declaration.initializer) { TRANSPILE_WRITE(" = "); transpile_emit_expression(visit, stmt->value.declaration.initializer, 0); } TRANSPILE_WRITE(";\n"); } void transpile_visit_statement_assign(struct Visit* visit, struct Statement* stmt) { TRANSPILE_PREAMBLE transpile_emit_expression(visit, stmt->value.assign.lhs, 0); TRANSPILE_WRITE(" = "); transpile_emit_expression(visit, stmt->value.assign.rhs, 0); TRANSPILE_WRITE(";\n"); } void transpile_visit_statement_expression(struct Visit* visit, struct Statement* stmt) { TRANSPILE_PREAMBLE transpile_emit_expression(visit, stmt->value.expression.inner, 0); TRANSPILE_WRITE(";\n"); } void transpile_visit_statement_conditional(struct Visit* visit, struct Statement* stmt) { TRANSPILE_PREAMBLE bool first = true; FOR_EACH_ARRAY (struct If_Branch, branch, &stmt->value.conditional.branches) { if (first) { TRANSPILE_WRITE("if ("); transpile_emit_expression(visit, branch->condition, 0); TRANSPILE_WRITE(") "); } else if (branch->condition) { TRANSPILE_WRITE(" else if ("); transpile_emit_expression(visit, branch->condition, 0); TRANSPILE_WRITE(") "); } else { TRANSPILE_WRITE(" else "); } VISIT(visit_block, branch->body); first = false; } TRANSPILE_WRITE("\n"); } void transpile_visit_statement_loop(struct Visit* visit, struct Statement* stmt) { TRANSPILE_PREAMBLE TRANSPILE_WRITE("while ("); transpile_emit_expression(visit, stmt->value.loop.condition, 0); TRANSPILE_WRITE(") "); VISIT(visit_block, stmt->value.loop.body); TRANSPILE_WRITE("\n"); } void transpile_visit_statement_return(struct Visit* visit, struct Statement* stmt) { TRANSPILE_PREAMBLE if (stmt->value.return_value.value) { TRANSPILE_WRITE("return "); transpile_emit_expression(visit, stmt->value.return_value.value, 0); TRANSPILE_WRITE(";\n"); } else { TRANSPILE_WRITE("return;\n"); } } void transpile_visit_statement_break(struct Visit* visit, struct Statement* stmt) { TRANSPILE_PREAMBLE(void) stmt; TRANSPILE_WRITE("break;\n"); } void transpile_visit_statement_continue(struct Visit* visit, struct Statement* stmt) { TRANSPILE_PREAMBLE(void) stmt; TRANSPILE_WRITE("continue;\n"); } void transpile_visit_statement_block(struct Visit* visit, struct Statement* stmt) { TRANSPILE_PREAMBLE VISIT(visit_block, stmt->value.block.inner); TRANSPILE_WRITE("\n"); } void transpile_visit_statement_label(struct Visit* visit, struct Statement* stmt) { TRANSPILE_PREAMBLE TRANSPILE_WRITE("%s:;\n", string_c_str(stmt->value.label.name)); } void transpile_visit_statement_goto(struct Visit* visit, struct Statement* stmt) { TRANSPILE_PREAMBLE TRANSPILE_WRITE("goto %s;\n", string_c_str(stmt->value.goto_target.target)); } void transpile_visit_block(struct Visit* visit, struct Block* block) { TRANSPILE_PREAMBLE TRANSPILE_WRITE("{\n"); transpiler->indent_level++; FOR_EACH_ARRAY (struct Statement*, statement, &block->statements) { VISIT(visit_statement, *statement); } transpiler->indent_level--; transpile_emit_indent(transpiler); TRANSPILE_WRITE("}"); } // transpilation pass 3 visit table // emits forward declarations of functions struct Visit_Table transpile_signature_visit_functions = { .visit_type_ref = transpile_visit_type_ref, .visit_function = transpile_visit_function_signature, }; // transpilation pass 4 visit table // emits full definitions of functions struct Visit_Table transpile_body_visit_functions = { .visit_type_ref = transpile_visit_type_ref, .visit_function = transpile_visit_function_body, .visit_block = transpile_visit_block, .visit_statement = transpile_visit_statement, .visit_statement_declaration = transpile_visit_statement_declaration, .visit_statement_assign = transpile_visit_statement_assign, .visit_statement_expression = transpile_visit_statement_expression, .visit_statement_conditional = transpile_visit_statement_conditional, .visit_statement_loop = transpile_visit_statement_loop, .visit_statement_return = transpile_visit_statement_return, .visit_statement_break = transpile_visit_statement_break, .visit_statement_continue = transpile_visit_statement_continue, .visit_statement_block = transpile_visit_statement_block, .visit_statement_label = transpile_visit_statement_label, .visit_statement_goto = transpile_visit_statement_goto, .visit_expression_integer_literal = transpile_visit_expression_integer_literal, .visit_expression_float_literal = transpile_visit_expression_float_literal, .visit_expression_string_literal = transpile_visit_expression_string_literal, .visit_expression_boolean_literal = transpile_visit_expression_boolean_literal, .visit_expression_name = transpile_visit_expression_name, .visit_expression_unary_operation = transpile_visit_expression_unary_operation, .visit_expression_binary_operation = transpile_visit_expression_binary_operation, .visit_expression_sizeof_operation = transpile_visit_expression_sizeof_operation, .visit_expression_call = transpile_visit_expression_call, .visit_expression_member = transpile_visit_expression_member, .visit_expression_subscript = transpile_visit_expression_subscript, .visit_expression_cast = transpile_visit_expression_cast, .visit_expression_construct = transpile_visit_expression_construct, .visit_expression_increment_decrement = transpile_visit_expression_increment_decrement, }; // walk a lowered translation unit and emit c source into the transpiler's output. // the order of the translation unit, and thus the final source output is fixed. // preamble, then imports, type forwards, type definitions in topological dependency // order, function forwads, function definitions, and runtime trailer (if main present). int transpile_unit(struct Transpiler* transpiler, struct Unit* unit) { transpiler->unit = unit; visit_table_fill_defaults(&transpile_forward_visit_functions); visit_table_fill_defaults(&transpile_definition_visit_functions); visit_table_fill_defaults(&transpile_signature_visit_functions); visit_table_fill_defaults(&transpile_body_visit_functions); struct Visit visit_value = { .user_data = transpiler }; struct Visit* visit = &visit_value; TRANSPILE_WRITE("#include \"core.c\"\n"); FOR_EACH_ARRAY (struct Import, import, &unit->imports) { TRANSPILE_WRITE("#include \"%.*s\"\n", (int)import->path.length, import->path.data); } // pass 1: type forwards visit->table = &transpile_forward_visit_functions; FOR_EACH_ARRAY (struct Type*, type_slot, &unit->types.entries) { VISIT(visit_type, *type_slot); } // pass 2: type definitions visit->table = &transpile_definition_visit_functions; FOR_EACH_ARRAY (Type_Id, ordered_id, &unit->type_emission_order) { struct Type* type = *array_at(struct Type*, &unit->types.entries, *ordered_id); VISIT(visit_type, type); } // pass 3: function signatures visit->table = &transpile_signature_visit_functions; FOR_EACH_ARRAY (struct Function*, function_slot, &unit->functions.entries) { VISIT(visit_function, *function_slot); } // pass 4: function definitions visit->table = &transpile_body_visit_functions; FOR_EACH_ARRAY (struct Function*, function_slot, &unit->functions.entries) { VISIT(visit_function, *function_slot); } bool main_takes_args = false; bool has_main = false; FOR_EACH_ARRAY (struct Function*, function_slot, &unit->functions.entries) { struct Function* function = *function_slot; if (function->is_main) { has_main = true; main_takes_args = function->main_takes_args; } } if (has_main) { if (main_takes_args) TRANSPILE_WRITE("#define CATSKILL_MAIN_TAKES_ARGS\n"); TRANSPILE_WRITE("#include \"runtime.c\"\n"); } return 0; }