@ohm-js/wasm 0.6.7 → 0.6.9

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/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- export * from './src/AstBuilder.js';
2
1
  export { Compiler } from './src/Compiler.js';
3
2
  export * from './src/miniohm.ts';
4
3
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,4 +1,3 @@
1
- export * from './src/AstBuilder.js';
2
1
  export { Compiler } from './src/Compiler.js';
3
2
  export * from "./src/miniohm.js";
4
3
  //# sourceMappingURL=index.js.map
@@ -1,20 +1,19 @@
1
- import type { CstNode, MatchResult } from './miniohm.ts';
2
- export type AstNodeTemplate<T> = {
3
- [property: string]: number | string | boolean | object | null | ((this: AstBuilder, children: CstNode[]) => T);
1
+ import type { CstNode, CstNodeChildren, MatchResult, NonterminalNode, TerminalNode } from './miniohm.ts';
2
+ export type AstNodeTemplate<R> = {
3
+ [property: string]: number | string | boolean | object | null | ((this: AstBuilder, children: CstNodeChildren) => R);
4
4
  };
5
- export type AstMapping<T> = Record<string, AstNodeTemplate<T> | number | ((this: AstBuilder, ...children: CstNode[]) => T)>;
6
- export declare class AstBuilder<T = any> {
5
+ export type AstMapping<R> = Record<string, AstNodeTemplate<R> | number | ((this: AstBuilder, ...children: CstNodeChildren) => R)>;
6
+ export declare class AstBuilder<TNode = any> {
7
7
  currNode?: CstNode;
8
8
  private _mapping;
9
9
  private _depth;
10
10
  private _debug;
11
- constructor(mapping: AstMapping<T>, opts?: {
11
+ constructor(mapping: AstMapping<TNode>, opts?: {
12
12
  debug?: boolean;
13
13
  });
14
14
  private _debugLog;
15
- _visitTerminal(node: CstNode): string;
16
- _visitNonterminal(node: CstNode): unknown;
17
- _visitIter(node: CstNode): T[] | T | null;
18
- toAst(nodeOrResult: MatchResult | CstNode): T;
15
+ _visitTerminal(node: TerminalNode): string;
16
+ _visitNonterminal(node: NonterminalNode): unknown;
17
+ toAst(nodeOrResult: MatchResult | CstNode): TNode;
19
18
  }
20
19
  //# sourceMappingURL=AstBuilder.d.ts.map
@@ -4,7 +4,7 @@ function childAt(children, idx, ruleName, propName = '') {
4
4
  const path = propName ? `${ruleName}.${propName}` : ruleName;
5
5
  throw new Error(`${path}: Child index ${idx} out of range`);
6
6
  }
7
- return children[idx];
7
+ return checkNotNull(children[idx]);
8
8
  }
9
9
  export class AstBuilder {
10
10
  currNode;
@@ -14,10 +14,10 @@ export class AstBuilder {
14
14
  constructor(mapping, opts = {}) {
15
15
  const handleListOf = (child) => this.toAst(child);
16
16
  const handleEmptyListOf = () => [];
17
- const handleNonemptyListOf = (first, iterSepAndElem) => [
18
- this.toAst(first),
19
- ...iterSepAndElem.map((_, elem) => this.toAst(elem)),
20
- ];
17
+ const handleNonemptyListOf = (first, sepAndElemList) => {
18
+ assert(!!sepAndElemList?.isList(), 'Expected a ListNode');
19
+ return [this.toAst(first), ...sepAndElemList.collect((_, elem) => this.toAst(elem))];
20
+ };
21
21
  this._mapping = {
22
22
  listOf: handleListOf,
23
23
  ListOf: handleListOf,
@@ -37,15 +37,15 @@ export class AstBuilder {
37
37
  return node.sourceString;
38
38
  }
39
39
  _visitNonterminal(node) {
40
- const { children, ruleName } = node;
40
+ const { children, ctorName } = node;
41
41
  const mapping = this._mapping;
42
- this._debugLog(`> ${ruleName}`);
42
+ this._debugLog(`> ${ctorName}`);
43
43
  const dbgReturn = (val) => {
44
- this._debugLog(`| ${ruleName} DONE`);
44
+ this._debugLog(`| ${ctorName} DONE`);
45
45
  return val;
46
46
  };
47
47
  // without customization
48
- if (!Object.hasOwn(mapping, ruleName)) {
48
+ if (!Object.hasOwn(mapping, ctorName)) {
49
49
  // lexical rule
50
50
  if (node.isLexical()) {
51
51
  return node.sourceString;
@@ -58,22 +58,22 @@ export class AstBuilder {
58
58
  // rest: terms with multiple children
59
59
  }
60
60
  // direct forward
61
- if (typeof mapping[ruleName] === 'number') {
62
- const idx = mapping[ruleName];
63
- return dbgReturn(this.toAst(childAt(children, idx, ruleName)));
61
+ if (typeof mapping[ctorName] === 'number') {
62
+ const idx = mapping[ctorName];
63
+ return dbgReturn(this.toAst(childAt(children, idx, ctorName)));
64
64
  }
65
- assert(typeof mapping[ruleName] !== 'function', "shouldn't be possible");
65
+ assert(typeof mapping[ctorName] !== 'function', "shouldn't be possible");
66
66
  // named/mapped children or unnamed children ('0', '1', '2', ...)
67
- const propMap = mapping[ruleName] || children;
67
+ const propMap = mapping[ctorName] || children;
68
68
  const ans = {
69
- type: ruleName,
69
+ type: ctorName,
70
70
  };
71
71
  // eslint-disable-next-line guard-for-in
72
72
  for (const prop in propMap) {
73
- const mappedProp = mapping[ruleName] && mapping[ruleName][prop];
73
+ const mappedProp = mapping[ctorName] && mapping[ctorName][prop];
74
74
  if (typeof mappedProp === 'number') {
75
75
  // direct forward
76
- ans[prop] = this.toAst(childAt(children, mappedProp, ruleName, prop));
76
+ ans[prop] = this.toAst(childAt(children, mappedProp, ctorName, prop));
77
77
  }
78
78
  else if (typeof mappedProp === 'string' ||
79
79
  typeof mappedProp === 'boolean' ||
@@ -102,33 +102,26 @@ export class AstBuilder {
102
102
  }
103
103
  return dbgReturn(ans);
104
104
  }
105
- _visitIter(node) {
106
- const { children } = node;
107
- if (node.isOptional()) {
108
- if (children.length === 0) {
109
- return null;
110
- }
111
- return this.toAst(children[0]);
112
- }
113
- return children.map(c => this.toAst(c));
114
- }
115
105
  toAst(nodeOrResult) {
116
106
  let node = nodeOrResult;
117
107
  if (typeof nodeOrResult.succeeded === 'function') {
118
108
  const matchResult = nodeOrResult;
119
109
  assert(matchResult.succeeded(), 'Cannot convert failed match result to AST');
120
- node = checkNotNull(matchResult._cst);
110
+ node = matchResult.grammar.getCstRoot();
121
111
  }
122
112
  let ans;
123
113
  this._depth++;
124
114
  if (node.isTerminal()) {
125
115
  ans = this._visitTerminal(node);
126
116
  }
127
- else if (node.isIter()) {
128
- ans = this._visitIter(node);
117
+ else if (node.isOptional()) {
118
+ ans = node.ifPresent((child) => this.toAst(child), () => null);
119
+ }
120
+ else if (node.isList() || node.isSeq()) {
121
+ ans = node.children.map(c => this.toAst(c));
129
122
  }
130
123
  else {
131
- assert(node.isNonterminal(), `Unknown node type: ${node._type}`);
124
+ assert(node.isNonterminal(), `Unknown node type: ${node.type}`);
132
125
  this.currNode = node;
133
126
  ans =
134
127
  typeof this._mapping[node.ctorName] === 'function'
@@ -5,4 +5,5 @@ export interface Grammar extends WasmGrammar {
5
5
  }
6
6
  export declare function grammar(source: string): Grammar;
7
7
  export declare function grammars(source: string): Record<string, Grammar>;
8
+ export { AstBuilder } from './AstBuilder.js';
8
9
  //# sourceMappingURL=compat.d.ts.map
@@ -21,4 +21,5 @@ export function grammars(source) {
21
21
  }
22
22
  return ans;
23
23
  }
24
+ export { AstBuilder } from './AstBuilder.js';
24
25
  //# sourceMappingURL=compat.js.map
@@ -1,3 +1,11 @@
1
+ export declare const CstNodeType: {
2
+ readonly NONTERMINAL: 0;
3
+ readonly TERMINAL: 1;
4
+ readonly LIST: 2;
5
+ readonly OPT: 3;
6
+ readonly SEQ: 4;
7
+ };
8
+ export type CstNodeType = (typeof CstNodeType)[keyof typeof CstNodeType];
1
9
  export declare class WasmGrammar {
2
10
  name: string;
3
11
  private _instance?;
@@ -30,62 +38,184 @@ export declare class WasmGrammar {
30
38
  private _fillInputBuffer;
31
39
  getRightmostFailurePosition(): number;
32
40
  }
33
- export declare class CstNode {
34
- _ruleNames: string[];
35
- _view: DataView;
36
- _children?: CstNode[];
41
+ export interface MatchContext {
42
+ ruleNames: string[];
43
+ view: DataView;
44
+ input: string;
45
+ }
46
+ export type CstNode = NonterminalNode | TerminalNode | ListNode | OptNode | SeqNode;
47
+ export type CstNodeChildren = readonly CstNode[];
48
+ export interface CstNodeBase {
49
+ ctorName: string;
50
+ source: {
51
+ startIdx: number;
52
+ endIdx: number;
53
+ };
54
+ sourceString: string;
55
+ matchLength: number;
56
+ isNonterminal(): this is NonterminalNode;
57
+ isTerminal(): this is TerminalNode;
58
+ isOptional(): this is OptNode;
59
+ isSeq(): this is SeqNode;
60
+ isList(): this is ListNode;
61
+ }
62
+ export interface NonterminalNode<TChildren extends CstNodeChildren = CstNodeChildren> extends CstNodeBase {
63
+ type: typeof CstNodeType.NONTERMINAL;
64
+ ctorName: string;
65
+ leadingSpaces?: NonterminalNode;
66
+ children: TChildren;
67
+ isSyntactic(ruleName?: string): boolean;
68
+ isLexical(ruleName?: string): boolean;
69
+ }
70
+ export interface TerminalNode extends CstNodeBase {
71
+ type: typeof CstNodeType.TERMINAL;
72
+ ctorName: '_terminal';
73
+ leadingSpaces?: NonterminalNode;
74
+ value: string;
75
+ }
76
+ export interface ListNode<TNode extends CstNode = CstNode> extends CstNodeBase {
77
+ type: typeof CstNodeType.LIST;
78
+ ctorName: '_list';
79
+ children: readonly TNode[];
80
+ collect: <R>(cb: (...children: CstNode[]) => R) => R[];
81
+ }
82
+ export interface OptNode<TChild extends CstNode = CstNode> extends CstNodeBase {
83
+ type: typeof CstNodeType.OPT;
84
+ ctorName: '_opt';
85
+ children: [] | [TChild];
86
+ ifPresent<R>(consume: TChild extends SeqNode<infer T> ? (...children: T) => R : (child: TChild) => R, orElse?: () => R): R;
87
+ ifPresent<R>(consume: TChild extends SeqNode<infer T> ? (...children: T) => R : (child: TChild) => R): R | undefined;
88
+ isPresent(): boolean;
89
+ isEmpty(): boolean;
90
+ }
91
+ export interface SeqNode<TChildren extends CstNodeChildren = CstNodeChildren> extends CstNodeBase {
92
+ type: typeof CstNodeType.SEQ;
93
+ ctorName: '_seq';
94
+ children: TChildren;
95
+ unpack: <R>(cb: (...children: TChildren) => R) => R;
96
+ }
97
+ export declare class CstNodeImpl implements CstNodeBase {
98
+ _ctx: MatchContext;
99
+ _children?: CstNodeChildren;
37
100
  _base: number;
38
- _input: string;
39
101
  startIdx: number;
40
- leadingSpaces: CstNode | undefined;
102
+ leadingSpaces?: NonterminalNode;
41
103
  source: {
42
104
  startIdx: number;
43
105
  endIdx: number;
44
106
  };
45
- constructor(ruleNames: string[], dataView: DataView, ptr: number, input: string, startIdx: number);
46
- get type(): number;
47
- isNonterminal(): boolean;
48
- isTerminal(): boolean;
49
- isIter(): boolean;
50
- isOptional(): boolean;
107
+ constructor(ctx: MatchContext, ptr: number, startIdx: number);
108
+ get type(): CstNodeType;
109
+ private get matchRecordType();
110
+ isNonterminal(): this is NonterminalNode;
111
+ isTerminal(): this is TerminalNode;
112
+ isList(): this is ListNode;
113
+ isOptional(): this is OptNode;
114
+ isSeq(): this is SeqNode;
51
115
  get ctorName(): string;
52
- get ruleName(): string;
53
116
  get count(): number;
54
117
  get matchLength(): number;
55
118
  get _typeAndDetails(): number;
56
119
  get arity(): number;
57
- get children(): CstNode[];
58
- _computeChildren(): CstNode[];
120
+ get children(): CstNodeChildren;
121
+ _computeChildren(): CstNodeImpl[];
59
122
  get sourceString(): string;
60
123
  isSyntactic(ruleName?: string): boolean;
61
124
  isLexical(ruleName?: string): boolean;
62
125
  toString(): string;
63
- map<T>(callbackFn: (...args: CstNode[]) => T): T[];
64
- when<T>({ some, none }: {
65
- some: (...args: CstNode[]) => T;
66
- none: () => T;
67
- }): T;
68
126
  }
69
- export declare function dumpCstNode(node: CstNode, depth?: number): void;
127
+ export declare class SeqNodeImpl<TChildren extends CstNodeChildren = CstNodeChildren> implements SeqNode<TChildren> {
128
+ type: 4;
129
+ ctorName: "_seq";
130
+ children: TChildren;
131
+ source: {
132
+ startIdx: number;
133
+ endIdx: number;
134
+ };
135
+ sourceString: string;
136
+ get matchLength(): number;
137
+ constructor(children: TChildren, source: {
138
+ startIdx: number;
139
+ endIdx: number;
140
+ }, sourceString: string);
141
+ isNonterminal(): this is NonterminalNode;
142
+ isTerminal(): this is TerminalNode;
143
+ isOptional(): this is OptNode;
144
+ isSeq(): this is SeqNode;
145
+ isList(): this is ListNode;
146
+ unpack<R>(cb: (...args: TChildren) => R): R;
147
+ }
148
+ export declare class ListNodeImpl<TNode extends CstNode = CstNode> implements ListNode<TNode> {
149
+ type: 2;
150
+ ctorName: "_list";
151
+ children: readonly TNode[];
152
+ source: {
153
+ startIdx: number;
154
+ endIdx: number;
155
+ };
156
+ sourceString: string;
157
+ get matchLength(): number;
158
+ constructor(children: readonly TNode[], source: {
159
+ startIdx: number;
160
+ endIdx: number;
161
+ }, sourceString: string);
162
+ isNonterminal(): this is NonterminalNode;
163
+ isTerminal(): this is TerminalNode;
164
+ isList(): this is ListNode;
165
+ isOptional(): this is OptNode;
166
+ isSeq(): this is SeqNode;
167
+ collect<R>(cb: (...args: CstNode[]) => R): R[];
168
+ }
169
+ export declare class OptNodeImpl<TNode extends CstNode = CstNode> implements OptNode<TNode> {
170
+ type: 3;
171
+ ctorName: "_opt";
172
+ children: [] | [TNode];
173
+ source: {
174
+ startIdx: number;
175
+ endIdx: number;
176
+ };
177
+ sourceString: string;
178
+ get matchLength(): number;
179
+ constructor(child: TNode | undefined, source: {
180
+ startIdx: number;
181
+ endIdx: number;
182
+ }, sourceString: string);
183
+ isNonterminal(): this is NonterminalNode;
184
+ isTerminal(): this is TerminalNode;
185
+ isList(): this is ListNode;
186
+ isOptional(): this is OptNode;
187
+ isSeq(): this is SeqNode;
188
+ ifPresent<R>(consume: TNode extends SeqNode<infer T> ? (...children: T) => R : (child: TNode) => R, orElse?: () => R): R | undefined;
189
+ isPresent(): boolean;
190
+ isEmpty(): boolean;
191
+ }
70
192
  export declare class MatchResult {
71
193
  grammar: WasmGrammar;
72
- input: string;
73
194
  startExpr: string;
74
- _cst: CstNode | null;
195
+ _ctx: MatchContext;
196
+ _succeeded: boolean;
197
+ constructor(grammar: WasmGrammar, startExpr: string, ctx: MatchContext, succeeded: boolean);
198
+ [Symbol.dispose](): void;
199
+ detach(): void;
200
+ succeeded(): this is SucceededMatchResult;
201
+ failed(): this is FailedMatchResult;
202
+ toString(): string;
203
+ use<T>(cb: (r: MatchResult) => T): T;
204
+ }
205
+ declare class SucceededMatchResult extends MatchResult {
206
+ _cst: CstNode;
207
+ constructor(grammar: WasmGrammar, startExpr: string, ctx: MatchContext, succeeded: boolean);
208
+ }
209
+ declare class FailedMatchResult extends MatchResult {
210
+ constructor(grammar: WasmGrammar, startExpr: string, ctx: MatchContext, succeeded: boolean, rightmostFailurePosition: number, optRecordedFailures?: any);
75
211
  _rightmostFailurePosition: number;
76
212
  _rightmostFailures: any;
77
213
  shortMessage?: string;
78
214
  message?: string;
79
- constructor(matcher: WasmGrammar, input: string, startExpr: string, cst: CstNode | null, rightmostFailurePosition: number, optRecordedFailures?: any);
80
- [Symbol.dispose](): void;
81
- detach(): void;
82
- succeeded(): boolean;
83
- failed(): boolean;
84
215
  getRightmostFailurePosition(): number;
85
216
  getRightmostFailures(): any;
86
- toString(): string;
87
217
  getExpectedText(): string;
88
218
  getInterval(): void;
89
- use<T>(cb: (r: MatchResult) => T): T;
90
219
  }
220
+ export {};
91
221
  //# sourceMappingURL=miniohm.d.ts.map
@@ -1,11 +1,20 @@
1
1
  import { assert, checkNotNull } from "./assert.js";
2
- const CST_NODE_TYPE_MASK = 0b11;
3
- const CstNodeType = {
2
+ const MATCH_RECORD_TYPE_MASK = 0b11;
3
+ // A MatchRecord is the representation of a CstNode in Wasm linear memory.
4
+ const MatchRecordType = {
4
5
  NONTERMINAL: 0,
5
6
  TERMINAL: 1,
6
7
  ITER_FLAG: 2,
7
8
  OPTIONAL: 3,
8
9
  };
10
+ // A _CST node_ is the user-facing representation, built from a match record.
11
+ export const CstNodeType = {
12
+ NONTERMINAL: 0,
13
+ TERMINAL: 1,
14
+ LIST: 2,
15
+ OPT: 3,
16
+ SEQ: 4,
17
+ };
9
18
  const compileOptions = {
10
19
  builtins: ['js-string'],
11
20
  };
@@ -189,7 +198,12 @@ export class WasmGrammar {
189
198
  debugger; // eslint-disable-line no-debugger
190
199
  const ruleId = checkNotNull(this._ruleIds.get(ruleName || this._ruleNames[0]), `unknown rule: '${ruleName}'`);
191
200
  const succeeded = this._instance.exports.match(input, ruleId);
192
- const result = new MatchResult(this, this._input, ruleName || this._ruleNames[0], succeeded ? this.getCstRoot() : null, this.getRightmostFailurePosition());
201
+ const ctx = {
202
+ ruleNames: this._ruleNames,
203
+ view: new DataView(this._instance.exports.memory.buffer),
204
+ input: this._instance.exports.input.value,
205
+ };
206
+ const result = createMatchResult(this, ruleName || this._ruleNames[0], ctx, !!succeeded);
193
207
  result.detach = this._detachMatchResult.bind(this, result);
194
208
  this._resultStack.push(result);
195
209
  return result;
@@ -200,13 +214,19 @@ export class WasmGrammar {
200
214
  getCstRoot() {
201
215
  const { exports } = this._instance;
202
216
  const { buffer } = exports.memory;
203
- const firstNode = new CstNode(this._ruleNames, new DataView(buffer), exports.bindingsAt(0), exports.input.value, 0);
217
+ const ctx = {
218
+ ruleNames: this._ruleNames,
219
+ view: new DataView(buffer),
220
+ input: exports.input.value,
221
+ };
222
+ const firstNode = new CstNodeImpl(ctx, exports.bindingsAt(0), 0);
223
+ assert(firstNode.isNonterminal());
204
224
  if (firstNode.ctorName !== '$spaces') {
205
225
  return firstNode;
206
226
  }
207
- assert(exports.getBindingsLength() > 1);
227
+ assert(exports.getBindingsLength() > 1 && firstNode.ctorName === '$spaces');
208
228
  const nextAddr = exports.bindingsAt(1);
209
- const root = new CstNode(this._ruleNames, new DataView(buffer), nextAddr, exports.input.value, firstNode.matchLength);
229
+ const root = new CstNodeImpl(ctx, nextAddr, firstNode.matchLength);
210
230
  root.leadingSpaces = firstNode;
211
231
  return root;
212
232
  }
@@ -222,33 +242,40 @@ export class WasmGrammar {
222
242
  return this._instance.exports.rightmostFailurePos.value;
223
243
  }
224
244
  }
225
- export class CstNode {
226
- _ruleNames;
227
- _view;
228
- _children;
245
+ export class CstNodeImpl {
246
+ _ctx;
247
+ _children = undefined;
229
248
  _base;
230
- _input;
231
249
  startIdx;
232
- leadingSpaces;
250
+ leadingSpaces = undefined;
233
251
  source;
234
- constructor(ruleNames, dataView, ptr, input, startIdx) {
252
+ constructor(ctx, ptr, startIdx) {
235
253
  // Non-enumerable properties
236
254
  Object.defineProperties(this, {
237
- _ruleNames: { value: ruleNames },
238
- _view: { value: dataView },
255
+ _ctx: { value: ctx },
239
256
  _children: { writable: true },
240
257
  });
241
258
  this._base = ptr;
242
- this._input = input;
243
259
  this.startIdx = startIdx;
244
- this.leadingSpaces = undefined;
245
260
  this.source = {
246
261
  startIdx,
247
262
  endIdx: startIdx + this.sourceString.length,
248
263
  };
249
264
  }
250
265
  get type() {
251
- return this._typeAndDetails & CST_NODE_TYPE_MASK;
266
+ switch (this._typeAndDetails & MATCH_RECORD_TYPE_MASK) {
267
+ case MatchRecordType.NONTERMINAL:
268
+ return CstNodeType.NONTERMINAL;
269
+ case MatchRecordType.TERMINAL:
270
+ return CstNodeType.TERMINAL;
271
+ case MatchRecordType.ITER_FLAG:
272
+ return CstNodeType.LIST;
273
+ default:
274
+ throw new Error('unreachable');
275
+ }
276
+ }
277
+ get matchRecordType() {
278
+ return (this._typeAndDetails & MATCH_RECORD_TYPE_MASK);
252
279
  }
253
280
  isNonterminal() {
254
281
  return this.type === CstNodeType.NONTERMINAL;
@@ -256,50 +283,90 @@ export class CstNode {
256
283
  isTerminal() {
257
284
  return this.type === CstNodeType.TERMINAL;
258
285
  }
259
- isIter() {
260
- return (this._typeAndDetails & CstNodeType.ITER_FLAG) !== 0;
286
+ isList() {
287
+ return this.type === CstNodeType.LIST;
261
288
  }
262
289
  isOptional() {
263
- return this.type === CstNodeType.OPTIONAL;
290
+ return this.type === CstNodeType.OPT;
264
291
  }
265
- get ctorName() {
266
- return this.isTerminal() ? '_terminal' : this.isIter() ? '_iter' : this.ruleName;
292
+ isSeq() {
293
+ return this.type === CstNodeType.SEQ;
267
294
  }
268
- get ruleName() {
269
- assert(this.isNonterminal(), 'Not a non-terminal');
270
- const ruleId = this._view.getInt32(this._base + 8, true) >>> 2;
271
- return this._ruleNames[ruleId].split('<')[0];
295
+ get ctorName() {
296
+ switch (this.type) {
297
+ case CstNodeType.NONTERMINAL: {
298
+ const { ruleNames, view } = this._ctx;
299
+ const ruleId = view.getInt32(this._base + 8, true) >>> 2;
300
+ return ruleNames[ruleId].split('<')[0];
301
+ }
302
+ case CstNodeType.TERMINAL:
303
+ return '_terminal';
304
+ case CstNodeType.LIST:
305
+ return '_list';
306
+ case CstNodeType.OPT:
307
+ return '_opt';
308
+ case CstNodeType.SEQ:
309
+ return '_seq';
310
+ }
272
311
  }
273
312
  get count() {
274
- return this._view.getUint32(this._base, true);
313
+ return this._ctx.view.getUint32(this._base, true);
275
314
  }
276
315
  get matchLength() {
277
- return this._view.getUint32(this._base + 4, true);
316
+ return this._ctx.view.getUint32(this._base + 4, true);
278
317
  }
279
318
  get _typeAndDetails() {
280
- return this._view.getInt32(this._base + 8, true);
319
+ return this._ctx.view.getInt32(this._base + 8, true);
281
320
  }
282
321
  get arity() {
283
322
  return this._typeAndDetails >>> 2;
284
323
  }
285
324
  get children() {
286
325
  if (!this._children) {
287
- this._children = this._computeChildren();
326
+ this._children = this._computeChildren().map((n) => {
327
+ const { matchRecordType } = n;
328
+ if (matchRecordType === MatchRecordType.OPTIONAL) {
329
+ const child = n.children.length <= 1
330
+ ? n.children[0]
331
+ : new SeqNodeImpl(n.children, n.source, n.sourceString);
332
+ return new OptNodeImpl(child, n.source, n.sourceString);
333
+ }
334
+ else if (matchRecordType === MatchRecordType.ITER_FLAG) {
335
+ if (n.arity <= 1) {
336
+ return new ListNodeImpl(n.children, n.source, n.sourceString);
337
+ }
338
+ const arr = [];
339
+ let startIdx = n.startIdx;
340
+ for (let i = 0; i < n.children.length; i += n.arity) {
341
+ // FIXME: We don't need any of this nonsense if we actually build the SeqNodes at parse time.
342
+ const seqChildren = n.children.slice(i, i + n.arity);
343
+ const endIdx = checkNotNull(seqChildren.at(-1)).source.endIdx;
344
+ const sourceString = n._ctx.input.slice(startIdx, endIdx);
345
+ arr.push(new SeqNodeImpl(seqChildren, { startIdx, endIdx }, sourceString));
346
+ startIdx = endIdx;
347
+ }
348
+ assert(startIdx === n.source.endIdx);
349
+ return new ListNodeImpl(arr, n.source, n.sourceString);
350
+ }
351
+ return n; // FIXME
352
+ });
288
353
  }
289
354
  return this._children;
290
355
  }
291
356
  _computeChildren() {
292
357
  const children = [];
358
+ const { ruleNames, view, input } = this._ctx;
293
359
  let spaces;
294
360
  let { startIdx } = this;
295
361
  for (let i = 0; i < this.count; i++) {
296
362
  const slotOffset = this._base + 16 + i * 4;
297
- const ptr = this._view.getUint32(slotOffset, true);
363
+ const ptr = view.getUint32(slotOffset, true);
298
364
  // TODO: Avoid allocating $spaces nodes altogether?
299
- const node = new CstNode(this._ruleNames, this._view, ptr, this._input, startIdx);
300
- if (node.ctorName === '$spaces') {
365
+ const node = new CstNodeImpl(this._ctx, ptr, startIdx);
366
+ if (node.matchRecordType === MatchRecordType.NONTERMINAL &&
367
+ node.ctorName === '$spaces') {
301
368
  assert(!spaces, 'Multiple $spaces nodes found');
302
- spaces = node;
369
+ spaces = node; // FIXME
303
370
  }
304
371
  else {
305
372
  if (spaces) {
@@ -314,70 +381,161 @@ export class CstNode {
314
381
  return children;
315
382
  }
316
383
  get sourceString() {
317
- return this._input.slice(this.startIdx, this.startIdx + this.matchLength);
384
+ return this._ctx.input.slice(this.startIdx, this.startIdx + this.matchLength);
318
385
  }
319
386
  isSyntactic(ruleName) {
320
- const firstChar = this.ruleName[0];
387
+ assert(this.isNonterminal(), 'Not a nonterminal');
388
+ const firstChar = this.ctorName[0];
321
389
  return firstChar === firstChar.toUpperCase();
322
390
  }
323
391
  isLexical(ruleName) {
392
+ assert(this.isNonterminal(), 'Not a nonterminal');
324
393
  return !this.isSyntactic(ruleName);
325
394
  }
326
395
  toString() {
327
- const ctorName = this.isTerminal() ? '_terminal' : this.isIter() ? '_iter' : this.ruleName;
396
+ const ctorName = this.isTerminal() ? '_terminal' : this.isSeq() ? '_iter' : this.ctorName;
328
397
  const { sourceString, startIdx } = this;
329
398
  return `CstNode {ctorName: ${ctorName}, sourceString: ${sourceString}, startIdx: ${startIdx} }`;
330
399
  }
331
- // Other possible names: collect, mapChildren, mapUnpack, unpackEach, …
332
- map(callbackFn) {
333
- const { arity, children } = this;
334
- assert(callbackFn.length === arity, 'bad arity');
335
- const ans = [];
336
- for (let i = 0; i < children.length; i += arity) {
337
- ans.push(callbackFn(...children.slice(i, i + arity)));
338
- }
339
- return ans;
400
+ }
401
+ export class SeqNodeImpl {
402
+ type = CstNodeType.SEQ;
403
+ ctorName = '_seq';
404
+ children;
405
+ source;
406
+ sourceString;
407
+ get matchLength() {
408
+ return this.sourceString.length;
409
+ }
410
+ constructor(children, source, sourceString) {
411
+ this.children = children;
412
+ this.source = source;
413
+ this.sourceString = sourceString;
414
+ }
415
+ isNonterminal() {
416
+ return false;
340
417
  }
341
- when({ some, none }) {
342
- assert(this.isOptional(), 'Not an optional');
343
- if (this.children.length === 0)
344
- return none();
345
- assert(some.length === this.children.length, `bad arity: expected ${this.children.length}, got ${some.length}`);
346
- return some(...this.children);
418
+ isTerminal() {
419
+ return false;
420
+ }
421
+ isOptional() {
422
+ return false;
423
+ }
424
+ isSeq() {
425
+ return true;
426
+ }
427
+ isList() {
428
+ return false;
429
+ }
430
+ unpack(cb) {
431
+ assert(cb.length === this.children.length, `bad arity: expected ${this.children.length}, got ${cb.length}`);
432
+ return cb.call(null, ...this.children); // FIXME
347
433
  }
348
434
  }
349
- export function dumpCstNode(node, depth = 0) {
350
- const { _base, children, ctorName, matchLength, startIdx } = node;
351
- const indent = Array.from({ length: depth }).join(' ');
352
- const addr = _base.toString(16);
353
- // eslint-disable-next-line no-console
354
- console.log(`${indent}${addr} ${ctorName}@${startIdx}, matchLength ${matchLength}, children ${children.length}` // eslint-disable-line max-len
355
- );
356
- node.children.forEach(c => dumpCstNode(c, depth + 1));
435
+ export class ListNodeImpl {
436
+ type = CstNodeType.LIST;
437
+ ctorName = '_list';
438
+ children;
439
+ source;
440
+ sourceString;
441
+ get matchLength() {
442
+ return this.sourceString.length;
443
+ }
444
+ constructor(children, source, sourceString) {
445
+ this.children = children;
446
+ this.source = source;
447
+ this.sourceString = sourceString;
448
+ }
449
+ isNonterminal() {
450
+ return false;
451
+ }
452
+ isTerminal() {
453
+ return false;
454
+ }
455
+ isList() {
456
+ return true;
457
+ }
458
+ isOptional() {
459
+ return false;
460
+ }
461
+ isSeq() {
462
+ return false;
463
+ }
464
+ collect(cb) {
465
+ return this.children.map(c => {
466
+ return c?.isSeq() ? c.unpack(cb) : cb(c);
467
+ });
468
+ }
357
469
  }
470
+ export class OptNodeImpl {
471
+ type = CstNodeType.OPT;
472
+ ctorName = '_opt';
473
+ children;
474
+ source;
475
+ sourceString;
476
+ get matchLength() {
477
+ return this.sourceString.length;
478
+ }
479
+ constructor(child, source, sourceString) {
480
+ this.children = child ? [child] : [];
481
+ this.source = source;
482
+ this.sourceString = sourceString;
483
+ }
484
+ isNonterminal() {
485
+ return false;
486
+ }
487
+ isTerminal() {
488
+ return false;
489
+ }
490
+ isList() {
491
+ return false;
492
+ }
493
+ isOptional() {
494
+ return true;
495
+ }
496
+ isSeq() {
497
+ return false;
498
+ }
499
+ ifPresent(consume, orElse) {
500
+ const child = this.children[0];
501
+ if (child) {
502
+ return child.isSeq()
503
+ ? child.unpack(consume)
504
+ : consume(child);
505
+ }
506
+ if (orElse)
507
+ return orElse();
508
+ }
509
+ isPresent() {
510
+ return this.children.length > 0;
511
+ }
512
+ isEmpty() {
513
+ return this.children.length === 0;
514
+ }
515
+ }
516
+ // export function dumpCstNode(node: CstNode, depth = 0): void {
517
+ // const {_base, children, ctorName, matchLength, startIdx} = node;
518
+ // const indent = Array.from({length: depth}).join(' ');
519
+ // const addr = _base.toString(16);
520
+ // // eslint-disable-next-line no-console
521
+ // console.log(
522
+ // `${indent}${addr} ${ctorName}@${startIdx}, matchLength ${matchLength}, children ${children.length}` // eslint-disable-line max-len
523
+ // );
524
+ // node.children.forEach(c => dumpCstNode(c, depth + 1));
525
+ // }
358
526
  export class MatchResult {
359
527
  // Note: This is different from the JS implementation, which has:
360
528
  // matcher: Matcher;
361
529
  // …instead.
362
530
  grammar;
363
- input;
364
531
  startExpr;
365
- _cst;
366
- _rightmostFailurePosition;
367
- _rightmostFailures;
368
- shortMessage;
369
- message;
370
- constructor(matcher, input, startExpr, cst, rightmostFailurePosition, optRecordedFailures) {
371
- this.grammar = matcher;
372
- this.input = input;
532
+ _ctx;
533
+ _succeeded;
534
+ constructor(grammar, startExpr, ctx, succeeded) {
535
+ this.grammar = grammar;
373
536
  this.startExpr = startExpr;
374
- this._cst = cst;
375
- this._rightmostFailurePosition = rightmostFailurePosition;
376
- this._rightmostFailures = optRecordedFailures;
377
- // TODO: Define these as lazy properties, like in the JS implementation.
378
- if (this.failed()) {
379
- this.shortMessage = this.message = `Match failed at pos ${rightmostFailurePosition}`;
380
- }
537
+ this._ctx = ctx;
538
+ this._succeeded = succeeded;
381
539
  }
382
540
  [Symbol.dispose]() {
383
541
  this.detach();
@@ -386,41 +544,66 @@ export class MatchResult {
386
544
  throw new Error('MatchResult is not attached to any grammar');
387
545
  }
388
546
  succeeded() {
389
- return !!this._cst;
547
+ return this._succeeded;
390
548
  }
391
549
  failed() {
392
- return !this.succeeded();
550
+ return !this._succeeded;
551
+ }
552
+ toString() {
553
+ return this.failed()
554
+ ? '[match failed at position ' + this.getRightmostFailurePosition() + ']'
555
+ : '[match succeeded]';
556
+ }
557
+ use(cb) {
558
+ try {
559
+ this.grammar._beginUse(this);
560
+ return cb(this);
561
+ }
562
+ finally {
563
+ this.grammar._endUse(this);
564
+ }
565
+ }
566
+ }
567
+ function createMatchResult(grammar, startExpr, ctx, succeeded) {
568
+ return succeeded
569
+ ? new SucceededMatchResult(grammar, startExpr, ctx, succeeded)
570
+ : new FailedMatchResult(grammar, startExpr, ctx, succeeded, grammar.getRightmostFailurePosition());
571
+ }
572
+ class SucceededMatchResult extends MatchResult {
573
+ _cst;
574
+ constructor(grammar, startExpr, ctx, succeeded) {
575
+ super(grammar, startExpr, ctx, succeeded);
576
+ this._cst = grammar.getCstRoot();
577
+ }
578
+ }
579
+ class FailedMatchResult extends MatchResult {
580
+ constructor(grammar, startExpr, ctx, succeeded, rightmostFailurePosition, optRecordedFailures) {
581
+ super(grammar, startExpr, ctx, succeeded);
582
+ this._rightmostFailurePosition = rightmostFailurePosition;
583
+ this._rightmostFailures = optRecordedFailures;
584
+ // TODO: Define these as lazy properties, like in the JS implementation.
585
+ if (this.failed()) {
586
+ this.shortMessage = this.message = `Match failed at pos ${rightmostFailurePosition}`;
587
+ }
393
588
  }
589
+ _rightmostFailurePosition;
590
+ _rightmostFailures;
591
+ shortMessage;
592
+ message;
394
593
  getRightmostFailurePosition() {
395
594
  return this._rightmostFailurePosition;
396
595
  }
397
596
  getRightmostFailures() {
398
597
  throw new Error('Not implemented yet: getRightmostFailures');
399
598
  }
400
- toString() {
401
- return this.succeeded()
402
- ? '[match succeeded]'
403
- : '[match failed at position ' + this.getRightmostFailurePosition() + ']';
404
- }
405
599
  // Return a string summarizing the expected contents of the input stream when
406
600
  // the match failure occurred.
407
601
  getExpectedText() {
408
- if (this.succeeded()) {
409
- throw new Error('cannot get expected text of a successful MatchResult');
410
- }
602
+ assert(!this._succeeded, 'cannot get expected text of a successful MatchResult');
411
603
  throw new Error('Not implemented yet: getExpectedText');
412
604
  }
413
605
  getInterval() {
414
606
  throw new Error('Not implemented yet: getInterval');
415
607
  }
416
- use(cb) {
417
- try {
418
- this.grammar._beginUse(this);
419
- return cb(this);
420
- }
421
- finally {
422
- this.grammar._endUse(this);
423
- }
424
- }
425
608
  }
426
609
  //# sourceMappingURL=miniohm.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohm-js/wasm",
3
- "version": "0.6.7",
3
+ "version": "0.6.9",
4
4
  "description": "Compile Ohm.js grammars to WebAsssembly",
5
5
  "main": "dist/index.js",
6
6
  "exports": {