Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
de77dc9
Phase Q1: migrate __extension__ + attribute-between-specs to grammar
claude May 2, 2026
c7b5be8
Phase Q2.1: migrate brace-init declarations to grammar
claude May 2, 2026
24c8510
specOwner: recognise struct_declaration as a self-owner
claude May 2, 2026
e464064
Phase Q2.3: migrate typedef-of-array to grammar
claude May 2, 2026
7c720e5
Strategy change: gate extensions behind `extended` plugin option
claude May 2, 2026
d908256
Plain-C grammar: support pointer type qualifiers (`int * const p`)
claude May 2, 2026
ccd7fef
Plain-C mode strips extension grammar rules from the spec
claude May 2, 2026
55203b1
Vendored expr: fix chained postfix paren forms (a[i][j], f(0)(1), f(0…
claude May 2, 2026
1dea4bd
Migrate function defs/decls + ternary + variadic to grammar
claude May 2, 2026
45ee0f7
Add identifier_list (K&R prototypes) + abstract function-pointer para…
claude May 2, 2026
56c5aaf
Add _BitInt(N) recognition (validator + grammar scaffold)
claude May 2, 2026
e0f76d2
Migrate struct/enum bodies + nested control flow to grammar
claude May 2, 2026
3e59fa3
Migrate _BitInt(N) to grammar
claude May 2, 2026
4f944e6
Migrate complex compound declarators to grammar
claude May 2, 2026
f8405c9
Strip more stale per-rule k state at @simple_declaration-bo
claude May 2, 2026
fb4f657
fetchDeep: document the push-only / no-fill-in-place choice
claude May 2, 2026
b2ca00e
Use multi-token \`s:\` patterns instead of ctx.t peeks in conds
claude May 2, 2026
d44c1e4
Plain-mode direct dispatches in external_declaration
claude May 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
317 changes: 267 additions & 50 deletions c-grammar.jsonic

Large diffs are not rendered by default.

1,120 changes: 942 additions & 178 deletions src/c.ts

Large diffs are not rendered by default.

13 changes: 10 additions & 3 deletions test/c.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import { join } from 'node:path'
import { Jsonic } from 'jsonic'
import { C } from '../dist/c.js'

const j = Jsonic.make().use(C, {})
// Most tests in this file exercise extension constructs (preprocessor,
// GCC __attribute__, MSVC __declspec, inline asm, etc.), so the shared
// instance enables them via { extended: true }. New plain-C-only tests
// should construct a separate instance with the default (no opt-in).
const j = Jsonic.make().use(C, { extended: true })

// Walk a CST node depth-first and yield its token-refs in source order.
function walkTokens(node: any): any[] {
Expand Down Expand Up @@ -1342,7 +1346,7 @@ describe('c parser smoke', () => {
// typed (not as identifier names).
// The parser does this via a typedef directive in the actual .h
// file; for this synthetic test we inject it inline.
const j2 = (Jsonic.make().use(C, {}) as any)
const j2 = (Jsonic.make().use(C, { extended: true }) as any)
const out = j2(`typedef struct { int x; int y; int z; } vec_t;\n${src}`)
// After the synthetic typedef, our source begins with #include then
// three function definitions.
Expand Down Expand Up @@ -1436,7 +1440,10 @@ describe('path-dispatch spec', () => {
? `${row.path}: ${row.src} (${row.notes})`
: `${row.path}: ${row.src}`
test(tag, () => {
const parser = Jsonic.make().use(C)
// path-dispatch rows include extension shapes (preprocessor,
// GCC __attribute__, asm). Construct the parser with extensions
// enabled so those rows assert the correct dispatch path.
const parser = Jsonic.make().use(C, { extended: true })
const out = parser(row.src)
const decls = (out.children || []).filter(
(c: any) => c.kind === 'external_declaration',
Expand Down
4 changes: 3 additions & 1 deletion test/csmith-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ export function fixturePath(seed: number): string {
}

// Parse a csmith-shaped source with stdint typedef-names pre-registered.
// csmith generates GCC-flavoured C with #include / __attribute__ etc.,
// so the parser is constructed with `extended: true`.
export function parseCsmithSource(src: string): any {
const j = Jsonic.make().use(C)
const j = Jsonic.make().use(C, { extended: true })
const cmeta = makeCMeta()
for (const n of STDINT_TYPEDEFS) cmeta.symbols.bindTypedef(n)
return j(src, { cmeta })
Expand Down
Binary file modified test/csmith-fixtures/seed-001.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-002.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-003.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-004.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-005.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-006.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-007.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-008.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-009.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-010.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-011.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-012.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-015.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-016.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-018.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-019.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-020.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-022.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-023.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-024.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-025.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-026.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-027.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-028.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-029.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-030.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-031.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-032.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-033.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-035.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-036.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-037.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-038.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-039.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-040.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-041.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-042.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-043.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-044.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-045.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-046.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-047.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-048.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-049.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-050.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-051.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-053.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-055.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-056.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-057.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-058.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-059.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-060.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-061.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-062.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-063.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-064.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-065.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-066.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-067.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-068.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-069.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-070.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-071.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-073.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-074.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-075.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-077.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-078.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-079.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-080.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-081.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-082.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-083.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-084.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-085.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-086.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-087.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-088.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-089.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-090.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-091.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-092.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-093.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-094.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-095.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-097.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-098.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-099.json.gz
Binary file not shown.
Binary file modified test/csmith-fixtures/seed-100.json.gz
Binary file not shown.
30 changes: 15 additions & 15 deletions test/spec/path-dispatch.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -58,26 +58,26 @@ static_assert(sizeof(int) == 4, "msg"); grammar declaration Phase O sizeof in co
# Bodies that block_item can structure flow through grammar; bodies
# with control-flow keywords or asm/static_assert fall back to chomp.
int f() { return 0; } grammar function_definition empty body via grammar
int f(int x) { return x; } legacy function_definition chomp (param-with-id case)
static int f(void) { return 0; } legacy function_definition chomp (void params)
int f(int x) { return x; } grammar function_definition param-with-id via grammar
static int f(void) { return 0; } grammar function_definition void params via grammar

# ---- legacy chomp path: tagged-type definitions with bodies ----
struct S { int a; }; legacy declaration standalone struct definition
union U { int a; }; legacy declaration standalone union definition
enum E { A, B }; legacy declaration standalone enum definition
struct { int a; } s; legacy declaration anonymous struct variable
# ---- grammar path: tagged-type definitions with bodies ----
struct S { int a; }; grammar declaration standalone struct definition
union U { int a; }; grammar declaration standalone union definition
enum E { A, B }; grammar declaration standalone enum definition
struct { int a; } s; grammar declaration anonymous struct variable

# ---- legacy chomp path: typedef shapes with postfix ----
typedef int Arr[10]; legacy declaration typedef of array
typedef int Arr[10]; grammar declaration typedef of array

# ---- legacy chomp path: complex declarators beyond Phase P ----
int (*p)[10]; legacy declaration pointer to array
int (*arr[3])(int); legacy declaration array of fn-pointers
# ---- grammar path: complex compound declarators ----
int (*p)[10]; grammar declaration pointer to array
int (*arr[3])(int); grammar declaration array of fn-pointers

# ---- legacy chomp path: brace initializers ----
int a[3] = { 1, 2, 3 }; legacy declaration brace init
int a[3] = { [0] = 1 }; legacy declaration designated init
int a[3] = { 1, 2, 3 }; grammar declaration brace init
int a[3] = { [0] = 1 }; grammar declaration designated init

# ---- legacy chomp path: GCC extensions ----
__extension__ int x; legacy declaration __extension__ keyword
int __attribute__((unused)) x; legacy declaration attribute between specs
__extension__ int x; grammar declaration __extension__ keyword
int __attribute__((unused)) x; grammar declaration attribute between specs
91 changes: 70 additions & 21 deletions vendor/jsonic-expr/src/expr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,19 +390,33 @@ let Expr: Plugin = function Expr(jsonic: Jsonic, options: ExprOptions) {
}
: NONE,

// WWW
// // The opening parenthesis of an expression with a preceding value.
// // foo(1) => ['(','foo',1]
// hasParen
// ? {
// s: [OP],
// b: 1,
// r: 'val',
// c: (r: Rule) => parenOTM[r.c0.tin].preval.active,
// u: { paren_preval: true },
// g: 'expr,expr-paren,expr-paren-preval',
// }
// : NONE,
// VENDORED-PATCH: chain for postfix paren forms.
// When a val has just produced a value (e.g. `a[0]`, `f(0)`,
// or a parenthesised expression `(*p)`) and the next token is
// another preval-active paren-open, push expr (which descends
// into paren) so the new paren-form picks up this val's node
// as the preval. We use `p: 'expr'` (NOT `r: 'val'`) so the
// current val rule stays alive and `ctx.root().node` still
// reflects the chained result on parser return.
// `u: { paren_preval: true }` is set so makeCloseParen finds
// it on r.parent.parent (= this val) and pushes our node into
// the new paren CST node.
hasParen
? {
s: [OP],
b: 1,
c: (r: Rule, _ctx: Context) => {
const pdef = parenOTM[r.c0.tin]
if (!pdef.preval.active) return false
if (undefined === r.node) return false
return null == pdef.preval.allow ||
pdef.preval.allow.includes(r.node)
},
p: 'expr',
u: { paren_preval: true },
g: 'expr,expr-paren,expr-paren-preval-chain',
}
: NONE,

hasTernary
? {
Expand Down Expand Up @@ -433,6 +447,7 @@ let Expr: Plugin = function Expr(jsonic: Jsonic, options: ExprOptions) {
g: 'expr,list,val,imp,space,top',
},
])

})


Expand Down Expand Up @@ -750,11 +765,6 @@ let Expr: Plugin = function Expr(jsonic: Jsonic, options: ExprOptions) {
])

.ac((r: Rule, ctx: Context) => {
// Only evaluate at root of expr (where r.n.expr === 0)

// console.log('EXPR-AC', r.name, r.i, r.n, p(r.node),
// 'P', r.parent.name, r.parent.i, r.parent.n, p(r.parent.node))

if (options.evaluate && 0 === r.n.expr) {
// The parent node will contain the root of the expr tree

Expand Down Expand Up @@ -832,9 +842,6 @@ let Expr: Plugin = function Expr(jsonic: Jsonic, options: ExprOptions) {
])

.ac((r: Rule, ctx: Context) => {

// QQQ
// console.log('PAREN-AC', r.i, p(r.node), 'C', r.parent.i, p(r.parent.node))
r.parent.node = r.node
r.parent.parent.node = r.node

Expand Down Expand Up @@ -947,6 +954,48 @@ let Expr: Plugin = function Expr(jsonic: Jsonic, options: ExprOptions) {
g: 'expr,expr-ternary,close',
},
])
// VENDORED-PATCH: ensure ternary results get evaluated.
// Without this, ternaries that aren't wrapped in expr
// (e.g. val.close's TERN0 alt does `r: 'ternary'`
// directly, not via an expr intermediate) leave the
// result as a raw `[op_ternary, ...]` op-array. We fire
// on every ternary instance's after-close, but only
// act when the chain has reached its final step (the
// node has accumulated all 3 operands and r.next is
// not another ternary). Walk the prev-chain back to the
// original rule (the val that initiated `r: 'ternary'`)
// and write the evaluated CST so jsonic returns the
// structured conditional_expression instead of the
// op-array.
.ac((r: Rule, ctx: Context) => {
if (!options.evaluate) return
// Skip when the chain is still ongoing: r:'ternary'
// replaces the current rule with another ternary
// instance, and we want to evaluate only on the FINAL
// step. r.next is the rule the parser will process
// after this .ac returns.
if (r.next && r.next.name === 'ternary') return
if (!Array.isArray(r.node)) return
if (!isOp(r.node)) return
// Op-array isn't fully populated until 3 operands
// (cond, then, else) sit at r.node[1..3]. Early steps
// have length 2 or 3.
if (r.node.length < 4) return
const out = evaluation(r, ctx, r.node, options.evaluate)
// Write the evaluated CST back to every rule along the
// r:-replacement chain (each successive ternary instance
// and the original val that started the chain). Their
// .node references all currently point at the same
// op-array; replace them all with the structured CST so
// ctx.root().node — whichever one jsonic returns —
// reflects the evaluated form.
let cur: any = r
while (cur) {
cur.node = out
cur = cur.prev
}
if (r.parent) r.parent.node = out
})
})
}
}
Expand Down
Loading