Skip to content

Commit 89e52a7

Browse files
committed
Merge branch 'main' into pdubroy/cstview-unify
2 parents fc921ef + 3c34a59 commit 89e52a7

13 files changed

Lines changed: 254 additions & 254 deletions

packages/compiler/scripts/bench.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {run, bench, group, summary} from 'mitata';
55
import * as ohm from 'ohm-js';
66

77
import * as es5js from '../../../examples/ecmascript/index.js';
8-
import {toWasmGrammar} from '../test/_helpers.js';
8+
import {legacyGrammarToWasm} from '../test/_helpers.js';
99

1010
const __dirname = dirname(fileURLToPath(import.meta.url));
1111
const datadir = join(__dirname, '../test/data');
@@ -96,15 +96,18 @@ group('JSON', () => {
9696
// Note: we are deliberately creating one instance of the matcher that's
9797
// reused. This takes advantage of JIT tier-up, and approximates usage
9898
// in a long-running process, e.g. LSP server.
99-
liquidHtmlWasm = await toWasmGrammar(
99+
liquidHtmlWasm = await legacyGrammarToWasm(
100100
liquid.LiquidHTML,
101101
readFileSync(join(__dirname, '../build/liquid-html.wasm'))
102102
);
103-
es5Wasm = await toWasmGrammar(
103+
es5Wasm = await legacyGrammarToWasm(
104104
es5js.grammar,
105105
readFileSync(join(__dirname, '../build/es5.wasm'))
106106
);
107-
jsonWasm = await toWasmGrammar(json, readFileSync(join(__dirname, '../build/json.wasm')));
107+
jsonWasm = await legacyGrammarToWasm(
108+
json,
109+
readFileSync(join(__dirname, '../build/json.wasm'))
110+
);
108111

109112
await run();
110113
})();

packages/compiler/scripts/parseLiquid.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@ import * as ohm from 'ohm-js-legacy';
1717
import {Bench} from 'tinybench';
1818

1919
import {Compiler} from '../src/Compiler.ts';
20-
import {unparse, toWasmGrammar} from '../test/_helpers.js';
20+
import {unparse} from '../test/_helpers.js';
2121
import {CstView} from '../../runtime/src/cstView.ts';
2222

2323
const __dirname = dirname(fileURLToPath(import.meta.url));
2424
const datadir = join(__dirname, '../test/data');
2525

26-
const liquid = ohm.grammars(readFileSync(join(datadir, 'liquid-html.ohm'), 'utf8'));
26+
const grammarSource = readFileSync(join(datadir, 'liquid-html.ohm'), 'utf8');
27+
const liquid = ohm.grammars(grammarSource);
2728

2829
// Parse flags and positional args.
2930
const flags = new Set(process.argv.slice(2).filter(a => a.startsWith('--')));
@@ -78,9 +79,10 @@ const pattern = positionalArgs[0];
7879

7980
// Compile to Wasm.
8081
const compileStart = bench.now();
81-
const modBytes = new Compiler(liquid.LiquidHTML).compile();
82+
const allBytes = compileGrammars(grammarSource);
8283
const compileTime = bench.now() - compileStart;
83-
const g = await toWasmGrammar(liquid.LiquidHTML, {modBytes});
84+
const modBytes = allBytes.LiquidHTML;
85+
const g = await Grammar.instantiate(modBytes);
8486
const {exports} = g._instance;
8587
let peakWasmHeapBytes = 0;
8688
let peakWasmMemoryBytes = 0;

packages/compiler/scripts/parsePipRequirements.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ import process from 'node:process';
1111
import {fileURLToPath} from 'node:url';
1212
import * as ohm from 'ohm-js-legacy';
1313

14-
import {unparse, toWasmGrammar} from '../test/_helpers.js';
14+
import {Grammar} from 'ohm-js';
15+
import {compile} from '../src/api.ts';
16+
import {unparse} from '../test/_helpers.js';
1517

1618
const __dirname = dirname(fileURLToPath(import.meta.url));
1719
const datadir = join(__dirname, '../test/data');
1820

19-
const grammar = ohm.grammar(readFileSync(join(datadir, 'pep-508.ohm'), 'utf8'));
21+
const grammarSource = readFileSync(join(datadir, 'pep-508.ohm'), 'utf8');
22+
const grammar = ohm.grammar(grammarSource);
2023
const input = readFileSync(join(datadir, 'requirements_all.txt'), 'utf8');
2124

2225
(async function main() {
@@ -32,7 +35,8 @@ const input = readFileSync(join(datadir, 'requirements_all.txt'), 'utf8');
3235
assert.equal(r.succeeded(), true, `JS parse failed: ${r.shortMessage}`);
3336

3437
// --- Wasm ---
35-
const g = await toWasmGrammar(grammar);
38+
const modBytes = compile(grammarSource);
39+
const g = await Grammar.instantiate(modBytes);
3640
const {exports} = g._instance;
3741

3842
const wasmStart = performance.now();

packages/compiler/test/_helpers.js

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,58 @@
22

33
import {Compiler} from '../src/Compiler.ts';
44
import {Grammar} from 'ohm-js';
5+
import {grammars as parseGrammars} from '../src/parseGrammars.ts';
56

67
const DEBUG = process.env.OHM_DEBUG === '1';
78

8-
export async function toWasmGrammar(grammar, {modBytes, ...compilerOpts} = {}) {
9+
// Compile and load a Wasm grammar from source, using the Wasm metagrammar
10+
// path (no legacy ohm-js). For single-grammar sources.
11+
export async function compileAndLoad(source, compilerOpts = {}) {
12+
const ns = parseGrammars(source);
13+
const names = Object.keys(ns);
14+
const g = ns[names[names.length - 1]];
15+
return _compileAndInstantiate(g, compilerOpts);
16+
}
17+
18+
// Compile and load all grammars from a multi-grammar source, returning
19+
// a {name: Grammar} map. Uses the Wasm metagrammar path.
20+
export async function compileAndLoadAll(source, compilerOpts = {}) {
21+
const ns = parseGrammars(source);
22+
const result = {};
23+
for (const [name, g] of Object.entries(ns)) {
24+
result[name] = await _compileAndInstantiate(g, compilerOpts);
25+
}
26+
return result;
27+
}
28+
29+
async function _compileAndInstantiate(parsedGrammar, compilerOpts) {
30+
const compiler = new Compiler(parsedGrammar, compilerOpts);
31+
const bytes = compiler.compile();
32+
33+
const wasmGrammar = new Grammar();
34+
35+
let depth = 0;
36+
let debugImports = {};
37+
if (DEBUG) {
38+
debugImports = compiler.getDebugImports((label, ret) => {
39+
const result = ret === 0 ? 'FAIL' : 'SUCCESS';
40+
const indented = s => new Array(depth).join(' ') + s;
41+
const pos = wasmGrammar._instance.exports.pos.value;
42+
if (label.startsWith('BEGIN')) depth += 1;
43+
const tail = label.startsWith('END') ? ` -> ${result}` : '';
44+
// eslint-disable-next-line no-console
45+
console.log(`pos: ${pos} ${indented(label)}${tail}`);
46+
if (label.startsWith('END')) depth -= 1;
47+
});
48+
}
49+
return wasmGrammar._instantiate(bytes, debugImports);
50+
}
51+
52+
// Takes a *legacy* ohm-js Grammar object and compiles it to Wasm.
53+
// Use this only in tests that deliberately compare Wasm vs legacy behavior.
54+
export async function legacyGrammarToWasm(grammar, compilerOpts = {}) {
955
const compiler = new Compiler(grammar, compilerOpts);
10-
const bytes = modBytes ?? compiler.compile();
56+
const bytes = compiler.compile();
1157

1258
const wasmGrammar = new Grammar();
1359

packages/compiler/test/_test-v24.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
// eslint-disable-next-line ava/no-ignored-test-files -- dynamically imported from test-wasm.js
22
import test from 'ava';
3-
import * as ohm from 'ohm-js-legacy';
4-
5-
import {toWasmGrammar} from './_helpers.js';
3+
import {compileAndLoad} from './_helpers.js';
64

75
test('nested matching with `using`', async t => {
8-
const g = await toWasmGrammar(ohm.grammar('G { Start = letter+ | digit+ }'));
6+
const g = await compileAndLoad('G { Start = letter+ | digit+ }');
97

108
{
119
using outer = g.match('abc');

packages/compiler/test/test-cst-compat.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {readFileSync} from 'node:fs';
1010
import * as ohm from 'ohm-js-legacy';
1111
import {grammar as v18Grammar, grammars as v18Grammars} from 'ohm-js-legacy/v18';
1212

13-
import {matchWithInput, scriptRel, toWasmGrammar} from './_helpers.js';
13+
import {matchWithInput, scriptRel, legacyGrammarToWasm} from './_helpers.js';
1414

1515
// --- Serializer: reduces a CST node to a plain JSON "shape" ---
1616

@@ -51,7 +51,7 @@ function serializeCst(node) {
5151
const arithmeticSrc = readFileSync(scriptRel('../../ohm-js/test/data/arithmetic.ohm'), 'utf8');
5252

5353
test.failing('compat: arithmetic', async t => {
54-
const wasmG = await toWasmGrammar(ohm.grammar(arithmeticSrc));
54+
const wasmG = await legacyGrammarToWasm(ohm.grammar(arithmeticSrc));
5555
const v18G = v18Grammar(arithmeticSrc);
5656

5757
for (const input of ['1', '10 + 20', '1+276*(3+4)', '(10+ 999)- 1 +222']) {
@@ -65,7 +65,7 @@ test.failing('compat: arithmetic', async t => {
6565
const liquidHtmlSrc = readFileSync(scriptRel('data/liquid-html.ohm'), 'utf8');
6666

6767
test.failing('compat: liquid-html', async t => {
68-
const wasmG = await toWasmGrammar(ohm.grammars(liquidHtmlSrc).LiquidHTML);
68+
const wasmG = await legacyGrammarToWasm(ohm.grammars(liquidHtmlSrc).LiquidHTML);
6969
const v18G = v18Grammars(liquidHtmlSrc).LiquidHTML;
7070

7171
const inputs = [

packages/compiler/test/test-debug.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import test from 'ava';
2-
import * as ohm from 'ohm-js-legacy';
32

43
import {getMatchStats} from 'ohm-js/unstableDebug';
5-
import {toWasmGrammar} from './_helpers.js';
4+
import {compileAndLoad} from './_helpers.js';
65

76
test('getMatchStats basic', async t => {
8-
const g = await toWasmGrammar(ohm.grammar('G { start = "a" "b" }'));
7+
const g = await compileAndLoad('G { start = "a" "b" }');
98
g.match('ab').use(r => {
109
t.true(r.succeeded());
1110
const stats = getMatchStats(r);
@@ -27,8 +26,8 @@ test('getMatchStats basic', async t => {
2726
});
2827

2928
test('getMatchStats with alternatives', async t => {
30-
const g = await toWasmGrammar(
31-
ohm.grammar('G { start = big | small\n big = "x" "y" "z"\n small = "a" }')
29+
const g = await compileAndLoad(
30+
'G { start = big | small\n big = "x" "y" "z"\n small = "a" }'
3231
);
3332
g.match('a').use(r => {
3433
t.true(r.succeeded());

packages/compiler/test/test-es5.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,37 @@
11
import test from 'ava';
2+
import {readFileSync} from 'node:fs';
23
import {readFile} from 'node:fs/promises';
34
import {dirname, join} from 'node:path';
45
import {performance} from 'node:perf_hooks';
56
import {fileURLToPath} from 'node:url';
67

78
import * as es5js from '../../../examples/ecmascript/index.js';
8-
import {matchWithInput, unparse, toWasmGrammar} from './_helpers.js';
9+
import {compileAndLoadAll, matchWithInput, unparse, legacyGrammarToWasm} from './_helpers.js';
910

1011
const __dirname = dirname(fileURLToPath(import.meta.url));
1112
const datadir = join(__dirname, 'data');
1213

1314
const html5shivPath = join(datadir, '_html5shiv-3.7.3.js');
15+
const es5GrammarSource = readFileSync(
16+
join(__dirname, '../../../examples/ecmascript/src/es5.ohm'),
17+
'utf8'
18+
);
19+
20+
async function loadES5() {
21+
const grammars = await compileAndLoadAll(es5GrammarSource);
22+
return grammars.ES5;
23+
}
1424

1525
test('basic es5 examples', async t => {
16-
const g = await toWasmGrammar(es5js.grammar);
26+
const g = await loadES5();
1727
t.is(matchWithInput(g, 'x = 3;'), 1);
1828
t.is(matchWithInput(g, 'function foo() { return 1; }'), 1);
1929
});
2030

2131
test('html5shiv', async t => {
2232
const source = await readFile(html5shivPath, 'utf8');
2333

24-
const g = await toWasmGrammar(es5js.grammar);
34+
const g = await legacyGrammarToWasm(es5js.grammar);
2535
let start = performance.now();
2636
es5js.grammar.match(source);
2737
t.log(`html5shiv (Ohm) match time: ${(performance.now() - start).toFixed(2)}ms`);
@@ -50,12 +60,12 @@ test('unparsing', async t => {
5060
/\d+/.test("123") && console.log(counter());
5161
`;
5262

53-
const g = await toWasmGrammar(es5js.grammar);
63+
const g = await loadES5();
5464
t.is(matchWithInput(g, source), 1);
5565
t.is(unparse(g).trimEnd(), source.trimEnd());
5666
});
5767

5868
test('matching at end', async t => {
59-
const g = await toWasmGrammar(es5js.grammar);
69+
const g = await loadES5();
6070
t.false(g.match('', 'letter').use(r => r.succeeded()));
6171
});

packages/compiler/test/test-failurePos.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import fc from 'fast-check';
44
import {readFileSync} from 'node:fs';
55
import {grammars, grammar} from 'ohm-js-legacy/v18';
66

7-
import {scriptRel, toWasmGrammar} from './_helpers.js';
7+
import {scriptRel, legacyGrammarToWasm} from './_helpers.js';
88

99
const grammarSource = readFileSync(scriptRel('data/liquid-html.ohm'), 'utf8');
1010
const ns = grammars(grammarSource);
@@ -79,7 +79,7 @@ function sameFailurePos(wasmGrammar) {
7979
}
8080

8181
test('failure pos (fast-check)', async t => {
82-
const g = await toWasmGrammar(ns.LiquidHTML);
82+
const g = await legacyGrammarToWasm(ns.LiquidHTML);
8383
const details = fc.check(sameFailurePos(g), {
8484
includeErrorInReport: true,
8585
interruptAfterTimeLimit: 1000,
@@ -94,7 +94,7 @@ test('failure pos: basic 1', async t => {
9494
Start = number+
9595
number = digit+
9696
}`);
97-
const wasmGrammar = await toWasmGrammar(g);
97+
const wasmGrammar = await legacyGrammarToWasm(g);
9898

9999
t.is(failurePos(g, 'a'), 0);
100100
t.is(failurePos(wasmGrammar, 'a'), 0);
@@ -113,7 +113,7 @@ test('failure pos: basic 2', async t => {
113113
| number
114114
number = digit+
115115
}`);
116-
const wasmGrammar = await toWasmGrammar(g);
116+
const wasmGrammar = await legacyGrammarToWasm(g);
117117

118118
t.is(failurePos(g, '99 + 66'), 7);
119119
t.is(failurePos(wasmGrammar, '99 + 66'), 7);
@@ -125,7 +125,7 @@ test('failure pos: basic 3', async t => {
125125
Start = letter letter
126126
space := "/*" (~"*/" any)* "*/"
127127
}`);
128-
const wasmGrammar = await toWasmGrammar(g);
128+
const wasmGrammar = await legacyGrammarToWasm(g);
129129

130130
t.is(failurePos(wasmGrammar, '99'), 0);
131131
});
@@ -137,7 +137,7 @@ test('failure pos: lookahead', async t => {
137137
start = ~(anyTwo "!") "a" "b"
138138
anyTwo = any any
139139
}`);
140-
const wasmGrammar = await toWasmGrammar(g);
140+
const wasmGrammar = await legacyGrammarToWasm(g);
141141

142142
// Original Ohm behaviour is to ignore failures inside the lookahead, so
143143
// it produces 'Expected "a"' at pos 0.
@@ -153,7 +153,7 @@ test('failure pos: memoization', async t => {
153153
start = ~anyTwo anyTwo
154154
anyTwo = any any
155155
}`);
156-
const wasmGrammar = await toWasmGrammar(g);
156+
const wasmGrammar = await legacyGrammarToWasm(g);
157157

158158
// Original Ohm behaviour is to ignore failures inside the lookahead, so
159159
// it produces 'Expected "a"' at pos 0.
@@ -168,7 +168,7 @@ test('failure pos: space skipping', async t => {
168168
Start = digit digit
169169
space += "/*" (~"*/" any)* "*/" -- comment
170170
}`);
171-
const wasmGrammar = await toWasmGrammar(g);
171+
const wasmGrammar = await legacyGrammarToWasm(g);
172172

173173
// Failure inside space skipping should be ignored.
174174
t.is(failurePos(g, '9 /* bad'), 2);
@@ -183,7 +183,7 @@ test('failure pos is always after space skipping', async t => {
183183
| "3." twice<"b">
184184
twice<x> = x x
185185
}`);
186-
const wasmGrammar = await toWasmGrammar(g);
186+
const wasmGrammar = await legacyGrammarToWasm(g);
187187

188188
// Regular terminal
189189
t.is(failurePos(g, '1. c'), 3);
@@ -204,7 +204,7 @@ test('failure pos is always after space skipping', async t => {
204204
});
205205

206206
test('fast-check zoo', async t => {
207-
const wasmGrammar = await toWasmGrammar(ns.LiquidHTML);
207+
const wasmGrammar = await legacyGrammarToWasm(ns.LiquidHTML);
208208

209209
const input = '< {% if swatch_value %}';
210210
t.is(failurePos(wasmGrammar, input), failurePos(ns.LiquidHTML, input));

0 commit comments

Comments
 (0)