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.
- package/LICENSE +21 -0
- package/README.md +443 -0
- package/bin/grammatik.js +115 -0
- package/dist/cjs/index.js +3010 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/runtime.js +639 -0
- package/dist/esm/index.js +2959 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/runtime.js +605 -0
- package/dist/types/index.d.ts +214 -0
- package/dist/types/runtime.d.ts +40 -0
- package/package.json +65 -0
- package/src/analyze.d.ts +78 -0
- package/src/analyze.js +602 -0
- package/src/codegen.d.ts +36 -0
- package/src/codegen.js +635 -0
- package/src/definition.d.ts +133 -0
- package/src/definition.js +291 -0
- package/src/emit.d.ts +23 -0
- package/src/emit.js +374 -0
- package/src/firstchars.d.ts +6 -0
- package/src/firstchars.js +353 -0
- package/src/index.d.ts +7 -0
- package/src/index.js +15 -0
- package/src/lexer.d.ts +96 -0
- package/src/lexer.js +358 -0
- package/src/methods.d.ts +20 -0
- package/src/methods.js +61 -0
- package/src/methodsblock.js +197 -0
- package/src/parser.d.ts +130 -0
- package/src/parser.js +194 -0
- package/src/plan.d.ts +29 -0
- package/src/plan.js +938 -0
- package/src/runtime.js +16 -0
- package/src/state.d.ts +98 -0
- package/src/state.js +121 -0
- package/src/values.js +67 -0
|
@@ -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
|
+
}
|
package/src/analyze.d.ts
ADDED
|
@@ -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;
|