ohm-js 17.4.0 → 17.5.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/dist/ohm-extras.cjs +32 -27
- package/dist/ohm-extras.js +32 -27
- package/dist/ohm.cjs +23 -10
- package/dist/ohm.cjs.map +1 -1
- package/dist/ohm.js +24 -11
- package/dist/ohm.min.js +1 -1
- package/extras/recoverSourceOrder.js +5 -34
- package/index.mjs +10 -1
- package/package.json +9 -6
- package/src/Builder.js +6 -1
- package/src/buildGrammar.js +2 -2
- package/src/groupIterSiblings.js +26 -0
- package/src/main.js +14 -6
- package/src/v18.js +335 -0
- package/src/version.js +1 -1
package/src/Builder.js
CHANGED
|
@@ -7,9 +7,10 @@ import * as pexprs from './pexprs.js';
|
|
|
7
7
|
// --------------------------------------------------------------------
|
|
8
8
|
|
|
9
9
|
export class Builder {
|
|
10
|
-
constructor() {
|
|
10
|
+
constructor(options) {
|
|
11
11
|
this.currentDecl = null;
|
|
12
12
|
this.currentRuleName = null;
|
|
13
|
+
this.options = options || {};
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
newGrammar(name) {
|
|
@@ -129,6 +130,10 @@ export class Builder {
|
|
|
129
130
|
if (!(expr instanceof pexprs.PExpr)) {
|
|
130
131
|
expr = this.fromRecipe(expr);
|
|
131
132
|
}
|
|
133
|
+
// For v18 compatibility, where we don't want a binding for lookahead.
|
|
134
|
+
if (this.options.eliminateLookaheads) {
|
|
135
|
+
return new pexprs.Not(new pexprs.Not(expr));
|
|
136
|
+
}
|
|
132
137
|
return new pexprs.Lookahead(expr);
|
|
133
138
|
}
|
|
134
139
|
|
package/src/buildGrammar.js
CHANGED
|
@@ -19,8 +19,8 @@ function namespaceHas(ns, name) {
|
|
|
19
19
|
// `tree`, which is the concrete syntax tree of a user-written grammar.
|
|
20
20
|
// The grammar will be assigned into `namespace` under the name of the grammar
|
|
21
21
|
// as specified in the source.
|
|
22
|
-
export function buildGrammar(match, namespace, optOhmGrammarForTesting) {
|
|
23
|
-
const builder = new Builder();
|
|
22
|
+
export function buildGrammar(match, namespace, optOhmGrammarForTesting, options) {
|
|
23
|
+
const builder = new Builder(options);
|
|
24
24
|
let decl;
|
|
25
25
|
let currentRuleName;
|
|
26
26
|
let currentRuleFormals;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const isIterSibling = (refNode, n) => {
|
|
2
|
+
return (
|
|
3
|
+
n.isIteration() &&
|
|
4
|
+
n.source.startIdx === refNode.source.startIdx &&
|
|
5
|
+
n.source.endIdx === refNode.source.endIdx &&
|
|
6
|
+
n.children.length === refNode.children.length
|
|
7
|
+
);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function groupIterSiblings(nodes) {
|
|
11
|
+
const groups = [];
|
|
12
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
13
|
+
const n = nodes[i];
|
|
14
|
+
if (!n.isIteration()) {
|
|
15
|
+
groups.push({kind: 'node', node: n});
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const siblings = [n];
|
|
19
|
+
for (let j = i + 1; j < nodes.length && isIterSibling(n, nodes[j]); j++) {
|
|
20
|
+
siblings.push(nodes[j]);
|
|
21
|
+
i = j;
|
|
22
|
+
}
|
|
23
|
+
groups.push({kind: 'iter', siblings});
|
|
24
|
+
}
|
|
25
|
+
return groups;
|
|
26
|
+
}
|
package/src/main.js
CHANGED
|
@@ -16,16 +16,16 @@ const isBuffer = obj =>
|
|
|
16
16
|
typeof obj.constructor.isBuffer === 'function' &&
|
|
17
17
|
obj.constructor.isBuffer(obj);
|
|
18
18
|
|
|
19
|
-
function compileAndLoad(source, namespace) {
|
|
19
|
+
function compileAndLoad(source, namespace, buildOptions) {
|
|
20
20
|
const m = ohmGrammar.match(source, 'Grammars');
|
|
21
21
|
if (m.failed()) {
|
|
22
22
|
throw errors.grammarSyntaxError(m);
|
|
23
23
|
}
|
|
24
|
-
return buildGrammar(m, namespace);
|
|
24
|
+
return buildGrammar(m, namespace, undefined, buildOptions);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export function
|
|
28
|
-
const ns =
|
|
27
|
+
export function _grammar(source, optNamespace, buildOptions) {
|
|
28
|
+
const ns = _grammars(source, optNamespace, buildOptions);
|
|
29
29
|
|
|
30
30
|
// Ensure that the source contained no more than one grammar definition.
|
|
31
31
|
const grammarNames = Object.keys(ns);
|
|
@@ -42,7 +42,7 @@ export function grammar(source, optNamespace) {
|
|
|
42
42
|
return ns[grammarNames[0]]; // Return the one and only grammar.
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export function
|
|
45
|
+
export function _grammars(source, optNamespace, buildOptions) {
|
|
46
46
|
const ns = Object.create(optNamespace || {});
|
|
47
47
|
if (typeof source !== 'string') {
|
|
48
48
|
// For convenience, detect Node.js Buffer objects and automatically call toString().
|
|
@@ -54,10 +54,18 @@ export function grammars(source, optNamespace) {
|
|
|
54
54
|
);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
compileAndLoad(source, ns);
|
|
57
|
+
compileAndLoad(source, ns, buildOptions);
|
|
58
58
|
return ns;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
export function grammar(source, optNamespace) {
|
|
62
|
+
return _grammar(source, optNamespace);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function grammars(source, optNamespace) {
|
|
66
|
+
return _grammars(source, optNamespace);
|
|
67
|
+
}
|
|
68
|
+
|
|
61
69
|
// This is used by ohm-editor to instantiate grammars after incremental
|
|
62
70
|
// parsing, which is not otherwise supported in the public API.
|
|
63
71
|
export {buildGrammar as _buildGrammar};
|
package/src/v18.js
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forward compatibility layer: provides v18-style API on top of the v17 engine.
|
|
3
|
+
*
|
|
4
|
+
* Importing this module patches v17 prototypes with v18 methods (isList, isSeq,
|
|
5
|
+
* getCstRoot, etc.) and re-exports the standard ohm-js API. Usage:
|
|
6
|
+
*
|
|
7
|
+
* import { grammar } from 'ohm-js/v18';
|
|
8
|
+
* const g = grammar('G { greeting = "hello" "world" }');
|
|
9
|
+
* const result = g.match('helloworld');
|
|
10
|
+
* if (result.succeeded()) {
|
|
11
|
+
* const root = result.getCstRoot();
|
|
12
|
+
* // work with v18-style CST nodes
|
|
13
|
+
* }
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import * as common from './common.js';
|
|
17
|
+
import {_grammar, _grammars} from './main.js';
|
|
18
|
+
import {MatchResult} from './MatchResult.js';
|
|
19
|
+
import {Node} from './nodes.js';
|
|
20
|
+
|
|
21
|
+
const buildOptions = {eliminateLookaheads: true};
|
|
22
|
+
|
|
23
|
+
export function grammar(source, optNamespace) {
|
|
24
|
+
return _grammar(source, optNamespace, buildOptions);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function grammars(source, optNamespace) {
|
|
28
|
+
return _grammars(source, optNamespace, buildOptions);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ===== v18 CST node base and types =====
|
|
32
|
+
|
|
33
|
+
class V18Node {
|
|
34
|
+
isNonterminal() {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
isTerminal() {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
isList() {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
isOptional() {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
isSeq() {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
class ListNode extends V18Node {
|
|
52
|
+
constructor(children, source, sourceString) {
|
|
53
|
+
super();
|
|
54
|
+
this.ctorName = '_list';
|
|
55
|
+
this.children = children;
|
|
56
|
+
this.source = source;
|
|
57
|
+
this.sourceString = sourceString;
|
|
58
|
+
this.matchLength = source.endIdx - source.startIdx;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
isList() {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
collect(cb) {
|
|
66
|
+
return this.children.map(c => (c?.isSeq() ? c.unpack(cb) : cb(c)));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
class OptNode extends V18Node {
|
|
71
|
+
constructor(child, source, sourceString) {
|
|
72
|
+
super();
|
|
73
|
+
this.ctorName = '_opt';
|
|
74
|
+
this.children = child ? [child] : [];
|
|
75
|
+
this.source = source;
|
|
76
|
+
this.sourceString = sourceString;
|
|
77
|
+
this.matchLength = source.endIdx - source.startIdx;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
isOptional() {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
ifPresent(consume, orElse) {
|
|
85
|
+
const child = this.children[0];
|
|
86
|
+
if (child) {
|
|
87
|
+
return child.isSeq() ? child.unpack(consume) : consume(child);
|
|
88
|
+
}
|
|
89
|
+
if (orElse) return orElse();
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
isPresent() {
|
|
94
|
+
return this.children.length > 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
isEmpty() {
|
|
98
|
+
return this.children.length === 0;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
class SeqNode extends V18Node {
|
|
103
|
+
constructor(children, source, sourceString) {
|
|
104
|
+
super();
|
|
105
|
+
this.ctorName = '_seq';
|
|
106
|
+
this.children = children;
|
|
107
|
+
this.source = source;
|
|
108
|
+
this.sourceString = sourceString;
|
|
109
|
+
this.matchLength = source.endIdx - source.startIdx;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
isSeq() {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
unpack(cb) {
|
|
117
|
+
return cb(...this.children);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
class TerminalAdapter extends V18Node {
|
|
122
|
+
constructor(absStart, absEnd, input) {
|
|
123
|
+
super();
|
|
124
|
+
this.ctorName = '_terminal';
|
|
125
|
+
this.source = {startIdx: absStart, endIdx: absEnd};
|
|
126
|
+
this.sourceString = input.slice(absStart, absEnd);
|
|
127
|
+
this.matchLength = absEnd - absStart;
|
|
128
|
+
this.children = [];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
isTerminal() {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
isLexical() {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
class NonterminalAdapter extends V18Node {
|
|
140
|
+
constructor(ctorName, absStart, absEnd, input, matchLength, adaptedChildren) {
|
|
141
|
+
super();
|
|
142
|
+
this.ctorName = ctorName;
|
|
143
|
+
this.source = {startIdx: absStart, endIdx: absEnd};
|
|
144
|
+
this.sourceString = input.slice(absStart, absEnd);
|
|
145
|
+
this.matchLength = matchLength;
|
|
146
|
+
this.children = adaptedChildren;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
isNonterminal() {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
isLexical() {
|
|
153
|
+
return common.isLexical(this.ctorName);
|
|
154
|
+
}
|
|
155
|
+
isSyntactic() {
|
|
156
|
+
return common.isSyntactic(this.ctorName);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ===== Direct CST transform =====
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Build a v18-style CST directly from a v17 MatchResult, without creating
|
|
164
|
+
* a Semantics instance or wrapper objects.
|
|
165
|
+
*
|
|
166
|
+
* Uses (absStart, baseStart) tracking to compute source positions:
|
|
167
|
+
* - absStart: absolute start index of the node in the input
|
|
168
|
+
* - baseStart: absolute start of the closest nonterminal ancestor (used as
|
|
169
|
+
* the offset base for iteration/terminal childOffsets)
|
|
170
|
+
*/
|
|
171
|
+
export function getCstRoot(matchResult) {
|
|
172
|
+
if (!matchResult.succeeded()) {
|
|
173
|
+
throw new Error('Cannot get CST root: no successful match');
|
|
174
|
+
}
|
|
175
|
+
const {input} = matchResult;
|
|
176
|
+
const root = matchResult._cst;
|
|
177
|
+
const rootStart = matchResult._cstOffset;
|
|
178
|
+
return adaptNode(root, rootStart, rootStart, input);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function adaptNode(node, absStart, baseStart, input) {
|
|
182
|
+
const absEnd = absStart + node.matchLength;
|
|
183
|
+
|
|
184
|
+
if (node.isTerminal()) {
|
|
185
|
+
return new TerminalAdapter(absStart, absEnd, input);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// For nonterminals, childOffsets are relative to their own start (absStart).
|
|
189
|
+
// For iteration nodes, childOffsets are relative to the enclosing
|
|
190
|
+
// nonterminal's base (baseStart).
|
|
191
|
+
const base = node.isNonterminal() ? absStart : baseStart;
|
|
192
|
+
const childEntries = [];
|
|
193
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
194
|
+
const child = node.children[i];
|
|
195
|
+
const childAbsStart = base + node.childOffsets[i];
|
|
196
|
+
const childAbsEnd = childAbsStart + child.matchLength;
|
|
197
|
+
const childBaseStart = child.isNonterminal() ? childAbsStart : base;
|
|
198
|
+
childEntries.push({
|
|
199
|
+
node: child,
|
|
200
|
+
absStart: childAbsStart,
|
|
201
|
+
absEnd: childAbsEnd,
|
|
202
|
+
baseStart: childBaseStart,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Group iteration siblings and adapt
|
|
207
|
+
const adapted = [];
|
|
208
|
+
for (const group of groupIterEntries(childEntries)) {
|
|
209
|
+
if (group.kind === 'node') {
|
|
210
|
+
const e = group.entry;
|
|
211
|
+
adapted.push(adaptNode(e.node, e.absStart, e.baseStart, input));
|
|
212
|
+
} else {
|
|
213
|
+
adapted.push(wrapIterGroup(group.entries, input));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return new NonterminalAdapter(
|
|
218
|
+
node.ctorName,
|
|
219
|
+
absStart,
|
|
220
|
+
absEnd,
|
|
221
|
+
input,
|
|
222
|
+
node.matchLength,
|
|
223
|
+
adapted
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Group consecutive iteration node entries that are siblings
|
|
229
|
+
* (same source interval and same child count).
|
|
230
|
+
*/
|
|
231
|
+
function groupIterEntries(entries) {
|
|
232
|
+
const groups = [];
|
|
233
|
+
for (let i = 0; i < entries.length; i++) {
|
|
234
|
+
const e = entries[i];
|
|
235
|
+
if (!e.node.isIteration()) {
|
|
236
|
+
groups.push({kind: 'node', entry: e});
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const siblings = [e];
|
|
240
|
+
for (let j = i + 1; j < entries.length; j++) {
|
|
241
|
+
const other = entries[j];
|
|
242
|
+
if (
|
|
243
|
+
other.node.isIteration() &&
|
|
244
|
+
other.absStart === e.absStart &&
|
|
245
|
+
other.absEnd === e.absEnd &&
|
|
246
|
+
other.node.children.length === e.node.children.length
|
|
247
|
+
) {
|
|
248
|
+
siblings.push(other);
|
|
249
|
+
i = j;
|
|
250
|
+
} else {
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
groups.push({kind: 'iter', entries: siblings});
|
|
255
|
+
}
|
|
256
|
+
return groups;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Wrap a group of iteration sibling entries into ListNode/OptNode/SeqNode.
|
|
261
|
+
*/
|
|
262
|
+
function wrapIterGroup(siblingEntries, input) {
|
|
263
|
+
const ref = siblingEntries[0];
|
|
264
|
+
const source = {startIdx: ref.absStart, endIdx: ref.absEnd};
|
|
265
|
+
const sourceStr = input.slice(ref.absStart, ref.absEnd);
|
|
266
|
+
|
|
267
|
+
if (ref.node.isOptional()) {
|
|
268
|
+
if (ref.node.children.length === 0) {
|
|
269
|
+
return new OptNode(undefined, source, sourceStr);
|
|
270
|
+
}
|
|
271
|
+
const child =
|
|
272
|
+
siblingEntries.length === 1
|
|
273
|
+
? adaptIterChild(ref, 0, input)
|
|
274
|
+
: new SeqNode(
|
|
275
|
+
siblingEntries.map(sib => adaptIterChild(sib, 0, input)),
|
|
276
|
+
source,
|
|
277
|
+
sourceStr
|
|
278
|
+
);
|
|
279
|
+
return new OptNode(child, source, sourceStr);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const numRows = ref.node.children.length;
|
|
283
|
+
|
|
284
|
+
if (siblingEntries.length === 1) {
|
|
285
|
+
const children = [];
|
|
286
|
+
for (let i = 0; i < numRows; i++) {
|
|
287
|
+
children.push(adaptIterChild(ref, i, input));
|
|
288
|
+
}
|
|
289
|
+
return new ListNode(children, source, sourceStr);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Multi-column: transpose columns to rows of SeqNodes
|
|
293
|
+
const rows = [];
|
|
294
|
+
for (let row = 0; row < numRows; row++) {
|
|
295
|
+
const seqChildren = siblingEntries.map(sib => adaptIterChild(sib, row, input));
|
|
296
|
+
const rowStart = seqChildren[0].source.startIdx;
|
|
297
|
+
const rowEnd = seqChildren[seqChildren.length - 1].source.endIdx;
|
|
298
|
+
const rowSource = {startIdx: rowStart, endIdx: rowEnd};
|
|
299
|
+
rows.push(new SeqNode(seqChildren, rowSource, input.slice(rowStart, rowEnd)));
|
|
300
|
+
}
|
|
301
|
+
return new ListNode(rows, source, sourceStr);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Adapt a single child of an iteration node entry.
|
|
306
|
+
*/
|
|
307
|
+
function adaptIterChild(iterEntry, childIdx, input) {
|
|
308
|
+
const iterNode = iterEntry.node;
|
|
309
|
+
const base = iterEntry.baseStart;
|
|
310
|
+
const child = iterNode.children[childIdx];
|
|
311
|
+
const childAbsStart = base + iterNode.childOffsets[childIdx];
|
|
312
|
+
const childBaseStart = child.isNonterminal() ? childAbsStart : base;
|
|
313
|
+
return adaptNode(child, childAbsStart, childBaseStart, input);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ===== Prototype patching =====
|
|
317
|
+
|
|
318
|
+
function patchProto(proto, name, value) {
|
|
319
|
+
if (!proto[name]) {
|
|
320
|
+
Object.defineProperty(proto, name, {value, configurable: true, writable: true});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// MatchResult
|
|
325
|
+
patchProto(MatchResult.prototype, 'getCstRoot', function () {
|
|
326
|
+
return getCstRoot(this);
|
|
327
|
+
});
|
|
328
|
+
patchProto(MatchResult.prototype, 'use', function (cb) {
|
|
329
|
+
return cb(this);
|
|
330
|
+
});
|
|
331
|
+
patchProto(MatchResult.prototype, 'detach', () => {});
|
|
332
|
+
|
|
333
|
+
// Node (v17 CST base class)
|
|
334
|
+
patchProto(Node.prototype, 'isList', () => false);
|
|
335
|
+
patchProto(Node.prototype, 'isSeq', () => false);
|
package/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by scripts/prebuild.js
|
|
2
|
-
export const version = '17.
|
|
2
|
+
export const version = '17.5.0';
|