@ohm-js/wasm 0.6.16 → 0.7.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/build/ohmRuntime.wasm_sections.d.ts +9 -3
- package/dist/build/ohmRuntime.wasm_sections.js +32 -26
- package/dist/src/Compiler.d.ts +26 -19
- package/dist/src/Compiler.js +240 -45
- package/dist/src/ir.d.ts +6 -3
- package/dist/src/ir.js +6 -4
- package/dist/src/miniohm.d.ts +29 -8
- package/dist/src/miniohm.js +154 -37
- package/dist/src/util.d.ts +16 -0
- package/dist/src/util.js +150 -0
- package/package.json +1 -1
- package/dist/src/AstBuilder.d.ts +0 -22
- package/dist/src/AstBuilder.js +0 -140
package/dist/src/ir.js
CHANGED
|
@@ -6,9 +6,10 @@ export const alt = (children) => ({
|
|
|
6
6
|
outArity: outArity(children[0]),
|
|
7
7
|
});
|
|
8
8
|
export const any = () => ({ type: 'Any' });
|
|
9
|
-
export const apply = (ruleName, children = []) => ({
|
|
9
|
+
export const apply = (ruleName, descriptionId, children = []) => ({
|
|
10
10
|
type: 'Apply',
|
|
11
11
|
ruleName,
|
|
12
|
+
descriptionId,
|
|
12
13
|
children,
|
|
13
14
|
});
|
|
14
15
|
export const applyGeneralized = (ruleName, caseIdx) => ({
|
|
@@ -47,7 +48,7 @@ export const seq = (children) => ({
|
|
|
47
48
|
outArity: children.reduce((acc, child) => acc + outArity(child), 0),
|
|
48
49
|
});
|
|
49
50
|
export const star = (child) => ({ type: 'Star', child });
|
|
50
|
-
export const terminal = (value
|
|
51
|
+
export const terminal = (value) => ({
|
|
51
52
|
type: 'Terminal',
|
|
52
53
|
value,
|
|
53
54
|
});
|
|
@@ -55,9 +56,10 @@ export const unicodeChar = (categoryOrProp) => ({
|
|
|
55
56
|
type: 'UnicodeChar',
|
|
56
57
|
categoryOrProp,
|
|
57
58
|
});
|
|
58
|
-
export const liftedTerminal = (terminalId) => ({
|
|
59
|
+
export const liftedTerminal = (terminalId, failureId) => ({
|
|
59
60
|
type: 'LiftedTerminal',
|
|
60
61
|
terminalId,
|
|
62
|
+
failureId,
|
|
61
63
|
});
|
|
62
64
|
// Helpers
|
|
63
65
|
// -------
|
|
@@ -119,7 +121,7 @@ export function substituteParams(exp, actuals) {
|
|
|
119
121
|
case 'Apply':
|
|
120
122
|
if (exp.children.length === 0)
|
|
121
123
|
return exp;
|
|
122
|
-
return apply(exp.ruleName, exp.children.map((c) => {
|
|
124
|
+
return apply(exp.ruleName, exp.descriptionId, exp.children.map((c) => {
|
|
123
125
|
const ans = substituteParams(c, actuals);
|
|
124
126
|
return checkApplyLike(ans);
|
|
125
127
|
}));
|
package/dist/src/miniohm.d.ts
CHANGED
|
@@ -6,6 +6,21 @@ export declare const CstNodeType: {
|
|
|
6
6
|
readonly SEQ: 4;
|
|
7
7
|
};
|
|
8
8
|
export type CstNodeType = (typeof CstNodeType)[keyof typeof CstNodeType];
|
|
9
|
+
export declare class Interval {
|
|
10
|
+
startIdx: number;
|
|
11
|
+
endIdx: number;
|
|
12
|
+
private _sourceString;
|
|
13
|
+
constructor(sourceString: string, startIdx: number, endIdx: number);
|
|
14
|
+
get sourceString(): string;
|
|
15
|
+
get contents(): string;
|
|
16
|
+
}
|
|
17
|
+
export declare class Failure {
|
|
18
|
+
private _description;
|
|
19
|
+
private _fluffy;
|
|
20
|
+
constructor(description: string, fluffy: boolean);
|
|
21
|
+
isFluffy(): boolean;
|
|
22
|
+
toString(): string;
|
|
23
|
+
}
|
|
9
24
|
export declare class WasmGrammar {
|
|
10
25
|
name: string;
|
|
11
26
|
private _instance?;
|
|
@@ -13,6 +28,7 @@ export declare class WasmGrammar {
|
|
|
13
28
|
private _ruleIds;
|
|
14
29
|
private _ruleNames;
|
|
15
30
|
private _input;
|
|
31
|
+
_failureDescriptions: string[];
|
|
16
32
|
private _resultStack;
|
|
17
33
|
private _managedResultCount;
|
|
18
34
|
/**
|
|
@@ -30,9 +46,11 @@ export declare class WasmGrammar {
|
|
|
30
46
|
_instantiate(source: BufferSource, debugImports?: any): Promise<this>;
|
|
31
47
|
_instantiateStreaming(source: Response | Promise<Response>, debugImports?: any): Promise<WasmGrammar>;
|
|
32
48
|
private _getGrammarName;
|
|
33
|
-
private
|
|
49
|
+
private _extractStrings;
|
|
34
50
|
private _detachMatchResult;
|
|
35
51
|
match<T>(input: string, ruleName?: string): MatchResult;
|
|
52
|
+
recordFailures(): number[];
|
|
53
|
+
getFailureDescription(id: number): string;
|
|
36
54
|
getMemorySizeBytes(): number;
|
|
37
55
|
getCstRoot(): CstNode;
|
|
38
56
|
private _fillInputBuffer;
|
|
@@ -196,27 +214,30 @@ export declare class MatchResult {
|
|
|
196
214
|
_ctx: MatchContext;
|
|
197
215
|
_succeeded: boolean;
|
|
198
216
|
constructor(grammar: WasmGrammar, startExpr: string, ctx: MatchContext, succeeded: boolean);
|
|
217
|
+
get input(): string;
|
|
199
218
|
[Symbol.dispose](): void;
|
|
200
219
|
detach(): void;
|
|
201
220
|
succeeded(): this is SucceededMatchResult;
|
|
202
221
|
failed(): this is FailedMatchResult;
|
|
203
222
|
toString(): string;
|
|
204
|
-
use<T>(cb: (r:
|
|
223
|
+
use<T>(cb: (r: this) => T): T;
|
|
205
224
|
}
|
|
206
225
|
export declare class SucceededMatchResult extends MatchResult {
|
|
207
226
|
_cst: CstNode;
|
|
208
227
|
constructor(grammar: WasmGrammar, startExpr: string, ctx: MatchContext, succeeded: boolean);
|
|
209
228
|
}
|
|
210
229
|
export declare class FailedMatchResult extends MatchResult {
|
|
211
|
-
constructor(grammar: WasmGrammar, startExpr: string, ctx: MatchContext, succeeded: boolean, rightmostFailurePosition: number
|
|
230
|
+
constructor(grammar: WasmGrammar, startExpr: string, ctx: MatchContext, succeeded: boolean, rightmostFailurePosition: number);
|
|
212
231
|
_rightmostFailurePosition: number;
|
|
213
|
-
_rightmostFailures
|
|
214
|
-
|
|
215
|
-
message: string;
|
|
232
|
+
private _rightmostFailures;
|
|
233
|
+
private _failureDescriptions;
|
|
216
234
|
getRightmostFailurePosition(): number;
|
|
217
|
-
getRightmostFailures():
|
|
235
|
+
getRightmostFailures(): Failure[];
|
|
236
|
+
private _getFailureDescriptions;
|
|
218
237
|
getExpectedText(): string;
|
|
219
|
-
getInterval():
|
|
238
|
+
getInterval(): Interval;
|
|
239
|
+
get message(): string;
|
|
240
|
+
get shortMessage(): string;
|
|
220
241
|
}
|
|
221
242
|
export {};
|
|
222
243
|
//# sourceMappingURL=miniohm.d.ts.map
|
package/dist/src/miniohm.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assert, checkNotNull } from "./assert.js";
|
|
2
|
+
import { getLineAndColumn, getLineAndColumnMessage } from 'ohm-js/extras';
|
|
2
3
|
const MATCH_RECORD_TYPE_MASK = 0b11;
|
|
3
4
|
// A MatchRecord is the representation of a CstNode in Wasm linear memory.
|
|
4
5
|
const MatchRecordType = {
|
|
@@ -30,6 +31,37 @@ const UnicodeCategoryNames = [
|
|
|
30
31
|
'Lo', // Other_Letter
|
|
31
32
|
];
|
|
32
33
|
const utf8 = new TextDecoder('utf-8');
|
|
34
|
+
// Minimal implementation of Interval (for FailedMatchResult)
|
|
35
|
+
export class Interval {
|
|
36
|
+
startIdx;
|
|
37
|
+
endIdx;
|
|
38
|
+
_sourceString;
|
|
39
|
+
constructor(sourceString, startIdx, endIdx) {
|
|
40
|
+
this._sourceString = sourceString;
|
|
41
|
+
this.startIdx = startIdx;
|
|
42
|
+
this.endIdx = endIdx;
|
|
43
|
+
}
|
|
44
|
+
get sourceString() {
|
|
45
|
+
return this._sourceString;
|
|
46
|
+
}
|
|
47
|
+
get contents() {
|
|
48
|
+
return this._sourceString.slice(this.startIdx, this.endIdx);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export class Failure {
|
|
52
|
+
_description;
|
|
53
|
+
_fluffy;
|
|
54
|
+
constructor(description, fluffy) {
|
|
55
|
+
this._description = description;
|
|
56
|
+
this._fluffy = fluffy;
|
|
57
|
+
}
|
|
58
|
+
isFluffy() {
|
|
59
|
+
return this._fluffy;
|
|
60
|
+
}
|
|
61
|
+
toString() {
|
|
62
|
+
return this._description;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
33
65
|
function regexFromCategoryBitmap(bitmap) {
|
|
34
66
|
const cats = [];
|
|
35
67
|
for (let i = 0; i < 32; i++) {
|
|
@@ -40,6 +72,35 @@ function regexFromCategoryBitmap(bitmap) {
|
|
|
40
72
|
return new RegExp(cats.map(cat => `\\p{${cat}}`).join('|'), 'uy' // u: unicode, y: sticky
|
|
41
73
|
);
|
|
42
74
|
}
|
|
75
|
+
function parseStringTable(module, sectionName) {
|
|
76
|
+
const sections = WebAssembly.Module.customSections(module, sectionName);
|
|
77
|
+
assert(sections.length === 1, `Expected one ${sectionName} section, found ${sections.length}`);
|
|
78
|
+
const data = new Uint8Array(sections[0]);
|
|
79
|
+
const dataView = new DataView(data.buffer);
|
|
80
|
+
let offset = 0;
|
|
81
|
+
const parseU32 = () => {
|
|
82
|
+
// Quick 'n dirty ULeb128 parsing, assuming no more than 2 bytes.
|
|
83
|
+
const b1 = dataView.getUint8(offset++);
|
|
84
|
+
let value = b1 & 0x7f;
|
|
85
|
+
if (b1 & 0x80) {
|
|
86
|
+
const b2 = dataView.getUint8(offset++);
|
|
87
|
+
assert((b2 & 0x80) === 0, 'Expected max two bytes');
|
|
88
|
+
value |= (b2 & 0x7f) << 7;
|
|
89
|
+
}
|
|
90
|
+
return value;
|
|
91
|
+
};
|
|
92
|
+
const decoder = new TextDecoder('utf-8');
|
|
93
|
+
const numEntries = parseU32();
|
|
94
|
+
const ans = [];
|
|
95
|
+
for (let i = 0; i < numEntries; i++) {
|
|
96
|
+
const stringLen = parseU32();
|
|
97
|
+
const bytes = data.slice(offset, offset + stringLen);
|
|
98
|
+
offset += stringLen;
|
|
99
|
+
const name = decoder.decode(bytes);
|
|
100
|
+
ans.push(name);
|
|
101
|
+
}
|
|
102
|
+
return ans;
|
|
103
|
+
}
|
|
43
104
|
export class WasmGrammar {
|
|
44
105
|
name = '';
|
|
45
106
|
_instance = undefined;
|
|
@@ -92,6 +153,7 @@ export class WasmGrammar {
|
|
|
92
153
|
_ruleIds = new Map();
|
|
93
154
|
_ruleNames = [];
|
|
94
155
|
_input = '';
|
|
156
|
+
_failureDescriptions = []; // Should be treated as package private
|
|
95
157
|
_resultStack = [];
|
|
96
158
|
_managedResultCount = 0;
|
|
97
159
|
/**
|
|
@@ -109,7 +171,7 @@ export class WasmGrammar {
|
|
|
109
171
|
}
|
|
110
172
|
_init(module, instance) {
|
|
111
173
|
this._instance = instance;
|
|
112
|
-
this.
|
|
174
|
+
this._extractStrings(module);
|
|
113
175
|
this.name = this._getGrammarName(module);
|
|
114
176
|
return this;
|
|
115
177
|
}
|
|
@@ -159,32 +221,15 @@ export class WasmGrammar {
|
|
|
159
221
|
const decoder = new TextDecoder('utf-8');
|
|
160
222
|
return decoder.decode(data);
|
|
161
223
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
assert(
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
let value = b1 & 0x7f;
|
|
172
|
-
if (b1 & 0x80) {
|
|
173
|
-
const b2 = dataView.getUint8(offset++);
|
|
174
|
-
assert((b2 & 0x80) === 0, 'Expected max two bytes');
|
|
175
|
-
value |= (b2 & 0x7f) << 7;
|
|
176
|
-
}
|
|
177
|
-
return value;
|
|
178
|
-
};
|
|
179
|
-
const decoder = new TextDecoder('utf-8');
|
|
180
|
-
const numEntries = parseU32();
|
|
181
|
-
for (let i = 0; i < numEntries; i++) {
|
|
182
|
-
const stringLen = parseU32();
|
|
183
|
-
const bytes = data.slice(offset, offset + stringLen);
|
|
184
|
-
offset += stringLen;
|
|
185
|
-
const name = decoder.decode(bytes);
|
|
186
|
-
this._ruleIds.set(name, i);
|
|
187
|
-
this._ruleNames.push(name);
|
|
224
|
+
_extractStrings(module) {
|
|
225
|
+
assert(this._ruleNames.length === 0);
|
|
226
|
+
assert(this._ruleIds.size === 0);
|
|
227
|
+
for (const ruleName of parseStringTable(module, 'ruleNames')) {
|
|
228
|
+
this._ruleIds.set(ruleName, this._ruleIds.size);
|
|
229
|
+
this._ruleNames.push(ruleName);
|
|
230
|
+
}
|
|
231
|
+
for (const str of parseStringTable(module, 'failureDescriptions')) {
|
|
232
|
+
this._failureDescriptions.push(str);
|
|
188
233
|
}
|
|
189
234
|
}
|
|
190
235
|
_detachMatchResult(result) {
|
|
@@ -209,6 +254,25 @@ export class WasmGrammar {
|
|
|
209
254
|
this._resultStack.push(result);
|
|
210
255
|
return result;
|
|
211
256
|
}
|
|
257
|
+
recordFailures() {
|
|
258
|
+
const { exports } = this._instance;
|
|
259
|
+
exports.recordFailures(this._ruleIds.get(this._ruleNames[0]));
|
|
260
|
+
const ans = [];
|
|
261
|
+
for (let i = 0; i < exports.getRecordedFailuresLength(); i++) {
|
|
262
|
+
if (!exports.isFluffy(i)) {
|
|
263
|
+
// Filter out fluffy failures
|
|
264
|
+
ans.push(exports.recordedFailuresAt(i));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Deduplicate
|
|
268
|
+
return [...new Set(ans)];
|
|
269
|
+
}
|
|
270
|
+
getFailureDescription(id) {
|
|
271
|
+
const isNot = (id & 0x80000000) !== 0;
|
|
272
|
+
const realId = id & 0x7fffffff;
|
|
273
|
+
const desc = this._failureDescriptions[realId];
|
|
274
|
+
return isNot ? 'not ' + desc : desc;
|
|
275
|
+
}
|
|
212
276
|
getMemorySizeBytes() {
|
|
213
277
|
return this._instance.exports.memory.buffer.byteLength;
|
|
214
278
|
}
|
|
@@ -541,6 +605,9 @@ export class MatchResult {
|
|
|
541
605
|
this._ctx = ctx;
|
|
542
606
|
this._succeeded = succeeded;
|
|
543
607
|
}
|
|
608
|
+
get input() {
|
|
609
|
+
return this.grammar._input;
|
|
610
|
+
}
|
|
544
611
|
[Symbol.dispose]() {
|
|
545
612
|
this.detach();
|
|
546
613
|
}
|
|
@@ -581,31 +648,81 @@ export class SucceededMatchResult extends MatchResult {
|
|
|
581
648
|
}
|
|
582
649
|
}
|
|
583
650
|
export class FailedMatchResult extends MatchResult {
|
|
584
|
-
constructor(grammar, startExpr, ctx, succeeded, rightmostFailurePosition
|
|
651
|
+
constructor(grammar, startExpr, ctx, succeeded, rightmostFailurePosition) {
|
|
585
652
|
super(grammar, startExpr, ctx, succeeded);
|
|
586
653
|
this._rightmostFailurePosition = rightmostFailurePosition;
|
|
587
|
-
this._rightmostFailures = optRecordedFailures;
|
|
588
|
-
// TODO: Define these as lazy properties, like in the JS implementation.
|
|
589
|
-
this.shortMessage = this.message = `Match failed at pos ${rightmostFailurePosition}`;
|
|
590
654
|
}
|
|
591
655
|
_rightmostFailurePosition;
|
|
592
|
-
_rightmostFailures;
|
|
593
|
-
|
|
594
|
-
message;
|
|
656
|
+
_rightmostFailures = null;
|
|
657
|
+
_failureDescriptions = null;
|
|
595
658
|
getRightmostFailurePosition() {
|
|
596
659
|
return this._rightmostFailurePosition;
|
|
597
660
|
}
|
|
598
661
|
getRightmostFailures() {
|
|
599
|
-
|
|
662
|
+
if (this._rightmostFailures === null) {
|
|
663
|
+
const { exports } = this.grammar._instance;
|
|
664
|
+
const ruleIds = this.grammar._ruleIds;
|
|
665
|
+
const ruleNames = this.grammar._ruleNames;
|
|
666
|
+
exports.recordFailures(ruleIds.get(ruleNames[0]));
|
|
667
|
+
// Use a Map to deduplicate by description while preserving fluffy status.
|
|
668
|
+
// A failure is only fluffy if ALL occurrences are fluffy.
|
|
669
|
+
const failureMap = new Map();
|
|
670
|
+
for (let i = 0; i < exports.getRecordedFailuresLength(); i++) {
|
|
671
|
+
const id = exports.recordedFailuresAt(i);
|
|
672
|
+
const desc = this.grammar.getFailureDescription(id);
|
|
673
|
+
const fluffy = exports.isFluffy(i);
|
|
674
|
+
if (failureMap.has(desc)) {
|
|
675
|
+
// Only keep fluffy=true if both are fluffy
|
|
676
|
+
failureMap.set(desc, failureMap.get(desc) && fluffy);
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
failureMap.set(desc, fluffy);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
this._rightmostFailures = Array.from(failureMap.entries()).map(([desc, fluffy]) => new Failure(desc, fluffy));
|
|
683
|
+
}
|
|
684
|
+
return this._rightmostFailures;
|
|
685
|
+
}
|
|
686
|
+
// Get the non-fluffy failure descriptions.
|
|
687
|
+
_getFailureDescriptions() {
|
|
688
|
+
if (this._failureDescriptions === null) {
|
|
689
|
+
this._failureDescriptions = this.getRightmostFailures()
|
|
690
|
+
.filter(f => !f.isFluffy())
|
|
691
|
+
.map(f => f.toString());
|
|
692
|
+
}
|
|
693
|
+
return this._failureDescriptions;
|
|
600
694
|
}
|
|
601
695
|
// Return a string summarizing the expected contents of the input stream when
|
|
602
696
|
// the match failure occurred.
|
|
603
697
|
getExpectedText() {
|
|
604
698
|
assert(!this._succeeded, 'cannot get expected text of a successful MatchResult');
|
|
605
|
-
|
|
699
|
+
const descriptions = this._getFailureDescriptions();
|
|
700
|
+
switch (descriptions.length) {
|
|
701
|
+
case 0:
|
|
702
|
+
return '';
|
|
703
|
+
case 1:
|
|
704
|
+
return descriptions[0];
|
|
705
|
+
case 2:
|
|
706
|
+
return descriptions[0] + ' or ' + descriptions[1];
|
|
707
|
+
default:
|
|
708
|
+
// For 3+ items: "a, b, or c"
|
|
709
|
+
return (descriptions.slice(0, -1).join(', ') +
|
|
710
|
+
', or ' +
|
|
711
|
+
descriptions[descriptions.length - 1]);
|
|
712
|
+
}
|
|
606
713
|
}
|
|
607
714
|
getInterval() {
|
|
608
|
-
|
|
715
|
+
const pos = this.getRightmostFailurePosition();
|
|
716
|
+
return new Interval(this.input, pos, pos);
|
|
717
|
+
}
|
|
718
|
+
get message() {
|
|
719
|
+
const detail = 'Expected ' + this.getExpectedText();
|
|
720
|
+
return getLineAndColumnMessage(this.input, this.getRightmostFailurePosition()) + detail;
|
|
721
|
+
}
|
|
722
|
+
get shortMessage() {
|
|
723
|
+
const detail = 'expected ' + this.getExpectedText();
|
|
724
|
+
const errorInfo = getLineAndColumn(this.input, this.getRightmostFailurePosition());
|
|
725
|
+
return 'Line ' + errorInfo.lineNum + ', col ' + errorInfo.colNum + ': ' + detail;
|
|
609
726
|
}
|
|
610
727
|
}
|
|
611
728
|
//# sourceMappingURL=miniohm.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function awaitBuiltInRules(cb: any): void;
|
|
2
|
+
export function announceBuiltInRules(grammar: any): void;
|
|
3
|
+
export function getLineAndColumn(str: any, offset: any): {
|
|
4
|
+
offset: any;
|
|
5
|
+
lineNum: number;
|
|
6
|
+
colNum: number;
|
|
7
|
+
line: any;
|
|
8
|
+
prevLine: any;
|
|
9
|
+
nextLine: any;
|
|
10
|
+
toString: typeof lineAndColumnToMessage;
|
|
11
|
+
};
|
|
12
|
+
export function getLineAndColumnMessage(str: any, offset: any, ...ranges: any[]): any;
|
|
13
|
+
export function uniqueId(prefix: any): string;
|
|
14
|
+
declare function lineAndColumnToMessage(...ranges: any[]): any;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=util.d.ts.map
|
package/dist/src/util.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import * as common from './common.js';
|
|
2
|
+
// --------------------------------------------------------------------
|
|
3
|
+
// Private stuff
|
|
4
|
+
// --------------------------------------------------------------------
|
|
5
|
+
// Given an array of numbers `arr`, return an array of the numbers as strings,
|
|
6
|
+
// right-justified and padded to the same length.
|
|
7
|
+
function padNumbersToEqualLength(arr) {
|
|
8
|
+
let maxLen = 0;
|
|
9
|
+
const strings = arr.map(n => {
|
|
10
|
+
const str = n.toString();
|
|
11
|
+
maxLen = Math.max(maxLen, str.length);
|
|
12
|
+
return str;
|
|
13
|
+
});
|
|
14
|
+
return strings.map(s => common.padLeft(s, maxLen));
|
|
15
|
+
}
|
|
16
|
+
// Produce a new string that would be the result of copying the contents
|
|
17
|
+
// of the string `src` onto `dest` at offset `offest`.
|
|
18
|
+
function strcpy(dest, src, offset) {
|
|
19
|
+
const origDestLen = dest.length;
|
|
20
|
+
const start = dest.slice(0, offset);
|
|
21
|
+
const end = dest.slice(offset + src.length);
|
|
22
|
+
return (start + src + end).substr(0, origDestLen);
|
|
23
|
+
}
|
|
24
|
+
// Casts the underlying lineAndCol object to a formatted message string,
|
|
25
|
+
// highlighting `ranges`.
|
|
26
|
+
function lineAndColumnToMessage(...ranges) {
|
|
27
|
+
const lineAndCol = this;
|
|
28
|
+
const { offset } = lineAndCol;
|
|
29
|
+
const { repeatStr } = common;
|
|
30
|
+
const sb = new common.StringBuffer();
|
|
31
|
+
sb.append('Line ' + lineAndCol.lineNum + ', col ' + lineAndCol.colNum + ':\n');
|
|
32
|
+
// An array of the previous, current, and next line numbers as strings of equal length.
|
|
33
|
+
const lineNumbers = padNumbersToEqualLength([
|
|
34
|
+
lineAndCol.prevLine == null ? 0 : lineAndCol.lineNum - 1,
|
|
35
|
+
lineAndCol.lineNum,
|
|
36
|
+
lineAndCol.nextLine == null ? 0 : lineAndCol.lineNum + 1,
|
|
37
|
+
]);
|
|
38
|
+
// Helper for appending formatting input lines to the buffer.
|
|
39
|
+
const appendLine = (num, content, prefix) => {
|
|
40
|
+
sb.append(prefix + lineNumbers[num] + ' | ' + content + '\n');
|
|
41
|
+
};
|
|
42
|
+
// Include the previous line for context if possible.
|
|
43
|
+
if (lineAndCol.prevLine != null) {
|
|
44
|
+
appendLine(0, lineAndCol.prevLine, ' ');
|
|
45
|
+
}
|
|
46
|
+
// Line that the error occurred on.
|
|
47
|
+
appendLine(1, lineAndCol.line, '> ');
|
|
48
|
+
// Build up the line that points to the offset and possible indicates one or more ranges.
|
|
49
|
+
// Start with a blank line, and indicate each range by overlaying a string of `~` chars.
|
|
50
|
+
const lineLen = lineAndCol.line.length;
|
|
51
|
+
let indicationLine = repeatStr(' ', lineLen + 1);
|
|
52
|
+
for (let i = 0; i < ranges.length; ++i) {
|
|
53
|
+
let startIdx = ranges[i][0];
|
|
54
|
+
let endIdx = ranges[i][1];
|
|
55
|
+
common.assert(startIdx >= 0 && startIdx <= endIdx, 'range start must be >= 0 and <= end');
|
|
56
|
+
const lineStartOffset = offset - lineAndCol.colNum + 1;
|
|
57
|
+
startIdx = Math.max(0, startIdx - lineStartOffset);
|
|
58
|
+
endIdx = Math.min(endIdx - lineStartOffset, lineLen);
|
|
59
|
+
indicationLine = strcpy(indicationLine, repeatStr('~', endIdx - startIdx), startIdx);
|
|
60
|
+
}
|
|
61
|
+
const gutterWidth = 2 + lineNumbers[1].length + 3;
|
|
62
|
+
sb.append(repeatStr(' ', gutterWidth));
|
|
63
|
+
indicationLine = strcpy(indicationLine, '^', lineAndCol.colNum - 1);
|
|
64
|
+
sb.append(indicationLine.replace(/ +$/, '') + '\n');
|
|
65
|
+
// Include the next line for context if possible.
|
|
66
|
+
if (lineAndCol.nextLine != null) {
|
|
67
|
+
appendLine(2, lineAndCol.nextLine, ' ');
|
|
68
|
+
}
|
|
69
|
+
return sb.contents();
|
|
70
|
+
}
|
|
71
|
+
// --------------------------------------------------------------------
|
|
72
|
+
// Exports
|
|
73
|
+
// --------------------------------------------------------------------
|
|
74
|
+
let builtInRulesCallbacks = [];
|
|
75
|
+
// Since Grammar.BuiltInRules is bootstrapped, most of Ohm can't directly depend it.
|
|
76
|
+
// This function allows modules that do depend on the built-in rules to register a callback
|
|
77
|
+
// that will be called later in the initialization process.
|
|
78
|
+
export function awaitBuiltInRules(cb) {
|
|
79
|
+
builtInRulesCallbacks.push(cb);
|
|
80
|
+
}
|
|
81
|
+
export function announceBuiltInRules(grammar) {
|
|
82
|
+
builtInRulesCallbacks.forEach(cb => {
|
|
83
|
+
cb(grammar);
|
|
84
|
+
});
|
|
85
|
+
builtInRulesCallbacks = null;
|
|
86
|
+
}
|
|
87
|
+
// Return an object with the line and column information for the given
|
|
88
|
+
// offset in `str`.
|
|
89
|
+
export function getLineAndColumn(str, offset) {
|
|
90
|
+
let lineNum = 1;
|
|
91
|
+
let colNum = 1;
|
|
92
|
+
let currOffset = 0;
|
|
93
|
+
let lineStartOffset = 0;
|
|
94
|
+
let nextLine = null;
|
|
95
|
+
let prevLine = null;
|
|
96
|
+
let prevLineStartOffset = -1;
|
|
97
|
+
while (currOffset < offset) {
|
|
98
|
+
const c = str.charAt(currOffset++);
|
|
99
|
+
if (c === '\n') {
|
|
100
|
+
lineNum++;
|
|
101
|
+
colNum = 1;
|
|
102
|
+
prevLineStartOffset = lineStartOffset;
|
|
103
|
+
lineStartOffset = currOffset;
|
|
104
|
+
}
|
|
105
|
+
else if (c !== '\r') {
|
|
106
|
+
colNum++;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Find the end of the target line.
|
|
110
|
+
let lineEndOffset = str.indexOf('\n', lineStartOffset);
|
|
111
|
+
if (lineEndOffset === -1) {
|
|
112
|
+
lineEndOffset = str.length;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
// Get the next line.
|
|
116
|
+
const nextLineEndOffset = str.indexOf('\n', lineEndOffset + 1);
|
|
117
|
+
nextLine =
|
|
118
|
+
nextLineEndOffset === -1
|
|
119
|
+
? str.slice(lineEndOffset)
|
|
120
|
+
: str.slice(lineEndOffset, nextLineEndOffset);
|
|
121
|
+
// Strip leading and trailing EOL char(s).
|
|
122
|
+
nextLine = nextLine.replace(/^\r?\n/, '').replace(/\r$/, '');
|
|
123
|
+
}
|
|
124
|
+
// Get the previous line.
|
|
125
|
+
if (prevLineStartOffset >= 0) {
|
|
126
|
+
// Strip trailing EOL char(s).
|
|
127
|
+
prevLine = str.slice(prevLineStartOffset, lineStartOffset).replace(/\r?\n$/, '');
|
|
128
|
+
}
|
|
129
|
+
// Get the target line, stripping a trailing carriage return if necessary.
|
|
130
|
+
const line = str.slice(lineStartOffset, lineEndOffset).replace(/\r$/, '');
|
|
131
|
+
return {
|
|
132
|
+
offset,
|
|
133
|
+
lineNum,
|
|
134
|
+
colNum,
|
|
135
|
+
line,
|
|
136
|
+
prevLine,
|
|
137
|
+
nextLine,
|
|
138
|
+
toString: lineAndColumnToMessage,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// Return a nicely-formatted string describing the line and column for the
|
|
142
|
+
// given offset in `str` highlighting `ranges`.
|
|
143
|
+
export function getLineAndColumnMessage(str, offset, ...ranges) {
|
|
144
|
+
return getLineAndColumn(str, offset).toString(...ranges);
|
|
145
|
+
}
|
|
146
|
+
export const uniqueId = (() => {
|
|
147
|
+
let idCounter = 0;
|
|
148
|
+
return prefix => '' + prefix + idCounter++;
|
|
149
|
+
})();
|
|
150
|
+
//# sourceMappingURL=util.js.map
|
package/package.json
CHANGED
package/dist/src/AstBuilder.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
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
|
-
};
|
|
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> {
|
|
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=AstBuilder.d.ts.map
|