/* * 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; }; void transpiler_new(struct Transpiler* transpiler, struct Transpile_Output output) { *transpiler = (struct Transpiler){ .output = output, }; } #define TRANSPILE_WRITE(...) transpile_output_write(&transpiler->output, __VA_ARGS__) // emit the forward declaration for a single type. // TODO: per-kind emission lands in a follow-up commit. void transpile_type_forward(struct Transpiler* transpiler, struct Type* type) { (void)transpiler; (void)type; } // emit the full definition for a single type. // TODO: per-kind emission lands in a follow-up commit. void transpile_type_definition(struct Transpiler* transpiler, struct Type* type) { (void)transpiler; (void)type; } // emit a function signature (used for both forward-decls and the line // preceding a function body). // TODO: per-kind emission lands in a follow-up commit. void transpile_function_signature(struct Transpiler* transpiler, struct Function* function) { (void)transpiler; (void)function; } // emit a function body in the form `signature { block }`. // TODO: per-kind emission lands in a follow-up commit. void transpile_function_body(struct Transpiler* transpiler, struct Function* function) { (void)transpiler; (void)function; } // 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; 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); } FOR_EACH_ARRAY (struct Type*, type_slot, &unit->types.entries) { transpile_type_forward(transpiler, *type_slot); } FOR_EACH_ARRAY (Type_Id, ordered_id, &unit->type_emission_order) { struct Type* type = *array_at(struct Type*, &unit->types.entries, *ordered_id); transpile_type_definition(transpiler, type); } bool main_takes_args = false; bool has_main = false; FOR_EACH_ARRAY (struct Function*, function_slot, &unit->functions.entries) { struct Function* function = *function_slot; transpile_function_signature(transpiler, function); if (function->is_main) { has_main = true; main_takes_args = function->main_takes_args; } } FOR_EACH_ARRAY (struct Function*, function_slot, &unit->functions.entries) { transpile_function_body(transpiler, *function_slot); } if (has_main) { if (main_takes_args) TRANSPILE_WRITE("#define CATSKILL_MAIN_TAKES_ARGS\n"); TRANSPILE_WRITE("#include \"runtime.c\"\n"); } return 0; }