Initial commit.
commit
c3dd696a3e
@ -0,0 +1,22 @@
|
||||
Copyright (c) 2010 David Majda
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
@ -0,0 +1,4 @@
|
||||
PEG.js: Parser Generator for JavaScript
|
||||
=======================================
|
||||
|
||||
Documentation is being written -- please be patient.
|
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
DIR=`dirname "$0"`
|
||||
java -jar "$DIR/../vendor/rhino/js.jar" "$DIR/pegjs-main.js" "$DIR" "$@"
|
@ -0,0 +1,160 @@
|
||||
importPackage(java.io);
|
||||
importPackage(java.lang);
|
||||
|
||||
/*
|
||||
* Rhino does not have __FILE__ or anything similar so we have to pass the
|
||||
* script path from the outside.
|
||||
*/
|
||||
load(arguments[0] + "/../lib/runtime.js");
|
||||
load(arguments[0] + "/../lib/compiler.js");
|
||||
|
||||
var FILE_STDIN = "-";
|
||||
var FILE_STDOUT = "-";
|
||||
|
||||
function readFile(file) {
|
||||
var f = new BufferedReader(new InputStreamReader(
|
||||
file === FILE_STDIN ? System["in"] : new FileInputStream(file)
|
||||
));
|
||||
|
||||
var result = "";
|
||||
var line = "";
|
||||
try {
|
||||
while ((line = f.readLine()) !== null) {
|
||||
result += line + "\n";
|
||||
}
|
||||
} finally {
|
||||
f.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function writeFile(file, text) {
|
||||
var f = new BufferedWriter(new OutputStreamWriter(
|
||||
file === FILE_STDOUT ? System.out : new FileOutputStream(file)
|
||||
));
|
||||
|
||||
try {
|
||||
f.write(text);
|
||||
} finally {
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
|
||||
function isOption(arg) {
|
||||
return /-.+/.test(arg);
|
||||
}
|
||||
|
||||
function printVersion() {
|
||||
print("PEG.js 0.1");
|
||||
}
|
||||
|
||||
function printHelp() {
|
||||
print("Usage: pegjs [options] [--] <parserVar> [<input_file>] [<output_file>]");
|
||||
print("");
|
||||
print("Generates a parser from the PEG grammar specified in the <input_file> and");
|
||||
print("writes it to the <output_file>. The parser object will be stored in a variable");
|
||||
print("named <parser_var>.");
|
||||
print("");
|
||||
print("If the <output_file> is omitted, its name is generated by changing the");
|
||||
print("<input_file> extension to \".js\". If both <input_file> and <output_file> are");
|
||||
print("omitted, standard input and output are used.");
|
||||
print("");
|
||||
print("Options:");
|
||||
print(" -s, --start-rule specify grammar start rule (default: \"start\")");
|
||||
print(" -v, --version print version information and exit");
|
||||
print(" -h, --help print help and exit");
|
||||
}
|
||||
|
||||
function nextArg() {
|
||||
args.shift();
|
||||
}
|
||||
|
||||
function exitSuccess() {
|
||||
quit(0);
|
||||
}
|
||||
|
||||
function exitFailure() {
|
||||
quit(1);
|
||||
}
|
||||
|
||||
function abort(message) {
|
||||
System.out.println(message);
|
||||
exitFailure();
|
||||
}
|
||||
|
||||
var startRule = "start";
|
||||
|
||||
/*
|
||||
* The trimmed first argument is the script path -- see the beginning of this
|
||||
* file.
|
||||
*/
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
while (args.length > 0 && isOption(args[0])) {
|
||||
switch (args[0]) {
|
||||
case "-s":
|
||||
case "--start":
|
||||
nextArg();
|
||||
if (args.length === 0) {
|
||||
abort("Missing parameter of the -s/--start option.");
|
||||
}
|
||||
startRule = args[0];
|
||||
break;
|
||||
|
||||
case "-v":
|
||||
case "--version":
|
||||
printVersion();
|
||||
exitSuccess();
|
||||
break;
|
||||
|
||||
case "-h":
|
||||
case "--help":
|
||||
printHelp();
|
||||
exitSuccess();
|
||||
break;
|
||||
|
||||
case "--":
|
||||
nextArg();
|
||||
break;
|
||||
|
||||
default:
|
||||
abort("Unknown option: " + args[0] + ".");
|
||||
}
|
||||
nextArg();
|
||||
}
|
||||
|
||||
if (args.length === 0) {
|
||||
abort("Too few arguments.");
|
||||
}
|
||||
var parserVar = args[0];
|
||||
nextArg();
|
||||
|
||||
switch (args.length) {
|
||||
case 0:
|
||||
var inputFile = FILE_STDIN;
|
||||
var outputFile = FILE_STDOUT;
|
||||
break;
|
||||
case 1:
|
||||
var inputFile = args[0];
|
||||
var outputFile = args[0].replace(/\.[^.]*$/, ".js");
|
||||
break;
|
||||
case 2:
|
||||
var inputFile = args[0];
|
||||
var outputFile = args[1];
|
||||
break;
|
||||
default:
|
||||
abort("Too many arguments.");
|
||||
}
|
||||
|
||||
var input = readFile(inputFile);
|
||||
try {
|
||||
var parser = PEG.buildParser(input, startRule);
|
||||
} catch (e) {
|
||||
if (e.line !== undefined && e.column !== undefined) {
|
||||
abort(e.line + ":" + e.column + ": " + e.message);
|
||||
} else {
|
||||
abort(e.message);
|
||||
}
|
||||
}
|
||||
writeFile(outputFile, parserVar + " = " + parser.toSource() + ";\n");
|
@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
|
||||
set DIR_WITH_SLASH=%~dp0
|
||||
set DIR=%DIR_WITH_SLASH:~0,-1%
|
||||
java -jar "%DIR%\..\vendor\rhino\js.jar" "%DIR%\pegjs-main.js" "%DIR%" %*
|
@ -0,0 +1,31 @@
|
||||
start : _ expression { return $2; }
|
||||
|
||||
expression : additive
|
||||
|
||||
additive : multiplicative (plus / minus) additive {
|
||||
if ($2 === "+") { return $1 + $3; }
|
||||
if ($2 === "-") { return $1 - $3; }
|
||||
}
|
||||
/ multiplicative
|
||||
|
||||
multiplicative : primary (times / divide) multiplicative {
|
||||
if ($2 === "*") { return $1 * $3; }
|
||||
if ($2 === "/") { return $1 / $3; }
|
||||
}
|
||||
/ primary
|
||||
|
||||
primary : integer
|
||||
/ lparen expression rparen { return $2 }
|
||||
|
||||
integer "integer"
|
||||
: [0-9]+ _ { return parseInt($1.join("")); }
|
||||
|
||||
plus : "+" _ { return $1; }
|
||||
minus : "-" _ { return $1; }
|
||||
times : "*" _ { return $1; }
|
||||
divide : "/" _ { return $1; }
|
||||
lparen : "(" _
|
||||
rparen : ")" _
|
||||
|
||||
_ "whitespace"
|
||||
: [ \t\n\r]*
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* PEG.js runtime.
|
||||
*
|
||||
* Required by all parsers generated by PEG.js.
|
||||
*/
|
||||
|
||||
PEG = {};
|
||||
|
||||
(function() {
|
||||
|
||||
/* ===== PEG.ArrayUtils ===== */
|
||||
|
||||
/* Array manipulation utility functions. */
|
||||
|
||||
PEG.ArrayUtils = {
|
||||
map: function(array, callback) {
|
||||
var result = [];
|
||||
var length = array.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
result[i] = callback(array[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/* ===== PEG.StringUtils ===== */
|
||||
|
||||
/* String manipulation utility functions. */
|
||||
|
||||
PEG.StringUtils = {
|
||||
/*
|
||||
* Surrounds the string with quotes and escapes characters inside so that the
|
||||
* result is a valid JavaScript string.
|
||||
*/
|
||||
quote: function(s) {
|
||||
/*
|
||||
* ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a string
|
||||
* literal except for the closing quote character, backslash, carriage
|
||||
* return, line separator, paragraph separator, and line feed. Any character
|
||||
* may appear in the form of an escape sequence.
|
||||
*/
|
||||
return '"' + s
|
||||
.replace(/\\/g, '\\\\') // backslash
|
||||
.replace(/"/g, '\\"') // closing quote character
|
||||
.replace(/\r/g, '\\r') // carriage return
|
||||
.replace(/\u2028/g, '\\u2028') // line separator
|
||||
.replace(/\u2029/g, '\\u2029') // paragraph separator
|
||||
.replace(/\n/g, '\\n') // line feed
|
||||
+ '"';
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
/* ===== PEG.Parser ===== */
|
||||
|
||||
/* Prototype of all parsers generated by PEG.js. */
|
||||
|
||||
PEG.Parser = function(startRule) { this._startRule = startRule; }
|
||||
|
||||
PEG.Parser.prototype = {
|
||||
_matchFailed: function(failure) {
|
||||
if (this._pos > this._rightmostMatchFailuresPos) {
|
||||
this._rightmostMatchFailuresPos = this._pos;
|
||||
this._rightmostMatchFailures = [];
|
||||
}
|
||||
this._rightmostMatchFailures.push(failure);
|
||||
},
|
||||
|
||||
/*
|
||||
* Parses the input with a generated parser. If the parsing is successfull,
|
||||
* returns a value explicitly or implicitly specified by the grammar from
|
||||
* which the parser was generated (see |PEG.buildParser|). If the parsing is
|
||||
* unsuccessful, throws |PEG.Parser.SyntaxError| describing the error.
|
||||
*/
|
||||
parse: function(input) {
|
||||
var that = this;
|
||||
|
||||
function initialize() {
|
||||
that._input = input;
|
||||
that._pos = 0;
|
||||
that._rightmostMatchFailuresPos = 0;
|
||||
that._rightmostMatchFailures = [];
|
||||
that._cache = {};
|
||||
}
|
||||
|
||||
function buildErrorMessage() {
|
||||
function buildExpectedFromMatchFailures(failures) {
|
||||
switch (failures.length) {
|
||||
case 0:
|
||||
return "nothing";
|
||||
case 1:
|
||||
return failures[0].toString();
|
||||
default:
|
||||
return PEG.ArrayUtils.map(
|
||||
failures.slice(0, failures.length - 1),
|
||||
function(failure) { return failure.toString(); }
|
||||
).join(", ")
|
||||
+ " or "
|
||||
+ failures[failures.length - 1].toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (that._pos === 0) {
|
||||
var expected = buildExpectedFromMatchFailures(
|
||||
that._rightmostMatchFailures
|
||||
);
|
||||
var actual = that._rightmostMatchFailuresPos < that._input.length
|
||||
? PEG.StringUtils.quote(
|
||||
that._input.charAt(that._rightmostMatchFailuresPos)
|
||||
)
|
||||
: "end of input";
|
||||
} else {
|
||||
var expected = "end of input";
|
||||
var actual = PEG.StringUtils.quote(that._input.charAt(that._pos));
|
||||
}
|
||||
|
||||
return "Expected " + expected + " but " + actual + " found.";
|
||||
}
|
||||
|
||||
function computeErrorPosition() {
|
||||
/*
|
||||
* The first idea was to use |String.split| to break the input up to the
|
||||
* error position along newlines and derive the line and column from
|
||||
* there. However IE's |split| implementation is so broken that it was
|
||||
* enough to prevent it.
|
||||
*/
|
||||
|
||||
var input = that._input;
|
||||
var pos = that._rightmostMatchFailuresPos;
|
||||
var line = 1;
|
||||
var column = 1;
|
||||
var seenCR = false;
|
||||
|
||||
for (var i = 0; i < pos; i++) {
|
||||
var ch = input.charAt(i);
|
||||
if (ch === "\n") {
|
||||
if (!seenCR) { line++; }
|
||||
column = 1;
|
||||
seenCR = false;
|
||||
} else if (ch === "\r" | ch === "\u2028" || ch === "\u2029") {
|
||||
line++;
|
||||
column = 1;
|
||||
seenCR = true;
|
||||
} else {
|
||||
column++;
|
||||
seenCR = false;
|
||||
}
|
||||
}
|
||||
|
||||
return { line: line, column: column };
|
||||
}
|
||||
|
||||
initialize();
|
||||
|
||||
var initialContext = {
|
||||
reportMatchFailures: true,
|
||||
};
|
||||
|
||||
var result = this["_parse_" + this._startRule](initialContext);
|
||||
if (result === null || this._pos !== input.length) {
|
||||
var errorPosition = computeErrorPosition();
|
||||
throw new PEG.Parser.SyntaxError(
|
||||
errorPosition.line,
|
||||
errorPosition.column,
|
||||
buildErrorMessage()
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/* Returns the parser source code. */
|
||||
toSource: function() { return this._source; }
|
||||
};
|
||||
|
||||
/* ===== PEG.Parser.LiteralMatchFailure ===== */
|
||||
|
||||
/* Stores information about a literal match failure. */
|
||||
|
||||
PEG.Parser.LiteralMatchFailure = function(value) { this._value = value; };
|
||||
|
||||
PEG.Parser.LiteralMatchFailure.prototype = {
|
||||
toString: function() { return PEG.StringUtils.quote(this._value); }
|
||||
};
|
||||
|
||||
/* ===== PEG.Parser.AnyMatchFailure ===== */
|
||||
|
||||
/* Stores information about a failure to match a "." expression. */
|
||||
|
||||
PEG.Parser.AnyMatchFailure = function() {}
|
||||
|
||||
PEG.Parser.AnyMatchFailure.prototype = {
|
||||
toString: function() { return "any character"; }
|
||||
};
|
||||
|
||||
/* ===== PEG.Parser.NamedRuleMatchFailure ===== */
|
||||
|
||||
/* Stores information about a failure to match a named rule. */
|
||||
|
||||
PEG.Parser.NamedRuleMatchFailure = function(humanName) { this._humanName = humanName; }
|
||||
|
||||
PEG.Parser.NamedRuleMatchFailure.prototype = {
|
||||
toString: function() { return this._humanName; }
|
||||
};
|
||||
|
||||
/* ===== PEG.Parser.SyntaxError ===== */
|
||||
|
||||
/* Thrown when a parser encounters a syntax error. */
|
||||
|
||||
PEG.Parser.SyntaxError = function(line, column, message) {
|
||||
this.name = "PEG.Parser.SyntaxError";
|
||||
this.line = line;
|
||||
this.column = column;
|
||||
this.message = message;
|
||||
};
|
||||
|
||||
PEG.Parser.SyntaxError.prototype = Error.prototype;
|
||||
|
||||
})();
|
@ -0,0 +1,919 @@
|
||||
(function() {
|
||||
|
||||
var global = this;
|
||||
|
||||
/* ===== Helpers ===== */
|
||||
|
||||
global.throws = function(block, exceptionType) {
|
||||
var exception = null;
|
||||
try {
|
||||
block();
|
||||
} catch (e) {
|
||||
exception = e;
|
||||
}
|
||||
|
||||
ok(
|
||||
exception !== null,
|
||||
exception !== null ? "okay: thrown something" : "failed, nothing thrown"
|
||||
);
|
||||
if (exception !== null) {
|
||||
ok(
|
||||
exception instanceof exceptionType,
|
||||
exception instanceof exceptionType
|
||||
? "okay: thrown " + exceptionType.name
|
||||
: "failed, thrown " + exception.name + " instead of " + exceptionType.name
|
||||
);
|
||||
}
|
||||
|
||||
return exception;
|
||||
};
|
||||
|
||||
global.parses = function(parser, input, expected) {
|
||||
deepEqual(parser.parse(input), expected);
|
||||
};
|
||||
|
||||
global.doesNotParse = function(parser, input) {
|
||||
throws(function() { parser.parse(input); }, PEG.Parser.SyntaxError);
|
||||
};
|
||||
|
||||
global.doesNotParseWithMessage = function(parser, input, message) {
|
||||
var exception = throws(
|
||||
function() { parser.parse(input); },
|
||||
PEG.Parser.SyntaxError
|
||||
);
|
||||
if (exception) {
|
||||
strictEqual(exception.message, message);
|
||||
}
|
||||
};
|
||||
|
||||
global.doesNotParseWithPos = function(parser, input, line, column) {
|
||||
var exception = throws(
|
||||
function() { parser.parse(input); },
|
||||
PEG.Parser.SyntaxError
|
||||
);
|
||||
if (exception) {
|
||||
strictEqual(exception.line, line);
|
||||
strictEqual(exception.column, column);
|
||||
}
|
||||
};
|
||||
|
||||
global.grammarParserParses = function(input, expected) {
|
||||
global.parses(PEG.grammarParser, input, expected);
|
||||
};
|
||||
|
||||
global.grammarParserDoesNotParse = function(input) {
|
||||
global.doesNotParse(PEG.grammarParser, input);
|
||||
}
|
||||
|
||||
/* ===== PEG.Compiler ===== */
|
||||
|
||||
module("PEG.Compiler");
|
||||
|
||||
test("formatCode joins parts", function() {
|
||||
strictEqual(PEG.Compiler.formatCode("foo", "bar"), "foo\nbar");
|
||||
});
|
||||
|
||||
test("formatCode interpolates variables", function() {
|
||||
strictEqual(
|
||||
PEG.Compiler.formatCode("foo", "${bar}", { bar: "baz" }),
|
||||
"foo\nbaz"
|
||||
);
|
||||
|
||||
throws(function() {
|
||||
PEG.Compiler.formatCode("foo", "${bar}");
|
||||
}, Error);
|
||||
});
|
||||
|
||||
test("formatCode filters variables", function() {
|
||||
strictEqual(
|
||||
PEG.Compiler.formatCode("foo", "${bar|string}", { bar: "baz" }),
|
||||
"foo\n\"baz\""
|
||||
);
|
||||
|
||||
throws(function() {
|
||||
PEG.Compiler.formatCode("foo", "${bar|eeek}", { bar: "baz" });
|
||||
}, Error);
|
||||
});
|
||||
|
||||
test("formatCode indents multiline parts", function() {
|
||||
strictEqual(
|
||||
PEG.Compiler.formatCode("foo", "${bar}", { bar: " baz\nqux" }),
|
||||
"foo\n baz\n qux"
|
||||
);
|
||||
});
|
||||
|
||||
test("generateUniqueIdentifier", function() {
|
||||
notStrictEqual(
|
||||
PEG.Compiler.generateUniqueIdentifier("prefix"),
|
||||
PEG.Compiler.generateUniqueIdentifier("prefix")
|
||||
);
|
||||
});
|
||||
|
||||
/* ===== PEG ===== */
|
||||
|
||||
module("PEG");
|
||||
|
||||
test("buildParser reports invalid grammar object", function() {
|
||||
throws(function() { PEG.buildParser(42); }, PEG.Grammar.GrammarError);
|
||||
});
|
||||
|
||||
test("buildParser reports missing start rule", function() {
|
||||
throws(function() { PEG.buildParser({}); }, PEG.Grammar.GrammarError);
|
||||
});
|
||||
|
||||
test("buildParser allows custom start rule", function() {
|
||||
var parser = PEG.buildParser('s: "abcd"', "s");
|
||||
parses(parser, "abcd", "abcd");
|
||||
});
|
||||
|
||||
/* ===== Generated Parser ===== */
|
||||
|
||||
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: .');
|
||||
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");
|
||||
|
||||
/*
|
||||
* 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("sequences", function() {
|
||||
var emptySequenceParser = PEG.buildParser('start: ');
|
||||
parses(emptySequenceParser, "", []);
|
||||
doesNotParse(emptySequenceParser, "abc");
|
||||
|
||||
var nonEmptySequenceParser = PEG.buildParser('start: "a" "b" "c"');
|
||||
parses(nonEmptySequenceParser, "abc", ["a", "b", "c"]);
|
||||
doesNotParse(nonEmptySequenceParser, "");
|
||||
doesNotParse(nonEmptySequenceParser, "ab");
|
||||
doesNotParse(nonEmptySequenceParser, "abcd");
|
||||
doesNotParse(nonEmptySequenceParser, "efg");
|
||||
|
||||
/*
|
||||
* Test that the parsing position returns after unsuccessful parsing of a
|
||||
* sequence.
|
||||
*/
|
||||
var posTestParser = PEG.buildParser('start: ("a" "b") / "a"');
|
||||
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"]);
|
||||
doesNotParse(parser, "ac");
|
||||
|
||||
/*
|
||||
* Test that the parsing position returns after successful parsing of a
|
||||
* predicate is not needed, it is implicit in the tests above.
|
||||
*/
|
||||
});
|
||||
|
||||
test("not predicate", function() {
|
||||
var parser = PEG.buildParser('start: "a" !"b"');
|
||||
parses(parser, "a", ["a", ""]);
|
||||
doesNotParse(parser, "ab");
|
||||
|
||||
/*
|
||||
* Test that the parsing position returns after successful parsing of a
|
||||
* predicate.
|
||||
*/
|
||||
var posTestParser = PEG.buildParser('start: "a" !"b" "c"');
|
||||
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("actions", function() {
|
||||
var singleMatchParser = PEG.buildParser(
|
||||
'start: "a" { return Array.prototype.slice.call(arguments).join("").toUpperCase(); }'
|
||||
);
|
||||
parses(singleMatchParser, "a", "A");
|
||||
|
||||
var multiMatchParser = PEG.buildParser(
|
||||
'start: "a" "b" "c" { return Array.prototype.slice.call(arguments).join("").toUpperCase(); }'
|
||||
);
|
||||
parses(multiMatchParser, "abc", "ABC");
|
||||
|
||||
var innerMatchParser = PEG.buildParser(
|
||||
'start: "a" ("b" "c" "d" { return Array.prototype.slice.call(arguments).join("").toUpperCase(); }) "e"'
|
||||
);
|
||||
parses(innerMatchParser, "abcde", ["a", "BCD", "e"]);
|
||||
|
||||
/* Test that the action is not called when its expression does not match. */
|
||||
var notAMatchParser = PEG.buildParser(
|
||||
'start: "a" { ok(false, "action got called when it should not be"); }'
|
||||
);
|
||||
doesNotParse(notAMatchParser, "b");
|
||||
|
||||
var variablesParser = PEG.buildParser([
|
||||
'start: "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" {',
|
||||
' return [$1, $2, $3, $4, $5, $6, $7, $8, $9, $10].join("").toUpperCase();',
|
||||
' }'
|
||||
].join("\n"));
|
||||
parses(variablesParser, "abcdefghij", "ABCDEFGHIJ");
|
||||
});
|
||||
|
||||
test("cache", function() {
|
||||
/*
|
||||
* Should trigger a codepath where the cache is used (for the "a" rule).
|
||||
*/
|
||||
var parser = PEG.buildParser([
|
||||
'start: (a b) / (a c)',
|
||||
'a: "a"',
|
||||
'b: "b"',
|
||||
'c: "c"'
|
||||
].join("\n"));
|
||||
parses(parser, "ac", ["a", "c"]);
|
||||
});
|
||||
|
||||
test("error messages", function() {
|
||||
var literalParser = PEG.buildParser('start: "abcd"');
|
||||
doesNotParseWithMessage(
|
||||
literalParser,
|
||||
"",
|
||||
'Expected "abcd" but end of input found.'
|
||||
);
|
||||
doesNotParseWithMessage(
|
||||
literalParser,
|
||||
"efgh",
|
||||
'Expected "abcd" but "e" found.'
|
||||
);
|
||||
doesNotParseWithMessage(
|
||||
literalParser,
|
||||
"abcde",
|
||||
'Expected end of input but "e" found.'
|
||||
);
|
||||
|
||||
var anyParser = PEG.buildParser('start: .');
|
||||
doesNotParseWithMessage(
|
||||
anyParser,
|
||||
"",
|
||||
'Expected any character but end of input found.'
|
||||
);
|
||||
|
||||
var namedRuleWithLiteralParser = PEG.buildParser('start "digit": [0-9]');
|
||||
doesNotParseWithMessage(
|
||||
namedRuleWithLiteralParser,
|
||||
"a",
|
||||
'Expected digit but "a" found.'
|
||||
);
|
||||
|
||||
var namedRuleWithAnyParser = PEG.buildParser('start "whatever": .');
|
||||
doesNotParseWithMessage(
|
||||
namedRuleWithAnyParser,
|
||||
"",
|
||||
'Expected whatever but end of input found.'
|
||||
);
|
||||
|
||||
var namedRuleWithNamedRuleParser = PEG.buildParser([
|
||||
'start "digits": digit+',
|
||||
'digit "digit": [0-9]'
|
||||
].join("\n"));
|
||||
doesNotParseWithMessage(
|
||||
namedRuleWithNamedRuleParser,
|
||||
"",
|
||||
'Expected digits but end of input found.'
|
||||
);
|
||||
|
||||
var choiceParser = PEG.buildParser('start: "a" / "b" / "c"');
|
||||
doesNotParseWithMessage(
|
||||
choiceParser,
|
||||
"def",
|
||||
'Expected "a", "b" or "c" but "d" found.'
|
||||
);
|
||||
|
||||
var emptyParser = PEG.buildParser('start: ');
|
||||
doesNotParseWithMessage(
|
||||
emptyParser,
|
||||
"something",
|
||||
'Expected nothing but "s" found.'
|
||||
);
|
||||
});
|
||||
|
||||
test("error positions", function() {
|
||||
var parser = PEG.buildParser([
|
||||
'start: line (("\\r" / "\\n" / "\\u2028" / "\\u2029")+ line)*',
|
||||
'line: digit (" "+ digit)*',
|
||||
'digit: [0-9]+ { return $1.join(""); }'
|
||||
].join("\n"));
|
||||
|
||||
doesNotParseWithPos(parser, "a", 1, 1);
|
||||
doesNotParseWithPos(parser, "1\n2\n\n3\n\n\n4 5 x", 7, 5);
|
||||
|
||||
/* Non-Unix newlines */
|
||||
doesNotParseWithPos(parser, "1\rx", 2, 1); // Old Mac
|
||||
doesNotParseWithPos(parser, "1\r\nx", 2, 1); // Windows
|
||||
doesNotParseWithPos(parser, "1\n\rx", 3, 1); // mismatched
|
||||
|
||||
/* Strange newlines */
|
||||
doesNotParseWithPos(parser, "1\u2028x", 2, 1); // line separator
|
||||
doesNotParseWithPos(parser, "1\u2029x", 2, 1); // paragraph separator
|
||||
});
|
||||
|
||||
/*
|
||||
* Following examples are from Wikipedia, see
|
||||
* http://en.wikipedia.org/w/index.php?title=Parsing_expression_grammar&oldid=335106938.
|
||||
*/
|
||||
|
||||
test("arithmetics", function() {
|
||||
/*
|
||||
* Value ← [0-9]+ / '(' Expr ')'
|
||||
* Product ← Value (('*' / '/') Value)*
|
||||
* Sum ← Product (('+' / '-') Product)*
|
||||
* Expr ← Sum
|
||||
*/
|
||||
var parser = PEG.buildParser([
|
||||
'Value : [0-9]+ { return parseInt($1.join("")); }',
|
||||
' / "(" Expr ")" { return $2; }',
|
||||
'Product : Value (("*" / "/") Value)* {',
|
||||
' var result = $1;',
|
||||
' for (var i = 0; i < $2.length; i++) {',
|
||||
' if ($2[i][0] == "*") { result *= $2[i][1]; }',
|
||||
' if ($2[i][0] == "/") { result /= $2[i][1]; }',
|
||||
' }',
|
||||
' return result;',
|
||||
' }',
|
||||
'Sum : Product (("+" / "-") Product)* {',
|
||||
' var result = $1;',
|
||||
' for (var i = 0; i < $2.length; i++) {',
|
||||
' if ($2[i][0] == "+") { result += $2[i][1]; }',
|
||||
' if ($2[i][0] == "-") { result -= $2[i][1]; }',
|
||||
' }',
|
||||
' return result;',
|
||||
' }',
|
||||
'Expr : Sum'
|
||||
].join("\n"), "Expr");
|
||||
|
||||
/* Test "value" rule. */
|
||||
parses(parser, "0", 0);
|
||||
parses(parser, "123", 123);
|
||||
parses(parser, "(42+43)", 42+43);
|
||||
|
||||
/* Test "product" rule. */
|
||||
parses(parser, "42*43", 42*43);
|
||||
parses(parser, "42*43*44*45", 42*43*44*45);
|
||||
|
||||
/* Test "sum" rule. */
|
||||
parses(parser, "42*43+44*45", 42*43+44*45);
|
||||
parses(parser, "42*43+44*45+46*47+48*49", 42*43+44*45+46*47+48*49);
|
||||
|
||||
/* Test "expr" rule. */
|
||||
parses(parser, "42+43", 42+43);
|
||||
|
||||
/* Complex test */
|
||||
parses(parser, "(1+2)*(3+4)",(1+2)*(3+4));
|
||||
});
|
||||
|
||||
test("non-context-free language", function() {
|
||||
/* The following parsing expression grammar describes the classic
|
||||
* non-context-free language { a^n b^n c^n : n >= 1 }:
|
||||
*
|
||||
* S ← &(A c) a+ B !(a/b/c)
|
||||
* A ← a A? b
|
||||
* B ← b B? c
|
||||
*/
|
||||
var parser = PEG.buildParser([
|
||||
'S: &(A "c") "a"+ B !("a" / "b" / "c") { return $2.join("") + $3; }',
|
||||
'A: "a" A? "b" { return $1 + $2 + $3; }',
|
||||
'B: "b" B? "c" { return $1 + $2 + $3; }',
|
||||
].join("\n"), "S");
|
||||
|
||||
parses(parser, "abc", "abc");
|
||||
parses(parser, "aaabbbccc", "aaabbbccc");
|
||||
doesNotParse(parser, "aabbbccc");
|
||||
doesNotParse(parser, "aaaabbbccc");
|
||||
doesNotParse(parser, "aaabbccc");
|
||||
doesNotParse(parser, "aaabbbbccc");
|
||||
doesNotParse(parser, "aaabbbcc");
|
||||
doesNotParse(parser, "aaabbbcccc");
|
||||
});
|
||||
|
||||
test("nested comments", function() {
|
||||
/*
|
||||
* Begin ← "(*"
|
||||
* End ← "*)"
|
||||
* C ← Begin N* End
|
||||
* N ← C / (!Begin !End Z)
|
||||
* Z ← any single character
|
||||
*/
|
||||
var parser = PEG.buildParser([
|
||||
'Begin : "(*"',
|
||||
'End : "*)"',
|
||||
'C : Begin N* End { return $1 + $2.join("") + $3; }',
|
||||
'N : C',
|
||||
' / (!Begin !End Z) { return $3; }',
|
||||
'Z : .'
|
||||
].join("\n"), "C");
|
||||
|
||||
parses(parser, "(**)", "(**)");
|
||||
parses(parser, "(*abc*)", "(*abc*)");
|
||||
parses(parser, "(*(**)*)", "(*(**)*)");
|
||||
parses(
|
||||
parser,
|
||||
"(*abc(*def*)ghi(*(*(*jkl*)*)*)mno*)",
|
||||
"(*abc(*def*)ghi(*(*(*jkl*)*)*)mno*)"
|
||||
);
|
||||
});
|
||||
|
||||
/* ===== Grammar Parser ===== */
|
||||
|
||||
module("Grammar Parser");
|
||||
|
||||
with (PEG.Grammar) {
|
||||
var literalEmpty = new Literal("");
|
||||
var literalAbcd = new Literal("abcd");
|
||||
var literalEfgh = new Literal("efgh");
|
||||
var literalIjkl = new Literal("ijkl");
|
||||
|
||||
var choice = new Choice([literalAbcd, literalEmpty]);
|
||||
|
||||
var notAbcd = new NotPredicate(literalAbcd);
|
||||
var notEfgh = new NotPredicate(literalEfgh);
|
||||
var notIjkl = new NotPredicate(literalIjkl);
|
||||
|
||||
var sequenceEmpty = new Sequence([]);
|
||||
var sequenceNots = new Sequence([notAbcd, notEfgh, notIjkl]);
|
||||
var sequenceLiterals = new Sequence([literalAbcd, literalEfgh, literalIjkl]);
|
||||
|
||||
function oneRuleGrammar(expression) {
|
||||
return { start: new PEG.Grammar.Rule("start", null, expression) };
|
||||
}
|
||||
|
||||
var simpleGrammar = oneRuleGrammar(new Literal("abcd"));
|
||||
|
||||
function identifierGrammar(identifier) {
|
||||
return oneRuleGrammar(new PEG.Grammar.RuleRef(identifier));
|
||||
}
|
||||
|
||||
function literalGrammar(literal) {
|
||||
return oneRuleGrammar(new PEG.Grammar.Literal(literal));
|
||||
}
|
||||
|
||||
function classGrammar(chars) {
|
||||
return oneRuleGrammar(new PEG.Grammar.Choice(
|
||||
PEG.ArrayUtils.map(
|
||||
chars.split(""),
|
||||
function(char) { return new PEG.Grammar.Literal(char); }
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
var anyGrammar = oneRuleGrammar(new Any());
|
||||
|
||||
function actionGrammar(action) {
|
||||
return oneRuleGrammar(new PEG.Grammar.Action(new PEG.Grammar.Literal("a"), action));
|
||||
}
|
||||
|
||||
/* Canonical grammar is "a: \"abcd\";\nb: \"efgh\";\nc: \"ijkl\";". */
|
||||
test("parses grammar", function() {
|
||||
grammarParserParses('a: "abcd"', { a: new Rule("a", null, literalAbcd) });
|
||||
grammarParserParses(
|
||||
'a: "abcd"\nb: "efgh"\nc: "ijkl"',
|
||||
{
|
||||
a: new Rule("a", null, literalAbcd),
|
||||
b: new Rule("b", null, literalEfgh),
|
||||
c: new Rule("c", null, literalIjkl)
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
/* Canonical rule is "a: \"abcd\"". */
|
||||
test("parses rule", function() {
|
||||
grammarParserParses(
|
||||
'start: "abcd" / "efgh" / "ijkl"',
|
||||
oneRuleGrammar(new Choice([literalAbcd, literalEfgh, literalIjkl]))
|
||||
);
|
||||
grammarParserParses(
|
||||
'start "start rule": "abcd" / "efgh" / "ijkl"',
|
||||
{
|
||||
start:
|
||||
new Rule(
|
||||
"start",
|
||||
"start rule",
|
||||
new Choice([literalAbcd, literalEfgh, literalIjkl])
|
||||
)
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
/* Canonical expression is "\"abcd\" / \"efgh\" / \"ijkl\"". */
|
||||
test("parses expression", function() {
|
||||
grammarParserParses(
|
||||
'start: "abcd" / "efgh" / "ijkl"',
|
||||
oneRuleGrammar(new Choice([literalAbcd, literalEfgh, literalIjkl]))
|
||||
);
|
||||
});
|
||||
|
||||
/* Canonical choice is "\"abcd\" / \"efgh\" / \"ijkl\"". */
|
||||
test("parses choice", function() {
|
||||
grammarParserParses(
|
||||
'start: "abcd" "efgh" "ijkl"',
|
||||
oneRuleGrammar(sequenceLiterals)
|
||||
);
|
||||
grammarParserParses(
|
||||
'start: "abcd" "efgh" "ijkl" / "abcd" "efgh" "ijkl" / "abcd" "efgh" "ijkl"',
|
||||
oneRuleGrammar(new Choice([
|
||||
sequenceLiterals,
|
||||
sequenceLiterals,
|
||||
sequenceLiterals
|
||||
]))
|
||||
);
|
||||
});
|
||||
|
||||
/* Canonical sequence is "\"abcd\" \"efgh\" \"ijkl\"". */
|
||||
test("parses sequence", function() {
|
||||
grammarParserParses(
|
||||
'start: { code }',
|
||||
oneRuleGrammar(new Action(sequenceEmpty, " code "))
|
||||
);
|
||||
grammarParserParses(
|
||||
'start: !"abcd" { code }',
|
||||
oneRuleGrammar(new Action(notAbcd, " code "))
|
||||
);
|
||||
grammarParserParses(
|
||||
'start: !"abcd" !"efgh" !"ijkl" { code }',
|
||||
oneRuleGrammar(new Action(sequenceNots, " code "))
|
||||
);
|
||||
|
||||
grammarParserParses('start: ', oneRuleGrammar(sequenceEmpty));
|
||||
grammarParserParses('start: !"abcd"', oneRuleGrammar(notAbcd));
|
||||
grammarParserParses(
|
||||
'start: !"abcd" !"efgh" !"ijkl"',
|
||||
oneRuleGrammar(sequenceNots)
|
||||
);
|
||||
});
|
||||
|
||||
/* Canonical prefixed is "!\"abcd\"". */
|
||||
test("parses prefixed", function() {
|
||||
grammarParserParses(
|
||||
'start: &"abcd"?',
|
||||
oneRuleGrammar(new NotPredicate(new NotPredicate(choice)))
|
||||
);
|
||||
grammarParserParses('start: !"abcd"?', oneRuleGrammar(new NotPredicate(choice)));
|
||||
grammarParserParses('start: "abcd"?', oneRuleGrammar(choice));
|
||||
});
|
||||
|
||||
/* Canonical suffixed is "\"abcd\"?". */
|
||||
test("parses suffixed", function() {
|
||||
grammarParserParses('start: "abcd"?', oneRuleGrammar(choice));
|
||||
grammarParserParses('start: "abcd"*', oneRuleGrammar(new ZeroOrMore(literalAbcd)));
|
||||
grammarParserParses(
|
||||
'start: "abcd"+',
|
||||
oneRuleGrammar(new Action(
|
||||
new Sequence([literalAbcd, new ZeroOrMore(literalAbcd)]),
|
||||
function(first, rest) { return [first].concat(rest); }
|
||||
))
|
||||
);
|
||||
grammarParserParses('start: "abcd"', literalGrammar("abcd"));
|
||||
});
|
||||
|
||||
/* Canonical primary is "\"abcd\"". */
|
||||
test("parses primary", function() {
|
||||
grammarParserParses('start: a', identifierGrammar("a"));
|
||||
grammarParserParses('start: "abcd"', literalGrammar("abcd"));
|
||||
grammarParserParses('start: .', anyGrammar);
|
||||
grammarParserParses('start: [a-d]', classGrammar("abcd"));
|
||||
grammarParserParses('start: ("abcd")', literalGrammar("abcd"));
|
||||
});
|
||||
|
||||
/* Canonical action is "{ code }". */
|
||||
test("parses action", function() {
|
||||
grammarParserParses('start: "a" { code }', actionGrammar(" code "));
|
||||
});
|
||||
|
||||
/* Canonical braced is "{ code }". */
|
||||
test("parses braced", function() {
|
||||
grammarParserParses('start: "a" {}', actionGrammar(""));
|
||||
grammarParserParses('start: "a" {a}', actionGrammar("a"));
|
||||
grammarParserParses('start: "a" {{a}}', actionGrammar("{a}"));
|
||||
grammarParserParses('start: "a" {aaa}', actionGrammar("aaa"));
|
||||
});
|
||||
|
||||
/* Trivial character rules are not tested. */
|
||||
|
||||
/* Canonical identifier is "a". */
|
||||
test("parses identifier", function() {
|
||||
grammarParserParses('start: a', identifierGrammar("a"));
|
||||
grammarParserParses('start: z', identifierGrammar("z"));
|
||||
grammarParserParses('start: A', identifierGrammar("A"));
|
||||
grammarParserParses('start: Z', identifierGrammar("Z"));
|
||||
grammarParserParses('start: _', identifierGrammar("_"));
|
||||
grammarParserParses('start: $', identifierGrammar("$"));
|
||||
grammarParserParses('start: aa', identifierGrammar("aa"));
|
||||
grammarParserParses('start: az', identifierGrammar("az"));
|
||||
grammarParserParses('start: aA', identifierGrammar("aA"));
|
||||
grammarParserParses('start: aZ', identifierGrammar("aZ"));
|
||||
grammarParserParses('start: a0', identifierGrammar("a0"));
|
||||
grammarParserParses('start: a9', identifierGrammar("a9"));
|
||||
grammarParserParses('start: a_', identifierGrammar("a_"));
|
||||
grammarParserParses('start: a$', identifierGrammar("a$"));
|
||||
grammarParserParses('start: abcd', identifierGrammar("abcd"));
|
||||
|
||||
grammarParserParses('start: a\n', identifierGrammar("a"));
|
||||
});
|
||||
|
||||
/* Canonical literal is "\"abcd\"". */
|
||||
test("parses literal", function() {
|
||||
grammarParserParses('start: "abcd"', literalGrammar("abcd"));
|
||||
grammarParserParses("start: 'abcd'", literalGrammar("abcd"));
|
||||
});
|
||||
|
||||
/* Canonical doubleQuotedLiteral is "\"abcd\"". */
|
||||
test("parses doubleQuotedLiteral", function() {
|
||||
grammarParserParses('start: ""', literalGrammar(""));
|
||||
grammarParserParses('start: "a"', literalGrammar("a"));
|
||||
grammarParserParses('start: "abc"', literalGrammar("abc"));
|
||||
|
||||
grammarParserParses('start: "abcd"\n', literalGrammar("abcd"));
|
||||
});
|
||||
|
||||
/* Canonical doubleQuotedCharacter is "a". */
|
||||
test("parses doubleQuotedCharacter", function() {
|
||||
grammarParserParses('start: "a"', literalGrammar("a"));
|
||||
grammarParserParses('start: "\\n"', literalGrammar("\n"));
|
||||
grammarParserParses('start: "\\0"', literalGrammar("\0"));
|
||||
grammarParserParses('start: "\\x00"', literalGrammar("\x00"));
|
||||
grammarParserParses('start: "\\u0120"', literalGrammar("\u0120"));
|
||||
grammarParserParses('start: "\\\n"', literalGrammar("\n"));
|
||||
});
|
||||
|
||||
/* Canonical simpleDoubleQuotedCharacter is "a". */
|
||||
test("parses simpleDoubleQuotedCharacter", function() {
|
||||
grammarParserParses('start: "a"', literalGrammar("a"));
|
||||
grammarParserParses('start: "\'"', literalGrammar("'"));
|
||||
grammarParserDoesNotParse('start: """');
|
||||
grammarParserDoesNotParse('start: "\\"');
|
||||
grammarParserDoesNotParse('start: "\n"');
|
||||
grammarParserDoesNotParse('start: "\r"');
|
||||
grammarParserDoesNotParse('start: "\u2028"');
|
||||
grammarParserDoesNotParse('start: "\u2029"');
|
||||
});
|
||||
|
||||
/* Canonical singleQuotedLiteral is "'abcd'". */
|
||||
test("parses singleQuotedLiteral", function() {
|
||||
grammarParserParses("start: ''", literalGrammar(""));
|
||||
grammarParserParses("start: 'a'", literalGrammar("a"));
|
||||
grammarParserParses("start: 'abc'", literalGrammar("abc"));
|
||||
|
||||
grammarParserParses("start: 'abcd'\n", literalGrammar("abcd"));
|
||||
});
|
||||
|
||||
/* Canonical singleQuotedCharacter is "a". */
|
||||
test("parses singleQuotedCharacter", function() {
|
||||
grammarParserParses("start: 'a'", literalGrammar("a"));
|
||||
grammarParserParses("start: '\\n'", literalGrammar("\n"));
|
||||
grammarParserParses("start: '\\0'", literalGrammar("\0"));
|
||||
grammarParserParses("start: '\\x00'", literalGrammar("\x00"));
|
||||
grammarParserParses("start: '\\u0120'", literalGrammar("\u0120"));
|
||||
grammarParserParses("start: '\\\n'", literalGrammar("\n"));
|
||||
});
|
||||
|
||||
/* Canonical simpleSingleQuotedCharacter is "a". */
|
||||
test("parses simpleSingleQuotedCharacter", function() {
|
||||
grammarParserParses("start: 'a'", literalGrammar("a"));
|
||||
grammarParserParses("start: '\"'", literalGrammar("\""));
|
||||
grammarParserDoesNotParse("start: '''");
|
||||
grammarParserDoesNotParse("start: '\\'");
|
||||
grammarParserDoesNotParse("start: '\n'");
|
||||
grammarParserDoesNotParse("start: '\r'");
|
||||
grammarParserDoesNotParse("start: '\u2028'");
|
||||
grammarParserDoesNotParse("start: '\u2029'");
|
||||
});
|
||||
|
||||
/* Canonical class is "[a-d]". */
|
||||
test("parses classCharacterRange", function() {
|
||||
grammarParserParses("start: []", classGrammar(""));
|
||||
grammarParserParses("start: [a-d]", classGrammar("abcd"));
|
||||
grammarParserParses("start: [a]", classGrammar("a"));
|
||||
grammarParserParses("start: [a-de-hi-l]", classGrammar("abcdefghijkl"));
|
||||
|
||||
grammarParserParses("start: [a-d]\n", classGrammar("abcd"));
|
||||
});
|
||||
|
||||
/* Canonical classCharacterRange is "a-d". */
|
||||
test("parses classCharacterRange", function() {
|
||||
grammarParserParses("start: [a-d]", classGrammar("abcd"));
|
||||
grammarParserParses("start: [a-a]", classGrammar("a"));
|
||||
grammarParserDoesNotParse("start: [b-a]");
|
||||
});
|
||||
|
||||
/* Canonical classCharacter is "a". */
|
||||
test("parses classCharacter", function() {
|
||||
grammarParserParses("start: [a]", classGrammar("a"));
|
||||
});
|
||||
|
||||
/* Canonical bracketDelimitedCharacter is "a". */
|
||||
test("parses bracketDelimitedCharacter", function() {
|
||||
grammarParserParses("start: [a]", classGrammar("a"));
|
||||
grammarParserParses("start: [\\n]", classGrammar("\n"));
|
||||
grammarParserParses("start: [\\0]", classGrammar("\0"));
|
||||
grammarParserParses("start: [\\x00]", classGrammar("\x00"));
|
||||
grammarParserParses("start: [\\u0120]", classGrammar("\u0120"));
|
||||
grammarParserParses("start: [\\\n]", classGrammar("\n"));
|
||||
});
|
||||
|
||||
/* Canonical simpleBracketDelimiedCharacter is "a". */
|
||||
test("parses simpleBracketDelimitedCharacter", function() {
|
||||
grammarParserParses("start: [a]", classGrammar("a"));
|
||||
grammarParserParses("start: [[]", classGrammar("["));
|
||||
grammarParserDoesNotParse("start: []]");
|
||||
grammarParserDoesNotParse("start: [\\]");
|
||||
grammarParserDoesNotParse("start: [\n]");
|
||||
grammarParserDoesNotParse("start: [\r]");
|
||||
grammarParserDoesNotParse("start: [\u2028]");
|
||||
grammarParserDoesNotParse("start: [\u2029]");
|
||||
});
|
||||
|
||||
/* Canonical simpleEscapeSequence is "\\n". */
|
||||
test("parses simpleEscapeSequence", function() {
|
||||
grammarParserParses('start: "\\\'"', literalGrammar("'"));
|
||||
grammarParserParses('start: "\\""', literalGrammar("\""));
|
||||
grammarParserParses('start: "\\\\"', literalGrammar("\\"));
|
||||
grammarParserParses('start: "\\b"', literalGrammar("\b"));
|
||||
grammarParserParses('start: "\\f"', literalGrammar("\f"));
|
||||
grammarParserParses('start: "\\n"', literalGrammar("\n"));
|
||||
grammarParserParses('start: "\\r"', literalGrammar("\r"));
|
||||
grammarParserParses('start: "\\t"', literalGrammar("\t"));
|
||||
grammarParserParses('start: "\\v"', literalGrammar("\v"));
|
||||
|
||||
grammarParserParses('start: "\\a"', literalGrammar("a"));
|
||||
});
|
||||
|
||||
/* Canonical zeroEscapeSequence is "\\0". */
|
||||
test("parses zeroEscapeSequence", function() {
|
||||
grammarParserParses('start: "\\0"', literalGrammar("\0"));
|
||||
grammarParserDoesNotParse('start: "\\00"');
|
||||
grammarParserDoesNotParse('start: "\\09"');
|
||||
});
|
||||
|
||||
/* Canonical hexEscapeSequence is "\\x00". */
|
||||
test("parses hexEscapeSequence", function() {
|
||||
grammarParserParses('start: "\\x00"', literalGrammar("\x00"));
|
||||
grammarParserParses('start: "\\x09"', literalGrammar("\x09"));
|
||||
grammarParserParses('start: "\\x0a"', literalGrammar("\x0a"));
|
||||
grammarParserParses('start: "\\x0f"', literalGrammar("\x0f"));
|
||||
grammarParserParses('start: "\\x0A"', literalGrammar("\x0A"));
|
||||
grammarParserParses('start: "\\x0F"', literalGrammar("\x0F"));
|
||||
grammarParserDoesNotParse('start: "\\x0"');
|
||||
grammarParserParses('start: "\\x000"', literalGrammar("\x000"));
|
||||
});
|
||||
|
||||
/* Canonical unicodeEscapeSequence is "\\u0120". */
|
||||
test("parses unicodeEscapeSequence", function() {
|
||||
grammarParserParses('start: "\\u0120"', literalGrammar("\u0120"));
|
||||
grammarParserParses('start: "\\u0129"', literalGrammar("\u0129"));
|
||||
grammarParserParses('start: "\\u012a"', literalGrammar("\u012a"));
|
||||
grammarParserParses('start: "\\u012f"', literalGrammar("\u012f"));
|
||||
grammarParserParses('start: "\\u012A"', literalGrammar("\u012A"));
|
||||
grammarParserParses('start: "\\u012F"', literalGrammar("\u012F"));
|
||||
grammarParserDoesNotParse('start: "\\u012"');
|
||||
grammarParserParses('start: "\\u01234"', literalGrammar("\u01234"));
|
||||
});
|
||||
|
||||
/* Canonical eolEscapeSequence is "\\\n". */
|
||||
test("parses eolEscapeSequence", function() {
|
||||
grammarParserParses('start: "\\\n"', literalGrammar("\n"));
|
||||
grammarParserParses('start: "\\\r\n"', literalGrammar("\r\n"));
|
||||
grammarParserParses('start: "\\\r"', literalGrammar("\r"));
|
||||
grammarParserParses('start: "\\\u2028"', literalGrammar("\u2028"));
|
||||
grammarParserParses('start: "\\\u2029"', literalGrammar("\u2029"));
|
||||
});
|
||||
|
||||
/* Canonical __ is "\n". */
|
||||
test("parses __", function() {
|
||||
grammarParserParses('start:"abcd"', simpleGrammar);
|
||||
grammarParserParses('start: "abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\n"abcd"', simpleGrammar);
|
||||
grammarParserParses('start: "abcd"', simpleGrammar);
|
||||
});
|
||||
|
||||
/* Trivial character class rules are not tested. */
|
||||
|
||||
/* Canonical eol is "\n". */
|
||||
test("parses eol", function() {
|
||||
grammarParserParses('start:\n"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\r\n"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\r"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u2028"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u2029"abcd"', simpleGrammar);
|
||||
});
|
||||
|
||||
/* Canonical eolChar is "\n". */
|
||||
test("parses eolChar", function() {
|
||||
grammarParserParses('start:\n"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\r"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u2028"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u2029"abcd"', simpleGrammar);
|
||||
});
|
||||
|
||||
/* Canonical whitespace is " ". */
|
||||
test("parses whitespace", function() {
|
||||
grammarParserParses('start:\t"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\v"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\f"abcd"', simpleGrammar);
|
||||
grammarParserParses('start: "abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\xA0"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\uFEFF"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u1680"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u180E"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u2000"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u2001"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u2002"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u2003"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u2004"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u2005"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u2006"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u2007"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u2008"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u2009"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u200A"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u202F"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u205F"abcd"', simpleGrammar);
|
||||
grammarParserParses('start:\u3000"abcd"', simpleGrammar);
|
||||
});
|
||||
}
|
||||
|
||||
})();
|
@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>PEG.js Test Suite</title>
|
||||
<link rel="stylesheet" href="../vendor/qunit/qunit.css">
|
||||
<script src="../vendor/qunit/qunit.js"></script>
|
||||
<script src="../lib/runtime.js"></script>
|
||||
<script src="../lib/compiler.js"></script>
|
||||
<script src="runtime-test.js"></script>
|
||||
<script src="compiler-test.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="qunit-header">PEG.js Test Suite</h1>
|
||||
<h2 id="qunit-banner"></h2>
|
||||
<div id="qunit-testrunner-toolbar"></div>
|
||||
<h2 id="qunit-userAgent"></h2>
|
||||
<ol id="qunit-tests"></ol>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,27 @@
|
||||
(function() {
|
||||
|
||||
/* ===== PEG.ArrayUtils ===== */
|
||||
|
||||
module("PEG.ArrayUtils");
|
||||
|
||||
test("map", function() {
|
||||
function square(x) { return x * x; }
|
||||
|
||||
deepEqual(PEG.ArrayUtils.map([], square), []);
|
||||
deepEqual(PEG.ArrayUtils.map([1, 2, 3], square), [1, 4, 9]);
|
||||
});
|
||||
|
||||
/* ===== PEG.StringUtils ===== */
|
||||
|
||||
module("PEG.StringUtils");
|
||||
|
||||
test("quote", function() {
|
||||
strictEqual(PEG.StringUtils.quote(""), '""');
|
||||
strictEqual(PEG.StringUtils.quote("abcd"), '"abcd"');
|
||||
strictEqual(
|
||||
PEG.StringUtils.quote("\"\\\r\u2028\u2029\n\"\\\r\u2028\u2029\n"),
|
||||
'"\\\"\\\\\\r\\u2028\\u2029\\n\\\"\\\\\\r\\u2028\\u2029\\n"'
|
||||
);
|
||||
});
|
||||
|
||||
})();
|
@ -0,0 +1,119 @@
|
||||
|
||||
ol#qunit-tests {
|
||||
font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
|
||||
margin:0;
|
||||
padding:0;
|
||||
list-style-position:inside;
|
||||
|
||||
font-size: smaller;
|
||||
}
|
||||
ol#qunit-tests li{
|
||||
padding:0.4em 0.5em 0.4em 2.5em;
|
||||
border-bottom:1px solid #fff;
|
||||
font-size:small;
|
||||
list-style-position:inside;
|
||||
}
|
||||
ol#qunit-tests li ol{
|
||||
box-shadow: inset 0px 2px 13px #999;
|
||||
-moz-box-shadow: inset 0px 2px 13px #999;
|
||||
-webkit-box-shadow: inset 0px 2px 13px #999;
|
||||
margin-top:0.5em;
|
||||
margin-left:0;
|
||||
padding:0.5em;
|
||||
background-color:#fff;
|
||||
border-radius:15px;
|
||||
-moz-border-radius: 15px;
|
||||
-webkit-border-radius: 15px;
|
||||
}
|
||||
ol#qunit-tests li li{
|
||||
border-bottom:none;
|
||||
margin:0.5em;
|
||||
background-color:#fff;
|
||||
list-style-position: inside;
|
||||
padding:0.4em 0.5em 0.4em 0.5em;
|
||||
}
|
||||
|
||||
ol#qunit-tests li li.pass{
|
||||
border-left:26px solid #C6E746;
|
||||
background-color:#fff;
|
||||
color:#5E740B;
|
||||
}
|
||||
ol#qunit-tests li li.fail{
|
||||
border-left:26px solid #EE5757;
|
||||
background-color:#fff;
|
||||
color:#710909;
|
||||
}
|
||||
ol#qunit-tests li.pass{
|
||||
background-color:#D2E0E6;
|
||||
color:#528CE0;
|
||||
}
|
||||
ol#qunit-tests li.fail{
|
||||
background-color:#EE5757;
|
||||
color:#000;
|
||||
}
|
||||
ol#qunit-tests li strong {
|
||||
cursor:pointer;
|
||||
}
|
||||
h1#qunit-header{
|
||||
background-color:#0d3349;
|
||||
margin:0;
|
||||
padding:0.5em 0 0.5em 1em;
|
||||
color:#fff;
|
||||
font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
|
||||
border-top-right-radius:15px;
|
||||
border-top-left-radius:15px;
|
||||
-moz-border-radius-topright:15px;
|
||||
-moz-border-radius-topleft:15px;
|
||||
-webkit-border-top-right-radius:15px;
|
||||
-webkit-border-top-left-radius:15px;
|
||||
text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px;
|
||||
}
|
||||
h2#qunit-banner{
|
||||
font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
|
||||
height:5px;
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
h2#qunit-banner.qunit-pass{
|
||||
background-color:#C6E746;
|
||||
}
|
||||
h2#qunit-banner.qunit-fail, #qunit-testrunner-toolbar {
|
||||
background-color:#EE5757;
|
||||
}
|
||||
#qunit-testrunner-toolbar {
|
||||
font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
|
||||
padding:0;
|
||||
/*width:80%;*/
|
||||
padding:0em 0 0.5em 2em;
|
||||
font-size: small;
|
||||
}
|
||||
h2#qunit-userAgent {
|
||||
font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
|
||||
background-color:#2b81af;
|
||||
margin:0;
|
||||
padding:0;
|
||||
color:#fff;
|
||||
font-size: small;
|
||||
padding:0.5em 0 0.5em 2.5em;
|
||||
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
|
||||
}
|
||||
p#qunit-testresult{
|
||||
font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
|
||||
margin:0;
|
||||
font-size: small;
|
||||
color:#2b81af;
|
||||
border-bottom-right-radius:15px;
|
||||
border-bottom-left-radius:15px;
|
||||
-moz-border-radius-bottomright:15px;
|
||||
-moz-border-radius-bottomleft:15px;
|
||||
-webkit-border-bottom-right-radius:15px;
|
||||
-webkit-border-bottom-left-radius:15px;
|
||||
background-color:#D2E0E6;
|
||||
padding:0.5em 0.5em 0.5em 2.5em;
|
||||
}
|
||||
strong b.fail{
|
||||
color:#710909;
|
||||
}
|
||||
strong b.pass{
|
||||
color:#5E740B;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Loading…
Reference in New Issue