commit c09a8d50742caad102d77ca255eb63ae2b68a6ce
parent 0460147a6eb6d881eee3041393e2f1c120c56f4d
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 1 Jun 2026 12:06:07 -0700
frontend: C registers its extensions; drop the fallback-to-C
cfree_language_for_path defaulted any unrecognized extension to
CFREE_LANG_C, making C a privileged fallback rather than a peer of the
other frontends. Have the C frontend claim c/h like every other language
claims its extensions, and return a new CFREE_LANG_UNKNOWN sentinel when
no registered frontend matches. The compile-session guards already reject
out-of-range languages, so an unrecognized input now surfaces an error
instead of being misparsed as C.
Diffstat:
5 files changed, 36 insertions(+), 13 deletions(-)
diff --git a/doc/FRONTENDS.md b/doc/FRONTENDS.md
@@ -76,10 +76,11 @@ registry just provides the default wiring.
`cfree_language_for_path` (`src/api/compile.c`) maps a file path to a language
by extracting the trailing extension and walking every registered frontend's
`extensions` list (case-insensitively, so `.S` and `.s` both hit asm's `"s"`).
-The C frontend claims *no* extensions on purpose: an unrecognized extension
-falls through to `CFREE_LANG_C` as the default, so listing `c`/`h` would only
-duplicate the fallback. asm claims `s`, toy claims `toy`, wasm claims `wat`
-and `wasm`.
+No frontend is privileged. C is just another language: it claims `c` and `h`,
+asm claims `s`, toy claims `toy`, and wasm claims `wat` and `wasm`. A path
+whose extension no frontend claims — or that has no extension — resolves to
+`CFREE_LANG_UNKNOWN` rather than silently defaulting to C, so an unrecognized
+input is rejected instead of misparsed.
```
path --cfree_language_for_path--> CfreeLanguage
diff --git a/driver/cmd/dbg.c b/driver/cmd/dbg.c
@@ -1782,6 +1782,7 @@ static const char* dbg_jit_language_name(CfreeLanguage lang) {
case CFREE_LANG_WASM:
return "wasm";
case CFREE_LANG_C:
+ case CFREE_LANG_UNKNOWN:
case CFREE_LANG_COUNT:
break;
}
@@ -1797,6 +1798,7 @@ static const char* dbg_jit_language_suffix(CfreeLanguage lang) {
case CFREE_LANG_WASM:
return ".wat";
case CFREE_LANG_C:
+ case CFREE_LANG_UNKNOWN:
case CFREE_LANG_COUNT:
break;
}
@@ -1812,6 +1814,7 @@ static const char* dbg_jit_default_name(CfreeLanguage lang) {
case CFREE_LANG_WASM:
return "<dbg-jit.wat>";
case CFREE_LANG_C:
+ case CFREE_LANG_UNKNOWN:
case CFREE_LANG_COUNT:
break;
}
diff --git a/include/cfree/compile.h b/include/cfree/compile.h
@@ -15,6 +15,10 @@
*/
typedef enum CfreeLanguage {
+ /* No registered frontend claims the input. cfree_language_for_path
+ * returns this when an extension matches nothing; it is never a valid
+ * frontend index (the compile paths reject it). */
+ CFREE_LANG_UNKNOWN = -1,
CFREE_LANG_C = 0,
CFREE_LANG_ASM = 1,
CFREE_LANG_TOY = 2,
@@ -103,6 +107,11 @@ typedef struct CfreeFrontendVTable {
CfreeFrontendAbortFn abort;
} CfreeFrontendVTable;
+/* Map a path to a language purely by its registered extension. Walks every
+ * registered frontend's `extensions` list (case-insensitively) and returns
+ * the owning CfreeLanguage. No frontend is privileged: a path whose extension
+ * is unclaimed (or that has no extension) returns CFREE_LANG_UNKNOWN rather
+ * than defaulting to any particular language. */
CFREE_API CfreeLanguage cfree_language_for_path(CfreeCompiler*,
const char* path);
CFREE_API CfreeStatus cfree_register_frontend(CfreeCompiler*, CfreeLanguage,
diff --git a/lang/c/c.c b/lang/c/c.c
@@ -136,11 +136,19 @@ static void c_frontend_free(CfreeFrontendState* frontend) {
h->free(h, fe, sizeof(*fe));
}
-/* C claims no extensions — the language-for-path lookup falls through
- * to CFREE_LANG_C as the default when nothing else matches, so listing
- * `c`/`h` here would only duplicate that fallback. */
+/* C is just another frontend: it claims its source/header extensions so the
+ * language-for-path lookup resolves them by the same registry walk as every
+ * other language, with no special fallback. */
+static const CfreeSlice c_extensions[] = {CFREE_SLICE_LIT("c"),
+ CFREE_SLICE_LIT("h")};
+
const CfreeFrontendVTable cfree_c_frontend_vtable = {
- c_frontend_new, c_frontend_compile, c_frontend_free, NULL, 0,
+ c_frontend_new,
+ c_frontend_compile,
+ c_frontend_free,
+ c_extensions,
+ (uint32_t)(sizeof c_extensions / sizeof c_extensions[0]),
/* commit/abort: C has no durable cross-compile state yet */
- NULL, NULL,
+ NULL,
+ NULL,
};
diff --git a/src/api/compile.c b/src/api/compile.c
@@ -87,11 +87,11 @@ CfreeLanguage cfree_language_for_path(CfreeCompiler* c, const char* path) {
int have_ext = 0;
unsigned lang;
- if (!c || !path) return CFREE_LANG_C;
+ if (!c || !path) return CFREE_LANG_UNKNOWN;
for (len = 0; path[len]; ++len) {
}
/* Strip back to the last `.` after the final `/`. No dot → no
- * extension → fall through to the C default. */
+ * extension → no language claims it. */
i = len;
while (i > 0) {
--i;
@@ -103,7 +103,7 @@ CfreeLanguage cfree_language_for_path(CfreeCompiler* c, const char* path) {
break;
}
}
- if (!have_ext) return CFREE_LANG_C;
+ if (!have_ext) return CFREE_LANG_UNKNOWN;
for (lang = 0; lang < CFREE_LANG_COUNT; ++lang) {
const CfreeFrontendVTable* v = c->frontends[lang];
@@ -113,7 +113,9 @@ CfreeLanguage cfree_language_for_path(CfreeCompiler* c, const char* path) {
if (ext_eq_ci(ext, v->extensions[e])) return (CfreeLanguage)lang;
}
}
- return CFREE_LANG_C;
+ /* No registered frontend claims this extension. C is not special — it does
+ * not absorb unrecognized inputs. */
+ return CFREE_LANG_UNKNOWN;
}
CfreeStatus cfree_register_frontend(CfreeCompiler* c, CfreeLanguage lang,