commit 32370256c3e48008e1c170caf80adeec41f9e5cc
parent c3b3b5777cd40a0ec300f0674e31c5e843be1b1a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 16 Jun 2026 14:00:02 -0700
Pluggable language frontends: public registry + driver extension
Make the frontend set extensible beyond the compiled-in builtins so a
downstream binary can register its own language(s) without forking the
driver.
- KitCompiler.frontends becomes a growable array; dynamic language ids
start at KIT_LANG_BUILTIN_COUNT. lang_registry_init now returns
KitStatus so registration failures propagate.
- Public kit/driver.h exposes kit_driver_main_ex + KitDriverExtension:
an embedder supplies a register_frontends hook, and driver_main
forwards it to the polyglot tools (build-exe/-lib/-obj, run, dbg) via
new *_ex entry points.
- Makefile `custom-bin` target links a kit binary with extra frontend
objects (KIT_DRIVER_EXT_OBJS / KIT_DRIVER_EXT_REGISTER).
- test/rpn-lang: end-to-end example external frontend (compile, link,
nm, run, dbg) wired in as test-rpn-lang.
Diffstat:
25 files changed, 979 insertions(+), 229 deletions(-)
diff --git a/Makefile b/Makefile
@@ -43,12 +43,13 @@ include mk/rt.mk
LIB_AR = $(BUILD_DIR)/libkit.a
BIN = $(BUILD_DIR)/kit
+KIT_DRIVER_CUSTOM_BIN ?= $(BUILD_DIR)/kit-custom
# ===========================================================================
# Main build sequence: objects -> archive -> binary.
# ===========================================================================
-.PHONY: all lib bin
+.PHONY: all lib bin custom-bin
all: lib bin rt
@@ -56,6 +57,8 @@ lib: $(LIB_AR)
bin: $(BIN) $(BUILD_DIR)/support/rt
+custom-bin: $(KIT_DRIVER_CUSTOM_BIN) $(dir $(KIT_DRIVER_CUSTOM_BIN))support/rt
+
# A kit binary resolves its runtime support from <bindir>/support/rt (an
# `rt` symlink in a `support` dir beside the executable). This pattern rule
# installs that symlink for ANY directory ending in /support/rt, so an
@@ -76,6 +79,13 @@ $(LIB_AR): $(LIB_OBJS) $(BUILD_CONFIG)
$(BIN): $(DRIVER_OBJS) $(LIB_AR) $(BUILD_CONFIG)
$(CC) $(HOST_LDFLAGS) -o $@ $(DRIVER_OBJS) $(LIB_AR) $(HOST_LDLIBS)
+CUSTOM_DRIVER_OBJS = $(patsubst $(BUILD_DIR)/driver/%.o,$(BUILD_DIR)/custom-driver/%.o,$(DRIVER_OBJS))
+CUSTOM_DRIVER_EXT_CFLAGS = $(if $(KIT_DRIVER_EXT_REGISTER),-DKIT_DRIVER_EXT_REGISTER=$(KIT_DRIVER_EXT_REGISTER))
+
+$(KIT_DRIVER_CUSTOM_BIN): $(CUSTOM_DRIVER_OBJS) $(LIB_AR) $(KIT_DRIVER_EXT_OBJS) $(BUILD_CONFIG)
+ @mkdir -p $(dir $@)
+ $(CC) $(HOST_LDFLAGS) -o $@ $(CUSTOM_DRIVER_OBJS) $(KIT_DRIVER_EXT_OBJS) $(LIB_AR) $(HOST_LDLIBS)
+
# ---------------------------------------------------------------------------
# Compile rules, grouped by source tree: src/ (libkit) -> lang/ (frontends) ->
# vendor/ -> driver/. Each tree gets its own include scope; see mk/flags.mk for
@@ -153,6 +163,14 @@ $(BUILD_DIR)/driver/env/%.o: driver/env/%.c Makefile $(BUILD_CONFIG)
@mkdir -p $(dir $@)
$(CC) $(DRIVER_ENV_CFLAGS) $(DRIVER_ENV_OS_CFLAGS) $(DEPFLAGS) -c $< -o $@
+$(BUILD_DIR)/custom-driver/%.o: driver/%.c Makefile $(BUILD_CONFIG)
+ @mkdir -p $(dir $@)
+ $(CC) $(DRIVER_CFLAGS) $(CUSTOM_DRIVER_EXT_CFLAGS) $(DEPFLAGS) -c $< -o $@
+
+$(BUILD_DIR)/custom-driver/env/%.o: driver/env/%.c Makefile $(BUILD_CONFIG)
+ @mkdir -p $(dir $@)
+ $(CC) $(DRIVER_ENV_CFLAGS) $(DRIVER_ENV_OS_CFLAGS) $(CUSTOM_DRIVER_EXT_CFLAGS) $(DEPFLAGS) -c $< -o $@
+
# ===========================================================================
# Runtime (libkit_rt.a): kit compiles and archives its own runtime, one
# variant per target. The variant table and per-variant source/flag lists come
diff --git a/driver/cmd/build.c b/driver/cmd/build.c
@@ -39,7 +39,7 @@
/* Stand-in for "no -x; resolve language from the path suffix at compile time."
*/
-#define BUILD_LANG_AUTO ((KitLanguage)KIT_LANG_COUNT)
+#define BUILD_LANG_AUTO KIT_LANG_AUTO
typedef enum BuildOutputKind {
BUILD_OUT_EXE,
@@ -64,8 +64,7 @@ typedef struct BuildSource {
/* One -X<lang> frontend flag token, scoped to a language. */
typedef struct BuildFeFlag {
- uint8_t lang; /* KitLanguage */
- uint8_t pad[3];
+ KitLanguage lang;
char* tok; /* argv-borrowed */
} BuildFeFlag;
@@ -96,6 +95,7 @@ typedef struct BuildGroup {
typedef struct BuildOptions {
DriverEnv* env;
+ KitFrontendRegistry* frontends;
const char* tool; /* "build-exe" | "build-lib" | "build-obj" */
int kind; /* BuildOutputKind */
const char* driver_path; /* argv[0] */
@@ -157,11 +157,12 @@ typedef struct BuildOptions {
/* small parse helpers */
/* ===================================================================== */
-static int build_lang_from_name(const char* name, KitLanguage* out) {
+static int build_lang_from_name(BuildOptions* o, const char* name,
+ KitLanguage* out) {
/* Resolve off the compile-time default frontend set (no compiler exists at
* arg-parse time). Value-equivalent to the former explicit map:
* c->C, asm/s->ASM, toy->TOY, wasm/wat->WASM. */
- KitLanguage lang = kit_language_for_name(NULL, name);
+ KitLanguage lang = kit_frontend_registry_language_for_name(o->frontends, name);
if (lang == KIT_LANG_UNKNOWN) return 1;
*out = lang;
return 0;
@@ -234,7 +235,10 @@ static void build_release(BuildOptions* o) {
* extension registry, headers excluded), so adding a frontend extension reaches
* cc/build/run/dbg at once. build still treats .h as a header, not a source —
* the helper excludes it. */
-static int build_is_source(const char* s) { return driver_path_is_source(s); }
+static int build_is_source(BuildOptions* o, const char* s) {
+ return kit_frontend_registry_path_kind(o->frontends, s, NULL) ==
+ KIT_FRONTEND_PATH_SOURCE;
+}
/* The language forced (via -x) for sources in group `gi`: the group's own -x,
* else the global baseline's, else -1 (resolve by suffix). */
@@ -278,7 +282,7 @@ static int build_classify_positional(BuildOptions* o, const char* a) {
/* Otherwise a source: a recognized source suffix, or any file at all when a
* language is forced in scope (so `-x toy mykernel` compiles an extensionless
* or odd-suffix file). */
- if (build_is_source(a) || build_scope_forced_lang(o, o->cur_group) >= 0) {
+ if (build_is_source(o, a) || build_scope_forced_lang(o, o->cur_group) >= 0) {
BuildSource* s = &o->sources[o->nsources];
s->path = a;
s->group = o->cur_group;
@@ -314,7 +318,7 @@ static int build_try_scopable(BuildOptions* o, BuildGroup* g, int argc,
driver_errf(o->tool, "-x requires an argument before `--`");
return -1;
}
- if (build_lang_from_name(argv[*i], &lang) != 0) {
+ if (build_lang_from_name(o, argv[*i], &lang) != 0) {
driver_errf(o->tool, "unsupported -x language: %.*s",
KIT_SLICE_ARG(kit_slice_cstr(argv[*i])));
return -1;
@@ -325,8 +329,8 @@ static int build_try_scopable(BuildOptions* o, BuildGroup* g, int argc,
if (driver_strneq(a, "-X", 2) && a[2] != '\0') {
KitLanguage lang;
BuildFeFlag* f;
- if (build_lang_from_name(a + 2, &lang) != 0) {
- driver_errf(o->tool, "unsupported -X language: %.*s (use c|asm|toy|wasm)",
+ if (build_lang_from_name(o, a + 2, &lang) != 0) {
+ driver_errf(o->tool, "unsupported -X language: %.*s",
KIT_SLICE_ARG(kit_slice_cstr(a + 2)));
return -1;
}
@@ -341,7 +345,7 @@ static int build_try_scopable(BuildOptions* o, BuildGroup* g, int argc,
return -1;
}
f = &g->fe[g->nfe++];
- f->lang = (uint8_t)lang;
+ f->lang = lang;
f->tok = argv[*i];
return 1;
}
@@ -1070,10 +1074,10 @@ static int build_collect_fe_argv(BuildOptions* o, uint32_t si, KitLanguage lang,
uint32_t n = 0, k, p = 0;
char** argv;
for (k = 0; k < o->groups[0].nfe; ++k)
- if (o->groups[0].fe[k].lang == (uint8_t)lang) n++;
+ if (o->groups[0].fe[k].lang == lang) n++;
if (gi != 0)
for (k = 0; k < o->groups[gi].nfe; ++k)
- if (o->groups[gi].fe[k].lang == (uint8_t)lang) n++;
+ if (o->groups[gi].fe[k].lang == lang) n++;
*out_argv = NULL;
*out_n = 0;
if (n == 0) return 0;
@@ -1083,11 +1087,11 @@ static int build_collect_fe_argv(BuildOptions* o, uint32_t si, KitLanguage lang,
return 1;
}
for (k = 0; k < o->groups[0].nfe; ++k)
- if (o->groups[0].fe[k].lang == (uint8_t)lang)
+ if (o->groups[0].fe[k].lang == lang)
argv[p++] = o->groups[0].fe[k].tok;
if (gi != 0)
for (k = 0; k < o->groups[gi].nfe; ++k)
- if (o->groups[gi].fe[k].lang == (uint8_t)lang)
+ if (o->groups[gi].fe[k].lang == lang)
argv[p++] = o->groups[gi].fe[k].tok;
*out_argv = argv;
*out_n = n;
@@ -2145,7 +2149,8 @@ static int build_apply_env(BuildOptions* o) {
return 0;
}
-static int build_main(int argc, char** argv, int kind, const char* tool) {
+static int build_main(int argc, char** argv, int kind, const char* tool,
+ const KitDriverExtension* ext) {
DriverEnv env;
BuildOptions o = {0};
DriverRuntimeSupport runtime = {0};
@@ -2159,11 +2164,21 @@ static int build_main(int argc, char** argv, int kind, const char* tool) {
int rc = 2;
driver_env_init(&env);
+ ctx = driver_env_to_context(&env);
o.env = &env;
o.tool = tool;
o.kind = kind;
o.driver_path = argv[0];
+ if (kit_frontend_registry_new(&ctx, &o.frontends) != KIT_OK ||
+ kit_frontend_registry_add_builtin(o.frontends) != KIT_OK ||
+ (ext && ext->register_frontends &&
+ ext->register_frontends(o.frontends) != KIT_OK)) {
+ driver_errf(tool, "failed to initialize frontend registry");
+ rc = 1;
+ goto done;
+ }
+
if (build_alloc(&o, argc) != 0) {
rc = 2;
goto done;
@@ -2242,14 +2257,23 @@ static int build_main(int argc, char** argv, int kind, const char* tool) {
}
}
- ctx = driver_env_to_context(&env);
if (driver_target_new(&ctx, o.target, &o.target_features, tool, &target) !=
- KIT_OK ||
- driver_compiler_new(target, &ctx, &compiler) != KIT_OK) {
+ KIT_OK) {
driver_errf(tool, "failed to initialize compiler");
rc = 1;
goto done;
}
+ {
+ KitCompilerOptions copts;
+ memset(&copts, 0, sizeof copts);
+ copts.frontends = o.frontends;
+ if (kit_compiler_new_ex(target, &ctx, &copts, &compiler) != KIT_OK) {
+ driver_errf(tool, "failed to initialize compiler");
+ rc = 1;
+ goto done;
+ }
+ driver_diag_set_compiler(compiler);
+ }
build_fill_code(&o, &code);
diag.warnings_are_errors = o.warnings_are_errors ? true : false;
@@ -2285,6 +2309,7 @@ done:
kit_target_free(target);
if (runtime_resolved) driver_runtime_support_fini(&env, &runtime);
build_release(&o);
+ if (o.frontends) kit_frontend_registry_free(o.frontends);
driver_env_fini(&env);
return rc;
}
@@ -2303,9 +2328,9 @@ void driver_help_build_exe(void) {
" kit build-exe [options] inputs...\n"
"\n"
"DESCRIPTION\n"
- " Compiles C / asm / toy / wasm sources (language per file) in\n"
- " memory and links them with any .o/.a/.so inputs into one\n"
- " executable. No intermediate files.\n"
+ " Compiles registered source languages (language per file) in memory\n"
+ " and links them with any .o/.a/.so inputs into one executable. No\n"
+ " intermediate files.\n"
"\n"
"OPTIONS (selection)\n"
" -o PATH Output (default a.out / a.exe)\n"
@@ -2317,8 +2342,7 @@ void driver_help_build_exe(void) {
" -e SYM -T script.ld Entry symbol / linker script\n"
" -Wl,... Linker pass-through\n"
" --group [flags] -- sources... Scope compile flags to sources\n"
- " -X<lang> FLAG Per-language frontend flag "
- "(c|asm|toy|wasm)\n"
+ " -X<lang> FLAG Per-language frontend flag\n"
" -h, --help Show this help\n")));
}
@@ -2344,8 +2368,7 @@ void driver_help_build_lib(void) {
" -flto Link-time optimization for source inputs\n"
" -target TRIPLE Cross-compile target\n"
" --group [flags] -- sources... Scope compile flags to sources\n"
- " -X<lang> FLAG Per-language frontend flag "
- "(c|asm|toy|wasm)\n"
+ " -X<lang> FLAG Per-language frontend flag\n"
" -h, --help Show this help\n")));
}
@@ -2359,8 +2382,7 @@ void driver_help_build_obj(void) {
" kit build-obj [options] sources...\n"
"\n"
"DESCRIPTION\n"
- " Compiles each source (C / asm / toy / wasm by suffix or -x) to "
- "an\n"
+ " Compiles each source (registered language by suffix or -x) to an\n"
" object. Multiple sources with --emit=obj combine into one\n"
" relocatable object (ld -r). The kit-native replacement for the\n"
" retired `compile` tool.\n"
@@ -2376,34 +2398,45 @@ void driver_help_build_obj(void) {
"obj\n"
" -target TRIPLE Cross-compile target\n"
" -I/-isystem/-D/-U Preprocessor flags (C/asm frontends)\n"
- " -x LANG Force language: c | asm | toy | wasm\n"
+ " -x LANG Force a registered language\n"
" --group [flags] -- sources... Scope compile flags to sources\n"
- " -X<lang> FLAG Per-language frontend flag "
- "(c|asm|toy|wasm)\n"
+ " -X<lang> FLAG Per-language frontend flag\n"
" -o - Write the emit to stdout\n"
" -h, --help Show this help\n")));
}
-int driver_build_exe(int argc, char** argv) {
+int driver_build_exe_ex(int argc, char** argv, const KitDriverExtension* ext) {
if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
driver_help_build_exe();
return 0;
}
- return build_main(argc, argv, BUILD_OUT_EXE, "build-exe");
+ return build_main(argc, argv, BUILD_OUT_EXE, "build-exe", ext);
}
-int driver_build_lib(int argc, char** argv) {
+int driver_build_lib_ex(int argc, char** argv, const KitDriverExtension* ext) {
if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
driver_help_build_lib();
return 0;
}
- return build_main(argc, argv, BUILD_OUT_LIB, "build-lib");
+ return build_main(argc, argv, BUILD_OUT_LIB, "build-lib", ext);
}
-int driver_build_obj(int argc, char** argv) {
+int driver_build_obj_ex(int argc, char** argv, const KitDriverExtension* ext) {
if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
driver_help_build_obj();
return 0;
}
- return build_main(argc, argv, BUILD_OUT_OBJ, "build-obj");
+ return build_main(argc, argv, BUILD_OUT_OBJ, "build-obj", ext);
+}
+
+int driver_build_exe(int argc, char** argv) {
+ return driver_build_exe_ex(argc, argv, NULL);
+}
+
+int driver_build_lib(int argc, char** argv) {
+ return driver_build_lib_ex(argc, argv, NULL);
+}
+
+int driver_build_obj(int argc, char** argv) {
+ return driver_build_obj_ex(argc, argv, NULL);
}
diff --git a/driver/cmd/cc.c b/driver/cmd/cc.c
@@ -487,7 +487,7 @@ static int cc_record_stdin(CcOptions* o, int forced_lang) {
/* Stored in source_langs[] during arg parsing to mean "no -x override —
* resolve from the path at compile time, once a compiler is around to
* consult its frontend extension registry." */
-#define CC_LANG_AUTO ((KitLanguage)KIT_LANG_COUNT)
+#define CC_LANG_AUTO KIT_LANG_AUTO
static KitLanguage cc_resolve_lang(KitCompiler* c, const char* path,
KitLanguage stored) {
diff --git a/driver/cmd/dbg.c b/driver/cmd/dbg.c
@@ -57,6 +57,7 @@ typedef struct {
typedef struct DbgOpts {
DriverEnv* env;
+ KitFrontendRegistry* frontends;
size_t argv_bound;
int opt_level;
@@ -189,6 +190,7 @@ static int dbg_alloc_arrays(DbgOpts* o, int argc) {
return 1;
}
if (driver_inputs_init(&o->inputs, o->env, DBG_TOOL, argc) != 0) return 1;
+ o->inputs.frontends = o->frontends;
if (driver_cflags_init(&o->cf, o->env, argc) != 0) {
driver_errf(DBG_TOOL, "out of memory");
return 1;
@@ -196,21 +198,19 @@ static int dbg_alloc_arrays(DbgOpts* o, int argc) {
return 0;
}
-static int dbg_parse_language_name(const char* name, KitLanguage* out) {
- /* Resolve off the compile-time default frontend set (no compiler exists at
- * arg-parse time). Value-equivalent to the former explicit map:
- * c->C, toy->TOY, asm/s->ASM, wasm/wat->WASM. */
+static int dbg_parse_language_name(DbgOpts* o, const char* name,
+ KitLanguage* out) {
KitLanguage lang;
if (!name || !*name || !out) return 0;
- lang = kit_language_for_name(NULL, name);
+ lang = kit_frontend_registry_language_for_name(o->frontends, name);
if (lang == KIT_LANG_UNKNOWN) return 0;
*out = lang;
return 1;
}
static int dbg_set_default_language(DbgOpts* o, const char* name) {
- KitLanguage lang = KIT_LANG_COUNT;
- if (!dbg_parse_language_name(name, &lang)) {
+ KitLanguage lang = KIT_LANG_AUTO;
+ if (!dbg_parse_language_name(o, name, &lang)) {
driver_errf(DBG_TOOL, "unsupported language: %.*s",
KIT_SLICE_ARG(kit_slice_cstr(name)));
return 1;
@@ -369,6 +369,7 @@ static void dbg_options_release(DbgOpts* o) {
if (o->script_entries)
driver_free(o->env, o->script_entries,
(size_t)o->script_entries_cap * sizeof(DbgScriptEntry));
+ if (o->frontends) kit_frontend_registry_free(o->frontends);
}
/* Compile every C source through a compiler owned by the caller and JIT-link
@@ -458,7 +459,9 @@ typedef struct DbgState {
/* Backing storage for default_jit_name (built from the canonical frontend
* extension), kept stable for the session's lifetime. */
char default_jit_name_buf[32];
- KitCompileSession* compile_sessions[KIT_LANG_COUNT];
+ KitCompileSession** compile_sessions;
+ uint32_t ncompile_sessions;
+ uint32_t compile_sessions_cap;
DbgSource* sources;
uint32_t nsources;
uint32_t sources_cap;
@@ -655,10 +658,18 @@ static void dbg_bps_release_all(DbgState* s) {
static void dbg_compile_sessions_release(DbgState* s) {
uint32_t i;
- for (i = 0; i < (uint32_t)KIT_LANG_COUNT; ++i) {
+ for (i = 0; i < s->ncompile_sessions; ++i) {
kit_compile_session_free(s->compile_sessions[i]);
s->compile_sessions[i] = NULL;
}
+ if (s->compile_sessions) {
+ driver_free(s->env, s->compile_sessions,
+ (size_t)s->compile_sessions_cap *
+ sizeof(*s->compile_sessions));
+ s->compile_sessions = NULL;
+ }
+ s->ncompile_sessions = 0;
+ s->compile_sessions_cap = 0;
}
static void dbg_sources_release_all(DbgState* s) {
@@ -1610,13 +1621,51 @@ static KitLanguage dbg_jit_language_for_tag(DbgState* s, const char* tag,
return kit_language_for_path(s->compiler, tag);
}
+static KitStatus dbg_compile_sessions_reserve(DbgState* s, KitLanguage lang) {
+ KitCompileSession** nb;
+ uint32_t want;
+ uint32_t ncap;
+ uint32_t i;
+ size_t old_sz;
+ size_t new_sz;
+
+ if (!s || lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO)
+ return KIT_INVALID;
+ if (lang == UINT32_MAX) return KIT_INVALID;
+ want = lang + 1u;
+ if (want <= s->compile_sessions_cap) return KIT_OK;
+ ncap = s->compile_sessions_cap ? s->compile_sessions_cap : 8u;
+ while (ncap < want) {
+ if (ncap > UINT32_MAX / 2u) {
+ ncap = want;
+ break;
+ }
+ ncap *= 2u;
+ }
+ if ((size_t)ncap > SIZE_MAX / sizeof(*s->compile_sessions))
+ return KIT_NOMEM;
+ old_sz = (size_t)s->compile_sessions_cap * sizeof(*s->compile_sessions);
+ new_sz = (size_t)ncap * sizeof(*s->compile_sessions);
+ nb = (KitCompileSession**)s->env->heap->realloc(
+ s->env->heap, s->compile_sessions, old_sz, new_sz,
+ _Alignof(KitCompileSession*));
+ if (!nb) return KIT_NOMEM;
+ for (i = s->compile_sessions_cap; i < ncap; ++i) nb[i] = NULL;
+ s->compile_sessions = nb;
+ s->compile_sessions_cap = ncap;
+ return KIT_OK;
+}
+
static KitStatus dbg_compile_session_for(DbgState* s, KitLanguage lang,
KitCompileSession** out) {
KitCompileSessionOptions sopts;
KitStatus st;
- if (!s || !out || (unsigned)lang >= KIT_LANG_COUNT) return KIT_INVALID;
+ if (!s || !out || lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO)
+ return KIT_INVALID;
*out = NULL;
+ st = dbg_compile_sessions_reserve(s, lang);
+ if (st != KIT_OK) return st;
if (s->compile_sessions[lang]) {
*out = s->compile_sessions[lang];
return KIT_OK;
@@ -1631,6 +1680,7 @@ static KitStatus dbg_compile_session_for(DbgState* s, KitLanguage lang,
sopts.compile.preprocess = s->pp;
st = kit_compile_session_new(s->compiler, &sopts, &s->compile_sessions[lang]);
if (st != KIT_OK) return st;
+ if (s->ncompile_sessions < lang + 1u) s->ncompile_sessions = lang + 1u;
*out = s->compile_sessions[lang];
return KIT_OK;
}
@@ -1873,6 +1923,10 @@ static void dbg_cmd_language(DbgState* s, const char* rest) {
while (p[n] && !dbg_isspace((unsigned char)p[n])) ++n;
if (n == 0) {
const KitPreprocessOptions* pp = &s->pp;
+ KitCompileSession* cached =
+ s->default_jit_lang < s->ncompile_sessions
+ ? s->compile_sessions[s->default_jit_lang]
+ : NULL;
driver_printf("Language: %.*s\n",
KIT_SLICE_ARG(kit_slice_cstr(dbg_jit_language_name(
s->compiler, s->default_jit_lang))));
@@ -1884,9 +1938,7 @@ static void dbg_cmd_language(DbgState* s, const char* rest) {
KIT_SLICE_ARG(kit_slice_cstr(s->copts.code.debug_info ? "on" : "off")),
(unsigned)pp->ninclude_dirs, (unsigned)pp->nsystem_include_dirs,
(unsigned)pp->ndefines, (unsigned)pp->nundefines,
- KIT_SLICE_ARG(kit_slice_cstr(s->compile_sessions[s->default_jit_lang]
- ? "cached"
- : "not-created")));
+ KIT_SLICE_ARG(kit_slice_cstr(cached ? "cached" : "not-created")));
return;
}
if (n >= sizeof(tmp)) {
@@ -1899,7 +1951,7 @@ static void dbg_cmd_language(DbgState* s, const char* rest) {
* recomputed below from dbg_jit_default_name, so no name buffer is requested.
*/
lang = dbg_jit_language_for_tag(s, tmp, NULL, 0, NULL);
- if (lang == KIT_LANG_COUNT) {
+ if (lang == KIT_LANG_UNKNOWN) {
dbg_errf(s, "unsupported language: %.*s",
KIT_SLICE_ARG(kit_slice_cstr(tmp)));
return;
@@ -3177,7 +3229,19 @@ static void dbg_repl(DbgState* s) {
* Top-level entry
* ============================================================ */
-int driver_dbg(int argc, char** argv) {
+static int dbg_init_frontends(DbgOpts* o, const KitContext* ctx,
+ const KitDriverExtension* ext) {
+ if (kit_frontend_registry_new(ctx, &o->frontends) != KIT_OK ||
+ kit_frontend_registry_add_builtin(o->frontends) != KIT_OK ||
+ (ext && ext->register_frontends &&
+ ext->register_frontends(o->frontends) != KIT_OK)) {
+ driver_errf(DBG_TOOL, "failed to initialize frontend registry");
+ return 1;
+ }
+ return 0;
+}
+
+int driver_dbg_ex(int argc, char** argv, const KitDriverExtension* ext) {
DriverEnv env;
DbgOpts o = {0};
KitCompiler* compiler = NULL;
@@ -3196,6 +3260,13 @@ int driver_dbg(int argc, char** argv) {
driver_env_init(&env);
o.env = &env;
+ ctx = driver_env_to_context(&env);
+
+ if (dbg_init_frontends(&o, &ctx, ext) != 0) {
+ dbg_options_release(&o);
+ driver_env_fini(&env);
+ return 1;
+ }
if (dbg_parse(argc, argv, &o) != 0) {
dbg_options_release(&o);
@@ -3203,21 +3274,32 @@ int driver_dbg(int argc, char** argv) {
return 2;
}
- ctx = driver_env_to_context(&env);
jhost = driver_env_to_jit_host(&env);
dhost = driver_env_to_dbg_host(&env);
{
KitTargetOptions topts;
memset(&topts, 0, sizeof topts);
topts.spec = driver_host_target();
- if (kit_target_new(&ctx, &topts, &target) != KIT_OK ||
- driver_compiler_new(target, &ctx, &compiler) != KIT_OK) {
+ if (kit_target_new(&ctx, &topts, &target) != KIT_OK) {
driver_errf(DBG_TOOL, "failed to initialize compiler");
kit_target_free(target);
dbg_options_release(&o);
driver_env_fini(&env);
return 1;
}
+ {
+ KitCompilerOptions copts;
+ memset(&copts, 0, sizeof copts);
+ copts.frontends = o.frontends;
+ if (kit_compiler_new_ex(target, &ctx, &copts, &compiler) != KIT_OK) {
+ driver_errf(DBG_TOOL, "failed to initialize compiler");
+ kit_target_free(target);
+ dbg_options_release(&o);
+ driver_env_fini(&env);
+ return 1;
+ }
+ driver_diag_set_compiler(compiler);
+ }
}
rc = dbg_compile_and_jit(&o, compiler, &jhost, &jit);
@@ -3292,3 +3374,5 @@ int driver_dbg(int argc, char** argv) {
driver_env_fini(&env);
return (st.batch_mode && st.error_count > 0) ? 1 : 0;
}
+
+int driver_dbg(int argc, char** argv) { return driver_dbg_ex(argc, argv, NULL); }
diff --git a/driver/cmd/run.c b/driver/cmd/run.c
@@ -30,6 +30,7 @@
typedef struct RunOptions {
DriverEnv* env;
+ KitFrontendRegistry* frontends;
size_t argv_bound;
int opt_level;
@@ -293,6 +294,7 @@ static int run_alloc_arrays(RunOptions* o, int argc) {
return 1;
}
if (driver_inputs_init(&o->inputs, o->env, RUN_TOOL, argc) != 0) return 1;
+ o->inputs.frontends = o->frontends;
if (driver_cflags_init(
&o->cf, o->env,
argc + DRIVER_HOSTED_MAX_INCLUDES + DRIVER_HOSTED_MAX_DEFINES) != 0) {
@@ -673,6 +675,7 @@ static void run_options_release(RunOptions* o) {
driver_target_features_fini(&o->target_features, o->env);
driver_cflags_fini(&o->cf, o->env);
driver_free(o->env, o->prog_argv, bound * sizeof(*o->prog_argv));
+ if (o->frontends) kit_frontend_registry_free(o->frontends);
}
static void run_fill_compile_opts(const RunOptions* o,
@@ -817,7 +820,19 @@ static void* interp_jit_resolve_tls(void* ctx, KitSlice name, int64_t addend) {
return tls ? (uint8_t*)tls + addend : NULL;
}
-int driver_run(int argc, char** argv) {
+static int run_init_frontends(RunOptions* o, const KitContext* ctx,
+ const KitDriverExtension* ext) {
+ if (kit_frontend_registry_new(ctx, &o->frontends) != KIT_OK ||
+ kit_frontend_registry_add_builtin(o->frontends) != KIT_OK ||
+ (ext && ext->register_frontends &&
+ ext->register_frontends(o->frontends) != KIT_OK)) {
+ driver_errf(RUN_TOOL, "failed to initialize frontend registry");
+ return 1;
+ }
+ return 0;
+}
+
+int driver_run_ex(int argc, char** argv, const KitDriverExtension* ext) {
DriverEnv env;
RunOptions ro = {0};
KitContext ctx;
@@ -843,6 +858,13 @@ int driver_run(int argc, char** argv) {
driver_env_init(&env);
ro.env = &env;
+ ctx = driver_env_to_context(&env);
+
+ if (run_init_frontends(&ro, &ctx, ext) != 0) {
+ run_options_release(&ro);
+ driver_env_fini(&env);
+ return 1;
+ }
if (run_parse(argc, argv, &ro) != 0) {
run_options_release(&ro);
@@ -867,11 +889,9 @@ int driver_run(int argc, char** argv) {
/* Compiler backs the JIT image — keep it alive across kit_jit_lookup
* and the entry call, free after kit_jit_free. */
- ctx = driver_env_to_context(&env);
jhost = driver_env_to_jit_host(&env);
if (driver_target_new(&ctx, ro.target, &ro.target_features, RUN_TOOL,
- &target) != KIT_OK ||
- driver_compiler_new(target, &ctx, &compiler) != KIT_OK) {
+ &target) != KIT_OK) {
driver_errf(RUN_TOOL, "failed to initialize compiler");
kit_target_free(target);
run_metrics_finish(metrics);
@@ -879,6 +899,20 @@ int driver_run(int argc, char** argv) {
driver_env_fini(&env);
return 1;
}
+ {
+ KitCompilerOptions copts;
+ memset(&copts, 0, sizeof copts);
+ copts.frontends = ro.frontends;
+ if (kit_compiler_new_ex(target, &ctx, &copts, &compiler) != KIT_OK) {
+ driver_errf(RUN_TOOL, "failed to initialize compiler");
+ kit_target_free(target);
+ run_metrics_finish(metrics);
+ run_options_release(&ro);
+ driver_env_fini(&env);
+ return 1;
+ }
+ driver_diag_set_compiler(compiler);
+ }
/* For --no-jit, attach an InterpProgram so the optimizer captures each
* function's IR as it compiles. The native object/JIT image is still built
@@ -1045,3 +1079,5 @@ after_entry:
driver_env_fini(&env);
return rc;
}
+
+int driver_run(int argc, char** argv) { return driver_run_ex(argc, argv, NULL); }
diff --git a/driver/driver.h b/driver/driver.h
@@ -4,6 +4,7 @@
#include <kit/compile.h>
#include <kit/core.h>
#include <kit/dbg.h>
+#include <kit/driver.h>
#include <kit/jit.h>
#include "env.h"
@@ -24,6 +25,9 @@ int driver_check(int argc, char** argv);
int driver_build_exe(int argc, char** argv);
int driver_build_lib(int argc, char** argv);
int driver_build_obj(int argc, char** argv);
+int driver_build_exe_ex(int argc, char** argv, const KitDriverExtension*);
+int driver_build_lib_ex(int argc, char** argv, const KitDriverExtension*);
+int driver_build_obj_ex(int argc, char** argv, const KitDriverExtension*);
int driver_install(int argc, char** argv);
int driver_cpp(int argc, char** argv);
int driver_as(int argc, char** argv);
@@ -36,6 +40,8 @@ int driver_image(int argc, char** argv);
int driver_objdump(int argc, char** argv);
int driver_dbg(int argc, char** argv);
int driver_run(int argc, char** argv);
+int driver_dbg_ex(int argc, char** argv, const KitDriverExtension*);
+int driver_run_ex(int argc, char** argv, const KitDriverExtension*);
int driver_emu(int argc, char** argv);
int driver_nm(int argc, char** argv);
int driver_size(int argc, char** argv);
diff --git a/driver/lib/inputs.c b/driver/lib/inputs.c
@@ -69,9 +69,7 @@ static int inputs_record_stdin(DriverInputs* in) {
* cc/build/run/dbg source classification. */
int driver_path_is_source(const char* path) {
if (!path) return 0;
- if (kit_language_for_path(NULL, path) == KIT_LANG_UNKNOWN) return 0;
- if (driver_has_suffix(path, ".h") || driver_has_suffix(path, ".H")) return 0;
- return 1;
+ return kit_language_path_kind(NULL, path, NULL) == KIT_FRONTEND_PATH_SOURCE;
}
int driver_inputs_classify(DriverInputs* in, const char* arg) {
@@ -80,6 +78,12 @@ int driver_inputs_classify(DriverInputs* in, const char* arg) {
* via the canonical case-insensitive extension registry (headers excluded),
* then the object/archive suffix tests. A .so/.dylib is KIT_INPUT_DSO, which
* run/dbg do not accept, so it falls through to "unrecognized" like before. */
+ if (in->frontends &&
+ kit_frontend_registry_path_kind(in->frontends, arg, NULL) ==
+ KIT_FRONTEND_PATH_SOURCE) {
+ in->sources[in->nsources++] = arg;
+ return 1;
+ }
switch (kit_input_kind_for_path(NULL, arg)) {
case KIT_INPUT_SOURCE:
in->sources[in->nsources++] = arg;
diff --git a/driver/lib/inputs.h b/driver/lib/inputs.h
@@ -31,6 +31,7 @@
typedef struct DriverInputs {
DriverEnv* env;
const char* tool;
+ const KitFrontendRegistry* frontends; /* borrowed; NULL means builtins */
size_t bound;
const char** sources; /* .c .cc .cpp paths (borrowed) */
diff --git a/driver/main.c b/driver/main.c
@@ -11,11 +11,14 @@
*/
typedef int (*DriverToolMain)(int argc, char** argv);
+typedef int (*DriverToolMainEx)(int argc, char** argv,
+ const KitDriverExtension*);
typedef void (*DriverToolHelp)(void);
typedef struct DriverToolDesc {
const char* name;
DriverToolMain main;
+ DriverToolMainEx main_ex;
DriverToolHelp help;
const char* summary;
/* DriverToolGroup bits; 0 => not in the default install set */
@@ -24,167 +27,167 @@ typedef struct DriverToolDesc {
static const DriverToolDesc driver_tools[] = {
#if KIT_TOOL_CC_ENABLED
- {"cc", driver_cc, driver_help_cc,
+ {"cc", driver_cc, NULL, driver_help_cc,
"Compile (and link) C sources, with cpp / dep-emit / -shared modes",
DRIVER_GROUP_TOOLCHAIN},
#endif
#if KIT_TOOL_CHECK_ENABLED
- {"check", driver_check, driver_help_check,
+ {"check", driver_check, NULL, driver_help_check,
"Run C frontend checks without emitting code", DRIVER_GROUP_OTHER},
#endif
#if KIT_TOOL_BUILD_EXE_ENABLED
- {"build-exe", driver_build_exe, driver_help_build_exe,
+ {"build-exe", driver_build_exe, driver_build_exe_ex, driver_help_build_exe,
"Compile a polyglot source set and link an executable (in memory)",
DRIVER_GROUP_TOOLCHAIN},
#endif
#if KIT_TOOL_BUILD_LIB_ENABLED
- {"build-lib", driver_build_lib, driver_help_build_lib,
+ {"build-lib", driver_build_lib, driver_build_lib_ex, driver_help_build_lib,
"Compile sources into a static .a or shared library (in memory)",
DRIVER_GROUP_TOOLCHAIN},
#endif
#if KIT_TOOL_BUILD_OBJ_ENABLED
- {"build-obj", driver_build_obj, driver_help_build_obj,
+ {"build-obj", driver_build_obj, driver_build_obj_ex, driver_help_build_obj,
"Compile sources to an object / asm / C / IR, or check (replaces compile)",
DRIVER_GROUP_TOOLCHAIN},
#endif
#if KIT_TOOL_INSTALL_ENABLED
- {"install", driver_install, driver_help_install,
+ {"install", driver_install, NULL, driver_help_install,
"Symlink the kit tools into a dir for drop-in toolchain use",
DRIVER_GROUP_OTHER},
#endif
#if KIT_TOOL_CPP_ENABLED
- {"cpp", driver_cpp, driver_help_cpp,
+ {"cpp", driver_cpp, NULL, driver_help_cpp,
"Standalone C preprocessor (alias for `cc -E` minus link scaffold)",
DRIVER_GROUP_TOOLCHAIN},
#endif
#if KIT_TOOL_AS_ENABLED
- {"as", driver_as, driver_help_as,
+ {"as", driver_as, NULL, driver_help_as,
"Assemble a GAS-subset text source into a relocatable object",
DRIVER_GROUP_TOOLCHAIN},
#endif
#if KIT_TOOL_LD_ENABLED
- {"ld", driver_ld, driver_help_ld,
+ {"ld", driver_ld, NULL, driver_help_ld,
"Link objects/archives into an executable or shared library",
DRIVER_GROUP_TOOLCHAIN},
#endif
#if KIT_TOOL_AR_ENABLED
- {"ar", driver_ar, driver_help_ar,
+ {"ar", driver_ar, NULL, driver_help_ar,
"Create / modify / list / extract POSIX `ar` archives",
DRIVER_GROUP_TOOLCHAIN},
#endif
#if KIT_TOOL_RANLIB_ENABLED
- {"ranlib", driver_ranlib, driver_help_ranlib,
+ {"ranlib", driver_ranlib, NULL, driver_help_ranlib,
"Refresh the symbol index of an `ar` archive", DRIVER_GROUP_TOOLCHAIN},
#endif
#if KIT_TOOL_STRIP_ENABLED
- {"strip", driver_strip, driver_help_strip,
+ {"strip", driver_strip, NULL, driver_help_strip,
"Drop debug sections and/or symbols from a .o or .a",
DRIVER_GROUP_TOOLCHAIN},
#endif
#if KIT_TOOL_OBJCOPY_ENABLED
- {"objcopy", driver_objcopy, driver_help_objcopy,
+ {"objcopy", driver_objcopy, NULL, driver_help_objcopy,
"Copy and transform an object file (rename / remove / format)",
DRIVER_GROUP_TOOLCHAIN},
#endif
#if KIT_TOOL_IMAGE_ENABLED
- {"image", driver_image, driver_help_image,
+ {"image", driver_image, NULL, driver_help_image,
"Emit flat kernel/load images from linked objects", DRIVER_GROUP_TOOLCHAIN},
#endif
#if KIT_TOOL_OBJDUMP_ENABLED
- {"objdump", driver_objdump, driver_help_objdump,
+ {"objdump", driver_objdump, NULL, driver_help_objdump,
"Dump sections, symbols, disassembly, hex, and relocations",
DRIVER_GROUP_TOOLCHAIN},
#endif
#if KIT_TOOL_RUN_ENABLED
- {"run", driver_run, driver_help_run,
+ {"run", driver_run, driver_run_ex, driver_help_run,
"JIT-compile inputs and invoke the entry symbol in-process",
DRIVER_GROUP_OTHER},
#endif
#if KIT_TOOL_DBG_ENABLED
- {"dbg", driver_dbg, driver_help_dbg,
+ {"dbg", driver_dbg, driver_dbg_ex, driver_help_dbg,
"Interactive JIT debugger (REPL on top of the JIT image)",
DRIVER_GROUP_OTHER},
#endif
#if KIT_TOOL_EMU_ENABLED
- {"emu", driver_emu, driver_help_emu,
+ {"emu", driver_emu, NULL, driver_help_emu,
"Run a guest user-mode ELF (aarch64/riscv64) on the host",
DRIVER_GROUP_OTHER},
#endif
#if KIT_TOOL_NM_ENABLED
- {"nm", driver_nm, driver_help_nm, "List symbols from object files",
+ {"nm", driver_nm, NULL, driver_help_nm, "List symbols from object files",
DRIVER_GROUP_TOOLCHAIN},
#endif
#if KIT_TOOL_SIZE_ENABLED
- {"size", driver_size, driver_help_size,
+ {"size", driver_size, NULL, driver_help_size,
"Display section sizes of object files", DRIVER_GROUP_TOOLCHAIN},
#endif
#if KIT_TOOL_ADDR2LINE_ENABLED
- {"addr2line", driver_addr2line, driver_help_addr2line,
+ {"addr2line", driver_addr2line, NULL, driver_help_addr2line,
"Translate addresses to file:line using debug info",
DRIVER_GROUP_TOOLCHAIN},
#endif
#if KIT_TOOL_SYMBOLIZE_ENABLED
- {"symbolize", driver_symbolize, driver_help_symbolize,
+ {"symbolize", driver_symbolize, NULL, driver_help_symbolize,
"Annotate a kit backtrace stream with func at file:line",
DRIVER_GROUP_OTHER},
#endif
#if KIT_TOOL_STRINGS_ENABLED
- {"strings", driver_strings, driver_help_strings,
+ {"strings", driver_strings, NULL, driver_help_strings,
"Print printable character sequences found in a file",
DRIVER_GROUP_TOOLCHAIN},
#endif
#if KIT_TOOL_CAS_ENABLED
- {"cas", driver_cas, driver_help_cas,
+ {"cas", driver_cas, NULL, driver_help_cas,
"Store, inspect, verify, and materialize kit CAS blobs and trees",
DRIVER_GROUP_OTHER},
#endif
#if KIT_TOOL_PKG_ENABLED
- {"pkg", driver_pkg, driver_help_pkg,
+ {"pkg", driver_pkg, NULL, driver_help_pkg,
"Bundle, sign, verify, and unpack distributable .kpkg packages",
DRIVER_GROUP_OTHER},
#endif
#if KIT_TOOL_XXD_ENABLED
- {"xxd", driver_xxd, driver_help_xxd,
+ {"xxd", driver_xxd, NULL, driver_help_xxd,
"Hex dump any file (and reverse a dump back to binary)",
DRIVER_GROUP_BYTEUTIL},
#endif
#if KIT_TOOL_CMP_ENABLED
- {"cmp", driver_cmp, driver_help_cmp, "Compare two files byte by byte",
+ {"cmp", driver_cmp, NULL, driver_help_cmp, "Compare two files byte by byte",
DRIVER_GROUP_BYTEUTIL},
#endif
#if KIT_TOOL_HASH_ENABLED
- {"hash", driver_hash, driver_help_hash,
+ {"hash", driver_hash, NULL, driver_help_hash,
"Hash files with SHA-256, BLAKE2b, or CRC-32", DRIVER_GROUP_OTHER},
- {"sha256sum", driver_sha256sum, driver_help_hash,
+ {"sha256sum", driver_sha256sum, NULL, driver_help_hash,
"SHA-256 of files or stdin (sha256sum-compatible)", DRIVER_GROUP_BYTEUTIL},
- {"b2sum", driver_b2sum, driver_help_hash,
+ {"b2sum", driver_b2sum, NULL, driver_help_hash,
"BLAKE2b-256 of files or stdin (b2sum-style)", DRIVER_GROUP_BYTEUTIL},
- {"crc32", driver_crc32, driver_help_hash, "CRC-32 of files or stdin",
+ {"crc32", driver_crc32, NULL, driver_help_hash, "CRC-32 of files or stdin",
DRIVER_GROUP_BYTEUTIL},
#endif
#if KIT_TOOL_COMPRESS_ENABLED
- {"compress", driver_compress, driver_help_compress,
+ {"compress", driver_compress, NULL, driver_help_compress,
"Compress or decompress data (gzip, lz4 frame)", DRIVER_GROUP_OTHER},
- {"gzip", driver_gzip, driver_help_compress,
+ {"gzip", driver_gzip, NULL, driver_help_compress,
"Compress to gzip (.gz); -d to decompress", DRIVER_GROUP_BYTEUTIL},
- {"gunzip", driver_gunzip, driver_help_compress,
+ {"gunzip", driver_gunzip, NULL, driver_help_compress,
"Decompress gzip (.gz) streams", DRIVER_GROUP_BYTEUTIL},
- {"lz4", driver_lz4, driver_help_compress,
+ {"lz4", driver_lz4, NULL, driver_help_compress,
"Compress to LZ4 frame (.lz4); -d to decompress", DRIVER_GROUP_BYTEUTIL},
- {"lz4c", driver_lz4c, driver_help_compress,
+ {"lz4c", driver_lz4c, NULL, driver_help_compress,
"Compress to LZ4 frame (.lz4) (lz4 alias)", DRIVER_GROUP_BYTEUTIL},
#endif
#if KIT_TOOL_DISAS_ENABLED
- {"disas", driver_disas, driver_help_disas,
+ {"disas", driver_disas, NULL, driver_help_disas,
"Disassemble raw machine-code bytes for a target arch",
DRIVER_GROUP_OTHER},
#endif
#if KIT_TOOL_MC_ENABLED
- {"mc", driver_mc, driver_help_mc,
+ {"mc", driver_mc, NULL, driver_help_mc,
"Assemble one instruction and show its machine-code encoding",
DRIVER_GROUP_OTHER},
#endif
- {NULL, NULL, NULL, NULL, DRIVER_GROUP_OTHER},
+ {NULL, NULL, NULL, NULL, NULL, DRIVER_GROUP_OTHER},
};
unsigned driver_tool_count(void) {
@@ -218,9 +221,13 @@ static const DriverToolDesc* find_tool(const char* name) {
return NULL;
}
-static int dispatch(const char* name, int argc, char** argv) {
+static int dispatch(const char* name, int argc, char** argv,
+ const KitDriverExtension* ext) {
const DriverToolDesc* tool = find_tool(name);
- if (tool) return tool->main(argc, argv);
+ if (tool) {
+ if (tool->main_ex) return tool->main_ex(argc, argv, ext);
+ return tool->main(argc, argv);
+ }
return -1;
}
@@ -283,7 +290,7 @@ void driver_help_top(void) {
" 2 bad command-line usage\n");
}
-int driver_main(int argc, char** argv) {
+int kit_driver_main_ex(int argc, char** argv, const KitDriverExtension* ext) {
const char* name;
int rc;
@@ -292,7 +299,7 @@ int driver_main(int argc, char** argv) {
/* Multi-call form first: argv[0] is the tool name (e.g. installed as
* a `cc` symlink). Help inside the tool is gated by the tool itself. */
- rc = dispatch(name, argc, argv);
+ rc = dispatch(name, argc, argv, ext);
if (rc != -1) return rc;
/* From here on argv[0] was the bare "kit" binary. Apply the
@@ -321,7 +328,7 @@ int driver_main(int argc, char** argv) {
{
char* tool_name = argv[1];
argv[1] = argv[0];
- rc = dispatch(tool_name, argc - 1, argv + 1);
+ rc = dispatch(tool_name, argc - 1, argv + 1, ext);
argv[1] = tool_name;
if (rc != -1) return rc;
}
@@ -332,4 +339,18 @@ int driver_main(int argc, char** argv) {
return 2;
}
-int main(int argc, char** argv) { return driver_main(argc, argv); }
+int driver_main(int argc, char** argv) { return kit_driver_main_ex(argc, argv, NULL); }
+
+#ifdef KIT_DRIVER_EXT_REGISTER
+extern KitStatus KIT_DRIVER_EXT_REGISTER(KitFrontendRegistry*);
+#endif
+
+int main(int argc, char** argv) {
+#ifdef KIT_DRIVER_EXT_REGISTER
+ KitDriverExtension ext;
+ ext.register_frontends = KIT_DRIVER_EXT_REGISTER;
+ return kit_driver_main_ex(argc, argv, &ext);
+#else
+ return kit_driver_main_ex(argc, argv, NULL);
+#endif
+}
diff --git a/include/kit/compile.h b/include/kit/compile.h
@@ -14,17 +14,20 @@
* should use kit/cg.h instead.
*/
-typedef enum KitLanguage {
- /* No registered frontend claims the input. kit_language_for_path
- * returns this when an extension matches nothing; it is never a valid
- * frontend index (the compile paths reject it). */
- KIT_LANG_UNKNOWN = -1,
- KIT_LANG_C = 0,
- KIT_LANG_ASM = 1,
- KIT_LANG_TOY = 2,
- KIT_LANG_WASM = 3,
- KIT_LANG_COUNT = 4,
-} KitLanguage;
+typedef uint32_t KitLanguage;
+
+/* Stable builtin language ids. External frontends registered through a
+ * KitFrontendRegistry receive ids starting at KIT_LANG_BUILTIN_COUNT. */
+#define KIT_LANG_C ((KitLanguage)0u)
+#define KIT_LANG_ASM ((KitLanguage)1u)
+#define KIT_LANG_TOY ((KitLanguage)2u)
+#define KIT_LANG_WASM ((KitLanguage)3u)
+#define KIT_LANG_BUILTIN_COUNT ((KitLanguage)4u)
+/* Backward-compatible name for the builtin count. Do not use as an AUTO
+ * sentinel: dynamic frontend ids may be >= this value. */
+#define KIT_LANG_COUNT KIT_LANG_BUILTIN_COUNT
+#define KIT_LANG_AUTO ((KitLanguage)0xfffffffeu)
+#define KIT_LANG_UNKNOWN ((KitLanguage)0xffffffffu)
typedef struct KitDiagnosticOptions {
bool warnings_are_errors;
@@ -95,6 +98,7 @@ typedef struct KitFrontendCompileOptions {
} KitFrontendCompileOptions;
typedef struct KitFrontend KitFrontend;
+typedef struct KitFrontendRegistry KitFrontendRegistry;
typedef struct KitFrontendState KitFrontendState;
typedef struct KitCg KitCg;
@@ -132,6 +136,12 @@ typedef struct KitFrontendCaps {
bool cache_repl_toplevel_source;
} KitFrontendCaps;
+typedef enum KitFrontendPathKind {
+ KIT_FRONTEND_PATH_UNKNOWN = 255,
+ KIT_FRONTEND_PATH_SOURCE = 0,
+ KIT_FRONTEND_PATH_HEADER = 1,
+} KitFrontendPathKind;
+
/* Parse the frontend-specific command-line flags a generic driver did not
* itself consume, producing an opaque options blob to plant in
* KitFrontendCompileOptions.language_options. The blob is allocated through the
@@ -160,6 +170,11 @@ typedef struct KitFrontendVTable {
* `"s"` entry. */
const KitSlice* extensions;
uint32_t nextensions;
+ /* Optional parallel array to `extensions`. NULL means every extension is a
+ * compilable source. Use KIT_FRONTEND_PATH_HEADER for extensions that the
+ * language owns for include/import resolution but that should not be treated
+ * as standalone translation units by generic drivers. */
+ const uint8_t* extension_kinds; /* KitFrontendPathKind per extension */
/* Counted list of canonical names and aliases (e.g. the `-x` spellings a
* driver accepts) that identify this frontend's language.
@@ -194,6 +209,31 @@ typedef struct KitFrontendVTable {
KitFrontendFreeOptionsFn free_options;
} KitFrontendVTable;
+typedef struct KitCompilerOptions {
+ /* Optional borrowed registry. NULL selects the compiled-in builtin frontend
+ * set. The compiler copies the registry's vtable pointers at construction
+ * time, so the registry may be freed after kit_compiler_new_ex returns. */
+ const KitFrontendRegistry* frontends;
+} KitCompilerOptions;
+
+KIT_API KitStatus kit_frontend_registry_new(const KitContext*,
+ KitFrontendRegistry** out);
+KIT_API void kit_frontend_registry_free(KitFrontendRegistry*);
+KIT_API KitStatus kit_frontend_registry_add_builtin(KitFrontendRegistry*);
+KIT_API KitStatus kit_frontend_registry_add(KitFrontendRegistry*,
+ const KitFrontendVTable*,
+ KitLanguage* out_lang);
+KIT_API KitLanguage kit_frontend_registry_language_for_path(
+ const KitFrontendRegistry*, const char* path);
+KIT_API KitLanguage kit_frontend_registry_language_for_name(
+ const KitFrontendRegistry*, const char* name);
+KIT_API KitFrontendPathKind kit_frontend_registry_path_kind(
+ const KitFrontendRegistry*, const char* path, KitLanguage* out_lang);
+
+KIT_API KitStatus kit_compiler_new_ex(const KitTarget*, const KitContext*,
+ const KitCompilerOptions*,
+ KitCompiler** out);
+
/* The four language resolvers below (kit_language_for_path,
* kit_language_for_name, kit_language_name, kit_language_default_extension) all
* accept a NULL compiler: with a non-NULL KitCompiler they consult that
@@ -209,6 +249,9 @@ typedef struct KitFrontendVTable {
* than defaulting to any particular language. Accepts a NULL compiler (see
* note above). */
KIT_API KitLanguage kit_language_for_path(KitCompiler*, const char* path);
+KIT_API KitFrontendPathKind kit_language_path_kind(KitCompiler*,
+ const char* path,
+ KitLanguage* out_lang);
/* Map a canonical name or alias (a `-x` spelling such as "c", "asm", "wat") to
* a language by walking every registered frontend's `names` list. Matching is
diff --git a/include/kit/driver.h b/include/kit/driver.h
@@ -0,0 +1,15 @@
+#ifndef KIT_DRIVER_PUBLIC_H
+#define KIT_DRIVER_PUBLIC_H
+
+#include <kit/compile.h>
+
+typedef KitStatus (*KitDriverRegisterFrontendsFn)(KitFrontendRegistry*);
+
+typedef struct KitDriverExtension {
+ KitDriverRegisterFrontendsFn register_frontends;
+} KitDriverExtension;
+
+KIT_API int kit_driver_main_ex(int argc, char** argv,
+ const KitDriverExtension* ext);
+
+#endif
diff --git a/lang/c/c.c b/lang/c/c.c
@@ -207,22 +207,24 @@ static void c_frontend_free(KitFrontendState* frontend) {
* language-for-path lookup resolves them by the same registry walk as every
* other language, with no special fallback. */
static const KitSlice c_extensions[] = {KIT_SLICE_LIT("c"), KIT_SLICE_LIT("h")};
+static const uint8_t c_extension_kinds[] = {KIT_FRONTEND_PATH_SOURCE,
+ KIT_FRONTEND_PATH_HEADER};
/* Canonical `-x` name; mirrors the driver's "c" spelling. */
static const KitSlice c_names[] = {KIT_SLICE_LIT("c")};
const KitFrontendVTable kit_c_frontend_vtable = {
- c_frontend_new,
- c_frontend_compile_cg,
- NULL, /* compile_obj: semantic frontends are wrapped by compile session */
- c_frontend_free,
- c_extensions,
- (uint32_t)(sizeof c_extensions / sizeof c_extensions[0]),
- c_names,
- (uint32_t)(sizeof c_names / sizeof c_names[0]),
- /* commit/abort: C has no durable cross-compile state yet */
- NULL,
- NULL,
- {true, KIT_FRONTEND_LTO_CG, false},
- NULL, /* parse_options: C has no frontend-specific flags */
- NULL, /* free_options */
+ .new_frontend = c_frontend_new,
+ .compile_cg = c_frontend_compile_cg,
+ .compile_obj = NULL,
+ .free_frontend = c_frontend_free,
+ .extensions = c_extensions,
+ .nextensions = (uint32_t)(sizeof c_extensions / sizeof c_extensions[0]),
+ .extension_kinds = c_extension_kinds,
+ .names = c_names,
+ .nnames = (uint32_t)(sizeof c_names / sizeof c_names[0]),
+ .commit = NULL,
+ .abort = NULL,
+ .caps = {true, KIT_FRONTEND_LTO_CG, false},
+ .parse_options = NULL,
+ .free_options = NULL,
};
diff --git a/lang/toy/compile.c b/lang/toy/compile.c
@@ -230,19 +230,18 @@ static const KitSlice toy_extensions[] = {KIT_SLICE_LIT("toy")};
static const KitSlice toy_names[] = {KIT_SLICE_LIT("toy")};
const KitFrontendVTable kit_toy_frontend_vtable = {
- toy_frontend_new,
- toy_frontend_compile_cg,
- NULL, /* compile_obj: semantic frontends are wrapped by compile session */
- toy_frontend_free,
- toy_extensions,
- (uint32_t)(sizeof toy_extensions / sizeof toy_extensions[0]),
- toy_names,
- (uint32_t)(sizeof toy_names / sizeof toy_names[0]),
- toy_frontend_commit,
- toy_frontend_abort,
- /* cache_repl_toplevel_source: the toy REPL re-reads earlier toplevel text.
- */
- {false, KIT_FRONTEND_LTO_CG, true},
- NULL, /* parse_options: no toy-specific flags yet */
- NULL, /* free_options */
+ .new_frontend = toy_frontend_new,
+ .compile_cg = toy_frontend_compile_cg,
+ .compile_obj = NULL,
+ .free_frontend = toy_frontend_free,
+ .extensions = toy_extensions,
+ .nextensions = (uint32_t)(sizeof toy_extensions / sizeof toy_extensions[0]),
+ .extension_kinds = NULL,
+ .names = toy_names,
+ .nnames = (uint32_t)(sizeof toy_names / sizeof toy_names[0]),
+ .commit = toy_frontend_commit,
+ .abort = toy_frontend_abort,
+ .caps = {false, KIT_FRONTEND_LTO_CG, true},
+ .parse_options = NULL,
+ .free_options = NULL,
};
diff --git a/lang/wasm/wasm.c b/lang/wasm/wasm.c
@@ -157,19 +157,21 @@ static const KitSlice wasm_names[] = {KIT_SLICE_LIT("wasm"),
KIT_SLICE_LIT("wat")};
const KitFrontendVTable kit_wasm_frontend_vtable = {
- wasm_frontend_new,
- wasm_frontend_compile_cg,
- NULL, /* compile_obj: semantic frontends are wrapped by compile session */
- wasm_frontend_free,
- wasm_extensions,
- (uint32_t)(sizeof wasm_extensions / sizeof wasm_extensions[0]),
- wasm_names,
- (uint32_t)(sizeof wasm_names / sizeof wasm_names[0]),
- NULL, /* commit: wasm has no durable cross-compile state */
- NULL, /* abort */
- {false, KIT_FRONTEND_LTO_CG, false},
- wasm_parse_options,
- wasm_free_options,
+ .new_frontend = wasm_frontend_new,
+ .compile_cg = wasm_frontend_compile_cg,
+ .compile_obj = NULL,
+ .free_frontend = wasm_frontend_free,
+ .extensions = wasm_extensions,
+ .nextensions =
+ (uint32_t)(sizeof wasm_extensions / sizeof wasm_extensions[0]),
+ .extension_kinds = NULL,
+ .names = wasm_names,
+ .nnames = (uint32_t)(sizeof wasm_names / sizeof wasm_names[0]),
+ .commit = NULL,
+ .abort = NULL,
+ .caps = {false, KIT_FRONTEND_LTO_CG, false},
+ .parse_options = wasm_parse_options,
+ .free_options = wasm_free_options,
};
KIT_API int kit_wasm_wat_to_wasm(KitCompiler* c, const KitSlice* input,
diff --git a/mk/test.mk b/mk/test.mk
@@ -113,6 +113,7 @@ TEST_TARGETS = \
test-pp-ok \
test-pp-file-escape \
test-pp-include-next \
+ test-rpn-lang \
test-rt-headers \
test-rt-runtime \
test-rt-backtrace \
@@ -220,6 +221,9 @@ test-driver-cc: bin
test-driver-build: bin
@KIT=$(abspath $(BIN)) sh test/buildcmds/run.sh
+test-rpn-lang: lib
+ @KIT_BUILD_DIR=$(abspath $(BUILD_DIR)/test/rpn-lang) sh test/rpn-lang/run.sh
+
ifeq ($(HOST_OS),linux)
test-driver-lsan:
@$(MAKE) test-driver RELEASE=0 BUILD_DIR=build/lsan \
diff --git a/src/api/compile.c b/src/api/compile.c
@@ -4,6 +4,7 @@
#include <kit/cg.h>
#include <kit/compile.h>
#include <kit/core.h>
+#include <stdint.h>
#include <string.h>
#include "api/lang_registry.h"
@@ -34,6 +35,13 @@ struct KitCompileSession {
KitFrontendCompileOptions opts;
};
+struct KitFrontendRegistry {
+ const KitContext* ctx;
+ const KitFrontendVTable** frontends;
+ uint32_t nfrontends;
+ uint32_t cap;
+};
+
static KitFrontendState* asm_frontend_new(KitCompiler* c);
static KitStatus asm_frontend_compile(KitFrontendState* fe,
const KitFrontendCompileOptions* opts,
@@ -46,19 +54,20 @@ static const KitSlice asm_extensions[] = {KIT_SLICE_LIT("s")};
static const KitSlice asm_names[] = {KIT_SLICE_LIT("asm"), KIT_SLICE_LIT("s")};
const KitFrontendVTable kit_asm_frontend_vtable = {
- asm_frontend_new,
- NULL, /* compile_cg: asm participates in LTO as an opaque object */
- asm_frontend_compile,
- asm_frontend_free,
- asm_extensions,
- (uint32_t)(sizeof asm_extensions / sizeof asm_extensions[0]),
- asm_names,
- (uint32_t)(sizeof asm_names / sizeof asm_names[0]),
- NULL, /* commit: asm has no durable cross-compile state */
- NULL, /* abort */
- {false, KIT_FRONTEND_LTO_OPAQUE, false},
- NULL, /* parse_options: no asm-specific flags */
- NULL, /* free_options */
+ .new_frontend = asm_frontend_new,
+ .compile_cg = NULL, /* asm participates in LTO as an opaque object */
+ .compile_obj = asm_frontend_compile,
+ .free_frontend = asm_frontend_free,
+ .extensions = asm_extensions,
+ .nextensions = (uint32_t)(sizeof asm_extensions / sizeof asm_extensions[0]),
+ .extension_kinds = NULL,
+ .names = asm_names,
+ .nnames = (uint32_t)(sizeof asm_names / sizeof asm_names[0]),
+ .commit = NULL,
+ .abort = NULL,
+ .caps = {false, KIT_FRONTEND_LTO_OPAQUE, false},
+ .parse_options = NULL,
+ .free_options = NULL,
};
static _Noreturn void panic_bad_options(Compiler* c, const char* msg) {
@@ -82,30 +91,152 @@ static int ext_eq_ci(KitSlice ext, KitSlice pat) {
return 1;
}
+static KitStatus validate_frontend_vtable(const KitFrontendVTable* vtable) {
+ if (vtable) {
+ uint8_t mode = vtable->caps.lto_mode;
+ uint32_t i;
+ if (!vtable->new_frontend || !vtable->free_frontend ||
+ mode > KIT_FRONTEND_LTO_OPAQUE) {
+ return KIT_INVALID;
+ }
+ if (mode == KIT_FRONTEND_LTO_CG) {
+ if (!vtable->compile_cg) return KIT_INVALID;
+ } else if (!vtable->compile_obj) {
+ return KIT_INVALID;
+ }
+ if (vtable->extension_kinds && vtable->nextensions && !vtable->extensions)
+ return KIT_INVALID;
+ for (i = 0; vtable->extension_kinds && i < vtable->nextensions; ++i) {
+ uint8_t k = vtable->extension_kinds[i];
+ if (k != KIT_FRONTEND_PATH_SOURCE && k != KIT_FRONTEND_PATH_HEADER)
+ return KIT_INVALID;
+ }
+ }
+ return KIT_OK;
+}
+
+static KitFrontendPathKind vtable_extension_kind(
+ const KitFrontendVTable* v, uint32_t index) {
+ if (!v || index >= v->nextensions) return KIT_FRONTEND_PATH_UNKNOWN;
+ if (!v->extension_kinds) return KIT_FRONTEND_PATH_SOURCE;
+ return (KitFrontendPathKind)v->extension_kinds[index];
+}
+
+static KitStatus registry_reserve(KitFrontendRegistry* r, uint32_t want) {
+ KitHeap* h;
+ const KitFrontendVTable** nv;
+ uint32_t ncap;
+ uint32_t i;
+ if (!r || !r->ctx || !r->ctx->heap) return KIT_INVALID;
+ if (want <= r->cap) return KIT_OK;
+ h = r->ctx->heap;
+ ncap = r->cap ? r->cap : 8u;
+ while (ncap < want) {
+ if (ncap > UINT32_MAX / 2u) {
+ ncap = want;
+ break;
+ }
+ ncap *= 2u;
+ }
+ if ((size_t)ncap > SIZE_MAX / sizeof(*r->frontends)) return KIT_NOMEM;
+ if (r->frontends) {
+ nv = (const KitFrontendVTable**)h->realloc(
+ h, (void*)r->frontends, r->cap * sizeof(*r->frontends),
+ ncap * sizeof(*r->frontends), _Alignof(const KitFrontendVTable*));
+ } else {
+ nv = (const KitFrontendVTable**)h->alloc(
+ h, ncap * sizeof(*r->frontends), _Alignof(const KitFrontendVTable*));
+ }
+ if (!nv) return KIT_NOMEM;
+ for (i = r->cap; i < ncap; ++i) nv[i] = NULL;
+ r->frontends = nv;
+ r->cap = ncap;
+ return KIT_OK;
+}
+
+static KitStatus compiler_frontends_reserve(Compiler* c, uint32_t want) {
+ Heap* h;
+ const KitFrontendVTable** nv;
+ uint32_t ncap;
+ uint32_t i;
+ if (!c || !c->ctx || !c->ctx->heap) return KIT_INVALID;
+ if (want <= c->frontends_cap) return KIT_OK;
+ h = (Heap*)c->ctx->heap;
+ ncap = c->frontends_cap ? c->frontends_cap : 8u;
+ while (ncap < want) {
+ if (ncap > UINT32_MAX / 2u) {
+ ncap = want;
+ break;
+ }
+ ncap *= 2u;
+ }
+ if ((size_t)ncap > SIZE_MAX / sizeof(*c->frontends)) return KIT_NOMEM;
+ if (c->frontends) {
+ nv = (const KitFrontendVTable**)h->realloc(
+ h, (void*)c->frontends, c->frontends_cap * sizeof(*c->frontends),
+ ncap * sizeof(*c->frontends), _Alignof(const KitFrontendVTable*));
+ } else {
+ nv = (const KitFrontendVTable**)h->alloc(
+ h, ncap * sizeof(*c->frontends), _Alignof(const KitFrontendVTable*));
+ }
+ if (!nv) return KIT_NOMEM;
+ for (i = c->frontends_cap; i < ncap; ++i) nv[i] = NULL;
+ c->frontends = nv;
+ c->frontends_cap = ncap;
+ return KIT_OK;
+}
+
+static KitStatus compiler_register_frontend(Compiler* c, KitLanguage lang,
+ const KitFrontendVTable* vtable) {
+ KitStatus st;
+ if (!c || lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO)
+ return KIT_INVALID;
+ st = validate_frontend_vtable(vtable);
+ if (st != KIT_OK) return st;
+ st = compiler_frontends_reserve(c, lang + 1u);
+ if (st != KIT_OK) return st;
+ c->frontends[lang] = vtable;
+ if (c->nfrontends < lang + 1u) c->nfrontends = lang + 1u;
+ return KIT_OK;
+}
+
/* Resolve `lang`'s frontend vtable: from the compiler's per-instance table
* when a compiler is supplied, else from the compile-time default registry so
* the language resolvers work with c==NULL (e.g. at CLI arg-parse time, before
* a KitCompiler exists). Out-of-range `lang` yields NULL via lang_registry. */
static const KitFrontendVTable* frontend_for(KitCompiler* c, unsigned lang) {
if (c) {
- if (lang >= KIT_LANG_COUNT) return NULL;
- return c->frontends[lang];
+ if (lang >= ((Compiler*)c)->nfrontends) return NULL;
+ return ((Compiler*)c)->frontends[lang];
}
return lang_registry_vtable((KitLanguage)lang);
}
-KitLanguage kit_language_for_path(KitCompiler* c, const char* path) {
+static const KitFrontendVTable* registry_frontend_for(
+ const KitFrontendRegistry* r, KitLanguage lang) {
+ if (!r || lang >= r->nfrontends) return NULL;
+ return r->frontends[lang];
+}
+
+static uint32_t frontend_count_for(KitCompiler* c) {
+ if (c) return ((Compiler*)c)->nfrontends;
+ return KIT_LANG_BUILTIN_COUNT;
+}
+
+static KitFrontendPathKind language_path_kind_common(
+ KitCompiler* c, const KitFrontendRegistry* r, const char* path,
+ KitLanguage* out_lang) {
size_t len;
size_t i;
KitSlice ext = SLICE_NULL;
int have_ext = 0;
- unsigned lang;
+ uint32_t lang;
+ uint32_t nlangs;
- if (!path) return KIT_LANG_UNKNOWN;
+ if (out_lang) *out_lang = KIT_LANG_UNKNOWN;
+ if (!path) return KIT_FRONTEND_PATH_UNKNOWN;
for (len = 0; path[len]; ++len) {
}
- /* Strip back to the last `.` after the final `/`. No dot → no
- * extension → no language claims it. */
i = len;
while (i > 0) {
--i;
@@ -117,19 +248,33 @@ KitLanguage kit_language_for_path(KitCompiler* c, const char* path) {
break;
}
}
- if (!have_ext) return KIT_LANG_UNKNOWN;
+ if (!have_ext) return KIT_FRONTEND_PATH_UNKNOWN;
- for (lang = 0; lang < KIT_LANG_COUNT; ++lang) {
- const KitFrontendVTable* v = frontend_for(c, lang);
+ nlangs = r ? r->nfrontends : frontend_count_for(c);
+ for (lang = 0; lang < nlangs; ++lang) {
+ const KitFrontendVTable* v =
+ r ? registry_frontend_for(r, lang) : frontend_for(c, lang);
uint32_t e;
if (!v || !v->extensions) continue;
for (e = 0; e < v->nextensions; ++e) {
- if (ext_eq_ci(ext, v->extensions[e])) return (KitLanguage)lang;
+ if (ext_eq_ci(ext, v->extensions[e])) {
+ if (out_lang) *out_lang = (KitLanguage)lang;
+ return vtable_extension_kind(v, e);
+ }
}
}
- /* No registered frontend claims this extension. C is not special — it does
- * not absorb unrecognized inputs. */
- return KIT_LANG_UNKNOWN;
+ return KIT_FRONTEND_PATH_UNKNOWN;
+}
+
+KitLanguage kit_language_for_path(KitCompiler* c, const char* path) {
+ KitLanguage lang = KIT_LANG_UNKNOWN;
+ (void)language_path_kind_common(c, NULL, path, &lang);
+ return lang;
+}
+
+KitFrontendPathKind kit_language_path_kind(KitCompiler* c, const char* path,
+ KitLanguage* out_lang) {
+ return language_path_kind_common(c, NULL, path, out_lang);
}
/* Case-sensitive trailing-substring match (mirrors the driver's exact suffix
@@ -143,9 +288,10 @@ static int path_has_suffix(const char* s, const char* suffix) {
KitInputKind kit_input_kind_for_path(KitCompiler* c, const char* path) {
if (!path) return KIT_INPUT_UNKNOWN;
- /* A registered frontend's extension wins first (case-insensitive registry,
- * headers excluded — they claim no language). */
- if (kit_language_for_path(c, path) != KIT_LANG_UNKNOWN)
+ /* A registered frontend's compilable-source extension wins first
+ * (case-insensitive registry). Header/interface extensions are owned by a
+ * language but are not standalone translation units. */
+ if (kit_language_path_kind(c, path, NULL) == KIT_FRONTEND_PATH_SOURCE)
return KIT_INPUT_SOURCE;
if (path_has_suffix(path, ".o") || path_has_suffix(path, ".obj"))
return KIT_INPUT_OBJECT;
@@ -168,9 +314,11 @@ static int name_eq(const char* name, KitSlice pat) {
}
KitLanguage kit_language_for_name(KitCompiler* c, const char* name) {
- unsigned lang;
+ uint32_t lang;
+ uint32_t nlangs;
if (!name) return KIT_LANG_UNKNOWN;
- for (lang = 0; lang < KIT_LANG_COUNT; ++lang) {
+ nlangs = frontend_count_for(c);
+ for (lang = 0; lang < nlangs; ++lang) {
const KitFrontendVTable* v = frontend_for(c, lang);
uint32_t n;
if (!v || !v->names) continue;
@@ -183,7 +331,7 @@ KitLanguage kit_language_for_name(KitCompiler* c, const char* name) {
const char* kit_language_name(KitCompiler* c, KitLanguage lang) {
const KitFrontendVTable* v;
- if ((unsigned)lang >= KIT_LANG_COUNT) return NULL;
+ if (lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO) return NULL;
v = frontend_for(c, (unsigned)lang);
if (!v || !v->names || v->nnames == 0) return NULL;
/* The first name is the canonical one. Name tables are KIT_SLICE_LIT over
@@ -193,7 +341,7 @@ const char* kit_language_name(KitCompiler* c, KitLanguage lang) {
const char* kit_language_default_extension(KitCompiler* c, KitLanguage lang) {
const KitFrontendVTable* v;
- if ((unsigned)lang >= KIT_LANG_COUNT) return NULL;
+ if (lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO) return NULL;
v = frontend_for(c, (unsigned)lang);
if (!v || !v->extensions || v->nextensions == 0) return NULL;
/* The first extension is the canonical one. Extension tables are
@@ -204,29 +352,15 @@ const char* kit_language_default_extension(KitCompiler* c, KitLanguage lang) {
KitStatus kit_register_frontend(KitCompiler* c, KitLanguage lang,
const KitFrontendVTable* vtable) {
- if (!c) return KIT_INVALID;
- if ((unsigned)lang >= KIT_LANG_COUNT) return KIT_INVALID;
- if (vtable) {
- uint8_t mode = vtable->caps.lto_mode;
- if (!vtable->new_frontend || !vtable->free_frontend ||
- mode > KIT_FRONTEND_LTO_OPAQUE) {
- return KIT_INVALID;
- }
- if (mode == KIT_FRONTEND_LTO_CG) {
- if (!vtable->compile_cg) return KIT_INVALID;
- } else if (!vtable->compile_obj) {
- return KIT_INVALID;
- }
- }
- c->frontends[lang] = vtable;
- return KIT_OK;
+ return compiler_register_frontend((Compiler*)c, lang, vtable);
}
KitStatus kit_frontend_caps(KitCompiler* c, KitLanguage lang,
KitFrontendCaps* out) {
const KitFrontendVTable* v;
- if (!c || !out || (unsigned)lang >= KIT_LANG_COUNT) return KIT_INVALID;
- v = c->frontends[lang];
+ if (!c || !out || lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO)
+ return KIT_INVALID;
+ v = frontend_for(c, lang);
if (!v) return KIT_INVALID;
*out = v->caps;
return KIT_OK;
@@ -236,19 +370,118 @@ KitStatus kit_frontend_parse_options(KitCompiler* c, KitLanguage lang, int argc,
char** argv, void** out_opts) {
const KitFrontendVTable* v;
if (out_opts) *out_opts = NULL;
- if (!c || !out_opts || (unsigned)lang >= KIT_LANG_COUNT) return KIT_INVALID;
- v = c->frontends[lang];
+ if (!c || !out_opts || lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO)
+ return KIT_INVALID;
+ v = frontend_for(c, lang);
if (!v || !v->parse_options) return KIT_INVALID;
return v->parse_options(c, argc, argv, out_opts);
}
void kit_frontend_free_options(KitCompiler* c, KitLanguage lang, void* opts) {
const KitFrontendVTable* v;
- if (!c || !opts || (unsigned)lang >= KIT_LANG_COUNT) return;
- v = c->frontends[lang];
+ if (!c || !opts || lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO) return;
+ v = frontend_for(c, lang);
if (v && v->free_options) v->free_options(c, opts);
}
+KitStatus kit_frontend_registry_new(const KitContext* ctx,
+ KitFrontendRegistry** out) {
+ KitHeap* h;
+ KitFrontendRegistry* r;
+ if (!out) return KIT_INVALID;
+ *out = NULL;
+ if (!ctx || !ctx->heap) return KIT_INVALID;
+ h = ctx->heap;
+ r = (KitFrontendRegistry*)h->alloc(h, sizeof(*r),
+ _Alignof(KitFrontendRegistry));
+ if (!r) return KIT_NOMEM;
+ memset(r, 0, sizeof(*r));
+ r->ctx = ctx;
+ *out = r;
+ return KIT_OK;
+}
+
+void kit_frontend_registry_free(KitFrontendRegistry* r) {
+ KitHeap* h;
+ if (!r || !r->ctx || !r->ctx->heap) return;
+ h = r->ctx->heap;
+ if (r->frontends)
+ h->free(h, (void*)r->frontends, r->cap * sizeof(*r->frontends));
+ h->free(h, r, sizeof(*r));
+}
+
+KitStatus kit_frontend_registry_add_builtin(KitFrontendRegistry* r) {
+ KitStatus st;
+ KitLanguage lang;
+ if (!r) return KIT_INVALID;
+ st = registry_reserve(r, KIT_LANG_BUILTIN_COUNT);
+ if (st != KIT_OK) return st;
+ for (lang = 0; lang < KIT_LANG_BUILTIN_COUNT; ++lang)
+ r->frontends[lang] = lang_registry_vtable(lang);
+ if (r->nfrontends < KIT_LANG_BUILTIN_COUNT)
+ r->nfrontends = KIT_LANG_BUILTIN_COUNT;
+ return KIT_OK;
+}
+
+KitStatus kit_frontend_registry_add(KitFrontendRegistry* r,
+ const KitFrontendVTable* vtable,
+ KitLanguage* out_lang) {
+ KitStatus st;
+ KitLanguage lang;
+ if (out_lang) *out_lang = KIT_LANG_UNKNOWN;
+ if (!r || !vtable) return KIT_INVALID;
+ st = validate_frontend_vtable(vtable);
+ if (st != KIT_OK) return st;
+ lang = r->nfrontends;
+ st = registry_reserve(r, lang + 1u);
+ if (st != KIT_OK) return st;
+ r->frontends[lang] = vtable;
+ r->nfrontends = lang + 1u;
+ if (out_lang) *out_lang = lang;
+ return KIT_OK;
+}
+
+KitLanguage kit_frontend_registry_language_for_path(
+ const KitFrontendRegistry* r, const char* path) {
+ KitLanguage lang = KIT_LANG_UNKNOWN;
+ (void)language_path_kind_common(NULL, r, path, &lang);
+ return lang;
+}
+
+KitLanguage kit_frontend_registry_language_for_name(
+ const KitFrontendRegistry* r, const char* name) {
+ uint32_t lang;
+ if (!r || !name) return KIT_LANG_UNKNOWN;
+ for (lang = 0; lang < r->nfrontends; ++lang) {
+ const KitFrontendVTable* v = registry_frontend_for(r, lang);
+ uint32_t n;
+ if (!v || !v->names) continue;
+ for (n = 0; n < v->nnames; ++n) {
+ if (name_eq(name, v->names[n])) return (KitLanguage)lang;
+ }
+ }
+ return KIT_LANG_UNKNOWN;
+}
+
+KitFrontendPathKind kit_frontend_registry_path_kind(
+ const KitFrontendRegistry* r, const char* path, KitLanguage* out_lang) {
+ return language_path_kind_common(NULL, r, path, out_lang);
+}
+
+KitStatus kit_compiler_install_frontend_registry(
+ KitCompiler* c, const KitFrontendRegistry* r) {
+ Compiler* cc = (Compiler*)c;
+ KitStatus st;
+ uint32_t i;
+ if (!cc || !r) return KIT_INVALID;
+ st = compiler_frontends_reserve(cc, r->nfrontends);
+ if (st != KIT_OK) return st;
+ for (i = 0; i < r->nfrontends; ++i) cc->frontends[i] = r->frontends[i];
+ for (i = r->nfrontends; i < cc->nfrontends; ++i) cc->frontends[i] = NULL;
+ cc->nfrontends = r->nfrontends;
+ return KIT_OK;
+}
+
uint32_t kit_compiler_arch_predefines(KitCompiler* c,
const KitPredefinedMacro** out) {
const ArchImpl* arch;
@@ -270,7 +503,9 @@ static void validate_bytes(Compiler* c, const KitSourceInput* in) {
static const KitFrontendVTable* frontend_for_language(Compiler* c,
KitLanguage lang) {
- if ((unsigned)lang >= KIT_LANG_COUNT) return NULL;
+ if (!c || lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO ||
+ lang >= c->nfrontends)
+ return NULL;
return c->frontends[lang];
}
@@ -289,7 +524,8 @@ static KitStatus kit_frontend_new(KitCompiler* c, KitLanguage lang,
if (!out) return KIT_INVALID;
*out = NULL;
- if (!c || (unsigned)lang >= KIT_LANG_COUNT) return KIT_INVALID;
+ if (!c || lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO)
+ return KIT_INVALID;
vtable = frontend_for_language((Compiler*)c, lang);
if (!vtable) return KIT_UNSUPPORTED;
state = vtable->new_frontend(c);
@@ -418,7 +654,9 @@ KitStatus kit_compile_session_new(KitCompiler* c,
if (!out) return KIT_INVALID;
*out = NULL;
- if (!c || !opts || (unsigned)opts->lang >= KIT_LANG_COUNT) return KIT_INVALID;
+ if (!c || !opts || opts->lang == KIT_LANG_UNKNOWN ||
+ opts->lang == KIT_LANG_AUTO)
+ return KIT_INVALID;
st = kit_frontend_new(c, opts->lang, &frontend);
if (st != KIT_OK) return st;
h = (Heap*)c->ctx->heap;
diff --git a/src/api/core.c b/src/api/core.c
@@ -1,6 +1,7 @@
/* Public core API bridge. */
#include "core/core.h"
+#include "api/lang_registry.h"
#include <kit/core.h>
#include <string.h>
@@ -168,6 +169,12 @@ int kit_target_has_feature(const KitTarget* t, KitSlice name) {
KitStatus kit_compiler_new(const KitTarget* target, const KitContext* ctx,
KitCompiler** out) {
+ return kit_compiler_new_ex(target, ctx, NULL, out);
+}
+
+KitStatus kit_compiler_new_ex(const KitTarget* target, const KitContext* ctx,
+ const KitCompilerOptions* opts,
+ KitCompiler** out) {
Heap* h;
Compiler* c;
KitStatus st;
@@ -182,6 +189,15 @@ KitStatus kit_compiler_new(const KitTarget* target, const KitContext* ctx,
h->free(h, c, sizeof(*c));
return st;
}
+ if (opts && opts->frontends) {
+ st = kit_compiler_install_frontend_registry(c, opts->frontends);
+ if (st != KIT_OK) {
+ compiler_fini(c);
+ h->free(h, c, sizeof(*c));
+ if (out) *out = NULL;
+ return st;
+ }
+ }
*out = c;
return KIT_OK;
}
diff --git a/src/api/lang_registry.c b/src/api/lang_registry.c
@@ -37,19 +37,36 @@ extern const KitFrontendVTable kit_asm_frontend_vtable;
#include "wasm/wasm.h"
#endif
-void lang_registry_init(KitCompiler* c) {
+KitStatus lang_registry_init(KitCompiler* c) {
#if KIT_LANG_ASM_ENABLED
- (void)kit_register_frontend(c, KIT_LANG_ASM, &kit_asm_frontend_vtable);
+ {
+ KitStatus st =
+ kit_register_frontend(c, KIT_LANG_ASM, &kit_asm_frontend_vtable);
+ if (st != KIT_OK) return st;
+ }
#endif
#if KIT_LANG_C_ENABLED
- (void)kit_register_frontend(c, KIT_LANG_C, &kit_c_frontend_vtable);
+ {
+ KitStatus st =
+ kit_register_frontend(c, KIT_LANG_C, &kit_c_frontend_vtable);
+ if (st != KIT_OK) return st;
+ }
#endif
#if KIT_LANG_TOY_ENABLED
- (void)kit_register_frontend(c, KIT_LANG_TOY, &kit_toy_frontend_vtable);
+ {
+ KitStatus st =
+ kit_register_frontend(c, KIT_LANG_TOY, &kit_toy_frontend_vtable);
+ if (st != KIT_OK) return st;
+ }
#endif
#if KIT_LANG_WASM_ENABLED
- (void)kit_register_frontend(c, KIT_LANG_WASM, &kit_wasm_frontend_vtable);
+ {
+ KitStatus st =
+ kit_register_frontend(c, KIT_LANG_WASM, &kit_wasm_frontend_vtable);
+ if (st != KIT_OK) return st;
+ }
#endif
+ return KIT_OK;
}
const KitFrontendVTable* lang_registry_vtable(KitLanguage lang) {
diff --git a/src/api/lang_registry.h b/src/api/lang_registry.h
@@ -6,7 +6,7 @@
/* Wire every KIT_LANG_*_ENABLED frontend into c->frontends[]. Called
* once during compiler construction; see src/api/lang_registry.c. */
-void lang_registry_init(KitCompiler* c);
+KitStatus lang_registry_init(KitCompiler* c);
/* Return the compile-time default frontend vtable for `lang` (the same
* KIT_LANG_*_ENABLED set lang_registry_init wires), or NULL for an
@@ -14,5 +14,7 @@ void lang_registry_init(KitCompiler* c);
* resolvers answer without a KitCompiler instance (e.g. at CLI arg-parse
* time). It does not see per-compiler kit_register_frontend overrides. */
const KitFrontendVTable* lang_registry_vtable(KitLanguage lang);
+KitStatus kit_compiler_install_frontend_registry(
+ KitCompiler*, const KitFrontendRegistry*);
#endif
diff --git a/src/core/core.c b/src/core/core.c
@@ -89,6 +89,7 @@ struct CompilerCleanup {
KitStatus compiler_init(Compiler* c, const KitTarget* target,
const KitContext* ctx) {
Heap* h = ctx->heap;
+ KitStatus st;
if (!c || !target || !ctx || !h) return KIT_INVALID;
memset(c, 0, sizeof(*c));
@@ -116,12 +117,17 @@ KitStatus compiler_init(Compiler* c, const KitTarget* target,
c->cleanup = NULL;
- lang_registry_init(c);
+ st = lang_registry_init(c);
+ if (st != KIT_OK) goto err_status;
return KIT_OK;
nomem:
compiler_fini(c);
return KIT_NOMEM;
+
+err_status:
+ compiler_fini(c);
+ return st;
}
void compiler_fini(Compiler* c) {
@@ -133,6 +139,12 @@ void compiler_fini(Compiler* c) {
c->cg_api_free(c);
c->cg_api_free = NULL;
}
+ if (c->frontends) {
+ h->free(h, c->frontends, c->frontends_cap * sizeof(*c->frontends));
+ c->frontends = NULL;
+ c->nfrontends = 0;
+ c->frontends_cap = 0;
+ }
if (c->abi) {
abi_free(c->abi);
diff --git a/src/core/core.h b/src/core/core.h
@@ -139,7 +139,9 @@ struct KitCompiler {
const KitTarget* target_ref;
Target target;
CompilerCleanup* cleanup;
- const KitFrontendVTable* frontends[KIT_LANG_COUNT];
+ const KitFrontendVTable** frontends;
+ u32 nfrontends;
+ u32 frontends_cap;
void* cg_api;
void (*cg_api_free)(Compiler*);
/* Borrowed pointer to the per-compiler builtin
diff --git a/test/rpn-lang/caller.c b/test/rpn-lang/caller.c
@@ -0,0 +1,5 @@
+int main(void);
+
+int _start(void) {
+ return main() == 14 ? 0 : 1;
+}
diff --git a/test/rpn-lang/prog.rpn b/test/rpn-lang/prog.rpn
@@ -0,0 +1 @@
+2 3 4 * +
diff --git a/test/rpn-lang/rpn_lang.c b/test/rpn-lang/rpn_lang.c
@@ -0,0 +1,143 @@
+#include <kit/frontend.h>
+
+#include <stddef.h>
+#include <stdint.h>
+
+typedef struct RpnFrontend {
+ KitCompiler* c;
+} RpnFrontend;
+
+static KitFrontendState* rpn_new(KitCompiler* c) {
+ KitHeap* h = kit_compiler_context(c)->heap;
+ RpnFrontend* fe = (RpnFrontend*)h->alloc(h, sizeof(*fe), _Alignof(RpnFrontend));
+ if (!fe) return NULL;
+ fe->c = c;
+ return (KitFrontendState*)fe;
+}
+
+static void rpn_free(KitFrontendState* st) {
+ RpnFrontend* fe = (RpnFrontend*)st;
+ KitHeap* h;
+ if (!fe) return;
+ h = kit_compiler_context(fe->c)->heap;
+ h->free(h, fe, sizeof(*fe));
+}
+
+static void rpn_skip_ws(const char** p, const char* end) {
+ while (*p < end && (**p == ' ' || **p == '\t' || **p == '\r' || **p == '\n'))
+ ++(*p);
+}
+
+static int rpn_read_int(const char** p, const char* end, int64_t* out) {
+ int neg = 0;
+ int any = 0;
+ int64_t v = 0;
+ if (*p < end && **p == '-') {
+ neg = 1;
+ ++(*p);
+ }
+ while (*p < end && **p >= '0' && **p <= '9') {
+ any = 1;
+ v = v * 10 + (int64_t)(**p - '0');
+ ++(*p);
+ }
+ if (!any) return 0;
+ *out = neg ? -v : v;
+ return 1;
+}
+
+static KitStatus rpn_compile_cg(KitFrontendState* st,
+ const KitFrontendCompileOptions* opts,
+ const KitSourceInput* input, KitCg* cg) {
+ RpnFrontend* fe = (RpnFrontend*)st;
+ const char* p = input->bytes.s;
+ const char* end = p + input->bytes.len;
+ KitCgTypeId i32_ty = kit_cg_type_builtin(fe->c, KIT_CG_BUILTIN_I32);
+ KitCgTypeId func_ty;
+ KitCgFuncResult result;
+ KitCgFuncSig sig;
+ KitCgDecl decl;
+ KitCgSym main_sym;
+ int depth = 0;
+ (void)opts;
+
+ result.type = i32_ty;
+ result.attrs.flags = 0;
+ result.attrs.align = 0;
+ result.attrs.dereferenceable_size = 0;
+ sig.result = result;
+ sig.params = NULL;
+ sig.nparams = 0;
+ sig.call_conv = KIT_CG_CC_TARGET_C;
+ sig.abi_variadic = false;
+ func_ty = kit_cg_type_func(fe->c, sig);
+
+ decl = (KitCgDecl){0};
+ decl.kind = KIT_CG_DECL_FUNC;
+ decl.linkage_name =
+ kit_cg_c_linkage_name(fe->c, kit_sym_intern(fe->c, KIT_SLICE_LIT("main")));
+ decl.display_name = kit_sym_intern(fe->c, KIT_SLICE_LIT("main"));
+ decl.type = func_ty;
+ decl.sym.bind = KIT_SB_GLOBAL;
+ decl.sym.visibility = KIT_CG_VIS_DEFAULT;
+ decl.sym.flags = 0;
+ decl.as.func.flags = 0;
+ decl.as.func.inline_policy = KIT_CG_INLINE_DEFAULT;
+
+ main_sym = kit_cg_decl(cg, decl);
+ if (main_sym == KIT_CG_SYM_NONE) return KIT_ERR;
+ kit_cg_func_begin(cg, main_sym);
+ while (p < end) {
+ int64_t v;
+ rpn_skip_ws(&p, end);
+ if (p >= end) break;
+ if ((*p >= '0' && *p <= '9') ||
+ (*p == '-' && p + 1 < end && p[1] >= '0' && p[1] <= '9')) {
+ if (!rpn_read_int(&p, end, &v)) return KIT_MALFORMED;
+ kit_cg_push_int(cg, (uint64_t)(int32_t)v, i32_ty);
+ ++depth;
+ continue;
+ }
+ if (*p == '+' || *p == '-' || *p == '*') {
+ char op = *p++;
+ if (depth < 2) return KIT_MALFORMED;
+ if (op == '+')
+ kit_cg_int_binop(cg, KIT_CG_INT_ADD, 0);
+ else if (op == '-')
+ kit_cg_int_binop(cg, KIT_CG_INT_SUB, 0);
+ else
+ kit_cg_int_binop(cg, KIT_CG_INT_MUL, 0);
+ --depth;
+ continue;
+ }
+ return KIT_MALFORMED;
+ }
+ if (depth != 1) return KIT_MALFORMED;
+ kit_cg_ret(cg);
+ kit_cg_func_end(cg);
+ return KIT_OK;
+}
+
+static const KitSlice rpn_exts[] = {KIT_SLICE_LIT("rpn")};
+static const KitSlice rpn_names[] = {KIT_SLICE_LIT("rpn")};
+
+const KitFrontendVTable kit_rpn_frontend_vtable = {
+ .new_frontend = rpn_new,
+ .compile_cg = rpn_compile_cg,
+ .compile_obj = NULL,
+ .free_frontend = rpn_free,
+ .extensions = rpn_exts,
+ .nextensions = (uint32_t)(sizeof rpn_exts / sizeof rpn_exts[0]),
+ .extension_kinds = NULL,
+ .names = rpn_names,
+ .nnames = (uint32_t)(sizeof rpn_names / sizeof rpn_names[0]),
+ .commit = NULL,
+ .abort = NULL,
+ .caps = {false, KIT_FRONTEND_LTO_CG, false},
+ .parse_options = NULL,
+ .free_options = NULL,
+};
+
+KitStatus kit_rpn_register_frontends(KitFrontendRegistry* registry) {
+ return kit_frontend_registry_add(registry, &kit_rpn_frontend_vtable, NULL);
+}
diff --git a/test/rpn-lang/run.sh b/test/rpn-lang/run.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+set -eu
+
+ROOT=$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd)
+KIT_BUILD_DIR=${KIT_BUILD_DIR:-"$ROOT/build/test/rpn-lang"}
+OUT="$KIT_BUILD_DIR/out"
+LOG="$KIT_BUILD_DIR/run.log"
+RPN_OBJ="$OUT/rpn_lang.o"
+KIT="$KIT_BUILD_DIR/kit-rpn"
+
+mkdir -p "$OUT"
+
+{
+ printf 'build rpn frontend object\n'
+ "${CC:-cc}" -std=c11 -I "$ROOT/include" -c "$ROOT/test/rpn-lang/rpn_lang.c" -o "$RPN_OBJ"
+ printf 'build custom kit driver\n'
+ make -C "$ROOT" custom-bin \
+ BUILD_DIR="$KIT_BUILD_DIR/base" \
+ KIT_DRIVER_CUSTOM_BIN="$KIT" \
+ KIT_DRIVER_EXT_OBJS="$RPN_OBJ" \
+ KIT_DRIVER_EXT_REGISTER=kit_rpn_register_frontends
+ printf 'compile rpn program with custom driver\n'
+ "$KIT" build-exe -target x86_64-none-elf -nostdlib \
+ "$ROOT/test/rpn-lang/caller.c" "$ROOT/test/rpn-lang/prog.rpn" \
+ -o "$OUT/rpn.elf"
+ printf 'inspect symbols\n'
+ "$KIT" nm "$OUT/rpn.elf"
+ printf 'run rpn program with custom driver\n'
+ if "$KIT" run "$ROOT/test/rpn-lang/prog.rpn"; then
+ rpn_status=0
+ else
+ rpn_status=$?
+ fi
+ if [ "$rpn_status" -ne 14 ]; then
+ printf 'expected kit run exit 14, got %s\n' "$rpn_status"
+ exit 1
+ fi
+ printf 'debugger accepts rpn input with custom driver\n'
+ "$KIT" dbg "$ROOT/test/rpn-lang/prog.rpn" --batch --command language
+ printf 'debugger appends rpn snippets with custom driver\n'
+ "$KIT" dbg --batch --language rpn --command 'jit { 2 3 + }' --command language
+} >"$LOG" 2>&1
+
+grep ' main$' "$LOG" >/dev/null
+grep 'Language: rpn' "$LOG" >/dev/null
+printf 'rpn-lang ok\n'