Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 33 additions & 6 deletions packages/ohm-js/src/Matcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ export class Matcher {
replaceInputRange(startIdx, endIdx, str) {
const prevInput = this._input;
const memoTable = this._memoTable;

// Grammar.match() creates a fresh Matcher and calls replaceInputRange(0, 0, input).
// In that scenario, there is nothing to preserve or invalidate, so avoid extra work.
if (startIdx === 0 && endIdx === 0 && prevInput.length === 0 && memoTable.length === 0) {
this._input = '' + str;
memoTable.length = str.length;
return this;
}
if (
startIdx < 0 ||
startIdx > prevInput.length ||
Expand All @@ -46,13 +54,32 @@ export class Matcher {
}

// update memo table (similar to the above)
const restOfMemoTable = memoTable.slice(endIdx);
memoTable.length = startIdx;
for (let idx = 0; idx < str.length; idx++) {
memoTable.push(undefined);
const oldLen = memoTable.length;
const deleteCount = endIdx - startIdx;
const insertCount = str.length;
const delta = insertCount - deleteCount;
const newLen = oldLen + delta;

// Move the tail segment to its new location.
if (oldLen > endIdx) {
if (delta > 0) {
// Grow first to make room, then shift the tail right.
memoTable.length = newLen;
memoTable.copyWithin(startIdx + insertCount, endIdx, oldLen);
} else if (delta < 0) {
// Shift the tail left first, then shrink (shrinking first would drop tail entries).
memoTable.copyWithin(startIdx + insertCount, endIdx, oldLen);
memoTable.length = newLen;
} else {
memoTable.copyWithin(startIdx + insertCount, endIdx, oldLen);
}
} else if (delta !== 0) {
memoTable.length = newLen;
}
for (const posInfo of restOfMemoTable) {
memoTable.push(posInfo);

// Clear the inserted range.
if (insertCount > 0) {
memoTable.fill(undefined, startIdx, startIdx + insertCount);
}

// Invalidate memoRecs
Expand Down
2 changes: 1 addition & 1 deletion packages/ohm-js/src/PosInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class PosInfo {
Object.keys(memo).forEach(k => {
const memoRec = memo[k];
if (pos + memoRec.examinedLength > invalidatedIdx) {
delete memo[k];
memo[k] = undefined;
} else {
this.maxExaminedLength = Math.max(this.maxExaminedLength, memoRec.examinedLength);
this.maxRightmostFailureOffset = Math.max(
Expand Down
12 changes: 6 additions & 6 deletions packages/ohm-js/src/Semantics.js
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ export class Semantics {
const thisThing = this._semantics[typePlural][name];

// Check that the caller passed the correct number of arguments.
if (arguments.length !== thisThing.formals.length) {
if (args.length !== thisThing.formals.length) {
throw new Error(
'Invalid number of arguments passed to ' +
name +
Expand All @@ -358,9 +358,9 @@ export class Semantics {
// Create an "arguments object" from the arguments that were passed to this
// operation / attribute.
const argsObj = Object.create(null);
for (const [idx, val] of Object.entries(args)) {
for (let idx = 0; idx < args.length; idx += 1) {
const formal = thisThing.formals[idx];
argsObj[formal] = val;
argsObj[formal] = args[idx];
}

const oldArgs = this.args;
Expand Down Expand Up @@ -569,12 +569,12 @@ Semantics.createSemantics = function (grammar, optSuperSemantics) {
let semantic;
if (operationOrAttributeName in s.operations) {
semantic = s.operations[operationOrAttributeName];
delete s.operations[operationOrAttributeName];
s.operations[operationOrAttributeName] = undefined;
} else if (operationOrAttributeName in s.attributes) {
semantic = s.attributes[operationOrAttributeName];
delete s.attributes[operationOrAttributeName];
s.attributes[operationOrAttributeName] = undefined;
}
delete s.Wrapper.prototype[operationOrAttributeName];
s.Wrapper.prototype[operationOrAttributeName] = undefined;
return semantic;
};
proxy.getOperationNames = function () {
Expand Down
157 changes: 137 additions & 20 deletions packages/ohm-js/src/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,136 @@

// Helpers

const escapeStringFor = {};
for (let c = 0; c < 128; c++) {
escapeStringFor[c] = String.fromCharCode(c);
}
escapeStringFor["'".charCodeAt(0)] = "\\'";
escapeStringFor['"'.charCodeAt(0)] = '\\"';
escapeStringFor['\\'.charCodeAt(0)] = '\\\\';
escapeStringFor['\b'.charCodeAt(0)] = '\\b';
escapeStringFor['\f'.charCodeAt(0)] = '\\f';
escapeStringFor['\n'.charCodeAt(0)] = '\\n';
escapeStringFor['\r'.charCodeAt(0)] = '\\r';
escapeStringFor['\t'.charCodeAt(0)] = '\\t';
escapeStringFor['\u000b'.charCodeAt(0)] = '\\v';
const escapeStringFor = {
0: '\x00',
1: '\x01',
2: '\x02',
3: '\x03',
4: '\x04',
5: '\x05',
6: '\x06',
7: '\x07',
8: '\\b',
9: '\\t',
10: '\\n',
11: '\\v',
12: '\\f',
13: '\\r',
14: '\x0E',
15: '\x0F',
16: '\x10',
17: '\x11',
18: '\x12',
19: '\x13',
20: '\x14',
21: '\x15',
22: '\x16',
23: '\x17',
24: '\x18',
25: '\x19',
26: '\x1A',
27: '\x1B',
28: '\x1C',
29: '\x1D',
30: '\x1E',
31: '\x1F',
32: ' ',
33: '!',
34: '\\"',
35: '#',
36: '$',
37: '%',
38: '&',
39: "\\'",
40: '(',
41: ')',
42: '*',
43: '+',
44: ',',
45: '-',
46: '.',
47: '/',
48: '0',
49: '1',
50: '2',
51: '3',
52: '4',
53: '5',
54: '6',
55: '7',
56: '8',
57: '9',
58: ':',
59: ';',
60: '<',
61: '=',
62: '>',
63: '?',
64: '@',
65: 'A',
66: 'B',
67: 'C',
68: 'D',
69: 'E',
70: 'F',
71: 'G',
72: 'H',
73: 'I',
74: 'J',
75: 'K',
76: 'L',
77: 'M',
78: 'N',
79: 'O',
80: 'P',
81: 'Q',
82: 'R',
83: 'S',
84: 'T',
85: 'U',
86: 'V',
87: 'W',
88: 'X',
89: 'Y',
90: 'Z',
91: '[',
92: '\\\\',
93: ']',
94: '^',
95: '_',
96: '`',
97: 'a',
98: 'b',
99: 'c',
100: 'd',
101: 'e',
102: 'f',
103: 'g',
104: 'h',
105: 'i',
106: 'j',
107: 'k',
108: 'l',
109: 'm',
110: 'n',
111: 'o',
112: 'p',
113: 'q',
114: 'r',
115: 's',
116: 't',
117: 'u',
118: 'v',
119: 'w',
120: 'x',
121: 'y',
122: 'z',
123: '{',
124: '|',
125: '}',
126: '~',
127: '\x7F',
};

// --------------------------------------------------------------------
// Exports
Expand Down Expand Up @@ -59,7 +176,7 @@ export function defineLazyProperty(obj, propName, getterFn) {

export function clone(obj) {
if (obj) {
return Object.assign({}, obj);
return {...obj};
}
return obj;
}
Expand All @@ -81,14 +198,14 @@ export function repeat(x, n) {
}

export function getDuplicates(array) {
const duplicates = [];
const duplicates = new Set();
for (let idx = 0; idx < array.length; idx++) {
const x = array[idx];
if (array.lastIndexOf(x) !== idx && duplicates.indexOf(x) < 0) {
duplicates.push(x);
if (array.lastIndexOf(x) !== idx && !duplicates.has(x)) {
duplicates.add(x);
}
}
return duplicates;
return [...duplicates];
}

export function copyWithoutDuplicates(array) {
Expand All @@ -102,8 +219,8 @@ export function copyWithoutDuplicates(array) {
}

export function isSyntactic(ruleName) {
const firstChar = ruleName[0];
return firstChar === firstChar.toUpperCase();
const code = ruleName.charCodeAt(0);
return code >= 65 && code <= 90;
}

export function isLexical(ruleName) {
Expand Down
26 changes: 17 additions & 9 deletions packages/ohm-js/src/pexprs-eval.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,15 @@ pexprs.Iter.prototype.eval = function (state) {
}
prevPos = inputStream.pos;
numMatches++;
const row = state._bindings.splice(state._bindings.length - arity, arity);
const rowOffsets = state._bindingOffsets.splice(
state._bindingOffsets.length - arity,
arity
);
for (idx = 0; idx < row.length; idx++) {
// Avoid using `splice` which allocates an array; use repeated `pop` which is faster
// in hot loops and preserves insertion order by filling from the end.
const row = new Array(arity);
const rowOffsets = new Array(arity);
for (let j = arity - 1; j >= 0; j--) {
row[j] = state._bindings.pop();
rowOffsets[j] = state._bindingOffsets.pop();
}
for (idx = 0; idx < arity; idx++) {
cols[idx].push(row[idx]);
colOffsets[idx].push(rowOffsets[idx]);
}
Expand Down Expand Up @@ -222,7 +225,7 @@ pexprs.Apply.prototype.eval = function (state) {
if (state.hasNecessaryInfo(memoRec)) {
return state.useMemoizedResult(state.inputStream.pos, memoRec);
}
delete posInfo.memo[memoKey];
posInfo.memo[memoKey] = undefined;
}
return app.reallyEval(state);
};
Expand Down Expand Up @@ -336,8 +339,13 @@ pexprs.Apply.prototype.evalOnce = function (expr, state) {

if (state.eval(expr)) {
const arity = expr.getArity();
const bindings = state._bindings.splice(state._bindings.length - arity, arity);
const offsets = state._bindingOffsets.splice(state._bindingOffsets.length - arity, arity);
// Avoid Array.prototype.splice allocation in hot path; use pop into preallocated arrays
const bindings = new Array(arity);
const offsets = new Array(arity);
for (let j = arity - 1; j >= 0; j--) {
bindings[j] = state._bindings.pop();
offsets[j] = state._bindingOffsets.pop();
}
const matchLength = inputStream.pos - origPos;
return new NonterminalNode(this.ruleName, bindings, offsets, matchLength);
} else {
Expand Down