Skip to content

Commit

Permalink
Avoid double extraction of substrings in various MATCH_ bytecodes
Browse files Browse the repository at this point in the history
  • Loading branch information
markw65 committed Aug 21, 2023
1 parent 736d6cc commit fe01e6f
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 22 deletions.
87 changes: 66 additions & 21 deletions lib/compiler/passes/generate-js.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ function generateJS(ast, options) {
function generateRuleFunction(rule) {
const parts = [];
const stack = new Stack(rule.name, "s", "var", rule.bytecode);
let skipAccept = false;

function compile(bc) {
let ip = 0;
Expand Down Expand Up @@ -351,6 +352,50 @@ function generateJS(ast, options) {
parts.push("}");
}

/*
MATCH_* opcodes typically do something like
if (<test>(input.substr(peg$currPos, length))) {
sN = input.substr(peg$currPos, length);
...
} else {
sN = peg$FAILED;
...
}
compileInputChunkCondition will convert that to
sN = input.substr(peg$currPos, length);
if (<test>(sN)) {
...
} else {
sN = peg$FAILED;
...
}
and avoid extracting the sub string twice.
*/
function compileInputChunkCondition(
cond, argCount, inputChunkLength
) {
const baseLength = argCount + 3;
let inputChunk = inputChunkLength === 1
? "input.charAt(peg$currPos)"
: "input.substr(peg$currPos, " + inputChunkLength + ")";
if (bc[ip + baseLength] === op.ACCEPT_N
&& bc[ip + baseLength + 1] === inputChunkLength) {
skipAccept = true;
parts.push(stack.push(inputChunk));
inputChunk = stack.pop();
} else if (inputChunkLength === 1) {
cond = cond.replace("$$$.charCodeAt(0)", "input.charCodeAt(peg$currPos)");
}
compileCondition(
cond.replace("$$$", inputChunk),
argCount
);
}

function compileLoop(cond) {
const baseLength = 2;
const bodyLength = bc[ip + baseLength - 1];
Expand Down Expand Up @@ -505,41 +550,41 @@ function generateJS(ast, options) {
break;

case op.MATCH_STRING: // MATCH_STRING s, a, f, ...
compileCondition(
compileInputChunkCondition(
ast.literals[bc[ip + 1]].length > 1
? "input.substr(peg$currPos, "
+ ast.literals[bc[ip + 1]].length
+ ") === "
+ l(bc[ip + 1])
: "input.charCodeAt(peg$currPos) === "
? "$$$ === " + l(bc[ip + 1])
: "$$$.charCodeAt(0) === "
+ ast.literals[bc[ip + 1]].charCodeAt(0),
1
1,
ast.literals[bc[ip + 1]].length
);
break;

case op.MATCH_STRING_IC: // MATCH_STRING_IC s, a, f, ...
compileCondition(
"input.substr(peg$currPos, "
+ ast.literals[bc[ip + 1]].length
+ ").toLowerCase() === "
+ l(bc[ip + 1]),
1
compileInputChunkCondition(
"$$$.toLowerCase() === " + l(bc[ip + 1]),
1,
ast.literals[bc[ip + 1]].length
);
break;

case op.MATCH_CHAR_CLASS: // MATCH_CHAR_CLASS c, a, f, ...
compileCondition(
r(bc[ip + 1]) + ".test(input.charAt(peg$currPos))",
1
compileInputChunkCondition(
r(bc[ip + 1]) + ".test($$$)", 1, 1
);
break;

case op.ACCEPT_N: // ACCEPT_N n
parts.push(stack.push(
bc[ip + 1] > 1
? "input.substr(peg$currPos, " + bc[ip + 1] + ")"
: "input.charAt(peg$currPos)"
));
if (skipAccept) {
stack.sp++;
skipAccept = false;
} else {
parts.push(stack.push(
bc[ip + 1] > 1
? "input.substr(peg$currPos, " + bc[ip + 1] + ")"
: "input.charAt(peg$currPos)"
));
}
parts.push(
bc[ip + 1] > 1
? "peg$currPos += " + bc[ip + 1] + ";"
Expand Down
2 changes: 1 addition & 1 deletion test/api/pegjs-api.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ describe("Peggy API", () => {
it("labelled rule name", () => check("RULE_2 'named'", source, "RULE_2", "peg$parseRULE_2() {"));
it("literal expression", () => check("'a'", source, null, "input.charCodeAt(peg$currPos) === 97"));
it("multichar literal", () => check("'def'", source, null, "input.substr(peg$currPos, 3) === peg$c3"));
it("chars expression", () => check("[abc]", source, null, "peg$r0.test(input.charAt(peg$currPos))"));
it("chars expression", () => check("[abc]", source, null, /s\d = input\.charAt\(peg\$currPos\);\s*if \(peg\$r0\.test\(s\d\)\)/));
it("rule expression", () => check("RULE_2", source, null, "peg$parseRULE_2();"));
it("choice expression", () => check(
"RULE_1 / RULE_2",
Expand Down

0 comments on commit fe01e6f

Please sign in to comment.