/* * # 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, Mel G. * * SPDX-License-Identifier: MPL-2.0 */ #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" " -?, --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) { fprintf(stderr, "parser finished with errors\n"); } tree_printer(&tree); return parser_result; } enum Command_Result { COMMAND_OK, COMMAND_FAIL, }; struct Command_Arguments { const ascii* input; }; 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 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), }; debug_lex_pass(source); printf("\n"); if (debug_parse_pass(source_file)) return COMMAND_FAIL; printf("\n"); 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 = "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; 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 { check(!input, "multiple inputs given"); input = arg; } } struct Command_Arguments arguments = { .input = input }; 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; } } } }