/* * # 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 "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; } struct String output_name_from_input_name(struct String input) { uint end = string_length(input); for (uint start = string_length(input) - 1; start > 0; --start) { switch (string_at(input, start)) { // we captured the file extension, we need to keep going until we find a slash, // but we move the end index to the current index first to exclude the extension. case '.': end = start; break; // we found a slash, so we captured the first part of the filename, // we can stop here. case '/': return string_slice(input, start + 1, end); // exclude the slash itself // otherwise, keep going. default: break; } } return string_slice(input, 0, end); } enum Command_Result default_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), }; 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"); return COMMAND_FAIL; } struct Transpiler transpiler; transpiler_new(&transpiler, transpile_output_from_string()); if (transpiler_catskill_to_c(&transpiler, &tree) != 0) { log_error("transpiler finished with errors\n"); return COMMAND_FAIL; } struct String transpiled_source = transpile_output_string(&transpiler.output); struct String output_file; if (arguments->output) { output_file = string_from_c_string(arguments->output); } else { output_file = output_name_from_input_name(source_file.path); } struct Build_Result build_result = build_executable(transpiled_source, output_file); 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); return COMMAND_FAIL; } log_debug("success! executable written to '%s'.\n", string_c_str(build_result.output_path)); return COMMAND_OK; } 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; } } } }