/* * # catboot * * the bootstrap compiler for catskill, * implemented as simply as possible, * depending only on the C standard library, * in this case musl, and built statically * in a single union build. * * should only be used to compile `catskill` itself, * as it is not a full-featured compiler, and never will be. * once `catskill` can compile itself using a C backend, * this compiler version will be permanently retired. * (although that's still very far away!! have fun! :3) * * Copyright (c) 2025-2026, Mel G. * * SPDX-License-Identifier: MPL-2.0 */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include "catboot.h" #define VERSION "0.0.0" struct String read_file(const ascii* path) { struct stat stat_info; if (stat(path, &stat_info) == -1) failure("i couldn't open that file, sorry :("); check(stat_info.st_size > 0, "file is empty, i can't map an empty file"); const int32 file_descriptor = open(path, O_RDONLY); check(file_descriptor != -1, "i couldn't open that file, sorry :("); const flags mmap_prot = PROT_READ; const flags mmap_flags = MAP_PRIVATE; const unknown* file_data = mmap(nil, stat_info.st_size, mmap_prot, mmap_flags, file_descriptor, 0); return string_from_static_c_string(file_data); } void usage(void) { fprintf( stderr, "usage:\n" " catboot \n" " catboot --test-lex \n" " catboot --test-parse \n" "\n" "options:\n" " --test-lex \t output token stream for file\n" " --test-parse \t output abstract syntax tree for file in s-expr format\n" " --test-transpile \t output transpiled c language source from file\n" " -?, --help\t\t\t print this help message\n" " -v, --version\t\t print current version\n"); } void version(void) { fprintf( stderr, "catboot (catskill), version " VERSION "\n" "\n" "This program's source code is subject to the terms of the Mozilla Public\n" "License, v. 2.0. If a copy of the MPL was not distributed with this\n" "file, You can obtain one at https://mozilla.org/MPL/2.0/.\n" "\n" "Copyright (c) 2025, Mel G. \n"); } integer debug_lex_pass(struct String source) { struct Lexer lexer; lexer_new(&lexer, source); struct Token token; do { token = lexer_next(&lexer); token_debug_print(&token); printf(" "); } while (token.kind != TOKEN_END_OF_FILE); return 0; } integer debug_parse_pass(struct Source_File source_file) { struct Lexer lexer; lexer_new(&lexer, source_file.source); struct Parser parser; parser_new(&parser, &lexer, source_file); struct Tree tree; int parser_result = parser_do_your_thing(&parser, &tree); if (parser_result != 0) { log_error("parser finished with errors\n"); return parser_result; } tree_printer(&tree); return parser_result; } integer debug_transpile_pass(struct Source_File source_file) { struct Lexer lexer; lexer_new(&lexer, source_file.source); struct Parser parser; parser_new(&parser, &lexer, source_file); struct Tree tree; int parser_result = parser_do_your_thing(&parser, &tree); if (parser_result != 0) { log_error("parser finished with errors\n"); return parser_result; } struct Transpile_Output output = transpile_output_from_file(stdout); struct Transpiler transpiler; transpiler_new(&transpiler, output); return transpiler_catskill_to_c(&transpiler, &tree); } enum Command_Result { COMMAND_OK, COMMAND_FAIL, }; struct Command_Arguments { const ascii* input; const ascii* output; }; enum Command_Result version_command(struct Command_Arguments* arguments) { version(); return COMMAND_OK; } enum Command_Result help_command(struct Command_Arguments* arguments) { usage(); return COMMAND_OK; } enum Command_Result test_lex_command(struct Command_Arguments* arguments) { struct String source = read_file(arguments->input); // TODO: lexer errors debug_lex_pass(source); return COMMAND_OK; } enum Command_Result test_parse_command(struct Command_Arguments* arguments) { struct String source = read_file(arguments->input); struct Source_File source_file = { .source = source, .path = string_from_static_c_string(arguments->input), }; if (debug_parse_pass(source_file)) return COMMAND_FAIL; return COMMAND_OK; } enum Command_Result test_transpile_command(struct Command_Arguments* arguments) { struct String source = read_file(arguments->input); struct Source_File source_file = { .source = source, .path = string_from_static_c_string(arguments->input), }; if (debug_transpile_pass(source_file)) return COMMAND_FAIL; return COMMAND_OK; } enum Command_Result default_command(struct Command_Arguments* arguments) { enum Command_Result result = COMMAND_OK; struct String source = read_file(arguments->input); struct Source_File source_file = { .source = source, .path = string_from_static_c_string(arguments->input), }; struct Lexer lexer; lexer_new(&lexer, source); struct Parser parser; parser_new(&parser, &lexer, source_file); struct Tree tree; if (parser_do_your_thing(&parser, &tree) != 0) { log_error("parser finished with errors\n"); result = COMMAND_FAIL; goto end; } ascii build_directory_template[] = "/tmp/catboot-build-XXXXXX"; // TODO: /tmp/catboot/build-XXXXXX ascii* build_directory_path = mkdtemp(build_directory_template); struct String build_path = string_from_c_string(build_directory_path); struct String source_path = string_append_c_str(build_path, "/input.c"); FILE* output_file = fopen(string_c_str(source_path), "w"); if (!output_file) { log_error("could not open temporary output file: %s\n", string_c_str(source_path)); result = COMMAND_FAIL; goto end; } struct Transpile_Output output = transpile_output_from_file(output_file); struct Transpiler transpiler; transpiler_new(&transpiler, output); if (transpiler_catskill_to_c(&transpiler, &tree) != 0) { log_error("transpiler finished with errors\n"); fclose(output_file); result = COMMAND_FAIL; goto end; } fclose(output_file); struct Build_Result build_result = build_executable(build_path); if (!build_result.success) { log_error("backend failure with exit code %ld\n", build_result.compiler_exit_code); log_error("compiler output:\n%.*s\n", (int)build_result.compiler_log.length, build_result.compiler_log.data); result = COMMAND_FAIL; goto end; } // copy the output executable to the desired location const ascii* output_filename = arguments->output ? arguments->output : "a.out"; // simple copy command for now ascii copy_command[2048]; snprintf(copy_command, sizeof(copy_command), "cp %s %s", string_c_str(build_result.output_path), output_filename); if (system(copy_command) != 0) { log_error("failed to copy executable to %s\n", output_filename); result = COMMAND_FAIL; } end: return result; } typedef enum Command_Result (*Command_Function)(struct Command_Arguments*); struct Command_Definition { bool is_default; const ascii* name; const ascii short_name; Command_Function function; }; struct Command_Definition command_definitions[] = { { .name = "test-lex", .function = test_lex_command }, { .name = "test-parse", .function = test_parse_command }, { .name = "test-transpile", .function = test_transpile_command }, { .name = "version", .short_name = 'v', .function = version_command }, { .name = "help", .short_name = '?', .function = help_command }, { .is_default = true, .function = default_command }, }; int32 main(const int32 argc, ascii* argv[]) { if (argc < 2) { usage(); return EXIT_FAILURE; } bool have_command = false; ascii *command_name = nil, short_command_name = '\0', *input = nil, *output = nil; for (uint a = 1; a < argc; ++a) { ascii* arg = argv[a]; if (arg[0] == '-') { check(!have_command, "multiple commands given"); if (arg[1] == '-') { check(!command_name, "multiple full-name commands given"); command_name = &arg[2]; } else { check(!short_command_name, "multiple short-name commands given"); short_command_name = arg[1]; } have_command = true; } else { if (!input) input = arg; else if (!output) output = arg; else failure("too many arguments, expected at most 2: input and output"); } } struct Command_Arguments arguments = { .input = input, .output = output }; for (uint d = 0; d < ARRAY_SIZE(command_definitions); ++d) { struct Command_Definition* definition = &command_definitions[d]; if (definition->is_default || (command_name && strcmp(definition->name, command_name) == 0) || (short_command_name && definition->short_name == short_command_name)) { switch (definition->function(&arguments)) { case COMMAND_OK: return EXIT_SUCCESS; case COMMAND_FAIL: return EXIT_FAILURE; } } } }