llm
Download
86 lines 3.5 KiBCModified 2026-09-01 raw
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
// `tomo transpile`: transpile a Tomo file to C and print the result

#include <unistd.h>

#include "../environment.h"
#include "../stdlib/bool.h"
#include "../stdlib/list.h"
#include "commands.h"
#include "common.h"
#include "compilation.h"

static OptionalPath_t file = NULL;
static OptionalBool_t raw = false;

static cli_arg_t transpile_spec[] = {
    {"file", &file, &Path$info, .positional = true, .required = true, .metavar = "file.tm",
     .description = "the file to transpile"}, //
    {"raw", &raw, &Bool$info,
     .description = "print the raw generated code, without formatting or syntax highlighting"}, //
    INSTRUMENT_FLAG, //
    DEBUG_FLAG, //
    VERBOSE_FLAG, //
};

static bool command_exists(const char *cmd) {
    int status = system(String("command -v ", cmd, " >/dev/null 2>&1"));
    return WIFEXITED(status) && WEXITSTATUS(status) == 0;
}

static int cmd_transpile(cli_command_t *self, List_t extra_args) {
    (void)self, (void)extra_args;
    set_default_logs(0);

    // --instrument changes the generated code, so fold it into the config
    // fingerprint before anything checks whether the artifacts are up to date:
    update_config_summary();
    List_t files = normalize_tm_paths(List(file));
    Path_t path = *(Path_t *)files.data;
    env_t *env = global_env(source_mapping, instrument, debugging);
    List_t object_files = EMPTY_LIST, extra_ldlibs = EMPTY_LIST;
    compile_files(env, List(path), &object_files, &extra_ldlibs, COMPILE_C_FILES);

    Text_t out = EMPTY_TEXT;
    const char *extensions[] = {".h", ".c"};
    for (size_t i = 0; i < sizeof(extensions) / sizeof(extensions[0]); i++) {
        Path_t generated = build_file(path, extensions[i]);
        OptionalText_t contents = Path$read(generated);
        if (contents.tag == TEXT_NONE) print_err("Could not read the generated file: ", generated);
        out = Texts(out, i > 0 ? Text("\n") : EMPTY_TEXT, "// file: ", Path$relative_to(generated, Path$current_dir()),
                    "\n", contents);
    }

    // Unless --raw, format the output with clang-format and (on a TTY)
    // syntax-highlight it with bat, whichever of the two is installed:
    Text_t pipeline = EMPTY_TEXT;
    if (!raw) {
        if (command_exists("clang-format")) pipeline = Text("clang-format");
        if (isatty(STDOUT_FILENO) && command_exists("bat")) {
            OptionalText_t base = Path$base_name(path);
            if (base.tag == TEXT_NONE) print_err("This file's name is not valid UTF-8: ", path);
            Text_t bat = Texts("bat -l c --file-name '", base, ".h/", base, ".c'");
            pipeline = pipeline.length > 0 ? Texts(pipeline, " | ", bat) : bat;
        }
    }
    if (pipeline.length == 0) {
        print_inline(out);
        return 0;
    }
    FILE *prog = run_cmd(pipeline);
    if (!prog) print_err("Failed to run: ", pipeline);
    Text$print(prog, out);
    int status = pclose(prog);
    return (WIFEXITED(status) && WEXITSTATUS(status) == 0) ? 0 : 1;
}

cli_command_t transpile_command = {
    .name = "transpile",
    .summary = "Transpile a Tomo file to C and print the result",
    .description = "The generated header and source (also written into the file's .tomo/\n"
                   "directory) are printed to stdout, each preceded by a \"// file:\" line.\n"
                   "The output is formatted with clang-format and syntax-highlighted with\n"
                   "bat when those tools are available (and stdout is a terminal).",
    .spec_len = sizeof(transpile_spec) / sizeof(transpile_spec[0]),
    .spec = transpile_spec,
    .handler = cmd_transpile,
};