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/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
 
@@ -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 grammar(source, optNamespace) {
28
- const ns = grammars(source, optNamespace);
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 grammars(source, optNamespace) {
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.4.0';
2
+ export const version = '17.5.0';