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,133 @@
|
|
|
1
|
+
export function createMetaMethods(): {};
|
|
2
|
+
export const definitionTokens: ({
|
|
3
|
+
name: any;
|
|
4
|
+
literal: any;
|
|
5
|
+
longerAlt: string;
|
|
6
|
+
} | {
|
|
7
|
+
name: string;
|
|
8
|
+
pattern: string;
|
|
9
|
+
skip: boolean;
|
|
10
|
+
literal?: undefined;
|
|
11
|
+
} | {
|
|
12
|
+
name: string;
|
|
13
|
+
pattern: string;
|
|
14
|
+
skip?: undefined;
|
|
15
|
+
literal?: undefined;
|
|
16
|
+
} | {
|
|
17
|
+
name: string;
|
|
18
|
+
literal: string;
|
|
19
|
+
pattern?: undefined;
|
|
20
|
+
skip?: undefined;
|
|
21
|
+
})[];
|
|
22
|
+
export namespace definitionRules {
|
|
23
|
+
export namespace Document {
|
|
24
|
+
export { items as seq };
|
|
25
|
+
}
|
|
26
|
+
export namespace Declaration {
|
|
27
|
+
export { items as alt };
|
|
28
|
+
}
|
|
29
|
+
export namespace TokenDeclaration { }
|
|
30
|
+
export namespace TokenKind { }
|
|
31
|
+
export namespace TokenFlag { }
|
|
32
|
+
export namespace RuleDeclaration { }
|
|
33
|
+
export namespace ParserExpression {
|
|
34
|
+
export { name as subrule };
|
|
35
|
+
}
|
|
36
|
+
export namespace Call { }
|
|
37
|
+
export namespace CallArguments { }
|
|
38
|
+
export namespace CallArgument { }
|
|
39
|
+
export namespace JsonValue { }
|
|
40
|
+
export namespace JsonObject { }
|
|
41
|
+
export namespace JsonPair { }
|
|
42
|
+
export namespace JsonArray { }
|
|
43
|
+
export { identifier as IdentifierValue };
|
|
44
|
+
export { stringValue as StringValue };
|
|
45
|
+
export namespace NumberValue { }
|
|
46
|
+
}
|
|
47
|
+
export namespace definitionGrammar {
|
|
48
|
+
export { definitionTokens as tokens };
|
|
49
|
+
export { definitionRules as rules };
|
|
50
|
+
export let start: string;
|
|
51
|
+
}
|
|
52
|
+
/** Constructor names the language understands. */
|
|
53
|
+
export const CONSTRUCTOR_NAMES: string[];
|
|
54
|
+
export function createDefinitionParser(options?: {}): {
|
|
55
|
+
parse: (text: string) => any;
|
|
56
|
+
parseTokens: (tokens: import("./lexer.js").Token[], source?: string, track?: boolean) => any;
|
|
57
|
+
tokenize: (text: string) => Token[];
|
|
58
|
+
lexer: {
|
|
59
|
+
tokenize: (text: string) => Token[];
|
|
60
|
+
tokenNames: string[];
|
|
61
|
+
positions: string;
|
|
62
|
+
};
|
|
63
|
+
engine: import("json-logic-engine").LogicEngine;
|
|
64
|
+
analysis: {
|
|
65
|
+
firsts: Map<string, First>;
|
|
66
|
+
errors: string[];
|
|
67
|
+
leftRecursive: string[];
|
|
68
|
+
};
|
|
69
|
+
planner: {
|
|
70
|
+
plan: (node: any) => (state: any) => any;
|
|
71
|
+
planOp: (op: string, args: any) => (state: any) => any;
|
|
72
|
+
planAction: (logic: any) => (bindings: any, state: any) => any;
|
|
73
|
+
planRule: (name: any) => {
|
|
74
|
+
fn: ((state: any) => any) | null;
|
|
75
|
+
};
|
|
76
|
+
declareSync: typeof import("./plan.js").declareSync;
|
|
77
|
+
};
|
|
78
|
+
codegen: {
|
|
79
|
+
compile: (start: string) => (state: any) => any;
|
|
80
|
+
functionFor: (name: any) => any;
|
|
81
|
+
sourceFor: (name: any) => string;
|
|
82
|
+
ruleIndex: Map<any, any>;
|
|
83
|
+
RULES: any[];
|
|
84
|
+
SOURCES: any[];
|
|
85
|
+
ORIGINS: any[];
|
|
86
|
+
guard: boolean;
|
|
87
|
+
};
|
|
88
|
+
execution: "generated" | "interpreted";
|
|
89
|
+
compiled: boolean;
|
|
90
|
+
readonly firsts: Map<string, import("./analyze.js").First>;
|
|
91
|
+
};
|
|
92
|
+
export function parseDefinition(source: string, options?: object): any;
|
|
93
|
+
export function createParserFromSource(source: string, options?: object): {
|
|
94
|
+
parse: (text: string) => any;
|
|
95
|
+
parseTokens: (tokens: import("./lexer.js").Token[], source?: string, track?: boolean) => any;
|
|
96
|
+
tokenize: (text: string) => Token[];
|
|
97
|
+
lexer: {
|
|
98
|
+
tokenize: (text: string) => Token[];
|
|
99
|
+
tokenNames: string[];
|
|
100
|
+
positions: string;
|
|
101
|
+
};
|
|
102
|
+
engine: import("json-logic-engine").LogicEngine;
|
|
103
|
+
analysis: {
|
|
104
|
+
firsts: Map<string, First>;
|
|
105
|
+
errors: string[];
|
|
106
|
+
leftRecursive: string[];
|
|
107
|
+
};
|
|
108
|
+
planner: {
|
|
109
|
+
plan: (node: any) => (state: any) => any;
|
|
110
|
+
planOp: (op: string, args: any) => (state: any) => any;
|
|
111
|
+
planAction: (logic: any) => (bindings: any, state: any) => any;
|
|
112
|
+
planRule: (name: any) => {
|
|
113
|
+
fn: ((state: any) => any) | null;
|
|
114
|
+
};
|
|
115
|
+
declareSync: typeof import("./plan.js").declareSync;
|
|
116
|
+
};
|
|
117
|
+
codegen: {
|
|
118
|
+
compile: (start: string) => (state: any) => any;
|
|
119
|
+
functionFor: (name: any) => any;
|
|
120
|
+
sourceFor: (name: any) => string;
|
|
121
|
+
ruleIndex: Map<any, any>;
|
|
122
|
+
RULES: any[];
|
|
123
|
+
SOURCES: any[];
|
|
124
|
+
ORIGINS: any[];
|
|
125
|
+
guard: boolean;
|
|
126
|
+
};
|
|
127
|
+
execution: "generated" | "interpreted";
|
|
128
|
+
compiled: boolean;
|
|
129
|
+
readonly firsts: Map<string, import("./analyze.js").First>;
|
|
130
|
+
};
|
|
131
|
+
declare namespace identifier { }
|
|
132
|
+
declare namespace stringValue { }
|
|
133
|
+
export {};
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { createParser } from './parser.js'
|
|
2
|
+
import { splitMethodsBlock, evaluateMethodsBlock } from './methodsblock.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The grammar-definition language: a small surface syntax for the same grammars
|
|
6
|
+
* `createParser` takes as JSON. `examples/*.gram` are written in it.
|
|
7
|
+
*
|
|
8
|
+
* It is defined by a grammar in this library's own format and parsed by this
|
|
9
|
+
* library — the bootstrap is the point. `test/meta-formula.test.js` asserts that
|
|
10
|
+
* parsing `formula.gram` produces a structure deep-equal to the hand-authored
|
|
11
|
+
* grammar object, so the two spellings cannot drift.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const consume = (name) => ({ consume: name })
|
|
15
|
+
const subrule = (name) => ({ subrule: name })
|
|
16
|
+
const label = (name, parser) => ({ label: [name, parser] })
|
|
17
|
+
const seq = (...items) => ({ seq: items })
|
|
18
|
+
const alt = (...items) => ({ alt: items })
|
|
19
|
+
const many = (item) => ({ many: item })
|
|
20
|
+
const action = (logic) => ({ action: logic })
|
|
21
|
+
const call = (name, value) => ({ [`meta.${name}`]: value })
|
|
22
|
+
const keyword = (name, literal) => ({ name, literal, longerAlt: 'Identifier' })
|
|
23
|
+
|
|
24
|
+
export const definitionTokens = [
|
|
25
|
+
{ name: 'Whitespace', pattern: '[ \\t\\r\\n]+', skip: true },
|
|
26
|
+
{ name: 'LineComment', pattern: '//[^\\r\\n]*', skip: true },
|
|
27
|
+
keyword('GrammarKeyword', 'grammar'),
|
|
28
|
+
keyword('StartKeyword', 'start'),
|
|
29
|
+
keyword('TokenKeyword', 'token'),
|
|
30
|
+
keyword('RuleKeyword', 'rule'),
|
|
31
|
+
keyword('PatternKeyword', 'pattern'),
|
|
32
|
+
keyword('LiteralKeyword', 'literal'),
|
|
33
|
+
keyword('SkipKeyword', 'skip'),
|
|
34
|
+
keyword('IgnoreCaseKeyword', 'ignoreCase'),
|
|
35
|
+
keyword('LongerAltKeyword', 'longerAlt'),
|
|
36
|
+
keyword('ModeKeyword', 'mode'),
|
|
37
|
+
keyword('PushModeKeyword', 'pushMode'),
|
|
38
|
+
keyword('PopModeKeyword', 'popMode'),
|
|
39
|
+
keyword('TrueKeyword', 'true'),
|
|
40
|
+
keyword('FalseKeyword', 'false'),
|
|
41
|
+
keyword('NullKeyword', 'null'),
|
|
42
|
+
{ name: 'String', pattern: '"(?:\\\\.|[^"\\\\])*"' },
|
|
43
|
+
{ name: 'Number', pattern: '-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?' },
|
|
44
|
+
{ name: 'Identifier', pattern: '[A-Za-z_][A-Za-z0-9_]*' },
|
|
45
|
+
{ name: 'LParen', literal: '(' },
|
|
46
|
+
{ name: 'RParen', literal: ')' },
|
|
47
|
+
{ name: 'LBrace', literal: '{' },
|
|
48
|
+
{ name: 'RBrace', literal: '}' },
|
|
49
|
+
{ name: 'LBracket', literal: '[' },
|
|
50
|
+
{ name: 'RBracket', literal: ']' },
|
|
51
|
+
{ name: 'Comma', literal: ',' },
|
|
52
|
+
{ name: 'Colon', literal: ':' },
|
|
53
|
+
{ name: 'Equals', literal: '=' },
|
|
54
|
+
{ name: 'Semicolon', literal: ';' }
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
const identifier = seq(label('token', consume('Identifier')), action({ val: ['token', 'image'] }))
|
|
58
|
+
const stringValue = seq(label('token', consume('String')), action(call('string', { val: ['token', 'image'] })))
|
|
59
|
+
|
|
60
|
+
export const definitionRules = {
|
|
61
|
+
Document: seq(
|
|
62
|
+
consume('GrammarKeyword'), label('name', subrule('IdentifierValue')), consume('Semicolon'),
|
|
63
|
+
consume('StartKeyword'), label('start', subrule('IdentifierValue')), consume('Semicolon'),
|
|
64
|
+
label('declarations', many(subrule('Declaration'))),
|
|
65
|
+
action(call('document', [{ val: 'name' }, { val: 'start' }, { val: 'declarations' }]))
|
|
66
|
+
),
|
|
67
|
+
Declaration: alt(subrule('TokenDeclaration'), subrule('RuleDeclaration')),
|
|
68
|
+
TokenDeclaration: seq(
|
|
69
|
+
consume('TokenKeyword'), label('name', subrule('IdentifierValue')),
|
|
70
|
+
label('kind', subrule('TokenKind')), label('flags', many(subrule('TokenFlag'))), consume('Semicolon'),
|
|
71
|
+
action(call('token', [{ val: 'name' }, { val: 'kind' }, { val: 'flags' }]))
|
|
72
|
+
),
|
|
73
|
+
TokenKind: alt(
|
|
74
|
+
seq(consume('PatternKeyword'), label('source', subrule('StringValue')), action(['pattern', { val: 'source' }])),
|
|
75
|
+
seq(consume('LiteralKeyword'), label('source', subrule('StringValue')), action(['literal', { val: 'source' }]))
|
|
76
|
+
),
|
|
77
|
+
TokenFlag: alt(
|
|
78
|
+
seq(consume('SkipKeyword'), action(['skip', true])),
|
|
79
|
+
seq(consume('IgnoreCaseKeyword'), action(['ignoreCase', true])),
|
|
80
|
+
seq(consume('LongerAltKeyword'), label('name', subrule('IdentifierValue')), action(['longerAlt', { val: 'name' }])),
|
|
81
|
+
// `mode` may be repeated, for a token that serves several modes.
|
|
82
|
+
seq(consume('ModeKeyword'), label('name', subrule('IdentifierValue')), action(['mode', { val: 'name' }])),
|
|
83
|
+
seq(consume('PushModeKeyword'), label('name', subrule('IdentifierValue')), action(['pushMode', { val: 'name' }])),
|
|
84
|
+
seq(consume('PopModeKeyword'), action(['popMode', true]))
|
|
85
|
+
),
|
|
86
|
+
RuleDeclaration: seq(
|
|
87
|
+
consume('RuleKeyword'), label('name', subrule('IdentifierValue')), consume('Equals'),
|
|
88
|
+
label('parser', subrule('ParserExpression')), consume('Semicolon'),
|
|
89
|
+
action(call('rule', [{ val: 'name' }, { val: 'parser' }]))
|
|
90
|
+
),
|
|
91
|
+
ParserExpression: subrule('Call'),
|
|
92
|
+
Call: seq(
|
|
93
|
+
label('name', subrule('IdentifierValue')), consume('LParen'),
|
|
94
|
+
label('args', subrule('CallArguments')), consume('RParen'),
|
|
95
|
+
action(call('call', [{ val: 'name' }, { val: 'args' }]))
|
|
96
|
+
),
|
|
97
|
+
CallArguments: alt(
|
|
98
|
+
seq({ lookahead: consume('RParen') }, action([])),
|
|
99
|
+
seq(
|
|
100
|
+
label('head', subrule('CallArgument')),
|
|
101
|
+
label('tail', many(seq(consume('Comma'), label('value', subrule('CallArgument')), action({ val: 'value' })))),
|
|
102
|
+
action(call('list', [{ val: 'head' }, { val: 'tail' }]))
|
|
103
|
+
)
|
|
104
|
+
),
|
|
105
|
+
CallArgument: alt(subrule('Call'), subrule('JsonValue'), subrule('IdentifierValue')),
|
|
106
|
+
JsonValue: alt(
|
|
107
|
+
subrule('JsonObject'), subrule('JsonArray'), subrule('StringValue'), subrule('NumberValue'),
|
|
108
|
+
seq(consume('TrueKeyword'), action(true)),
|
|
109
|
+
seq(consume('FalseKeyword'), action(false)),
|
|
110
|
+
seq(consume('NullKeyword'), action(null))
|
|
111
|
+
),
|
|
112
|
+
JsonObject: alt(
|
|
113
|
+
seq(consume('LBrace'), consume('RBrace'), action(call('object', []))),
|
|
114
|
+
seq(
|
|
115
|
+
consume('LBrace'), label('head', subrule('JsonPair')),
|
|
116
|
+
label('tail', many(seq(consume('Comma'), label('pair', subrule('JsonPair')), action({ val: 'pair' })))),
|
|
117
|
+
consume('RBrace'), action(call('object', [{ val: 'head' }, { val: 'tail' }]))
|
|
118
|
+
)
|
|
119
|
+
),
|
|
120
|
+
JsonPair: seq(
|
|
121
|
+
label('key', subrule('StringValue')), consume('Colon'), label('value', subrule('JsonValue')),
|
|
122
|
+
action([{ val: 'key' }, { val: 'value' }])
|
|
123
|
+
),
|
|
124
|
+
JsonArray: alt(
|
|
125
|
+
seq(consume('LBracket'), consume('RBracket'), action([])),
|
|
126
|
+
seq(
|
|
127
|
+
consume('LBracket'), label('head', subrule('JsonValue')),
|
|
128
|
+
label('tail', many(seq(consume('Comma'), label('value', subrule('JsonValue')), action({ val: 'value' })))),
|
|
129
|
+
consume('RBracket'), action(call('list', [{ val: 'head' }, { val: 'tail' }]))
|
|
130
|
+
)
|
|
131
|
+
),
|
|
132
|
+
IdentifierValue: identifier,
|
|
133
|
+
StringValue: stringValue,
|
|
134
|
+
NumberValue: seq(label('token', consume('Number')), action(call('number', { val: ['token', 'image'] })))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const definitionGrammar = { tokens: definitionTokens, rules: definitionRules, start: 'Document' }
|
|
138
|
+
|
|
139
|
+
function assertArity(name, args, minimum, maximum = minimum) {
|
|
140
|
+
if (args.length < minimum || args.length > maximum) {
|
|
141
|
+
const expected = minimum === maximum ? String(minimum) : `${minimum}-${maximum}`
|
|
142
|
+
throw new Error(`${name} expects ${expected} argument(s), received ${args.length}`)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function createMetaMethods () {
|
|
147
|
+
const methods = {
|
|
148
|
+
string: (image) => JSON.parse(image),
|
|
149
|
+
number: (image) => Number(image),
|
|
150
|
+
list: ([head, tail]) => {
|
|
151
|
+
const output = new Array(tail.length + 1)
|
|
152
|
+
output[0] = head
|
|
153
|
+
for (let i = 0; i < tail.length; i++) output[i + 1] = tail[i]
|
|
154
|
+
return output
|
|
155
|
+
},
|
|
156
|
+
object: (value) => {
|
|
157
|
+
const output = {}
|
|
158
|
+
if (value.length === 0) return output
|
|
159
|
+
const head = value[0]
|
|
160
|
+
const tail = value[1]
|
|
161
|
+
output[head[0]] = head[1]
|
|
162
|
+
for (let i = 0; i < tail.length; i++) output[tail[i][0]] = tail[i][1]
|
|
163
|
+
return output
|
|
164
|
+
},
|
|
165
|
+
token: ([name, kind, flags]) => {
|
|
166
|
+
const value = { name, [kind[0]]: kind[1] }
|
|
167
|
+
// `mode` is the one repeatable flag: one occurrence stays `mode`, several
|
|
168
|
+
// collapse to `modes`, which is what a token serving two modes looks like.
|
|
169
|
+
const modes = []
|
|
170
|
+
for (let i = 0; i < flags.length; i++) {
|
|
171
|
+
if (flags[i][0] === 'mode') modes.push(flags[i][1])
|
|
172
|
+
else value[flags[i][0]] = flags[i][1]
|
|
173
|
+
}
|
|
174
|
+
if (modes.length === 1) value.mode = modes[0]
|
|
175
|
+
else if (modes.length > 1) value.modes = modes
|
|
176
|
+
return ['token', value]
|
|
177
|
+
},
|
|
178
|
+
rule: ([name, parser]) => ['rule', name, parser],
|
|
179
|
+
document: ([name, start, declarations]) => {
|
|
180
|
+
const tokens = []
|
|
181
|
+
const rules = {}
|
|
182
|
+
for (let i = 0; i < declarations.length; i++) {
|
|
183
|
+
const declaration = declarations[i]
|
|
184
|
+
if (declaration[0] === 'token') tokens.push(declaration[1])
|
|
185
|
+
else rules[declaration[1]] = declaration[2]
|
|
186
|
+
}
|
|
187
|
+
return { name, tokens, rules, start }
|
|
188
|
+
},
|
|
189
|
+
call: ([name, args]) => {
|
|
190
|
+
const build = CONSTRUCTORS[name]
|
|
191
|
+
if (!build) throw new Error(`Unknown parser constructor ${name}`)
|
|
192
|
+
if (args.length < build.arity[0] || args.length > build.arity[1]) {
|
|
193
|
+
const [min, max] = build.arity
|
|
194
|
+
const expected = min === max ? String(min) : `${min}-${max}`
|
|
195
|
+
throw new Error(`${name} expects ${expected} argument(s), received ${args.length}`)
|
|
196
|
+
}
|
|
197
|
+
return build.of(args)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const descriptors = {}
|
|
202
|
+
for (const key of Object.keys(methods)) {
|
|
203
|
+
descriptors[`meta.${key}`] = { method: methods[key], optimizeUnary: true }
|
|
204
|
+
}
|
|
205
|
+
return descriptors
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* How each constructor in the language maps onto a grammar node.
|
|
210
|
+
*
|
|
211
|
+
* Table-driven so the language cannot fall behind the operator set: adding a
|
|
212
|
+
* combinator means adding a line here, and `test/definition.test.js` asserts
|
|
213
|
+
* every operator in `OPERATORS` has one.
|
|
214
|
+
*/
|
|
215
|
+
const VARIADIC = [0, Infinity]
|
|
216
|
+
|
|
217
|
+
/** `action(...)` is accepted where raw logic is wanted; unwrap it. */
|
|
218
|
+
const logicOf = (node) => (node && node.action !== undefined ? node.action : node)
|
|
219
|
+
|
|
220
|
+
const CONSTRUCTORS = {
|
|
221
|
+
seq: { arity: VARIADIC, of: (args) => ({ seq: args }) },
|
|
222
|
+
alt: { arity: VARIADIC, of: (args) => ({ alt: args }) },
|
|
223
|
+
oneOf: { arity: VARIADIC, of: (args) => ({ oneOf: args }) },
|
|
224
|
+
|
|
225
|
+
consume: { arity: [1, 1], of: ([a]) => ({ consume: a }) },
|
|
226
|
+
subrule: { arity: [1, 1], of: ([a]) => ({ subrule: a }) },
|
|
227
|
+
many: { arity: [1, 1], of: ([a]) => ({ many: a }) },
|
|
228
|
+
many1: { arity: [1, 1], of: ([a]) => ({ many1: a }) },
|
|
229
|
+
option: { arity: [1, 1], of: ([a]) => ({ option: a }) },
|
|
230
|
+
action: { arity: [1, 1], of: ([a]) => ({ action: a }) },
|
|
231
|
+
lookahead: { arity: [1, 1], of: ([a]) => ({ lookahead: a }) },
|
|
232
|
+
negLookahead: { arity: [1, 1], of: ([a]) => ({ negLookahead: a }) },
|
|
233
|
+
text: { arity: [1, 1], of: ([a]) => ({ text: a }) },
|
|
234
|
+
|
|
235
|
+
eof: { arity: [0, 0], of: () => ({ eof: null }) },
|
|
236
|
+
epsilon: { arity: [0, 0], of: () => ({ epsilon: null }) },
|
|
237
|
+
|
|
238
|
+
label: { arity: [2, 2], of: ([a, b]) => ({ label: [a, b] }) },
|
|
239
|
+
as: { arity: [2, 2], of: ([a, b]) => ({ as: [a, logicOf(b)] }) },
|
|
240
|
+
manySep: { arity: [2, 2], of: ([a, b]) => ({ manySep: { rule: a, sep: b } }) },
|
|
241
|
+
many1Sep: { arity: [2, 2], of: ([a, b]) => ({ many1Sep: { rule: a, sep: b } }) },
|
|
242
|
+
|
|
243
|
+
between: { arity: [3, 3], of: ([a, b, c]) => ({ between: { open: a, body: b, close: c } }) },
|
|
244
|
+
infixLeft: { arity: [3, 3], of: ([a, b, c]) => ({ infixLeft: { operand: a, operator: b, combine: logicOf(c) } }) },
|
|
245
|
+
infixRight: { arity: [3, 3], of: ([a, b, c]) => ({ infixRight: { operand: a, operator: b, combine: logicOf(c) } }) },
|
|
246
|
+
prefix: { arity: [3, 3], of: ([a, b, c]) => ({ prefix: { operator: a, operand: b, combine: logicOf(c) } }) },
|
|
247
|
+
postfix: { arity: [3, 3], of: ([a, b, c]) => ({ postfix: { operand: a, suffix: b, combine: logicOf(c) } }) }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Constructor names the language understands. */
|
|
251
|
+
export const CONSTRUCTOR_NAMES = Object.keys(CONSTRUCTORS)
|
|
252
|
+
|
|
253
|
+
/** A parser for the grammar-definition language itself. */
|
|
254
|
+
export const createDefinitionParser = (options = {}) =>
|
|
255
|
+
createParser(definitionGrammar, { ...options, methods: createMetaMethods() })
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Parses definition source into a grammar object — plain data, safe to
|
|
259
|
+
* `JSON.stringify` and hand to `createParser`.
|
|
260
|
+
*
|
|
261
|
+
* A trailing `methods { ... }` block is separated out before the language sees
|
|
262
|
+
* the source and carried on the result as `methodsBlock`, an opaque string. The
|
|
263
|
+
* grammar itself is unchanged by its presence.
|
|
264
|
+
*
|
|
265
|
+
* @param {string} source
|
|
266
|
+
* @param {object} [options] Passed to the definition parser itself.
|
|
267
|
+
*/
|
|
268
|
+
export function parseDefinition (source, options = {}) {
|
|
269
|
+
const { grammar: text, block } = splitMethodsBlock(source)
|
|
270
|
+
const grammar = createDefinitionParser(options).parse(text)
|
|
271
|
+
if (block !== null) grammar.methodsBlock = block
|
|
272
|
+
return grammar
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Definition source straight to a working parser.
|
|
277
|
+
*
|
|
278
|
+
* A `methods` block is evaluated here, which needs `new Function`. Explicit
|
|
279
|
+
* `options.methods` win over it, and a block that imports has to go through
|
|
280
|
+
* `emitModule` or the CLI instead — see `evaluateMethodsBlock`.
|
|
281
|
+
*
|
|
282
|
+
* @param {string} source
|
|
283
|
+
* @param {object} [options] Passed to `createParser` for the resulting grammar.
|
|
284
|
+
*/
|
|
285
|
+
export function createParserFromSource (source, options = {}) {
|
|
286
|
+
const grammar = parseDefinition(source, { execution: options.execution })
|
|
287
|
+
if (grammar.methodsBlock === undefined || options.methods) return createParser(grammar, options)
|
|
288
|
+
|
|
289
|
+
const { methods } = evaluateMethodsBlock(grammar.methodsBlock)
|
|
290
|
+
return createParser(grammar, { ...options, methods })
|
|
291
|
+
}
|
package/src/emit.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {import('./parser.js').GrammarSpec} spec
|
|
3
|
+
* @param {{ memo?: boolean, strict?: boolean, ll1?: boolean, maxSteps?: number,
|
|
4
|
+
* positions?: 'full'|'offset', execution?: 'generated'|'interpreted',
|
|
5
|
+
* format?: 'esm'|'cjs', validate?: boolean, moduleName?: string,
|
|
6
|
+
* runtimeSpecifier?: string, engineSpecifier?: string,
|
|
7
|
+
* methods?: Record<string, any> }} [options]
|
|
8
|
+
* @returns {string} JavaScript module source
|
|
9
|
+
*/
|
|
10
|
+
export function emitModule(spec: import("./parser.js").GrammarSpec, options?: {
|
|
11
|
+
memo?: boolean;
|
|
12
|
+
strict?: boolean;
|
|
13
|
+
ll1?: boolean;
|
|
14
|
+
maxSteps?: number;
|
|
15
|
+
positions?: "full" | "offset";
|
|
16
|
+
execution?: "generated" | "interpreted";
|
|
17
|
+
format?: "esm" | "cjs";
|
|
18
|
+
validate?: boolean;
|
|
19
|
+
moduleName?: string;
|
|
20
|
+
runtimeSpecifier?: string;
|
|
21
|
+
engineSpecifier?: string;
|
|
22
|
+
methods?: Record<string, any>;
|
|
23
|
+
}): string;
|