Skip to content

Commit 60fc35e

Browse files
committed
fix 5 bugs, _Generic inner-generic prefix drop, _Generic paren-complex target, _Generic array/member target boundary, bitfield raw leak at colon, C23 enum fixed underlying type; update README.md, SPEC.md; add 5 regression tests
1 parent c039946 commit 60fc35e

File tree

5 files changed

+267
-8
lines changed

5 files changed

+267
-8
lines changed

.github/SPEC.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Prism Transpiler Specification
22

33
**Version:** 1.0.5
4-
**Status:** Implemented — every item in this document corresponds to behavior that exists in the codebase and is exercised by the test suite (4376+ tests + self-host stage1==stage2).
4+
**Status:** Implemented — every item in this document corresponds to behavior that exists in the codebase and is exercised by the test suite (4398+ tests + self-host stage1==stage2).
55

66
This document describes what the transpiler **does**, not what it aspires to do.
77

@@ -11,6 +11,8 @@ This document describes what the transpiler **does**, not what it aspires to do.
1111

1212
Prism is a source-to-source C transpiler. It reads preprocessed C, transforms it, and emits standard C. It adds three language features — `defer`, `orelse`, and automatic zero-initialization — and enforces safety rules at transpile time. It is a single compilation unit (`prism.c` includes `parse.c`; `windows.c` is a native Windows shim).
1313

14+
**Standards compatibility:** Prism accepts C99, C11, and C23 input and emits standard C compatible with GCC, Clang, and MSVC. All standard C type specifiers, qualifiers, storage classes, attributes, and control-flow constructs are recognized and passed through correctly. C23 features including `typeof_unqual`, `constexpr`, `auto` type inference, `_BitInt(N)`, `[[...]]` attributes, `alignas`/`alignof`, `static_assert`, fixed-underlying-type enums (`enum E : int { ... }`), labeled declarations, and if/switch initializers are supported.
15+
1416
The transpiler operates in two passes:
1517

1618
1. **Pass 1** — Full semantic analysis over all tokens at all depths. Builds immutable data structures (scope tree, symbol table, shadow table, per-function CFG arrays). Raises all errors. No output is emitted.

.github/test.api.c

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8316,6 +8316,195 @@ static void test_generic_ternary_branch_prefix(void) {
83168316
}
83178317
}
83188318

8319+
static void test_generic_array_member_target_prefix(void) {
8320+
printf("\n--- _Generic array/member target prefix ---\n");
8321+
/* Array subscript target: handlers[0] must get obj. prefix */
8322+
{
8323+
const char *code =
8324+
"typedef struct {\n"
8325+
" void (*handlers[3])(void);\n"
8326+
" struct { void (*nested_func)(void); } sub;\n"
8327+
"} Obj;\n"
8328+
"Obj obj;\n"
8329+
"void dispatch(int sel) {\n"
8330+
" obj._Generic(sel,\n"
8331+
" int: handlers[0],\n"
8332+
" float: sub.nested_func\n"
8333+
" )();\n"
8334+
"}\n";
8335+
PrismResult r = prism_transpile_source(code, "gamt1.c", prism_defaults());
8336+
CHECK_EQ(r.status, PRISM_OK, "generic-array-member: transpiles OK");
8337+
if (r.output) {
8338+
CHECK(strstr(r.output, "obj.handlers[0]") != NULL,
8339+
"generic-array-member: array target gets prefix");
8340+
CHECK(strstr(r.output, "obj.sub.nested_func") != NULL,
8341+
"generic-array-member: nested member target gets prefix");
8342+
}
8343+
prism_free(&r);
8344+
}
8345+
}
8346+
8347+
static void test_bitfield_raw_leak(void) {
8348+
printf("\n--- Bitfield raw leak ---\n");
8349+
{
8350+
const char *code =
8351+
"struct Flags {\n"
8352+
" unsigned int a, raw b : 4;\n"
8353+
"};\n";
8354+
PrismResult r = prism_transpile_source(code, "bfrl1.c", prism_defaults());
8355+
CHECK_EQ(r.status, PRISM_OK, "bitfield-raw: transpiles OK");
8356+
if (r.output) {
8357+
CHECK(strstr(r.output, "raw") == NULL,
8358+
"bitfield-raw: raw keyword stripped");
8359+
CHECK(strstr(r.output, "b : 4") != NULL || strstr(r.output, "b :4") != NULL ||
8360+
strstr(r.output, "b: 4") != NULL || strstr(r.output, "b:4") != NULL,
8361+
"bitfield-raw: bitfield syntax preserved");
8362+
}
8363+
prism_free(&r);
8364+
}
8365+
}
8366+
8367+
static void test_generic_paren_complex_target_prefix(void) {
8368+
printf("\n--- _Generic paren-wrapped complex target prefix ---\n");
8369+
{
8370+
const char *code =
8371+
"typedef struct {\n"
8372+
" void (*handlers[3])(void);\n"
8373+
" struct { void (*nested_func)(void); } sub;\n"
8374+
"} Obj;\n"
8375+
"Obj obj;\n"
8376+
"void dispatch(int sel) {\n"
8377+
" obj._Generic(sel,\n"
8378+
" int: (handlers[0]),\n"
8379+
" float: (sub.nested_func)\n"
8380+
" )();\n"
8381+
"}\n";
8382+
PrismResult r = prism_transpile_source(code, "gpct1.c", prism_defaults());
8383+
CHECK_EQ(r.status, PRISM_OK, "paren-complex-target: transpiles OK");
8384+
if (r.output) {
8385+
CHECK(strstr(r.output, "obj.handlers[0]") != NULL,
8386+
"paren-complex-target: array subscript gets prefix");
8387+
CHECK(strstr(r.output, "obj.sub.nested_func") != NULL,
8388+
"paren-complex-target: nested member gets prefix");
8389+
}
8390+
prism_free(&r);
8391+
}
8392+
}
8393+
8394+
static void test_generic_inner_generic_prefix(void) {
8395+
printf("\n--- _Generic inner _Generic prefix ---\n");
8396+
{
8397+
const char *code =
8398+
"typedef struct { int kind; } Api;\n"
8399+
"int handle_int(int);\n"
8400+
"int handle_float(int);\n"
8401+
"Api api;\n"
8402+
"int sel1, sel2;\n"
8403+
"\n"
8404+
"int test(int data) {\n"
8405+
" return api._Generic(sel1,\n"
8406+
" int: _Generic(sel2, int: handle_int, float: handle_float)\n"
8407+
" )(data);\n"
8408+
"}\n";
8409+
PrismResult r = prism_transpile_source(code, "gigp1.c", prism_defaults());
8410+
CHECK_EQ(r.status, PRISM_OK, "inner-generic: transpiles OK");
8411+
if (r.output) {
8412+
CHECK(strstr(r.output, "api.handle_int") != NULL,
8413+
"inner-generic: first branch gets prefix");
8414+
CHECK(strstr(r.output, "api.handle_float") != NULL,
8415+
"inner-generic: second branch gets prefix");
8416+
}
8417+
prism_free(&r);
8418+
}
8419+
}
8420+
8421+
// C23 enum with fixed underlying type: enum E : int { A, B, C }
8422+
// Verify that (1) enum constants are registered in typedef table,
8423+
// (2) the body is classified as struct/enum scope (orelse rejected),
8424+
// (3) zero-init works with variables declared using the enum type, and
8425+
// (4) parse_type_specifier correctly parses the full type specifier.
8426+
static void test_c23_enum_fixed_underlying_type(void) {
8427+
printf("\n--- C23 enum fixed underlying type ---\n");
8428+
8429+
// 1. Enum constant registration — constants should be recognized
8430+
{
8431+
const char *code =
8432+
"enum Color : int { RED, GREEN, BLUE };\n"
8433+
"int test(void) {\n"
8434+
" enum Color : int c;\n"
8435+
" return c;\n"
8436+
"}\n";
8437+
PrismResult r = prism_transpile_source(code, "c23e1.c", prism_defaults());
8438+
CHECK_EQ(r.status, PRISM_OK, "c23-enum: basic fixed type transpiles OK");
8439+
if (r.output) {
8440+
CHECK(strstr(r.output, "RED") == NULL || strstr(r.output, "enum Color") != NULL,
8441+
"c23-enum: type preserved in output");
8442+
}
8443+
prism_free(&r);
8444+
}
8445+
8446+
// 2. Orelse rejected inside fixed-type enum body
8447+
{
8448+
const char *code =
8449+
"int f(void);\n"
8450+
"enum Status : unsigned int { OK = f() orelse 0, ERR = 1 };\n";
8451+
PrismResult r = prism_transpile_source(code, "c23e2.c", prism_defaults());
8452+
CHECK_EQ(r.status, PRISM_ERR_SYNTAX, "c23-enum: orelse rejected inside fixed-type enum body");
8453+
prism_free(&r);
8454+
}
8455+
8456+
// 3. Anonymous fixed-type enum
8457+
{
8458+
const char *code =
8459+
"enum : int { VAL_A = 10, VAL_B = 20 };\n"
8460+
"int test(void) { return VAL_A + VAL_B; }\n";
8461+
PrismResult r = prism_transpile_source(code, "c23e3.c", prism_defaults());
8462+
CHECK_EQ(r.status, PRISM_OK, "c23-enum: anonymous fixed-type transpiles OK");
8463+
prism_free(&r);
8464+
}
8465+
8466+
// 4. Multi-type underlying: enum E : unsigned long long
8467+
{
8468+
const char *code =
8469+
"enum Big : unsigned long long { HUGE_A = 1, HUGE_B = 2 };\n"
8470+
"int test(void) {\n"
8471+
" enum Big : unsigned long long b;\n"
8472+
" return (int)b;\n"
8473+
"}\n";
8474+
PrismResult r = prism_transpile_source(code, "c23e4.c", prism_defaults());
8475+
CHECK_EQ(r.status, PRISM_OK, "c23-enum: unsigned long long transpiles OK");
8476+
prism_free(&r);
8477+
}
8478+
8479+
// 5. Fixed-type enum in typedef
8480+
{
8481+
const char *code =
8482+
"typedef enum Color : int { RED, GREEN, BLUE } Color;\n"
8483+
"int test(void) {\n"
8484+
" Color c;\n"
8485+
" return c;\n"
8486+
"}\n";
8487+
PrismResult r = prism_transpile_source(code, "c23e5.c", prism_defaults());
8488+
CHECK_EQ(r.status, PRISM_OK, "c23-enum: typedef fixed-type transpiles OK");
8489+
prism_free(&r);
8490+
}
8491+
8492+
// 6. Enum constant with defer shadow check
8493+
{
8494+
const char *code =
8495+
"int cleanup(void);\n"
8496+
"enum Priority : int { LOW = 1, MEDIUM = 2, HIGH = 3 };\n"
8497+
"int test(void) {\n"
8498+
" defer cleanup();\n"
8499+
" enum Priority : int p;\n"
8500+
" return p;\n"
8501+
"}\n";
8502+
PrismResult r = prism_transpile_source(code, "c23e6.c", prism_defaults());
8503+
CHECK_EQ(r.status, PRISM_OK, "c23-enum: with defer transpiles OK");
8504+
prism_free(&r);
8505+
}
8506+
}
8507+
83198508
void run_api_tests_4(void) {
83208509
printf("\n=== API TESTS (group 4) ===\n");
83218510
test_collect_source_defines_long_line_truncation();
@@ -8390,4 +8579,9 @@ void run_api_tests_4(void) {
83908579
test_nested_generic_member_rewrite();
83918580
test_typeof_unqual_const_stripping();
83928581
test_generic_ternary_branch_prefix();
8582+
test_generic_array_member_target_prefix();
8583+
test_bitfield_raw_leak();
8584+
test_generic_paren_complex_target_prefix();
8585+
test_generic_inner_generic_prefix();
8586+
test_c23_enum_fixed_underlying_type();
83938587
}

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
![Prism Banner](https://github.com/user-attachments/assets/051187c2-decd-497e-9beb-b74031eb84ed)
22

3-
![License](https://img.shields.io/badge/license-Apache_2.0-blue) ![Language](https://img.shields.io/badge/language-C-lightgrey) ![Tests](https://img.shields.io/badge/tests-4376_passing-brightgreen) ![Zero deps](https://img.shields.io/badge/dependencies-0-brightgreen)
3+
![License](https://img.shields.io/badge/license-Apache_2.0-blue) ![Language](https://img.shields.io/badge/language-C-lightgrey) ![Tests](https://img.shields.io/badge/tests-4398_pass-brightgreen) ![Zero deps](https://img.shields.io/badge/dependencies-0-brightgreen)
44

55
## Robust C by default
66
**A dialect of C with `defer`, `orelse`, automatic zero-initialization, and progressive optimization.**
77

88
Prism is a lightweight and very fast transpiler that makes C safer and faster without changing how you write it.
99

10-
- **4376 tests** — edge cases, control flow, nightmares, trying hard to break Prism
10+
- **4398 tests** — edge cases, control flow, nightmares, trying hard to break Prism
11+
- **C99 / C11 / C23** — accepts all standard C input
1112
- **Building Real C** — OpenSSL, SQLite, Bash, GNU Coreutils, Make, Curl
1213
- **Two-pass transpiler** — full semantic analysis before a single byte is emitted
1314
- **Opt-out features** — Disable parts of the transpiler, like zero-init, with CLI flags

parse.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2431,7 +2431,10 @@ static Token *find_struct_body_brace(Token *tok) {
24312431
Token *t = tok_next(tok);
24322432
while (t && t->kind != TK_EOF) {
24332433
SKIP_NOISE_CONTINUE(t);
2434-
if (is_valid_varname(t) || (t->tag & TT_QUALIFIER)) {
2434+
if (is_valid_varname(t) || (t->tag & TT_QUALIFIER) || is_type_keyword(t)) {
2435+
t = tok_next(t);
2436+
} else if (t->len == 1 && t->ch0 == ':') {
2437+
// C23 enum fixed underlying type: enum E : int { ... }
24352438
t = tok_next(t);
24362439
} else break;
24372440
}
@@ -2795,6 +2798,15 @@ static TypeSpecResult parse_type_specifier(Token *tok) {
27952798
}
27962799
Token *sue_tag = NULL;
27972800
if (tok && is_valid_varname(tok)) { sue_tag = tok; tok = tok_next(tok); }
2801+
// C23 enum fixed underlying type: enum E : int { ... }
2802+
if (tok && tok->len == 1 && tok->ch0 == ':') {
2803+
tok = tok_next(tok);
2804+
while (tok && tok->kind != TK_EOF) {
2805+
SKIP_NOISE_CONTINUE(tok);
2806+
if (is_type_keyword(tok) || (tok->tag & TT_QUALIFIER)) tok = tok_next(tok);
2807+
else break;
2808+
}
2809+
}
27982810
if (tok && tok->len == 1 && tok->ch0 == '{') {
27992811
if (struct_body_contains_vla(tok)) r.is_vla = true;
28002812
tok = skip_balanced_group(tok);
@@ -3645,7 +3657,7 @@ static bool is_raw_strip_context(Token *after_raw) {
36453657
!is_known_typedef(after_raw) && !(after_raw->tag & (TT_QUALIFIER | TT_SUE)) &&
36463658
boundary &&
36473659
(match_ch(boundary, ',') || match_ch(boundary, ';') ||
3648-
match_set(boundary, CH('[') | CH('(') | CH('=')));
3660+
match_set(boundary, CH('[') | CH('(') | CH('=') | CH(':')));
36493661
}
36503662

36513663
static bool has_effective_const_qual(Token *type_start, TypeSpecResult *type, DeclResult *decl) {

prism.c

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1701,8 +1701,23 @@ static Token *try_generic_member_rewrite(Token *tok) {
17011701
int val_peel_depth = 0; // number of remaining paren layers to drill through
17021702
bool val_has_ternary = false; // branch value contains a ternary expression
17031703
bool val_ternary_arm_fresh = false; // just entered a ternary arm (? or ternary :)
1704+
Token *inner_generic_close = NULL; // close paren of inner _Generic being processed
17041705
for (Token *t = assoc_start; t && t != close; t = tok_next(t)) {
1706+
// Inner _Generic close paren — emit and exit inner mode.
1707+
if (t == inner_generic_close) {
1708+
emit_tok(t);
1709+
inner_generic_close = NULL;
1710+
continue;
1711+
}
17051712
if ((t->flags & TF_OPEN) && tok_match(t)) {
1713+
// Let inner _Generic's parens flow through individually
1714+
// so each association value gets prefix injection.
1715+
if (in_value && !inner_generic_close && match_ch(t, '(') &&
1716+
last_emitted && (last_emitted->tag & TT_GENERIC)) {
1717+
inner_generic_close = tok_match(t);
1718+
emit_tok(t);
1719+
continue;
1720+
}
17061721
// Don't skip parens that wrap the injection target —
17071722
// drill in so the injection logic below can fire on the
17081723
// inner identifier.
@@ -1757,7 +1772,17 @@ static Token *try_generic_member_rewrite(Token *tok) {
17571772
if (peel_open && match_ch(peel_open, '(') && tok_match(peel_open)) {
17581773
Token *inner = tok_next(peel_open);
17591774
Token *pclose = tok_match(peel_open);
1760-
if (inner && is_valid_varname(inner) && tok_next(inner) == pclose) {
1775+
if (inner && is_valid_varname(inner)) {
1776+
// Check if the inner content is a valid target:
1777+
// single ident, or ident followed by [, ., ->
1778+
bool inner_ok = (tok_next(inner) == pclose);
1779+
if (!inner_ok) {
1780+
Token *after_inner = tok_next(inner);
1781+
if (after_inner && (match_ch(after_inner, '[') ||
1782+
(after_inner->tag & TT_MEMBER)))
1783+
inner_ok = true;
1784+
}
1785+
if (inner_ok) {
17611786
// Verify all outer paren layers close tightly
17621787
Token *outer_close = pclose;
17631788
bool layers_ok = true;
@@ -1777,6 +1802,7 @@ static Token *try_generic_member_rewrite(Token *tok) {
17771802
val_peel_depth = peel_depth;
17781803
}
17791804
}
1805+
}
17801806
}
17811807
}
17821808
if (!val_bare_ident && !val_has_call) {
@@ -1791,7 +1817,10 @@ static Token *try_generic_member_rewrite(Token *tok) {
17911817
if (!val_has_call && vs && is_valid_varname(vs)) {
17921818
Token *after_vs = tok_next(vs);
17931819
if (!after_vs || after_vs == close ||
1794-
match_ch(after_vs, ','))
1820+
match_ch(after_vs, ',') ||
1821+
match_ch(after_vs, ')') ||
1822+
match_ch(after_vs, '[') ||
1823+
(after_vs->tag & TT_MEMBER))
17951824
val_bare_ident = true;
17961825
}
17971826
// Check for ternary expression in branch value.
@@ -1807,7 +1836,8 @@ static Token *try_generic_member_rewrite(Token *tok) {
18071836
continue;
18081837
}
18091838
if (match_ch(t, ',') && ternary == 0) {
1810-
in_value = false;
1839+
if (!inner_generic_close)
1840+
in_value = false;
18111841
emit_tok(t);
18121842
continue;
18131843
}
@@ -5593,6 +5623,26 @@ static void p1_build_scope_tree(Token *start) {
55935623
}
55945624
break;
55955625
}
5626+
} else if (is_type_keyword(prev)) {
5627+
// C23 enum with fixed underlying type:
5628+
// enum E : int { or enum : unsigned long long {
5629+
for (uint32_t si2 = tok_idx(prev) - 1; si2 > 0; si2--) {
5630+
Token *st = &token_pool[si2];
5631+
if (st->kind == TK_PREP_DIR) continue;
5632+
if (is_type_keyword(st) || (st->tag & TT_QUALIFIER)) continue;
5633+
if (st->len == 1 && st->ch0 == ':') continue;
5634+
if (match_ch(st, ']') && tok_match(st) && (tok_match(st)->flags & TF_C23_ATTR)) {
5635+
si2 = tok_idx(tok_match(st)); continue;
5636+
}
5637+
if (match_ch(st, ')') && tok_match(st)) { si2 = tok_idx(tok_match(st)); continue; }
5638+
if (st->tag & TT_ATTR) continue;
5639+
if (is_valid_varname(st)) continue; // enum tag name
5640+
if (is_enum_kw(st)) {
5641+
si->is_struct = true;
5642+
si->is_enum = true;
5643+
}
5644+
break;
5645+
}
55965646
} else if (depth == 0 && (match_ch(prev, ']') || match_ch(prev, ';'))) {
55975647
// Array-returning function: int (*fn(void))[5] {
55985648
// K&R function definition: fn(a) int a; {

0 commit comments

Comments
 (0)