A C11 parser written in TypeScript with GCC extensions support and a built-in preprocessor. Zero runtime dependencies.
Paste real-world C — macros, conditionals, X-macro tables and all — and get a
correct AST without running gcc -E first: the 83k-line quickjs-ng
amalgamation parses with zero errors out of the box.
Try the online playground.
Built for use with AST Explorer.
npm install c11-parserimport { parse } from 'c11-parser';
const ast = parse(`
int main(void) {
return 0;
}
`);
console.log(JSON.stringify(ast, (_key, value) => typeof value === 'bigint' ? value.toString() : value, 2));parse(source, {
gnuExtensions: true, // Enable GCC extensions (default: true)
loc: false, // Compute line/column locations on demand (default: false)
preprocess: true, // Run the built-in preprocessor (default: true)
profile: 'gcc-linux-x64', // Predefined-macro profile, or 'none' (default: 'gcc-linux-x64')
macros: { DEBUG: 1 }, // Extra macros, like -D on a compiler command line
maxPreprocessedTokens: 1_000_000, // Absolute preprocessing output budget
});parse() runs a built-in C preprocessor by default: it deletes
backslash-newline splices (translation phase 2), executes directives in
stream order, evaluates #if/#elif conditions (64-bit arithmetic,
defined(), short-circuiting), expands object-like and function-like
macros — including # stringification, ## token pasting, __VA_ARGS__,
and GNU extensions like , ## __VA_ARGS__ comma swallowing and named
variadics — and handles the _Pragma operator.
- Directives become AST nodes. Every directive at an active level is
recorded in
ast.directives(DefineDirective,IfDirectivewith itsactiveflag and skipped range,IncludeDirective, …), so tools can see the preprocessor structure alongside the parsed code._Pragma("…")in the token stream is recorded as aPragmaDirectivenode. - Diagnostics are collected, not thrown.
ast.errorsholds{ message, start, end, phase, severity }records from all three phases (lexer/preprocessor/parser); the parser recovers and keeps going. - Dynamic builtins are deterministic.
__LINE__and__FILE__expand and follow#lineoverrides (__FILE__defaults to<source>);__COUNTER__increments (GNU mode). For reproducible output,__DATE__is always"Jan 1 1970"and__TIME__is"00:00:00". #includeis recorded but not resolved. Macro-produced operands are expanded before recognizing their"..."or<...>target. The defaultgcc-linux-x64profile predefines compiler/target macros (__GNUC__,__x86_64__, …) up front, and holds back what system headers would provide —<limits.h>/<stdint.h>constants,<inttypes.h>format macros,<stdbool.h>, and<stdarg.h>shims that forward to the__builtin_va_*forms the parser understands — until the matching header is actually#included (soint bool;still parses in a file that never includes<stdbool.h>). Passprofile: 'none'to start from an empty macro table.macrosworks like-D:{ NDEBUG: true, VERSION: '"1.0"', 'MAX(a, b)': '((a) > (b) ? (a) : (b))' };falseforce-undefines a profile macro, including the header-gated ones.maxPreprocessedTokensis an absolute resource budget for emitted non-EOF tokens (default 1,000,000). It can be raised for intentionally expansion-heavy generated sources.preprocess: falserestores the raw token-stream behavior: directives and macro names flow to the parser exactly as written (backslash-newline splices are still deleted during lexing, as in translation phase 2).
import adapter from 'c11-parser/adapter';| Export | Description |
|---|---|
parse |
Parse C source into an AST |
AST |
All AST node type definitions |
Scanner |
Lexer class for tokenization |
Parser |
Parser class with token helpers |
TokenKind |
Token kind enumeration (180+ tokens) |
- Full C11 syntax support
- Built-in preprocessor: conditionals, object/function-like macros,
#/##,__VA_ARGS__, GNU extensions — parses the unpreprocessed quickjs-ng amalgamation (83k lines) with zero errors - Preprocessor directives preserved as AST nodes (
ast.directives) - Error recovery with collected diagnostics (
ast.errors) - GCC
__attribute__extensions - Inline assembly (
asm/__asm__) - Byte-range tracking on every AST node (
start,end) - Optional line/column location tracking (
loc) when enabled - Dual package: ESM + CommonJS
pnpm build # Build with tsup
pnpm test # Run tests
pnpm lint # Lint with oxlint
pnpm fmt # Format with oxfmtMIT
TokenKind is a TypeScript const enum exported as a type; there is no runtime TokenKind object for JavaScript imports or enumeration. Token kind values are numeric. AST integer values can include bigint; use the replacer above when exporting JSON. Decimal strings in that JSON preserve large integers but must be converted back explicitly if numeric operations are needed.