(function() {

module("PEG.compiler");

function testWithVaryingTrackLineAndColumn(name, callback) {
  test(
    name + " (with trackLineAndColumn: false) ",
    function() { callback({ trackLineAndColumn: false }); }
  );
  test(
    name + " (with trackLineAndColumn: true) ",
    function() { callback({ trackLineAndColumn: true }); }
  );
}

testWithVaryingTrackLineAndColumn("choices", function(options) {
  var parser = PEG.buildParser('start = "a" / "b" / "c"', options);
  parses(parser, "a", "a");
  parses(parser, "b", "b");
  parses(parser, "c", "c");
  doesNotParse(parser, "");
  doesNotParse(parser, "ab");
  doesNotParse(parser, "d");
});

testWithVaryingTrackLineAndColumn("sequences", function(options) {
  var emptySequenceParser = PEG.buildParser('start = ', options);
  parses(emptySequenceParser, "", []);
  doesNotParse(emptySequenceParser, "abc");

  var nonEmptySequenceParser = PEG.buildParser('start = "a" "b" "c"', options);
  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"', options);
  parses(posTestParser, "a", "a");
});

testWithVaryingTrackLineAndColumn("labels", function(options) {
  var parser = PEG.buildParser('start = label:"a"', options);
  parses(parser, "a", "a");
  doesNotParse(parser, "b");
});

testWithVaryingTrackLineAndColumn("simple and", function(options) {
  var parser = PEG.buildParser('start = "a" &"b" "b"', options);
  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.
   */
});

testWithVaryingTrackLineAndColumn("simple not", function(options) {
  var parser = PEG.buildParser('start = "a" !"b"', options);
  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("semantic and (with trackLineAndColumn: false)", function() {
  var options = { trackLineAndColumn: false };

  var acceptingParser = PEG.buildParser(
    'start = "a" &{ return true; } "b"',
    options
  );
  parses(acceptingParser, "ab", ["a", "", "b"]);

  var rejectingParser = PEG.buildParser(
    'start = "a" &{ return false; } "b"',
    options
  );
  doesNotParse(rejectingParser, "ab");

  var singleElementUnlabeledParser = PEG.buildParser(
    'start = "a" &{ return arguments.length === 1 && offset === 1; }',
    options
  );
  parses(singleElementUnlabeledParser, "a", ["a", ""]);

  var singleElementLabeledParser = PEG.buildParser([
    'start = a:"a" &{',
    '          return arguments.length === 2',
    '            && offset === 1',
    '            && a === "a";',
    '        }'
  ].join("\n"), options);
  parses(singleElementLabeledParser, "a", ["a", ""]);

  var multiElementUnlabeledParser = PEG.buildParser(
    'start = "a" "b" "c" &{ return arguments.length === 1 && offset === 3; }',
    options
  );
  parses(multiElementUnlabeledParser, "abc", ["a", "b", "c", ""]);

  var multiElementLabeledParser = PEG.buildParser([
    'start = a:"a" "b" c:"c" &{',
    '          return arguments.length === 3',
    '            && offset === 3',
    '            && a === "a"',
    '            && c === "c";',
    '        }'
  ].join("\n"), options);
  parses(multiElementLabeledParser, "abc", ["a", "b", "c", ""]);

  var innerElementsUnlabeledParser = PEG.buildParser([
    'start = "a"',
    '        ("b" "c" "d" &{ return arguments.length === 1 && offset === 4; })',
    '        "e"'
  ].join("\n"), options);
  parses(innerElementsUnlabeledParser, "abcde", ["a", ["b", "c", "d", ""], "e"]);

  var innerElementsLabeledParser = PEG.buildParser([
    'start = "a"',
    '        (',
    '          b:"b" "c" d:"d" &{',
    '            return arguments.length === 3',
    '              && offset === 4',
    '              && b === "b"',
    '              && d === "d";',
    '          }',
    '        )',
    '        "e"'
  ].join("\n"), options);
  parses(innerElementsLabeledParser, "abcde", ["a", ["b", "c", "d", ""], "e"]);
});

test("semantic and (with trackLineAndColumn: true)", function() {
  var options = { trackLineAndColumn: true };

  var acceptingParser = PEG.buildParser(
    'start = "a" &{ return true; } "b"',
    options
  );
  parses(acceptingParser, "ab", ["a", "", "b"]);

  var rejectingParser = PEG.buildParser(
    'start = "a" &{ return false; } "b"',
    options
  );
  doesNotParse(rejectingParser, "ab");

  var singleElementUnlabeledParser = PEG.buildParser([
    'start = "a" &{',
    '          return arguments.length === 3',
    '            && offset === 1',
    '            && line === 1',
    '            && column === 2;',
    '        }'
  ].join("\n"), options);
  parses(singleElementUnlabeledParser, "a", ["a", ""]);

  var singleElementLabeledParser = PEG.buildParser([
    'start = a:"a" &{',
    '          return arguments.length === 4',
    '            && offset === 1',
    '            && line === 1',
    '            && column === 2',
    '            && a === "a";',
    '        }'
  ].join("\n"), options);
  parses(singleElementLabeledParser, "a", ["a", ""]);

  var multiElementUnlabeledParser = PEG.buildParser([
    'start = "a" "b" "c" &{',
    '          return arguments.length === 3',
    '            && offset === 3',
    '            && line === 1',
    '            && column === 4;',
    '        }'
  ].join("\n"), options);
  parses(multiElementUnlabeledParser, "abc", ["a", "b", "c", ""]);

  var multiElementLabeledParser = PEG.buildParser([
    'start = a:"a" "b" c:"c" &{',
    '          return arguments.length === 5',
    '            && offset === 3',
    '            && line === 1',
    '            && column === 4',
    '            && a === "a"',
    '            && c === "c";',
    '        }'
  ].join("\n"), options);
  parses(multiElementLabeledParser, "abc", ["a", "b", "c", ""]);

  var innerElementsUnlabeledParser = PEG.buildParser([
    'start = "a"',
    '        (',
    '          "b" "c" "d" &{',
    '            return arguments.length === 3',
    '              && offset === 4',
    '              && line === 1',
    '              && column === 5;',
    '           }',
    '        )',
    '        "e"'
  ].join("\n"), options);
  parses(innerElementsUnlabeledParser, "abcde", ["a", ["b", "c", "d", ""], "e"]);

  var innerElementsLabeledParser = PEG.buildParser([
    'start = "a"',
    '        (',
    '          b:"b" "c" d:"d" &{',
    '            return arguments.length === 5',
    '              && offset === 4',
    '              && line === 1',
    '              && column === 5',
    '              && b === "b"',
    '              && d === "d";',
    '          }',
    '        )',
    '        "e"'
  ].join("\n"), options);
  parses(innerElementsLabeledParser, "abcde", ["a", ["b", "c", "d", ""], "e"]);

  var digitsParser = PEG.buildParser([
    '{ var result; }',
    'start  = line (nl+ line)* { return result; }',
    'line   = thing (" "+ thing)*',
    'thing  = digit / mark',
    'digit  = [0-9]',
    'mark   = &{ result = [line, column]; return true; } "x"',
    'nl     = ("\\r" / "\\n" / "\\u2028" / "\\u2029")'
  ].join("\n"), options);

  parses(digitsParser, "1\n2\n\n3\n\n\n4 5 x", [7, 5]);

  /* Non-Unix newlines */
  parses(digitsParser, "1\rx", [2, 1]);   // Old Mac
  parses(digitsParser, "1\r\nx", [2, 1]); // Windows
  parses(digitsParser, "1\n\rx", [3, 1]); // mismatched

  /* Strange newlines */
  parses(digitsParser, "1\u2028x", [2, 1]); // line separator
  parses(digitsParser, "1\u2029x", [2, 1]); // paragraph separator
});

test("semantic not (with trackLineAndColumn: false)", function() {
  var options = { trackLineAndColumn: false };

  var acceptingParser = PEG.buildParser(
    'start = "a" !{ return false; } "b"',
    options
  );
  parses(acceptingParser, "ab", ["a", "", "b"]);

  var rejectingParser = PEG.buildParser(
    'start = "a" !{ return true; } "b"',
    options
  );
  doesNotParse(rejectingParser, "ab");

  var singleElementUnlabeledParser = PEG.buildParser(
    'start = "a" !{ return arguments.length !== 1 || offset !== 1; }',
    options
  );
  parses(singleElementUnlabeledParser, "a", ["a", ""]);

  var singleElementLabeledParser = PEG.buildParser([
    'start = a:"a" !{',
    '          return arguments.length !== 2',
    '            || offset !== 1',
    '            || a !== "a";',
    '        }'
  ].join("\n"), options);
  parses(singleElementLabeledParser, "a", ["a", ""]);

  var multiElementUnlabeledParser = PEG.buildParser(
    'start = "a" "b" "c" !{ return arguments.length !== 1 || offset !== 3; }',
    options
  );
  parses(multiElementUnlabeledParser, "abc", ["a", "b", "c", ""]);

  var multiElementLabeledParser = PEG.buildParser([
    'start = a:"a" "b" c:"c" !{',
    '          return arguments.length !== 3',
    '            || offset !== 3',
    '            || a !== "a"',
    '            || c !== "c";',
    '        }'
  ].join("\n"), options);
  parses(multiElementLabeledParser, "abc", ["a", "b", "c", ""]);

  var innerElementsUnlabeledParser = PEG.buildParser([
    'start = "a"',
    '        ("b" "c" "d" !{ return arguments.length !== 1 || offset !== 4; })',
    '        "e"'
  ].join("\n"), options);
  parses(innerElementsUnlabeledParser, "abcde", ["a", ["b", "c", "d", ""], "e"]);

  var innerElementsLabeledParser = PEG.buildParser([
    'start = "a"',
    '        (',
    '          b:"b" "c" d:"d" !{',
    '            return arguments.length !== 3',
    '              || offset !== 4',
    '              || b !== "b"',
    '              || d !== "d";',
    '          }',
    '        )',
    '        "e"'
  ].join("\n"), options);
  parses(innerElementsLabeledParser, "abcde", ["a", ["b", "c", "d", ""], "e"]);
});

test("semantic not (with trackLineAndColumn: true)", function() {
  var options = { trackLineAndColumn: true };

  var acceptingParser = PEG.buildParser(
    'start = "a" !{ return false; } "b"',
    options
  );
  parses(acceptingParser, "ab", ["a", "", "b"]);

  var rejectingParser = PEG.buildParser(
    'start = "a" !{ return true; } "b"',
    options
  );
  doesNotParse(rejectingParser, "ab");

  var singleElementUnlabeledParser = PEG.buildParser([
    'start = "a" !{',
    '          return arguments.length !== 3',
    '            || offset !== 1',
    '            || line !== 1',
    '            || column !== 2;',
    '        }'
  ].join("\n"), options);
  parses(singleElementUnlabeledParser, "a", ["a", ""]);

  var singleElementLabeledParser = PEG.buildParser([
    'start = a:"a" !{',
    '          return arguments.length !== 4',
    '            || offset !== 1',
    '            || line !== 1',
    '            || column !== 2',
    '            || a !== "a";',
    '        }'
  ].join("\n"), options);
  parses(singleElementLabeledParser, "a", ["a", ""]);

  var multiElementUnlabeledParser = PEG.buildParser([
    'start = "a" "b" "c" !{',
    '          return arguments.length !== 3',
    '            || offset !== 3',
    '            || line !== 1',
    '            || column !== 4;',
    '        }'
  ].join("\n"), options);
  parses(multiElementUnlabeledParser, "abc", ["a", "b", "c", ""]);

  var multiElementLabeledParser = PEG.buildParser([
    'start = a:"a" "b" c:"c" !{',
    '          return arguments.length !== 5',
    '            || offset !== 3',
    '            || line !== 1',
    '            || column !== 4',
    '            || a !== "a"',
    '            || c !== "c";',
    '        }'
  ].join("\n"), options);
  parses(multiElementLabeledParser, "abc", ["a", "b", "c", ""]);

  var innerElementsUnlabeledParser = PEG.buildParser([
    'start = "a"',
    '        (',
    '          "b" "c" "d" !{',
    '            return arguments.length !== 3',
    '              || offset !== 4',
    '              || line !== 1',
    '              || column !== 5;',
    '           }',
    '        )',
    '        "e"'
  ].join("\n"), options);
  parses(innerElementsUnlabeledParser, "abcde", ["a", ["b", "c", "d", ""], "e"]);

  var innerElementsLabeledParser = PEG.buildParser([
    'start = "a"',
    '        (',
    '          b:"b" "c" d:"d" !{',
    '            return arguments.length !== 5',
    '              || offset !== 4',
    '              || line !== 1',
    '              || column !== 5',
    '              || b !== "b"',
    '              || d !== "d";',
    '          }',
    '        )',
    '        "e"'
  ].join("\n"), options);
  parses(innerElementsLabeledParser, "abcde", ["a", ["b", "c", "d", ""], "e"]);

  var digitsParser = PEG.buildParser([
    '{ var result; }',
    'start  = line (nl+ line)* { return result; }',
    'line   = thing (" "+ thing)*',
    'thing  = digit / mark',
    'digit  = [0-9]',
    'mark   = !{ result = [line, column]; return false; } "x"',
    'nl     = ("\\r" / "\\n" / "\\u2028" / "\\u2029")'
  ].join("\n"), options);

  parses(digitsParser, "1\n2\n\n3\n\n\n4 5 x", [7, 5]);

  /* Non-Unix newlines */
  parses(digitsParser, "1\rx", [2, 1]);   // Old Mac
  parses(digitsParser, "1\r\nx", [2, 1]); // Windows
  parses(digitsParser, "1\n\rx", [3, 1]); // mismatched

  /* Strange newlines */
  parses(digitsParser, "1\u2028x", [2, 1]); // line separator
  parses(digitsParser, "1\u2029x", [2, 1]); // paragraph separator
});

testWithVaryingTrackLineAndColumn("optional expressions", function(options) {
  var parser = PEG.buildParser('start = "a"?', options);
  parses(parser, "", "");
  parses(parser, "a", "a");
});

testWithVaryingTrackLineAndColumn("zero or more expressions", function(options) {
  var parser = PEG.buildParser('start = "a"*', options);
  parses(parser, "", []);
  parses(parser, "a", ["a"]);
  parses(parser, "aaa", ["a", "a", "a"]);
});

testWithVaryingTrackLineAndColumn("one or more expressions", function(options) {
  var parser = PEG.buildParser('start = "a"+', options);
  doesNotParse(parser, "");
  parses(parser, "a", ["a"]);
  parses(parser, "aaa", ["a", "a", "a"]);
});

test("actions (with trackLineAndColumn: false)", function() {
  var options = { trackLineAndColumn: false };

  var singleElementUnlabeledParser = PEG.buildParser(
    'start = "a" { return arguments.length; }',
    options
  );
  parses(singleElementUnlabeledParser, "a", 1);

  var singleElementLabeledParser = PEG.buildParser(
    'start = a:"a" { return [arguments.length, offset, a]; }',
    options
  );
  parses(singleElementLabeledParser, "a", [2, 0, "a"]);

  var multiElementUnlabeledParser = PEG.buildParser(
    'start = "a" "b" "c" { return arguments.length; }',
    options
  );
  parses(multiElementUnlabeledParser, "abc", 1);

  var multiElementLabeledParser = PEG.buildParser(
    'start = a:"a" "b" c:"c" { return [arguments.length, offset, a, c]; }',
    options
  );
  parses(multiElementLabeledParser, "abc", [3, 0, "a", "c"]);

  var innerElementsUnlabeledParser = PEG.buildParser(
    'start = "a" ("b" "c" "d" { return arguments.length; }) "e"',
    options
  );
  parses(innerElementsUnlabeledParser, "abcde", ["a", 1, "e"]);

  var innerElementsLabeledParser = PEG.buildParser([
    'start = "a"',
    '        (b:"b" "c" d:"d" { return [arguments.length, offset, b, d]; })',
    '        "e"'
  ].join("\n"), options);
  parses(innerElementsLabeledParser, "abcde", ["a", [3, 1, "b", "d"], "e"]);

  /*
   * Test that the parsing position returns after successfull parsing of the
   * action expression and action returning |null|.
   */
  var posTestParser = PEG.buildParser(
    'start = "a" { return null; } / "a"',
    options
  );
  parses(posTestParser, "a", "a");

  /* 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"); }',
    options
  );
  doesNotParse(notAMatchParser, "b");
});

test("actions (with trackLineAndColumn: true)", function() {
  var options = { trackLineAndColumn: true };

  var singleElementUnlabeledParser = PEG.buildParser(
    'start = "a" { return arguments.length; }',
    options
  );
  parses(singleElementUnlabeledParser, "a", 3);

  var singleElementLabeledParser = PEG.buildParser(
    'start = a:"a" { return [arguments.length, offset, line, column, a]; }',
    options
  );
  parses(singleElementLabeledParser, "a", [4, 0, 1, 1, "a"]);

  var multiElementUnlabeledParser = PEG.buildParser(
    'start = "a" "b" "c" { return arguments.length; }',
    options
  );
  parses(multiElementUnlabeledParser, "abc", 3);

  var multiElementLabeledParser = PEG.buildParser([
    'start = a:"a" "b" c:"c" {',
    '  return [arguments.length, offset, line, column, a, c];',
    '}'
    ].join("\n"), options);
  parses(multiElementLabeledParser, "abc", [5, 0, 1, 1, "a", "c"]);

  var innerElementsUnlabeledParser = PEG.buildParser(
    'start = "a" ("b" "c" "d" { return arguments.length; }) "e"',
    options
  );
  parses(innerElementsUnlabeledParser, "abcde", ["a", 3, "e"]);

  var innerElementsLabeledParser = PEG.buildParser([
    'start = "a"',
    '        (',
    '          b:"b" "c" d:"d" {',
    '            return [arguments.length, offset, line, column, b, d];',
    '          }',
    '        )',
    '        "e"'
  ].join("\n"), options);
  parses(
    innerElementsLabeledParser,
    "abcde",
    ["a", [5, 1, 1, 2, "b", "d"], "e"]
  );

  /*
   * Test that the parsing position returns after successfull parsing of the
   * action expression and action returning |null|.
   */
  var posTestParser = PEG.buildParser(
    'start = "a" { return null; } / "a"',
    options
  );
  parses(posTestParser, "a", "a");

  /* 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"); }',
    options
  );
  doesNotParse(notAMatchParser, "b");

  var numbersParser = PEG.buildParser([
    '{ var result; }',
    'start  = line (nl+ line)* { return result; }',
    'line   = thing (" "+ thing)*',
    'thing  = digit / mark',
    'digit  = [0-9]',
    'mark   = "x" { result = [line, column]; }',
    'nl     = ("\\r" / "\\n" / "\\u2028" / "\\u2029")'
  ].join("\n"), options);

  parses(numbersParser, "1\n2\n\n3\n\n\n4 5 x", [7, 5]);

  /* Non-Unix newlines */
  parses(numbersParser, "1\rx", [2, 1]);   // Old Mac
  parses(numbersParser, "1\r\nx", [2, 1]); // Windows
  parses(numbersParser, "1\n\rx", [3, 1]); // mismatched

  /* Strange newlines */
  parses(numbersParser, "1\u2028x", [2, 1]); // line separator
  parses(numbersParser, "1\u2029x", [2, 1]); // paragraph separator
});

testWithVaryingTrackLineAndColumn("initializer", function(options) {
  var variableInActionParser = PEG.buildParser(
    '{ a = 42; }; start = "a" { return a; }',
    options
  );
  parses(variableInActionParser, "a", 42);

  var functionInActionParser = PEG.buildParser(
    '{ function f() { return 42; } }; start = "a" { return f(); }',
    options
  );
  parses(functionInActionParser, "a", 42);

  var variableInSemanticAndParser = PEG.buildParser(
    '{ a = 42; }; start = "a" &{ return a === 42; }',
    options
  );
  parses(variableInSemanticAndParser, "a", ["a", ""]);

  var functionInSemanticAndParser = PEG.buildParser(
    '{ function f() { return 42; } }; start = "a" &{ return f() === 42; }',
    options
  );
  parses(functionInSemanticAndParser, "a", ["a", ""]);

  var variableInSemanticNotParser = PEG.buildParser(
    '{ a = 42; }; start = "a" !{ return a !== 42; }',
    options
  );
  parses(variableInSemanticNotParser, "a", ["a", ""]);

  var functionInSemanticNotParser = PEG.buildParser(
    '{ function f() { return 42; } }; start = "a" !{ return f() !== 42; }',
    options
  );
  parses(functionInSemanticNotParser, "a", ["a", ""]);
});

testWithVaryingTrackLineAndColumn("rule references", function(options) {
  var parser = PEG.buildParser([
    'start   = static / dynamic',
    'static  = "C" / "C++" / "Java" / "C#"',
    'dynamic = "Ruby" / "Python" / "JavaScript"'
  ].join("\n"), options);
  parses(parser, "Java", "Java");
  parses(parser, "Python", "Python");
});

testWithVaryingTrackLineAndColumn("literals", function(options) {
  var zeroCharParser = PEG.buildParser('start = ""', options);
  parses(zeroCharParser, "", "");
  doesNotParse(zeroCharParser, "a");

  var oneCharCaseSensitiveParser = PEG.buildParser('start = "a"', options);
  parses(oneCharCaseSensitiveParser, "a", "a");
  doesNotParse(oneCharCaseSensitiveParser, "");
  doesNotParse(oneCharCaseSensitiveParser, "A");
  doesNotParse(oneCharCaseSensitiveParser, "b");

  var multiCharCaseSensitiveParser = PEG.buildParser('start = "abcd"', options);
  parses(multiCharCaseSensitiveParser, "abcd", "abcd");
  doesNotParse(multiCharCaseSensitiveParser, "");
  doesNotParse(multiCharCaseSensitiveParser, "abc");
  doesNotParse(multiCharCaseSensitiveParser, "abcde");
  doesNotParse(multiCharCaseSensitiveParser, "ABCD");
  doesNotParse(multiCharCaseSensitiveParser, "efgh");

  var oneCharCaseInsensitiveParser = PEG.buildParser('start = "a"i', options);
  parses(oneCharCaseInsensitiveParser, "a", "a");
  parses(oneCharCaseInsensitiveParser, "A", "A");
  doesNotParse(oneCharCaseInsensitiveParser, "");
  doesNotParse(oneCharCaseInsensitiveParser, "b");

  var multiCharCaseInsensitiveParser = PEG.buildParser(
    'start = "abcd"i',
    options
  );
  parses(multiCharCaseInsensitiveParser, "abcd", "abcd");
  parses(multiCharCaseInsensitiveParser, "ABCD", "ABCD");
  doesNotParse(multiCharCaseInsensitiveParser, "");
  doesNotParse(multiCharCaseInsensitiveParser, "abc");
  doesNotParse(multiCharCaseInsensitiveParser, "abcde");
  doesNotParse(multiCharCaseInsensitiveParser, "efgh");

  /*
   * Test that the parsing position moves forward after successful parsing of
   * a literal.
   */
  var posTestParser = PEG.buildParser('start = "a" "b"', options);
  parses(posTestParser, "ab", ["a", "b"]);
});

testWithVaryingTrackLineAndColumn("anys", function(options) {
  var parser = PEG.buildParser('start = .', options);
  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 = . .', options);
  parses(posTestParser, "ab", ["a", "b"]);
});

testWithVaryingTrackLineAndColumn("classes", function(options) {
  var emptyClassParser = PEG.buildParser('start = []', options);
  doesNotParse(emptyClassParser, "");
  doesNotParse(emptyClassParser, "a");
  doesNotParse(emptyClassParser, "ab");

  var invertedEmptyClassParser = PEG.buildParser('start = [^]', options);
  doesNotParse(invertedEmptyClassParser, "");
  parses(invertedEmptyClassParser, "a", "a");
  doesNotParse(invertedEmptyClassParser, "ab");

  var nonEmptyCaseSensitiveClassParser = PEG.buildParser(
    'start = [ab-d]',
    options
  );
  parses(nonEmptyCaseSensitiveClassParser, "a", "a");
  parses(nonEmptyCaseSensitiveClassParser, "b", "b");
  parses(nonEmptyCaseSensitiveClassParser, "c", "c");
  parses(nonEmptyCaseSensitiveClassParser, "d", "d");
  doesNotParse(nonEmptyCaseSensitiveClassParser, "");
  doesNotParse(nonEmptyCaseSensitiveClassParser, "A");
  doesNotParse(nonEmptyCaseSensitiveClassParser, "B");
  doesNotParse(nonEmptyCaseSensitiveClassParser, "C");
  doesNotParse(nonEmptyCaseSensitiveClassParser, "D");
  doesNotParse(nonEmptyCaseSensitiveClassParser, "e");
  doesNotParse(nonEmptyCaseSensitiveClassParser, "ab");

  var invertedNonEmptyCaseSensitiveClassParser = PEG.buildParser(
    'start = [^ab-d]',
    options
  );
  parses(invertedNonEmptyCaseSensitiveClassParser, "A", "A");
  parses(invertedNonEmptyCaseSensitiveClassParser, "B", "B");
  parses(invertedNonEmptyCaseSensitiveClassParser, "C", "C");
  parses(invertedNonEmptyCaseSensitiveClassParser, "D", "D");
  parses(invertedNonEmptyCaseSensitiveClassParser, "e", "e");
  doesNotParse(invertedNonEmptyCaseSensitiveClassParser, "a", "a");
  doesNotParse(invertedNonEmptyCaseSensitiveClassParser, "b", "b");
  doesNotParse(invertedNonEmptyCaseSensitiveClassParser, "c", "c");
  doesNotParse(invertedNonEmptyCaseSensitiveClassParser, "d", "d");
  doesNotParse(invertedNonEmptyCaseSensitiveClassParser, "");
  doesNotParse(invertedNonEmptyCaseSensitiveClassParser, "ab");

  var nonEmptyCaseInsensitiveClassParser = PEG.buildParser(
    'start = [ab-d]i',
    options
  );
  parses(nonEmptyCaseInsensitiveClassParser, "a", "a");
  parses(nonEmptyCaseInsensitiveClassParser, "b", "b");
  parses(nonEmptyCaseInsensitiveClassParser, "c", "c");
  parses(nonEmptyCaseInsensitiveClassParser, "d", "d");
  parses(nonEmptyCaseInsensitiveClassParser, "A", "A");
  parses(nonEmptyCaseInsensitiveClassParser, "B", "B");
  parses(nonEmptyCaseInsensitiveClassParser, "C", "C");
  parses(nonEmptyCaseInsensitiveClassParser, "D", "D");
  doesNotParse(nonEmptyCaseInsensitiveClassParser, "");
  doesNotParse(nonEmptyCaseInsensitiveClassParser, "e");
  doesNotParse(nonEmptyCaseInsensitiveClassParser, "ab");

  var invertedNonEmptyCaseInsensitiveClassParser = PEG.buildParser(
    'start = [^ab-d]i',
    options
  );
  parses(invertedNonEmptyCaseInsensitiveClassParser, "e", "e");
  doesNotParse(invertedNonEmptyCaseInsensitiveClassParser, "a", "a");
  doesNotParse(invertedNonEmptyCaseInsensitiveClassParser, "b", "b");
  doesNotParse(invertedNonEmptyCaseInsensitiveClassParser, "c", "c");
  doesNotParse(invertedNonEmptyCaseInsensitiveClassParser, "d", "d");
  doesNotParse(invertedNonEmptyCaseInsensitiveClassParser, "A", "A");
  doesNotParse(invertedNonEmptyCaseInsensitiveClassParser, "B", "B");
  doesNotParse(invertedNonEmptyCaseInsensitiveClassParser, "C", "C");
  doesNotParse(invertedNonEmptyCaseInsensitiveClassParser, "D", "D");
  doesNotParse(invertedNonEmptyCaseInsensitiveClassParser, "");
  doesNotParse(invertedNonEmptyCaseInsensitiveClassParser, "ab");

  /*
   * Test that the parsing position moves forward after successful parsing of
   * a class.
   */
  var posTestParser = PEG.buildParser('start = [ab-d] [ab-d]', options);
  parses(posTestParser, "ab", ["a", "b"]);
});

testWithVaryingTrackLineAndColumn("cache", function(options) {
  var grammar = [
    '{ var n = 0; }',
    'start = (a "b") / (a "c") { return n; }',
    'a     = "a" { n++; }'
  ].join("\n");

  /* Without cache */

  parses(PEG.buildParser(grammar, options), "ac", 2);

  options.cache = false;
  parses(PEG.buildParser(grammar, options), "ac", 2);

  /* With cache */

  options.cache = true;
  parses(PEG.buildParser(grammar, options), "ac", 1);
});

testWithVaryingTrackLineAndColumn("indempotence", function(options) {
  var parser1 = PEG.buildParser('start = "abcd"', options);
  var parser2 = PEG.buildParser('start = "abcd"', options);

  strictEqual(parser1.toSource(), parser2.toSource());
});

testWithVaryingTrackLineAndColumn("error details", function(options) {
  var literalParser = PEG.buildParser('start = "abcd"', options);
  doesNotParseWithDetails(
    literalParser,
    "",
    ["\"abcd\""],
    null,
    'Expected "abcd" but end of input found.'
  );
  doesNotParseWithDetails(
    literalParser,
    "efgh",
    ["\"abcd\""],
    "e",
    'Expected "abcd" but "e" found.'
  );
  doesNotParseWithDetails(
    literalParser,
    "abcde",
    [],
    "e",
    'Expected end of input but "e" found.'
  );

  var classParser = PEG.buildParser('start = [a-d]', options);
  doesNotParseWithDetails(
    classParser,
    "",
    ["[a-d]"],
    null,
    'Expected [a-d] but end of input found.'
  );
  var negativeClassParser = PEG.buildParser('start = [^a-d]', options);
  doesNotParseWithDetails(
    negativeClassParser,
    "",
    ["[^a-d]"],
    null,
    'Expected [^a-d] but end of input found.'
  );

  var anyParser = PEG.buildParser('start = .', options);
  doesNotParseWithDetails(
    anyParser,
    "",
    ["any character"],
    null,
    'Expected any character but end of input found.'
  );

  var namedRuleWithLiteralParser = PEG.buildParser(
    'start "digit" = [0-9]',
    options
  );
  doesNotParseWithDetails(
    namedRuleWithLiteralParser,
    "a",
    ["digit"],
    "a",
    'Expected digit but "a" found.'
  );

  var namedRuleWithAnyParser = PEG.buildParser('start "whatever" = .', options);
  doesNotParseWithDetails(
    namedRuleWithAnyParser,
    "",
    ["whatever"],
    null,
    'Expected whatever but end of input found.'
  );

  var namedRuleWithNamedRuleParser = PEG.buildParser([
    'start "digits" = digit+',
    'digit "digit"  = [0-9]'
  ].join("\n"), options);
  doesNotParseWithDetails(
    namedRuleWithNamedRuleParser,
    "",
    ["digits"],
    null,
    'Expected digits but end of input found.'
  );

  var choiceParser1 = PEG.buildParser('start = "a" / "b" / "c"', options);
  doesNotParseWithDetails(
    choiceParser1,
    "def",
    ["\"a\"", "\"b\"", "\"c\""],
    "d",
    'Expected "a", "b" or "c" but "d" found.'
  );

  var choiceParser2 = PEG.buildParser('start = "a" "b" "c" / "a"', options);
  doesNotParseWithDetails(
    choiceParser2,
    "abd",
    ["\"c\""],
    "d",
    'Expected "c" but "d" found.'
  );

  var simpleNotParser = PEG.buildParser('start = !"a" "b"', options);
  doesNotParseWithDetails(
    simpleNotParser,
    "c",
    ["\"b\""],
    "c",
    'Expected "b" but "c" found.'
  );

  var simpleAndParser = PEG.buildParser('start = &"a" [a-b]', options);
  doesNotParseWithDetails(
    simpleAndParser,
    "c",
    [],
    "c",
    'Expected end of input but "c" found.'
  );

  var emptyParser = PEG.buildParser('start = ', options);
  doesNotParseWithDetails(
    emptyParser,
    "something",
    [],
    "s",
    'Expected end of input but "s" found.'
  );

  var duplicateErrorParser = PEG.buildParser('start = "a" / "a"', options);
  doesNotParseWithDetails(
    duplicateErrorParser,
    "",
    ["\"a\""],
    null,
    'Expected "a" but end of input found.'
  );

  var unsortedErrorsParser = PEG.buildParser('start = "b" / "a"', options);
  doesNotParseWithDetails(
    unsortedErrorsParser,
    "",
    ["\"a\"", "\"b\""],
    null,
    'Expected "a" or "b" but end of input found.'
  );
});

testWithVaryingTrackLineAndColumn("error positions", function(options) {
  var simpleParser = PEG.buildParser('start = "a"', options);

  /* Regular match failure */
  doesNotParseWithPos(simpleParser, "b", 0, 1, 1);

  /* Trailing input */
  doesNotParseWithPos(simpleParser, "ab", 1, 1, 2);

  var digitsParser = PEG.buildParser([
    'start  = line (("\\r" / "\\n" / "\\u2028" / "\\u2029")+ line)*',
    'line   = digits (" "+ digits)*',
    'digits = digits:[0-9]+ { return digits.join(""); }'
  ].join("\n"), options);

  doesNotParseWithPos(digitsParser, "1\n2\n\n3\n\n\n4 5 x", 13, 7, 5);

  /* Non-Unix newlines */
  doesNotParseWithPos(digitsParser, "1\rx", 2, 2, 1);   // Old Mac
  doesNotParseWithPos(digitsParser, "1\r\nx", 3, 2, 1); // Windows
  doesNotParseWithPos(digitsParser, "1\n\rx", 3, 3, 1); // mismatched

  /* Strange newlines */
  doesNotParseWithPos(digitsParser, "1\u2028x", 2, 2, 1); // line separator
  doesNotParseWithPos(digitsParser, "1\u2029x", 2, 2, 1); // paragraph separator
});

testWithVaryingTrackLineAndColumn("start rule", function(options) {
  var parser = PEG.buildParser([
    'a = .* { return "alpha"; }',
    'b = .* { return "beta"; }'
  ].join("\n"), options);

  /* Default start rule = the first one */
  parses(parser, "whatever", "alpha");

  /* Explicit specification of the start rule */
  parsesWithStartRule(parser, "whatever", "a", "alpha");
  parsesWithStartRule(parser, "whatever", "b", "beta");

  /* Invalid rule name */
  raises(
    function() { parser.parse("whatever", "c"); },
    function(e) {
      return e instanceof Error && e.message === "Invalid rule name: \"c\".";
    }
  );
});

/*
 * Following examples are from Wikipedia, see
 * http://en.wikipedia.org/w/index.php?title=Parsing_expression_grammar&oldid=335106938.
 */

testWithVaryingTrackLineAndColumn("arithmetics", function(options) {
  /*
   * Value   ← [0-9]+ / '(' Expr ')'
   * Product ← Value (('*' / '/') Value)*
   * Sum     ← Product (('+' / '-') Product)*
   * Expr    ← Sum
   */
  var parser = PEG.buildParser([
    'Expr    = Sum',
    'Sum     = head:Product tail:(("+" / "-") Product)* {',
    '            var result = head;',
    '            for (var i = 0; i < tail.length; i++) {',
    '              if (tail[i][0] == "+") { result += tail[i][1]; }',
    '              if (tail[i][0] == "-") { result -= tail[i][1]; }',
    '            }',
    '            return result;',
    '          }',
    'Product = head:Value tail:(("*" / "/") Value)* {',
    '            var result = head;',
    '            for (var i = 0; i < tail.length; i++) {',
    '              if (tail[i][0] == "*") { result *= tail[i][1]; }',
    '              if (tail[i][0] == "/") { result /= tail[i][1]; }',
    '            }',
    '            return result;',
    '          }',
    'Value   = digits:[0-9]+     { return parseInt(digits.join("")); }',
    '        / "(" expr:Expr ")" { return expr; }'
  ].join("\n"), options);

  /* Test "value" rule. */
  parses(parser, "0", 0);
  parses(parser, "123", 123);
  parses(parser, "(42+43)", 42+43);

  /* Test "product" rule. */
  parses(parser, "42", 42);
  parses(parser, "42*43", 42*43);
  parses(parser, "42*43*44*45", 42*43*44*45);
  parses(parser, "42/43", 42/43);
  parses(parser, "42/43/44/45", 42/43/44/45);

  /* Test "sum" rule. */
  parses(parser, "42*43", 42*43);
  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);
  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));
});

testWithVaryingTrackLineAndColumn("non-context-free language", function(options) {
  /* 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:"a"+ B:B !("a" / "b" / "c") { return a.join("") + B; }',
    'A = a:"a" A:A? b:"b" { return a + A + b; }',
    'B = b:"b" B:B? c:"c" { return b + B + c; }'
  ].join("\n"), options);

  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");
});

testWithVaryingTrackLineAndColumn("nested comments", function(options) {
  /*
   * Begin ← "(*"
   * End ← "*)"
   * C ← Begin N* End
   * N ← C / (!Begin !End Z)
   * Z ← any single character
   */
  var parser = PEG.buildParser([
    'C     = begin:Begin ns:N* end:End { return begin + ns.join("") + end; }',
    'N     = C',
    '      / !Begin !End z:Z { return z; }',
    'Z     = .',
    'Begin = "(*"',
    'End   = "*)"'
  ].join("\n"), options);

  parses(parser, "(**)", "(**)");
  parses(parser, "(*abc*)", "(*abc*)");
  parses(parser, "(*(**)*)", "(*(**)*)");
  parses(
    parser,
    "(*abc(*def*)ghi(*(*(*jkl*)*)*)mno*)",
    "(*abc(*def*)ghi(*(*(*jkl*)*)*)mno*)"
  );
});

})();
