grammatik 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Type declarations for grammatik.
3
+ *
4
+ * Hand-written rather than inferred: JSON Logic is `any` by construction, so
5
+ * generated declarations would say `any` everywhere the interesting shapes are.
6
+ */
7
+
8
+ /** A JSON Logic node. Deliberately loose — grammars are open-ended data. */
9
+ export type Logic = any
10
+
11
+ /** A parser expression: one of the combinators, as JSON Logic. */
12
+ export type Parser = Logic
13
+
14
+ export interface TokenDef {
15
+ /** The token type produced. */
16
+ name: string
17
+ /** Regex source, matched sticky at the cursor. Mutually exclusive with `literal`. */
18
+ pattern?: string
19
+ /** A literal string. Cheaper than a pattern; prefer it. */
20
+ literal?: string
21
+ /** Consume but produce no token, e.g. whitespace and comments. */
22
+ skip?: boolean
23
+ ignoreCase?: boolean
24
+ /** Defer to this token when it matches more text, e.g. `if` yielding to an identifier. */
25
+ longerAlt?: string
26
+ /** The lexer mode this token belongs to. Defaults to `'default'`. */
27
+ mode?: string
28
+ /** Several modes, for a token that serves more than one. */
29
+ modes?: string[]
30
+ /** Enter this mode after the token matches. */
31
+ pushMode?: string
32
+ /** Leave the current mode after the token matches. */
33
+ popMode?: boolean
34
+ }
35
+
36
+ export interface Token {
37
+ type: string
38
+ image: string
39
+ start: number
40
+ end: number
41
+ /** 1-based; 0 under `positions: 'offset'`. */
42
+ line: number
43
+ /** 1-based; 0 under `positions: 'offset'`. */
44
+ col: number
45
+ }
46
+
47
+ export interface GrammarSpec {
48
+ tokens: TokenDef[]
49
+ rules: Record<string, Parser>
50
+ start: string
51
+ /** Recorded in a generated file's header; also the default `moduleName`. */
52
+ name?: string
53
+ /** A .gram file's `methods { ... }` block, as source. */
54
+ methodsBlock?: string
55
+ }
56
+
57
+ export type ExecutionMode = 'generated' | 'interpreted'
58
+ export type PositionMode = 'full' | 'offset'
59
+
60
+ export interface ParserOptions {
61
+ /** Extra JSON Logic methods available to semantic actions. */
62
+ methods?: Record<string, any>
63
+ /**
64
+ * `generated` builds one JavaScript function per rule and compiles semantic
65
+ * actions; `interpreted` uses closures and walks the actions, which needs no
66
+ * `eval` and so runs under a strict Content-Security-Policy.
67
+ */
68
+ execution?: ExecutionMode
69
+ /** `offset` skips scanning the input for newlines. */
70
+ positions?: PositionMode
71
+ /** Packrat memoization on (rule, position). */
72
+ memo?: boolean
73
+ /** Require the whole token stream to be consumed. */
74
+ strict?: boolean
75
+ /** FIRST-set dispatch for `alt`. Off forces ordered backtracking. */
76
+ ll1?: boolean
77
+ /** Throw `GrammarError` on left recursion and unknown rule references. */
78
+ validate?: boolean
79
+ /** Runaway guard; 0 disables. */
80
+ maxSteps?: number
81
+ /** @deprecated Legacy alias: `false` selects `execution: 'interpreted'`. */
82
+ compile?: boolean
83
+ /** @deprecated Legacy alias: `false` selects `execution: 'interpreted'`. */
84
+ unsafeEval?: boolean
85
+ }
86
+
87
+ export interface Analysis {
88
+ /** Rule name -> the token types that can begin it. */
89
+ firsts: Record<string, Set<string>>
90
+ /** Rule names that can match the empty string. */
91
+ nullable: Set<string>
92
+ /** Left recursion, unknown references and malformed nodes. */
93
+ errors: string[]
94
+ }
95
+
96
+ export interface Lexer {
97
+ tokenize (text: string): Token[]
98
+ tokenNames: string[]
99
+ positions: PositionMode
100
+ /** Every mode the token definitions mention, `'default'` first. */
101
+ modeNames: string[]
102
+ }
103
+
104
+ export interface JLParser {
105
+ parse (text: string): any
106
+ parseTokens (tokens: Token[], source?: string): any
107
+ tokenize (text: string): Token[]
108
+ lexer: Lexer
109
+ engine: any
110
+ analysis: Analysis
111
+ execution: ExecutionMode
112
+ compiled: boolean
113
+ readonly firsts: Record<string, Set<string>>
114
+ }
115
+
116
+ export declare function createParser (spec: GrammarSpec, options?: ParserOptions): JLParser
117
+
118
+ export declare function createLexer (
119
+ tokens: TokenDef[],
120
+ options?: { positions?: PositionMode }
121
+ ): Lexer
122
+
123
+ export declare function analyze (
124
+ rules: Record<string, Parser>,
125
+ options?: { tokenNames?: string[] }
126
+ ): Analysis
127
+
128
+ export interface EmitOptions extends ParserOptions {
129
+ /** Module format of the generated file. */
130
+ format?: 'esm' | 'cjs'
131
+ /** Name recorded in the file header. Defaults to `spec.name`. */
132
+ moduleName?: string
133
+ /** What the file imports the runtime from. Defaults to `grammatik/runtime`. */
134
+ runtimeSpecifier?: string
135
+ /** What the file imports the engine from, when it still needs one. */
136
+ engineSpecifier?: string
137
+ /** Overrides the block carried on the grammar. */
138
+ methodsBlock?: string
139
+ }
140
+
141
+ /**
142
+ * Writes a grammar out as a JavaScript module: one function per rule, as source.
143
+ * Supplying `methods` lets semantic actions be compiled to source too, which can
144
+ * remove the `json-logic-engine` dependency from the output entirely.
145
+ */
146
+ export declare function emitModule (spec: GrammarSpec, options?: EmitOptions): string
147
+
148
+ /** Parses the grammar-definition language into a plain grammar object. */
149
+ export declare function parseDefinition (source: string, options?: ParserOptions): GrammarSpec
150
+ export declare function createParserFromSource (source: string, options?: ParserOptions): JLParser
151
+ export declare function createDefinitionParser (options?: ParserOptions): JLParser
152
+
153
+ export declare const definitionGrammar: GrammarSpec
154
+ export declare const definitionTokens: TokenDef[]
155
+ export declare const definitionRules: Record<string, Parser>
156
+ export declare const CONSTRUCTOR_NAMES: string[]
157
+
158
+ /** Separates a trailing `methods { ... }` block from the grammar text. */
159
+ export declare function splitMethodsBlock (source: string): { grammar: string, block: string | null }
160
+
161
+ export declare function analyzeMethodsBlock (body: string): {
162
+ names: string[]
163
+ hasDefault: boolean
164
+ imports: string[]
165
+ }
166
+
167
+ /** Evaluates a block to its methods table. Needs `new Function`; no imports. */
168
+ export declare function evaluateMethodsBlock (body: string): {
169
+ methods: Record<string, any>
170
+ exports: Record<string, any>
171
+ }
172
+
173
+ /** Every combinator name, e.g. `seq`, `alt`, `infixLeft`. */
174
+ export declare const OPERATORS: string[]
175
+ export declare const PARSER_OPS: Set<string>
176
+ export declare function createMethods (): Record<string, any>
177
+
178
+ /**
179
+ * JSON Logic methods available inside semantic actions, beyond the engine's own.
180
+ * Currently `obj`, which builds an object from flat key/value pairs so a node's
181
+ * key can come from the input. Registered automatically by `createParser` and
182
+ * `emitModule`; exported for hosts that build their own engine.
183
+ */
184
+ export declare function createValueMethods (): Record<string, any>
185
+ export declare function buildDispatch (...args: any[]): any
186
+ export declare function firstOf (...args: any[]): any
187
+
188
+ /** The sentinel a parser returns when it did not match. */
189
+ export declare const FAIL: symbol
190
+
191
+ export declare function createState (
192
+ tokens: Token[],
193
+ grammar: Record<string, Parser>,
194
+ options?: { memo?: boolean, maxSteps?: number, track?: boolean }
195
+ ): any
196
+
197
+ export declare class ParseError extends Error {
198
+ name: 'ParseError'
199
+ /** The token the parse stopped at, or null at end of input. */
200
+ token: Token | null
201
+ /** Token types that would have been accepted there. */
202
+ expected: string[]
203
+ /** The rule nesting at the point of failure, outermost first. */
204
+ ruleStack: string[]
205
+ }
206
+
207
+ export declare class GrammarError extends Error {
208
+ name: 'GrammarError'
209
+ problems: string[]
210
+ }
211
+
212
+ export declare class LexError extends Error {
213
+ name: 'LexError'
214
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * `grammatik/runtime` — the surface a generated parser imports.
3
+ *
4
+ * Deliberately small: a generated file needs the lexer, the parse state and
5
+ * error construction, and nothing else. The analyzer, planner and code
6
+ * generator were already run when the file was written.
7
+ */
8
+
9
+ import type { Token, TokenDef, PositionMode, Lexer, Parser } from './index.js'
10
+
11
+ export declare const FAIL: symbol
12
+
13
+ export declare function createState (
14
+ tokens: Token[],
15
+ grammar: Record<string, Parser>,
16
+ options?: { memo?: boolean, maxSteps?: number, track?: boolean }
17
+ ): any
18
+
19
+ /** Records that `expectation` would have been accepted at the cursor. */
20
+ export declare function expect (state: any, expectation: string): void
21
+
22
+ export declare function buildParseError (state: any): ParseError
23
+
24
+ export declare function createLexer (
25
+ tokens: TokenDef[],
26
+ options?: { positions?: PositionMode }
27
+ ): Lexer
28
+
29
+ export declare class ParseError extends Error {
30
+ name: 'ParseError'
31
+ token: Token | null
32
+ expected: string[]
33
+ ruleStack: string[]
34
+ }
35
+
36
+ export declare class LexError extends Error {
37
+ name: 'LexError'
38
+ }
39
+
40
+ export type { Token, TokenDef, PositionMode, Lexer }
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "grammatik",
3
+ "version": "0.1.0",
4
+ "description": "A parser toolkit whose grammars are data: tokens, rules and semantic actions all serializable, compiled ahead of time.",
5
+ "type": "module",
6
+ "main": "./dist/cjs/index.js",
7
+ "module": "./dist/esm/index.js",
8
+ "types": "./dist/types/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/types/index.d.ts",
12
+ "import": "./dist/esm/index.js",
13
+ "require": "./dist/cjs/index.js"
14
+ },
15
+ "./runtime": {
16
+ "types": "./dist/types/runtime.d.ts",
17
+ "import": "./dist/esm/runtime.js",
18
+ "require": "./dist/cjs/runtime.js"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "src",
25
+ "bin",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "sideEffects": false,
30
+ "scripts": {
31
+ "build": "node scripts/build.js",
32
+ "prepublishOnly": "npm run build && npm test && npm run test:csp",
33
+ "test": "vitest run",
34
+ "test:watch": "vitest",
35
+ "test:csp": "node --disallow-code-generation-from-strings scripts/check-no-eval.mjs",
36
+ "generate": "node bin/grammatik.js generate",
37
+ "bench": "node bench/bench.js",
38
+ "bench:compare": "node bench/compare-codex.js",
39
+ "bench:formula": "node bench/peggy/build.js && node bench/formula.js",
40
+ "bench:generated": "node bench/peggy/build.js && node bench/generate-formula.js && node bench/generated.js",
41
+ "bench:json-parsers": "node bench/jsonParsers/run.js"
42
+ },
43
+ "keywords": [
44
+ "parser",
45
+ "grammar",
46
+ "lexer",
47
+ "parser-generator",
48
+ "json-logic",
49
+ "chevrotain",
50
+ "peg"
51
+ ],
52
+ "license": "MIT",
53
+ "dependencies": {
54
+ "json-logic-engine": "^5.0.7"
55
+ },
56
+ "devDependencies": {
57
+ "chevrotain": "^13.2.0",
58
+ "esbuild": "^0.28.2",
59
+ "peggy": "^5.1.0",
60
+ "vitest": "^3.2.4"
61
+ },
62
+ "bin": {
63
+ "grammatik": "./bin/grammatik.js"
64
+ }
65
+ }
@@ -0,0 +1,78 @@
1
+ /** Returns the single operator key of a JSON Logic node, or null. */
2
+ export function opOf(node: any): string;
3
+ /**
4
+ * Rewrites a `seq` into a cheaper equivalent combinator when it matches a known
5
+ * idiom, in the spirit of json-logic-engine's own `checkIdioms`.
6
+ *
7
+ * Grammars are usually written with the general primitives, because that is
8
+ * what a grammar author reaches for. Two of those spellings are both very
9
+ * common and needlessly expensive, and both can be recognized mechanically:
10
+ *
11
+ * seq(open, label(n, body), close, action({val: n})) -> between(open, body, close)
12
+ * seq(label(n, parser), action(A)) -> as(parser, A')
13
+ *
14
+ * Together they were 40% of all `seq` invocations in the formula benchmark.
15
+ * Recognition means an existing grammar gets the faster path without being
16
+ * rewritten by hand.
17
+ *
18
+ * Returns the replacement node, or null when nothing matched.
19
+ *
20
+ * @param {any[]} children
21
+ */
22
+ export function recognizeSeqIdiom(children: any[]): {
23
+ between: {
24
+ open: any;
25
+ body: any;
26
+ close: any;
27
+ };
28
+ as?: undefined;
29
+ } | {
30
+ as: any[];
31
+ between?: undefined;
32
+ };
33
+ /**
34
+ * @param {Record<string, any>} grammar
35
+ * @param {{ tokenNames?: string[] }} [options]
36
+ * @returns {{ firsts: Map<string, First>, errors: string[], leftRecursive: string[] }}
37
+ */
38
+ export function analyze(grammar: Record<string, any>, options?: {
39
+ tokenNames?: string[];
40
+ }): {
41
+ firsts: Map<string, First>;
42
+ errors: string[];
43
+ leftRecursive: string[];
44
+ };
45
+ /**
46
+ * Builds an O(1) dispatch table for an `alt`, or null when the branches are not
47
+ * LL(1)-separable and we must fall back to ordered backtracking.
48
+ *
49
+ * @param {any[]} branches
50
+ * @param {Map<string, First>} firsts
51
+ * @returns {Map<string, any>|null}
52
+ */
53
+ export function buildDispatch(branches: any[], firsts: Map<string, First>): Map<string, any> | null;
54
+ /** The set of node shapes this module understands as *parsers*. */
55
+ export const PARSER_OPS: Set<string>;
56
+ export type First = {
57
+ /**
58
+ * Token types that can begin this node.
59
+ */
60
+ tokens: Set<string>;
61
+ /**
62
+ * Whether the node can match while consuming nothing.
63
+ */
64
+ nullable: boolean;
65
+ /**
66
+ * Whether analysis could not fully determine the set.
67
+ * Any `unknown` disables LL(1) dispatch for the enclosing
68
+ * `alt`, which is a correctness guard, not a bug.
69
+ */
70
+ unknown: boolean;
71
+ };
72
+ /**
73
+ * Computes the FIRST set of a node given current per-rule estimates.
74
+ * @param {*} node
75
+ * @param {Map<string, First>} ruleFirsts
76
+ * @returns {First}
77
+ */
78
+ export function firstOf(node: any, ruleFirsts: Map<string, First>): First;