Skip to content

Commit db98667

Browse files
committed
wasm: Implement MatchResult and return that from match()
1 parent 5f320b6 commit db98667

File tree

9 files changed

+83
-41
lines changed

9 files changed

+83
-41
lines changed

packages/miniohm-js/index.js

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,14 @@ export class WasmMatcher {
156156

157157
match() {
158158
if (process.env.OHM_DEBUG === '1') debugger; // eslint-disable-line no-debugger
159-
return this._instance.exports.match(0);
159+
const succeeded = this._instance.exports.match(0);
160+
return new MatchResult(
161+
this,
162+
this._input,
163+
this._ruleNames[0],
164+
succeeded ? this.getCstRoot() : null,
165+
this.getRightmostFailurePosition(),
166+
);
160167
}
161168

162169
getMemorySizeBytes() {
@@ -288,3 +295,54 @@ class CstNode {
288295
return `CstNode {ctorName: ${ctorName}, sourceString: ${sourceString}, startIdx: ${startIdx} }`;
289296
}
290297
}
298+
299+
export class MatchResult {
300+
constructor(matcher, input, startExpr, cst, rightmostFailurePosition, optRecordedFailures) {
301+
this.matcher = matcher;
302+
this.input = input;
303+
this.startExpr = startExpr;
304+
this._cst = cst;
305+
this._rightmostFailurePosition = rightmostFailurePosition;
306+
this._rightmostFailures = optRecordedFailures;
307+
308+
// TODO: Define these as lazy properties, like in the JS implementation.
309+
if (this.failed()) {
310+
this.shortMessage = this.message = `Match failed at pos ${rightmostFailurePosition}`;
311+
}
312+
}
313+
314+
succeeded() {
315+
return !!this._cst;
316+
}
317+
318+
failed() {
319+
return !this.succeeded();
320+
}
321+
322+
getRightmostFailurePosition() {
323+
return this._rightmostFailurePosition;
324+
}
325+
326+
getRightmostFailures() {
327+
throw new Error('Not implemented yet: getRightmostFailures');
328+
}
329+
330+
toString() {
331+
return this.succeeded() ?
332+
'[match succeeded]' :
333+
'[match failed at position ' + this.getRightmostFailurePosition() + ']';
334+
}
335+
336+
// Return a string summarizing the expected contents of the input stream when
337+
// the match failure occurred.
338+
getExpectedText() {
339+
if (this.succeeded()) {
340+
throw new Error('cannot get expected text of a successful MatchResult');
341+
}
342+
throw new Error('Not implemented yet: getExpectedText');
343+
}
344+
345+
getInterval() {
346+
throw new Error('Not implemented yet: getInterval');
347+
}
348+
}

packages/miniohm-js/toAST.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class Visitor {
4343
}
4444

4545
visitTerminal(node, offset) {
46-
return node.sourceString(offset);
46+
return node.sourceString;
4747
}
4848

4949
visitNonterminal(node, offset) {

packages/wasm/scripts/parseLiquid.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import process from 'node:process';
1717
import {fileURLToPath} from 'node:url';
1818
import * as ohm from 'ohm-js';
1919

20-
import {unparse, wasmMatcherForGrammar} from '../test/_helpers.js';
20+
import {matchWithInput, unparse, wasmMatcherForGrammar} from '../test/_helpers.js';
2121

2222
const __dirname = dirname(fileURLToPath(import.meta.url));
2323
const datadir = join(__dirname, '../test/data');
@@ -27,8 +27,6 @@ const liquid = ohm.grammars(readFileSync(join(datadir, 'liquid-html.ohm'), 'utf8
2727
// Get pattern from command line arguments
2828
const pattern = process.argv[2];
2929

30-
const matchWithInput = (m, str) => (m.setInput(str), m.match());
31-
3230
(async function main() {
3331
const parsedPaths = new Set();
3432
const jsTimes = [];

packages/wasm/test/_helpers.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,9 @@ export function unparse(m) {
4646
}
4747

4848
export const scriptRel = relPath => new URL(relPath, import.meta.url);
49+
50+
// TODO: Consider refactoring this to return true/false.
51+
export function matchWithInput(m, str) {
52+
m.setInput(str);
53+
return m.match().succeeded() ? 1 : 0;
54+
}

packages/wasm/test/test-es5.js

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,14 @@ import {performance} from 'node:perf_hooks';
55
import {fileURLToPath} from 'node:url';
66

77
import es5js from '../../../examples/ecmascript/index.js';
8-
import {wasmMatcherForGrammar} from './_helpers.js';
8+
import {matchWithInput, unparse, wasmMatcherForGrammar} from './_helpers.js';
99
import es5 from './data/_es5.js';
1010

1111
const __dirname = dirname(fileURLToPath(import.meta.url));
1212
const datadir = join(__dirname, 'data');
1313

1414
const html5shivPath = join(datadir, '_html5shiv-3.7.3.js');
1515

16-
const matchWithInput = (m, str) => (m.setInput(str), m.match());
17-
1816
test('basic es5 examples', async t => {
1917
const m = await wasmMatcherForGrammar(es5);
2018
t.is(matchWithInput(m, 'x = 3;'), 1);
@@ -34,8 +32,9 @@ test('html5shiv', async t => {
3432
});
3533

3634
test('unparsing', async t => {
35+
// TODO: Change it back to "Müller" once any properly matches code points.
3736
const source = String.raw`
38-
var obj = {_nm: "Thomas", "full-name": "Thomas Müller", name: function() { return this._nm; }};
37+
var obj = {_nm: "Thomas", "full-name": "Thomas Mueller", name: function() { return this._nm; }};
3938
var arr = [1, "hello", true, null, {x: 2}];
4039
function Car(brand) { this.brand = brand; }
4140
Car.prototype.start = function() { return this.brand + " started"; };
@@ -54,19 +53,5 @@ test('unparsing', async t => {
5453

5554
const m = await wasmMatcherForGrammar(es5);
5655
t.is(matchWithInput(m, source), 1);
57-
58-
let unparsed = '';
59-
60-
let pos = 0;
61-
function walk(node) {
62-
if (node.isTerminal()) {
63-
unparsed += source.slice(pos, pos + node.matchLength);
64-
pos += node.matchLength;
65-
}
66-
for (const child of node.children) {
67-
walk(child);
68-
}
69-
}
70-
walk(m.getCstRoot());
71-
t.is(unparsed, source);
56+
t.is(unparse(m), source);
7257
});

packages/wasm/test/test-failurePos.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,6 @@ const ns = ohm.grammars(grammarSource);
1212
function failurePos(matcher, input) {
1313
matcher.setInput(input);
1414
const result = matcher.match();
15-
// TODO: Unify the APIs.
16-
if (typeof result === 'number') {
17-
assert.equal(result, 0);
18-
return matcher.getRightmostFailurePosition();
19-
}
2015
assert.equal(result.failed(), true, 'expected match failure');
2116
return result.getRightmostFailurePosition();
2217
}
@@ -76,7 +71,7 @@ function arbitraryEdit(input) {
7671
function sameFailurePos(wasmMatcher) {
7772
return fc.property(arbitraryEdit(validInput), input => {
7873
wasmMatcher.setInput(input);
79-
fc.pre(wasmMatcher.match() === 0);
74+
fc.pre(wasmMatcher.match().succeeded());
8075
assert.equal(
8176
ns.LiquidHTML.match(input).getRightmostFailurePosition(),
8277
wasmMatcher.getRightmostFailurePosition(),

packages/wasm/test/test-liquid-html.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ import fs from 'node:fs';
55
import * as ohm from 'ohm-js';
66
import {performance} from 'perf_hooks';
77

8-
import {unparse, wasmMatcherForGrammar} from './_helpers.js';
9-
10-
const matchWithInput = (m, str) => (m.setInput(str), m.match());
8+
import {matchWithInput, unparse, wasmMatcherForGrammar} from './_helpers.js';
119

1210
const scriptRel = relPath => new URL(relPath, import.meta.url);
1311
const grammarSource = fs.readFileSync(scriptRel('data/liquid-html.ohm'), 'utf8');

packages/wasm/test/test-toAST.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ import {toAST} from '@ohm-js/miniohm-js/toAST.js';
22
import test from 'ava';
33
import * as ohm from 'ohm-js';
44

5-
import {wasmMatcherForGrammar} from './_helpers.js';
6-
7-
const matchWithInput = (m, str) => (m.setInput(str), m.match());
5+
import {matchWithInput, wasmMatcherForGrammar} from './_helpers.js';
86

97
const arithmetic = ohm.grammar(`
108
Arithmetic {

packages/wasm/test/test-wasm.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ import * as ohm from 'ohm-js';
55
import {performance} from 'perf_hooks';
66

77
import {Compiler, ConstantsForTesting as Constants} from '../src/index.js';
8-
import {unparse, wasmMatcherForGrammar} from './_helpers.js';
9-
10-
const matchWithInput = (m, str) => (m.setInput(str), m.match());
8+
import {matchWithInput, unparse, wasmMatcherForGrammar} from './_helpers.js';
119

1210
const SIZEOF_UINT32 = 4;
1311

@@ -949,8 +947,7 @@ test('unicode built-ins: non-ASII (fast-check)', async t => {
949947
const m = await wasmMatcherForGrammar(g);
950948
const hasExpectedResult = wasmMatcher => {
951949
return fc.property(arbitraryStringMatching(/^\p{L}\p{L}$/u), str => {
952-
wasmMatcher.setInput(str);
953-
assert.equal(wasmMatcher.match(), 1);
950+
assert.equal(matchWithInput(wasmMatcher, str), 1);
954951
});
955952
};
956953
const details = fc.check(hasExpectedResult(m), {
@@ -975,3 +972,10 @@ test('caseInsensitive', async t => {
975972

976973
t.is(matchWithInput(m, '.BLAH!'), 1);
977974
});
975+
976+
test.failing('unicode', async t => {
977+
const source = 'Nöö';
978+
const m = await wasmMatcherForGrammar(ohm.grammar('G { Start = any* }'));
979+
t.is(matchWithInput(m, source), 1);
980+
t.is(unparse(m), source);
981+
});

0 commit comments

Comments
 (0)