Skip to content

Commit d5d3c8c

Browse files
committed
wasm: Implement unicodeChar
1 parent e3f6c11 commit d5d3c8c

4 files changed

Lines changed: 76 additions & 25 deletions

File tree

packages/wasm/src/index.js

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -406,8 +406,8 @@ class Assembler {
406406
this.emit(instr.br, w.labelidx(depth));
407407
}
408408

409-
brTable(labels, defaultIdx) {
410-
this.emit(w.instr.br_table, w.vec(labels.map(i => w.labelidx(i))), w.labelidx(defaultIdx));
409+
brTable(labels, defaultLabelidx) {
410+
this.emit(w.instr.br_table, w.vec(labels), defaultLabelidx);
411411
}
412412

413413
return() {
@@ -417,25 +417,29 @@ class Assembler {
417417
}
418418

419419
// Emit a dense jump table (switch-like) using br_table.
420-
switch(bt, condThunk, caseThunks, defaultThunk) {
420+
switch(bt, discrimThunk, numCases, caseCb, defaultThunk) {
421421
const startStackHeight = this._blockStack.length;
422422

423-
// Emit one block per case…
424-
caseThunks.forEach(_ => this._blockOnly(bt));
423+
const labels = [];
425424

426-
const labels = caseThunks.map((_, i) => w.labelidx(i));
425+
// Emit one block per case…
426+
for (let i = 0; i < numCases; i++) {
427+
this._blockOnly(bt);
428+
labels.push(w.labelidx(i));
429+
}
427430

428431
// …and one inner block containing the condition and the br_table.
429432
this.block(w.blocktype.empty, () => {
430-
condThunk();
433+
discrimThunk();
431434
this.brTable(labels, w.labelidx(labels.length));
432435
});
433-
caseThunks.forEach((fn, i) => {
436+
437+
for (let i = 0; i < numCases; i++) {
434438
const depth = labels.length - (i + 1);
435-
fn(depth);
439+
caseCb(i, depth);
436440
this.break(depth); // Jump to end.
437441
this._endBlock();
438-
});
442+
}
439443
assert(this._blockStack.length === startStackHeight);
440444
}
441445

@@ -820,7 +824,7 @@ export class Compiler {
820824
case pexprs.Terminal:
821825
return ir.terminal(exp.obj);
822826
case pexprs.UnicodeChar:
823-
return ir.unicodeChar(exp.codePoint);
827+
return ir.unicodeChar(exp.category);
824828
default:
825829
throw new Error(`not handled: ${exp.constructor.name}`);
826830
}
@@ -849,12 +853,12 @@ export class Compiler {
849853
asm.addFunction(`$${name}`, [w.valtype.i32], [w.valtype.i32], () => {
850854
asm.addLocal('ret', w.valtype.i32);
851855
asm.addLocal('tmp', w.valtype.i32);
856+
const values = this.liftedTerminals.values();
852857
asm.switch(
853858
w.blocktype.empty,
854859
() => asm.localGet('__arg0'),
855-
this.liftedTerminals
856-
.values()
857-
.map(str => depth => this.emitTerminal(ir.terminal(str), depth)),
860+
values.length,
861+
(i, depth) => this.emitTerminal(ir.terminal(values[i]), depth),
858862
() => asm.emit(w.instr.unreachable),
859863
);
860864
asm.localGet('ret');
@@ -1125,28 +1129,30 @@ export class Compiler {
11251129
emitDispatch({child: exp, patterns}) {
11261130
const {asm} = this;
11271131

1128-
const cases = patterns.map((actuals, i) => () => {
1132+
const handleCase = i => {
11291133
// Substitute the params to get the concrete expression that
11301134
// needs to be inserted here.
1131-
let newExp = ir.substituteParams(exp, actuals);
1135+
let newExp = ir.substituteParams(exp, patterns[i]);
11321136
if (newExp.type === 'Apply') {
11331137
// If the application has arguments, we need to dispatch to the
11341138
// correct specialized version of the rule.
11351139
newExp = ir.apply(ir.specializedName(newExp));
11361140
}
11371141
this.emitPExpr(newExp);
1138-
});
1139-
if (cases.length === 1) {
1140-
cases[0](); // No need for a switch.
1142+
};
1143+
if (patterns.length === 1) {
1144+
handleCase(0); // No need for a switch.
11411145
return;
11421146
}
1143-
assert(cases.length > 1);
1147+
assert(patterns.length > 1);
11441148

11451149
asm.switch(
11461150
w.blocktype.empty,
11471151
() => asm.localGet('__arg0'),
1148-
cases,
1152+
patterns.length,
1153+
handleCase,
11491154
() => {
1155+
asm.emit('herre');
11501156
asm.emit(w.instr.unreachable);
11511157
},
11521158
);
@@ -1195,7 +1201,7 @@ export class Compiler {
11951201
case 'Range': this.emitRange(exp); break;
11961202
case 'Plus': this.emitPlus(exp); break;
11971203
case 'Terminal': this.emitTerminal(exp); break;
1198-
case 'UnicodeChar': this.emitFail(); break; // TODO: Handle this properly
1204+
case 'UnicodeChar': this.emitUnicodeChar(exp); break;
11991205
case 'Param':
12001206
// Fall through (Params should not exist at codegen time).
12011207
default:
@@ -1422,7 +1428,37 @@ export class Compiler {
14221428
asm.newTerminalNodeWithSavedPos();
14231429
asm.localSet('ret');
14241430
}
1431+
1432+
emitUnicodeChar(exp) {
1433+
const {asm} = this;
1434+
this.maybeEmitSpaceSkipping();
1435+
1436+
const handleDefault = () => {
1437+
// TODO: Implement the slow case by calling out to the host.
1438+
asm.emit(w.instr.unreachable);
1439+
};
1440+
1441+
// TODO: Add support for more categories, by calling out to the host.
1442+
assert(['Ll', 'Lu', 'Ltmo'].includes(exp.category));
1443+
1444+
const caseCb = (i, depth) => {
1445+
const c = String.fromCharCode(i);
1446+
if (
1447+
(exp.category === 'Lu' && 'A' <= c && c <= 'Z') ||
1448+
(exp.category === 'Ll' && 'a' <= c && c <= 'z')
1449+
) {
1450+
asm.incPos();
1451+
asm.newTerminalNodeWithSavedPos();
1452+
asm.localSet('ret');
1453+
} else {
1454+
asm.setRet(0);
1455+
}
1456+
asm.break(depth);
1457+
};
1458+
asm.switch(w.blocktype.empty, () => asm.currCharCode(), 128, caseCb, handleDefault);
1459+
}
14251460
}
1461+
14261462
// Memory layout:
14271463
// - First page is for the PExpr stack (origPos, etc.), growing downards.
14281464
// - 2nd page is for input buffer (max 64k for now).

packages/wasm/src/ir.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,13 @@ export const terminal = (value: string): Terminal => ({type: 'Terminal', value})
162162

163163
export interface UnicodeChar {
164164
type: 'UnicodeChar';
165-
value: string;
165+
category: string;
166166
}
167167

168-
export const unicodeChar = (value: string): UnicodeChar => ({type: 'UnicodeChar', value});
168+
export const unicodeChar = (category: string): UnicodeChar => ({
169+
type: 'UnicodeChar',
170+
category
171+
});
169172

170173
// Types that are specific to the IR
171174

packages/wasm/test/data/liquid-html.ohm

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ Helpers {
44
openControl = end
55

66
// Temp overrides for things not yet support in Wasm grammars
7-
letter := "a".."z" | "A".."Z"
87
caseInsensitive<t> := t
98

109
empty = /* nothing */

packages/wasm/test/test-wasm.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,3 +951,16 @@ test('space skipping & lex', async t => {
951951
t.is(matchWithInput(m, '> 9'), 1, "neg lookahead doesn't consume anything");
952952
}
953953
});
954+
955+
test('unicode built-ins', async t => {
956+
const g = ohm.grammar(`
957+
G {
958+
Start = lower upper
959+
}`);
960+
const m = await wasmMatcherForGrammar(g);
961+
t.is(matchWithInput(m, 'aA'), 1);
962+
t.is(matchWithInput(m, ' aZ'), 1);
963+
t.is(matchWithInput(m, ' zA'), 1);
964+
t.is(matchWithInput(m, 'a@'), 0);
965+
t.is(matchWithInput(m, 'a['), 0);
966+
});

0 commit comments

Comments
 (0)