@ohm-js/wasm 0.6.8 → 0.6.10

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,22 @@
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 function createToAst<TNode = any>(mapping: AstMapping<TNode>, opts?: {
7
+ debug?: boolean;
8
+ }): (nodeOrResult: MatchResult | CstNode) => TNode;
9
+ export declare class AstBuilder<TNode = any> {
7
10
  currNode?: CstNode;
8
11
  private _mapping;
9
12
  private _depth;
10
13
  private _debug;
11
- constructor(mapping: AstMapping<T>, opts?: {
14
+ constructor(mapping: AstMapping<TNode>, opts?: {
12
15
  debug?: boolean;
13
16
  });
14
17
  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;
18
+ _visitTerminal(node: TerminalNode): string;
19
+ _visitNonterminal(node: NonterminalNode): unknown;
20
+ toAst(nodeOrResult: MatchResult | CstNode): TNode;
19
21
  }
20
22
  //# sourceMappingURL=AstBuilder.d.ts.map
@@ -4,7 +4,11 @@ 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
+ }
9
+ export function createToAst(mapping, opts = {}) {
10
+ const builder = new AstBuilder(mapping, opts);
11
+ return (nodeOrResult) => builder.toAst(nodeOrResult);
8
12
  }
9
13
  export class AstBuilder {
10
14
  currNode;
@@ -14,10 +18,10 @@ export class AstBuilder {
14
18
  constructor(mapping, opts = {}) {
15
19
  const handleListOf = (child) => this.toAst(child);
16
20
  const handleEmptyListOf = () => [];
17
- const handleNonemptyListOf = (first, iterSepAndElem) => [
18
- this.toAst(first),
19
- ...iterSepAndElem.collect((_, elem) => this.toAst(elem)),
20
- ];
21
+ const handleNonemptyListOf = (first, sepAndElemList) => {
22
+ assert(!!sepAndElemList?.isList(), 'Expected a ListNode');
23
+ return [this.toAst(first), ...sepAndElemList.collect((_, elem) => this.toAst(elem))];
24
+ };
21
25
  this._mapping = {
22
26
  listOf: handleListOf,
23
27
  ListOf: handleListOf,
@@ -37,15 +41,15 @@ export class AstBuilder {
37
41
  return node.sourceString;
38
42
  }
39
43
  _visitNonterminal(node) {
40
- const { children, ruleName } = node;
44
+ const { children, ctorName } = node;
41
45
  const mapping = this._mapping;
42
- this._debugLog(`> ${ruleName}`);
46
+ this._debugLog(`> ${ctorName}`);
43
47
  const dbgReturn = (val) => {
44
- this._debugLog(`| ${ruleName} DONE`);
48
+ this._debugLog(`| ${ctorName} DONE`);
45
49
  return val;
46
50
  };
47
51
  // without customization
48
- if (!Object.hasOwn(mapping, ruleName)) {
52
+ if (!Object.hasOwn(mapping, ctorName)) {
49
53
  // lexical rule
50
54
  if (node.isLexical()) {
51
55
  return node.sourceString;
@@ -58,22 +62,22 @@ export class AstBuilder {
58
62
  // rest: terms with multiple children
59
63
  }
60
64
  // direct forward
61
- if (typeof mapping[ruleName] === 'number') {
62
- const idx = mapping[ruleName];
63
- return dbgReturn(this.toAst(childAt(children, idx, ruleName)));
65
+ if (typeof mapping[ctorName] === 'number') {
66
+ const idx = mapping[ctorName];
67
+ return dbgReturn(this.toAst(childAt(children, idx, ctorName)));
64
68
  }
65
- assert(typeof mapping[ruleName] !== 'function', "shouldn't be possible");
69
+ assert(typeof mapping[ctorName] !== 'function', "shouldn't be possible");
66
70
  // named/mapped children or unnamed children ('0', '1', '2', ...)
67
- const propMap = mapping[ruleName] || children;
71
+ const propMap = mapping[ctorName] || children;
68
72
  const ans = {
69
- type: ruleName,
73
+ type: ctorName,
70
74
  };
71
75
  // eslint-disable-next-line guard-for-in
72
76
  for (const prop in propMap) {
73
- const mappedProp = mapping[ruleName] && mapping[ruleName][prop];
77
+ const mappedProp = mapping[ctorName] && mapping[ctorName][prop];
74
78
  if (typeof mappedProp === 'number') {
75
79
  // direct forward
76
- ans[prop] = this.toAst(childAt(children, mappedProp, ruleName, prop));
80
+ ans[prop] = this.toAst(childAt(children, mappedProp, ctorName, prop));
77
81
  }
78
82
  else if (typeof mappedProp === 'string' ||
79
83
  typeof mappedProp === 'boolean' ||
@@ -102,33 +106,26 @@ export class AstBuilder {
102
106
  }
103
107
  return dbgReturn(ans);
104
108
  }
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
109
  toAst(nodeOrResult) {
116
110
  let node = nodeOrResult;
117
111
  if (typeof nodeOrResult.succeeded === 'function') {
118
112
  const matchResult = nodeOrResult;
119
113
  assert(matchResult.succeeded(), 'Cannot convert failed match result to AST');
120
- node = checkNotNull(matchResult._cst);
114
+ node = matchResult.grammar.getCstRoot();
121
115
  }
122
116
  let ans;
123
117
  this._depth++;
124
118
  if (node.isTerminal()) {
125
119
  ans = this._visitTerminal(node);
126
120
  }
127
- else if (node.isIter()) {
128
- ans = this._visitIter(node);
121
+ else if (node.isOptional()) {
122
+ ans = node.ifPresent((child) => this.toAst(child), () => null);
123
+ }
124
+ else if (node.isList() || node.isSeq()) {
125
+ ans = node.children.map(c => this.toAst(c));
129
126
  }
130
127
  else {
131
- assert(node.isNonterminal(), `Unknown node type: ${node._type}`);
128
+ assert(node.isNonterminal(), `Unknown node type: ${node.type}`);
132
129
  this.currNode = node;
133
130
  ans =
134
131
  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 { createToAst } from './createToAst.ts';
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 { createToAst } from "./createToAst.js";
24
25
  //# sourceMappingURL=compat.js.map
@@ -0,0 +1,22 @@
1
+ import type { CstNode, CstNodeChildren, MatchResult, NonterminalNode, TerminalNode } from './miniohm.ts';
2
+ export type AstNodeTemplate<R> = {
3
+ [property: string]: number | string | boolean | object | null | ((children: CstNodeChildren) => R);
4
+ };
5
+ export type AstMapping<R> = Record<string, AstNodeTemplate<R> | number | ((...children: CstNodeChildren) => R)>;
6
+ export declare function createToAst<TNode = any>(mapping: AstMapping<TNode>, opts?: {
7
+ debug?: boolean;
8
+ }): (nodeOrResult: MatchResult | CstNode) => TNode;
9
+ export declare class AstBuilder<TNode = any> {
10
+ currNode?: CstNode;
11
+ private _mapping;
12
+ private _depth;
13
+ private _debug;
14
+ constructor(mapping: AstMapping<TNode>, opts?: {
15
+ debug?: boolean;
16
+ });
17
+ private _debugLog;
18
+ _visitTerminal(node: TerminalNode): string;
19
+ _visitNonterminal(node: NonterminalNode): unknown;
20
+ toAst(nodeOrResult: MatchResult | CstNode): TNode;
21
+ }
22
+ //# sourceMappingURL=createToAst.d.ts.map
@@ -0,0 +1,140 @@
1
+ import { assert, checkNotNull } from "./assert.js";
2
+ function childAt(children, idx, ruleName, propName = '') {
3
+ if (idx > children.length) {
4
+ const path = propName ? `${ruleName}.${propName}` : ruleName;
5
+ throw new Error(`${path}: Child index ${idx} out of range`);
6
+ }
7
+ return checkNotNull(children[idx]);
8
+ }
9
+ export function createToAst(mapping, opts = {}) {
10
+ const builder = new AstBuilder(mapping, opts);
11
+ return (nodeOrResult) => builder.toAst(nodeOrResult);
12
+ }
13
+ export class AstBuilder {
14
+ currNode;
15
+ _mapping;
16
+ _depth = -1;
17
+ _debug = false;
18
+ constructor(mapping, opts = {}) {
19
+ const handleListOf = (child) => this.toAst(child);
20
+ const handleEmptyListOf = () => [];
21
+ const handleNonemptyListOf = (first, sepAndElemList) => {
22
+ assert(!!sepAndElemList?.isList(), 'Expected a ListNode');
23
+ return [this.toAst(first), ...sepAndElemList.collect((_, elem) => this.toAst(elem))];
24
+ };
25
+ this._mapping = {
26
+ listOf: handleListOf,
27
+ ListOf: handleListOf,
28
+ emptyListOf: handleEmptyListOf,
29
+ EmptyListOf: handleEmptyListOf,
30
+ nonemptyListOf: handleNonemptyListOf,
31
+ NonemptyListOf: handleNonemptyListOf,
32
+ ...mapping,
33
+ };
34
+ this._debug = opts.debug ?? false;
35
+ }
36
+ _debugLog(...data) {
37
+ if (this._debug)
38
+ console.log(' '.repeat(this._depth), ...data);
39
+ }
40
+ _visitTerminal(node) {
41
+ return node.sourceString;
42
+ }
43
+ _visitNonterminal(node) {
44
+ const { children, ctorName } = node;
45
+ const mapping = this._mapping;
46
+ this._debugLog(`> ${ctorName}`);
47
+ const dbgReturn = (val) => {
48
+ this._debugLog(`| ${ctorName} DONE`);
49
+ return val;
50
+ };
51
+ // without customization
52
+ if (!Object.hasOwn(mapping, ctorName)) {
53
+ // lexical rule
54
+ if (node.isLexical()) {
55
+ return node.sourceString;
56
+ }
57
+ // singular node (e.g. only surrounded by literals or lookaheads)
58
+ const realChildren = children.filter(c => !c.isTerminal());
59
+ if (realChildren.length === 1) {
60
+ return dbgReturn(this.toAst(realChildren[0]));
61
+ }
62
+ // rest: terms with multiple children
63
+ }
64
+ // direct forward
65
+ if (typeof mapping[ctorName] === 'number') {
66
+ const idx = mapping[ctorName];
67
+ return dbgReturn(this.toAst(childAt(children, idx, ctorName)));
68
+ }
69
+ assert(typeof mapping[ctorName] !== 'function', "shouldn't be possible");
70
+ // named/mapped children or unnamed children ('0', '1', '2', ...)
71
+ const propMap = mapping[ctorName] || children;
72
+ const ans = {
73
+ type: ctorName,
74
+ };
75
+ // eslint-disable-next-line guard-for-in
76
+ for (const prop in propMap) {
77
+ const mappedProp = mapping[ctorName] && mapping[ctorName][prop];
78
+ if (typeof mappedProp === 'number') {
79
+ // direct forward
80
+ ans[prop] = this.toAst(childAt(children, mappedProp, ctorName, prop));
81
+ }
82
+ else if (typeof mappedProp === 'string' ||
83
+ typeof mappedProp === 'boolean' ||
84
+ mappedProp === null) {
85
+ // primitive value
86
+ ans[prop] = mappedProp;
87
+ }
88
+ else if (typeof mappedProp === 'object' && mappedProp instanceof Number) {
89
+ // primitive number (must be unboxed)
90
+ ans[prop] = Number(mappedProp);
91
+ }
92
+ else if (typeof mappedProp === 'function') {
93
+ // computed value
94
+ ans[prop] = mappedProp.call(this, children);
95
+ }
96
+ else if (mappedProp === undefined) {
97
+ const child = children[Number(prop)];
98
+ if (child && !child.isTerminal()) {
99
+ ans[prop] = this.toAst(child);
100
+ }
101
+ else {
102
+ // delete predefined 'type' properties, like 'type', if explicitly removed
103
+ delete ans[prop];
104
+ }
105
+ }
106
+ }
107
+ return dbgReturn(ans);
108
+ }
109
+ toAst(nodeOrResult) {
110
+ let node = nodeOrResult;
111
+ if (typeof nodeOrResult.succeeded === 'function') {
112
+ const matchResult = nodeOrResult;
113
+ assert(matchResult.succeeded(), 'Cannot convert failed match result to AST');
114
+ node = matchResult.grammar.getCstRoot();
115
+ }
116
+ let ans;
117
+ this._depth++;
118
+ if (node.isTerminal()) {
119
+ ans = this._visitTerminal(node);
120
+ }
121
+ else if (node.isOptional()) {
122
+ ans = node.ifPresent((child) => this.toAst(child), () => null);
123
+ }
124
+ else if (node.isList() || node.isSeq()) {
125
+ ans = node.children.map(c => this.toAst(c));
126
+ }
127
+ else {
128
+ assert(node.isNonterminal(), `Unknown node type: ${node.type}`);
129
+ this.currNode = node;
130
+ ans =
131
+ typeof this._mapping[node.ctorName] === 'function'
132
+ ? this._mapping[node.ctorName].apply(this, node.children)
133
+ : this._visitNonterminal(node);
134
+ this.currNode = undefined;
135
+ }
136
+ this._depth--;
137
+ return ans;
138
+ }
139
+ }
140
+ //# sourceMappingURL=createToAst.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
- collect<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
- collect(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.8",
3
+ "version": "0.6.10",
4
4
  "description": "Compile Ohm.js grammars to WebAsssembly",
5
5
  "main": "dist/index.js",
6
6
  "exports": {