commit
c3dd696a3e
15 changed files with 3918 additions and 0 deletions
@ -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 @@ |
|||
0.1 |
@ -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
@ -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
Binary file not shown.
Loading…
Reference in new issue