Reorder AST stuff more consistently and sensibly

There is no functional change in this commit.
redux
David Majda 15 years ago
parent f28c52fde2
commit 85930cbcfe

@ -200,11 +200,9 @@ PEG.Grammar.Rule.prototype = {
getName: function() { return this._name; }
};
PEG.Grammar.Literal = function(value) { this._value = value; };
PEG.Grammar.Class = function(characters) { this._characters = characters; };
PEG.Grammar.Any = function() {};
PEG.Grammar.Choice = function(alternatives) {
this._alternatives = alternatives;
};
PEG.Grammar.Sequence = function(elements) { this._elements = elements; };
@ -212,16 +210,6 @@ PEG.Grammar.Sequence.prototype = {
getElements: function() { return this._elements; }
};
PEG.Grammar.Choice = function(alternatives) {
this._alternatives = alternatives;
};
PEG.Grammar.Optional = function(expression) { this._expression = expression; };
PEG.Grammar.ZeroOrMore = function(expression) { this._expression = expression; };
PEG.Grammar.OneOrMore = function(expression) { this._expression = expression; };
PEG.Grammar.AndPredicate = function(expression) {
this._expression = expression;
};
@ -230,13 +218,25 @@ PEG.Grammar.NotPredicate = function(expression) {
this._expression = expression;
};
PEG.Grammar.RuleRef = function(name) { this._name = name; };
PEG.Grammar.Optional = function(expression) { this._expression = expression; };
PEG.Grammar.ZeroOrMore = function(expression) { this._expression = expression; };
PEG.Grammar.OneOrMore = function(expression) { this._expression = expression; };
PEG.Grammar.Action = function(expression, action) {
this._expression = expression;
this._action = action;
};
PEG.Grammar.RuleRef = function(name) { this._name = name; };
PEG.Grammar.Literal = function(value) { this._value = value; };
PEG.Grammar.Any = function() {};
PEG.Grammar.Class = function(characters) { this._characters = characters; };
/* ===== Referenced Rule Existence Checks ===== */
PEG.Grammar.extendNodes("checkReferencedRulesExist", {
@ -245,9 +245,12 @@ PEG.Grammar.extendNodes("checkReferencedRulesExist", {
this._expression.checkReferencedRulesExist(grammar);
},
Literal: nop,
Class: nop,
Any: nop,
Choice:
function(grammar) {
PEG.ArrayUtils.each(this._alternatives, function(alternative) {
alternative.checkReferencedRulesExist(grammar);
});
},
Sequence:
function(grammar) {
@ -256,12 +259,11 @@ PEG.Grammar.extendNodes("checkReferencedRulesExist", {
});
},
Choice:
function(grammar) {
PEG.ArrayUtils.each(this._alternatives, function(alternative) {
alternative.checkReferencedRulesExist(grammar);
});
},
AndPredicate:
function(grammar) { this._expression.checkReferencedRulesExist(grammar); },
NotPredicate:
function(grammar) { this._expression.checkReferencedRulesExist(grammar); },
Optional:
function(grammar) { this._expression.checkReferencedRulesExist(grammar); },
@ -272,10 +274,7 @@ PEG.Grammar.extendNodes("checkReferencedRulesExist", {
OneOrMore:
function(grammar) { this._expression.checkReferencedRulesExist(grammar); },
AndPredicate:
function(grammar) { this._expression.checkReferencedRulesExist(grammar); },
NotPredicate:
Action:
function(grammar) { this._expression.checkReferencedRulesExist(grammar); },
RuleRef:
@ -287,8 +286,9 @@ PEG.Grammar.extendNodes("checkReferencedRulesExist", {
}
},
Action:
function(grammar) { this._expression.checkReferencedRulesExist(grammar); }
Literal: nop,
Any: nop,
Class: nop
});
@ -300,9 +300,12 @@ PEG.Grammar.extendNodes("checkNoLeftRecursion", {
this._expression.checkNoLeftRecursion(grammar, appliedRules.concat(this._name));
},
Literal: nop,
Class: nop,
Any: nop,
Choice:
function(grammar, appliedRules) {
PEG.ArrayUtils.each(this._alternatives, function(alternative) {
alternative.checkNoLeftRecursion(grammar, appliedRules);
});
},
Sequence:
function(grammar, appliedRules) {
@ -311,34 +314,32 @@ PEG.Grammar.extendNodes("checkNoLeftRecursion", {
}
},
Choice:
AndPredicate:
function(grammar, appliedRules) {
PEG.ArrayUtils.each(this._alternatives, function(alternative) {
alternative.checkNoLeftRecursion(grammar, appliedRules);
});
this._expression.checkNoLeftRecursion(grammar, appliedRules);
},
Optional:
NotPredicate:
function(grammar, appliedRules) {
this._expression.checkNoLeftRecursion(grammar, appliedRules);
},
ZeroOrMore:
Optional:
function(grammar, appliedRules) {
this._expression.checkNoLeftRecursion(grammar, appliedRules);
},
OneOrMore:
ZeroOrMore:
function(grammar, appliedRules) {
this._expression.checkNoLeftRecursion(grammar, appliedRules);
},
AndPredicate:
OneOrMore:
function(grammar, appliedRules) {
this._expression.checkNoLeftRecursion(grammar, appliedRules);
},
NotPredicate:
Action:
function(grammar, appliedRules) {
this._expression.checkNoLeftRecursion(grammar, appliedRules);
},
@ -351,10 +352,9 @@ PEG.Grammar.extendNodes("checkNoLeftRecursion", {
grammar[this._name].checkNoLeftRecursion(grammar, appliedRules);
},
Action:
function(grammar, appliedRules) {
this._expression.checkNoLeftRecursion(grammar, appliedRules);
}
Literal: nop,
Any: nop,
Class: nop
});
/* ===== PEG.Compiler ===== */
@ -754,69 +754,31 @@ PEG.Grammar.Rule.prototype.compile = function() {
* |null|.
*/
PEG.Grammar.Literal.prototype.compile = function(resultVar) {
return PEG.Compiler.formatCode(
"if (this._input.substr(this._pos, ${length}) === ${value|string}) {",
" var ${resultVar} = ${value|string};",
" this._pos += ${length};",
"} else {",
" var ${resultVar} = null;",
" if (context.reportMatchFailures) {",
" this._matchFailed(this._quoteString(${value|string}));",
" }",
"}",
{
value: this._value,
length: this._value.length,
resultVar: resultVar
}
PEG.Grammar.Choice.prototype.compile = function(resultVar) {
var code = PEG.Compiler.formatCode(
"var ${resultVar} = null;",
{ resultVar: resultVar }
);
};
PEG.Grammar.Class.prototype.compile = function(resultVar) {
/*
* Stupid IE considers regexps /[]/ and /[^]/ syntactically invalid, so we
* translate them into euqivalents it can handle.
*/
if (this._characters === "") {
var regexp = "/^(?!)/";
} else if (this._characters === "^") {
var regexp = "/^[\\S\\s]/";
} else {
var regexp = "/^[" + this._characters + "]/";
for (var i = this._alternatives.length - 1; i >= 0; i--) {
var alternativeResultVar = PEG.Compiler.generateUniqueIdentifier("result");
code = PEG.Compiler.formatCode(
"${alternativeCode}",
"if (${alternativeResultVar} !== null) {",
" var ${resultVar} = ${alternativeResultVar};",
"} else {",
" ${code};",
"}",
{
alternativeCode: this._alternatives[i].compile(alternativeResultVar),
alternativeResultVar: alternativeResultVar,
code: code,
resultVar: resultVar
}
);
}
return PEG.Compiler.formatCode(
"if (this._input.substr(this._pos).match(${regexp}) !== null) {",
" var ${resultVar} = this._input.charAt(this._pos);",
" this._pos++;",
"} else {",
" var ${resultVar} = null;",
" if (context.reportMatchFailures) {",
" this._matchFailed('[' + ${characters|string} + ']');",
" }",
"}",
{
characters: this._characters,
regexp: regexp,
resultVar: resultVar
}
);
};
PEG.Grammar.Any.prototype.compile = function(resultVar) {
return PEG.Compiler.formatCode(
"if (this._input.length > this._pos) {",
" var ${resultVar} = this._input.charAt(this._pos);",
" this._pos++;",
"} else {",
" var ${resultVar} = null;",
" if (context.reportMatchFailures) {",
" this._matchFailed('any character');",
" }",
"}",
{ resultVar: resultVar }
);
return code;
};
PEG.Grammar.Sequence.prototype.compile = function(resultVar) {
@ -863,31 +825,58 @@ PEG.Grammar.Sequence.prototype.compile = function(resultVar) {
);
};
PEG.Grammar.Choice.prototype.compile = function(resultVar) {
var code = PEG.Compiler.formatCode(
"var ${resultVar} = null;",
{ resultVar: resultVar }
PEG.Grammar.AndPredicate.prototype.compile = function(resultVar) {
var savedPosVar = PEG.Compiler.generateUniqueIdentifier("savedPos");
var savedReportMatchFailuresVar = PEG.Compiler.generateUniqueIdentifier("savedReportMatchFailuresVar");
var expressionResultVar = PEG.Compiler.generateUniqueIdentifier("result");
return PEG.Compiler.formatCode(
"var ${savedPosVar} = this._pos;",
"var ${savedReportMatchFailuresVar} = context.reportMatchFailures;",
"context.reportMatchFailures = false;",
"${expressionCode}",
"context.reportMatchFailures = ${savedReportMatchFailuresVar};",
"if (${expressionResultVar} !== null) {",
" var ${resultVar} = '';",
" this._pos = ${savedPosVar};",
"} else {",
" var ${resultVar} = null;",
"}",
{
expressionCode: this._expression.compile(expressionResultVar),
expressionResultVar: expressionResultVar,
savedPosVar: savedPosVar,
savedReportMatchFailuresVar: savedReportMatchFailuresVar,
resultVar: resultVar
}
);
};
for (var i = this._alternatives.length - 1; i >= 0; i--) {
var alternativeResultVar = PEG.Compiler.generateUniqueIdentifier("result");
code = PEG.Compiler.formatCode(
"${alternativeCode}",
"if (${alternativeResultVar} !== null) {",
" var ${resultVar} = ${alternativeResultVar};",
"} else {",
" ${code};",
"}",
{
alternativeCode: this._alternatives[i].compile(alternativeResultVar),
alternativeResultVar: alternativeResultVar,
code: code,
resultVar: resultVar
}
);
}
PEG.Grammar.NotPredicate.prototype.compile = function(resultVar) {
var savedPosVar = PEG.Compiler.generateUniqueIdentifier("savedPos");
var savedReportMatchFailuresVar = PEG.Compiler.generateUniqueIdentifier("savedReportMatchFailuresVar");
var expressionResultVar = PEG.Compiler.generateUniqueIdentifier("result");
return code;
return PEG.Compiler.formatCode(
"var ${savedPosVar} = this._pos;",
"var ${savedReportMatchFailuresVar} = context.reportMatchFailures;",
"context.reportMatchFailures = false;",
"${expressionCode}",
"context.reportMatchFailures = ${savedReportMatchFailuresVar};",
"if (${expressionResultVar} === null) {",
" var ${resultVar} = '';",
"} else {",
" var ${resultVar} = null;",
" this._pos = ${savedPosVar};",
"}",
{
expressionCode: this._expression.compile(expressionResultVar),
expressionResultVar: expressionResultVar,
savedPosVar: savedPosVar,
savedReportMatchFailuresVar: savedReportMatchFailuresVar,
resultVar: resultVar
}
);
};
PEG.Grammar.Optional.prototype.compile = function(resultVar) {
@ -944,70 +933,6 @@ PEG.Grammar.OneOrMore.prototype.compile = function(resultVar) {
);
};
PEG.Grammar.NotPredicate.prototype.compile = function(resultVar) {
var savedPosVar = PEG.Compiler.generateUniqueIdentifier("savedPos");
var savedReportMatchFailuresVar = PEG.Compiler.generateUniqueIdentifier("savedReportMatchFailuresVar");
var expressionResultVar = PEG.Compiler.generateUniqueIdentifier("result");
return PEG.Compiler.formatCode(
"var ${savedPosVar} = this._pos;",
"var ${savedReportMatchFailuresVar} = context.reportMatchFailures;",
"context.reportMatchFailures = false;",
"${expressionCode}",
"context.reportMatchFailures = ${savedReportMatchFailuresVar};",
"if (${expressionResultVar} === null) {",
" var ${resultVar} = '';",
"} else {",
" var ${resultVar} = null;",
" this._pos = ${savedPosVar};",
"}",
{
expressionCode: this._expression.compile(expressionResultVar),
expressionResultVar: expressionResultVar,
savedPosVar: savedPosVar,
savedReportMatchFailuresVar: savedReportMatchFailuresVar,
resultVar: resultVar
}
);
};
PEG.Grammar.AndPredicate.prototype.compile = function(resultVar) {
var savedPosVar = PEG.Compiler.generateUniqueIdentifier("savedPos");
var savedReportMatchFailuresVar = PEG.Compiler.generateUniqueIdentifier("savedReportMatchFailuresVar");
var expressionResultVar = PEG.Compiler.generateUniqueIdentifier("result");
return PEG.Compiler.formatCode(
"var ${savedPosVar} = this._pos;",
"var ${savedReportMatchFailuresVar} = context.reportMatchFailures;",
"context.reportMatchFailures = false;",
"${expressionCode}",
"context.reportMatchFailures = ${savedReportMatchFailuresVar};",
"if (${expressionResultVar} !== null) {",
" var ${resultVar} = '';",
" this._pos = ${savedPosVar};",
"} else {",
" var ${resultVar} = null;",
"}",
{
expressionCode: this._expression.compile(expressionResultVar),
expressionResultVar: expressionResultVar,
savedPosVar: savedPosVar,
savedReportMatchFailuresVar: savedReportMatchFailuresVar,
resultVar: resultVar
}
);
};
PEG.Grammar.RuleRef.prototype.compile = function(resultVar) {
return PEG.Compiler.formatCode(
"var ${resultVar} = this.${ruleMethod}(context);",
{
ruleMethod: "_parse_" + this._name,
resultVar: resultVar
}
);
};
PEG.Grammar.Action.prototype.compile = function(resultVar) {
/*
* In case of sequences, we splat their elements into function arguments one
@ -1058,4 +983,80 @@ PEG.Grammar.Action.prototype.compile = function(resultVar) {
);
};
PEG.Grammar.RuleRef.prototype.compile = function(resultVar) {
return PEG.Compiler.formatCode(
"var ${resultVar} = this.${ruleMethod}(context);",
{
ruleMethod: "_parse_" + this._name,
resultVar: resultVar
}
);
};
PEG.Grammar.Literal.prototype.compile = function(resultVar) {
return PEG.Compiler.formatCode(
"if (this._input.substr(this._pos, ${length}) === ${value|string}) {",
" var ${resultVar} = ${value|string};",
" this._pos += ${length};",
"} else {",
" var ${resultVar} = null;",
" if (context.reportMatchFailures) {",
" this._matchFailed(this._quoteString(${value|string}));",
" }",
"}",
{
value: this._value,
length: this._value.length,
resultVar: resultVar
}
);
};
PEG.Grammar.Any.prototype.compile = function(resultVar) {
return PEG.Compiler.formatCode(
"if (this._input.length > this._pos) {",
" var ${resultVar} = this._input.charAt(this._pos);",
" this._pos++;",
"} else {",
" var ${resultVar} = null;",
" if (context.reportMatchFailures) {",
" this._matchFailed('any character');",
" }",
"}",
{ resultVar: resultVar }
);
};
PEG.Grammar.Class.prototype.compile = function(resultVar) {
/*
* Stupid IE considers regexps /[]/ and /[^]/ syntactically invalid, so we
* translate them into euqivalents it can handle.
*/
if (this._characters === "") {
var regexp = "/^(?!)/";
} else if (this._characters === "^") {
var regexp = "/^[\\S\\s]/";
} else {
var regexp = "/^[" + this._characters + "]/";
}
return PEG.Compiler.formatCode(
"if (this._input.substr(this._pos).match(${regexp}) !== null) {",
" var ${resultVar} = this._input.charAt(this._pos);",
" this._pos++;",
"} else {",
" var ${resultVar} = null;",
" if (context.reportMatchFailures) {",
" this._matchFailed('[' + ${characters|string} + ']');",
" }",
"}",
{
characters: this._characters,
regexp: regexp,
resultVar: resultVar
}
);
};
})();

@ -202,15 +202,15 @@ test("buildParser reports missing start rule", function() {
test("buildParser reports missing referenced rules", function() {
var grammars = [
'start: missing',
'start: missing "a" "b"',
'start: "a" "b" missing',
'start: missing / "a" / "b"',
'start: "a" / "b" / missing',
'start: missing "a" "b"',
'start: "a" "b" missing',
'start: &missing',
'start: !missing',
'start: missing?',
'start: missing*',
'start: missing+',
'start: &missing',
'start: !missing',
'start: missing { }'
];
@ -227,14 +227,14 @@ test("buildParser reports left recursion", function() {
var grammars = [
/* Direct */
'start: start',
'start: start "a" "b"',
'start: start / "a" / "b"',
'start: "a" / "b" / start',
'start: start "a" "b"',
'start: &start',
'start: !start',
'start: start?',
'start: start*',
'start: start+',
'start: &start',
'start: !start',
'start: start { }',
/* Indirect */
@ -259,69 +259,14 @@ test("buildParser allows custom start rule", function() {
module("Generated Parser");
test("literals", function() {
var parser = PEG.buildParser('start: "abcd"');
parses(parser, "abcd", "abcd");
doesNotParse(parser, "");
doesNotParse(parser, "abc");
doesNotParse(parser, "abcde");
doesNotParse(parser, "efgh");
/*
* Test that the parsing position moves forward after successful parsing of
* a literal.
*/
var posTestParser = PEG.buildParser('start: "a" "b"');
parses(posTestParser, "ab", ["a", "b"]);
});
test("anys", function() {
var parser = PEG.buildParser('start: .');
test("choices", function() {
var parser = PEG.buildParser('start: "a" / "b" / "c"');
parses(parser, "a", "a");
parses(parser, "b", "b");
parses(parser, "c", "c");
doesNotParse(parser, "");
doesNotParse(parser, "ab");
/*
* Test that the parsing position moves forward after successful parsing of
* an any.
*/
var posTestParser = PEG.buildParser('start: . .');
parses(posTestParser, "ab", ["a", "b"]);
});
test("classes", function() {
var emptyClassParser = PEG.buildParser('start: []');
doesNotParse(emptyClassParser, "");
doesNotParse(emptyClassParser, "a");
doesNotParse(emptyClassParser, "ab");
var nonEmptyClassParser = PEG.buildParser('start: [ab-d]');
parses(nonEmptyClassParser, "a", "a");
parses(nonEmptyClassParser, "b", "b");
parses(nonEmptyClassParser, "c", "c");
parses(nonEmptyClassParser, "d", "d");
doesNotParse(nonEmptyClassParser, "");
doesNotParse(nonEmptyClassParser, "ab");
var invertedEmptyClassParser = PEG.buildParser('start: [^]');
doesNotParse(invertedEmptyClassParser, "");
parses(invertedEmptyClassParser, "a", "a");
doesNotParse(invertedEmptyClassParser, "ab");
var invertedNonEmptyClassParser = PEG.buildParser('start: [^ab-d]');
doesNotParse(invertedNonEmptyClassParser, "a", "a");
doesNotParse(invertedNonEmptyClassParser, "b", "b");
doesNotParse(invertedNonEmptyClassParser, "c", "c");
doesNotParse(invertedNonEmptyClassParser, "d", "d");
doesNotParse(invertedNonEmptyClassParser, "");
doesNotParse(invertedNonEmptyClassParser, "ab");
/*
* Test that the parsing position moves forward after successful parsing of
* a class.
*/
var posTestParser = PEG.buildParser('start: [ab-d] [ab-d]');
parses(posTestParser, "ab", ["a", "b"]);
doesNotParse(parser, "d");
});
test("sequences", function() {
@ -344,36 +289,6 @@ test("sequences", function() {
parses(posTestParser, "a", "a");
});
test("choices", function() {
var parser = PEG.buildParser('start: "a" / "b" / "c"');
parses(parser, "a", "a");
parses(parser, "b", "b");
parses(parser, "c", "c");
doesNotParse(parser, "");
doesNotParse(parser, "ab");
doesNotParse(parser, "d");
});
test("optional expressions", function() {
var parser = PEG.buildParser('start: "a"?');
parses(parser, "", "");
parses(parser, "a", "a");
});
test("zero or more expressions", function() {
var parser = PEG.buildParser('start: "a"*');
parses(parser, "", []);
parses(parser, "a", ["a"]);
parses(parser, "aaa", ["a", "a", "a"]);
});
test("one or more expressions", function() {
var parser = PEG.buildParser('start: "a"+');
doesNotParse(parser, "");
parses(parser, "a", ["a"]);
parses(parser, "aaa", ["a", "a", "a"]);
});
test("and predicate", function() {
var parser = PEG.buildParser('start: "a" &"b" "b"');
parses(parser, "ab", ["a", "", "b"]);
@ -398,14 +313,24 @@ test("not predicate", function() {
parses(posTestParser, "ac", ["a", "", "c"]);
});
test("rule references", function() {
var parser = PEG.buildParser([
'start: static / dynamic',
'static: "C" / "C++" / "Java" / "C#"',
'dynamic: "Ruby" / "Python" / "JavaScript"'
].join("\n"));
parses(parser, "Java", "Java");
parses(parser, "Python", "Python");
test("optional expressions", function() {
var parser = PEG.buildParser('start: "a"?');
parses(parser, "", "");
parses(parser, "a", "a");
});
test("zero or more expressions", function() {
var parser = PEG.buildParser('start: "a"*');
parses(parser, "", []);
parses(parser, "a", ["a"]);
parses(parser, "aaa", ["a", "a", "a"]);
});
test("one or more expressions", function() {
var parser = PEG.buildParser('start: "a"+');
doesNotParse(parser, "");
parses(parser, "a", ["a"]);
parses(parser, "aaa", ["a", "a", "a"]);
});
test("actions", function() {
@ -438,6 +363,81 @@ test("actions", function() {
parses(variablesParser, "abcdefghij", "ABCDEFGHIJ");
});
test("rule references", function() {
var parser = PEG.buildParser([
'start: static / dynamic',
'static: "C" / "C++" / "Java" / "C#"',
'dynamic: "Ruby" / "Python" / "JavaScript"'
].join("\n"));
parses(parser, "Java", "Java");
parses(parser, "Python", "Python");
});
test("literals", function() {
var parser = PEG.buildParser('start: "abcd"');
parses(parser, "abcd", "abcd");
doesNotParse(parser, "");
doesNotParse(parser, "abc");
doesNotParse(parser, "abcde");
doesNotParse(parser, "efgh");
/*
* Test that the parsing position moves forward after successful parsing of
* a literal.
*/
var posTestParser = PEG.buildParser('start: "a" "b"');
parses(posTestParser, "ab", ["a", "b"]);
});
test("anys", function() {
var parser = PEG.buildParser('start: .');
parses(parser, "a", "a");
doesNotParse(parser, "");
doesNotParse(parser, "ab");
/*
* Test that the parsing position moves forward after successful parsing of
* an any.
*/
var posTestParser = PEG.buildParser('start: . .');
parses(posTestParser, "ab", ["a", "b"]);
});
test("classes", function() {
var emptyClassParser = PEG.buildParser('start: []');
doesNotParse(emptyClassParser, "");
doesNotParse(emptyClassParser, "a");
doesNotParse(emptyClassParser, "ab");
var nonEmptyClassParser = PEG.buildParser('start: [ab-d]');
parses(nonEmptyClassParser, "a", "a");
parses(nonEmptyClassParser, "b", "b");
parses(nonEmptyClassParser, "c", "c");
parses(nonEmptyClassParser, "d", "d");
doesNotParse(nonEmptyClassParser, "");
doesNotParse(nonEmptyClassParser, "ab");
var invertedEmptyClassParser = PEG.buildParser('start: [^]');
doesNotParse(invertedEmptyClassParser, "");
parses(invertedEmptyClassParser, "a", "a");
doesNotParse(invertedEmptyClassParser, "ab");
var invertedNonEmptyClassParser = PEG.buildParser('start: [^ab-d]');
doesNotParse(invertedNonEmptyClassParser, "a", "a");
doesNotParse(invertedNonEmptyClassParser, "b", "b");
doesNotParse(invertedNonEmptyClassParser, "c", "c");
doesNotParse(invertedNonEmptyClassParser, "d", "d");
doesNotParse(invertedNonEmptyClassParser, "");
doesNotParse(invertedNonEmptyClassParser, "ab");
/*
* Test that the parsing position moves forward after successful parsing of
* a class.
*/
var posTestParser = PEG.buildParser('start: [ab-d] [ab-d]');
parses(posTestParser, "ab", ["a", "b"]);
});
test("cache", function() {
/*
* Should trigger a codepath where the cache is used (for the "a" rule).

Loading…
Cancel
Save