text: "use strict";
  
  const chai = require( "chai" );
  const peg = require( "pegjs" );
  const sinon = require( "sinon" );
  
  const expect = chai.expect;
  
  describe( "generated parser behavior", function () {
  
      function varyOptimizationOptions( block ) {
  
          const optionsVariants = [
              { cache: false, optimize: "speed", trace: false },
              { cache: false, optimize: "speed", trace: true },
              { cache: false, optimize: "size", trace: false },
              { cache: false, optimize: "size", trace: true },
              { cache: true, optimize: "speed", trace: false },
              { cache: true, optimize: "speed", trace: true },
              { cache: true, optimize: "size", trace: false },
              { cache: true, optimize: "size", trace: true },
          ];
  
          optionsVariants.forEach( variant => {
  
              describe(
                  "with options " + chai.util.inspect( variant ),
                  function () {
  
                      block( peg.util.clone( variant ) );
  
                  },
              );
  
          } );
  
      }
  
      function withConsoleStub( block ) {
  
          if ( typeof console === "object" ) sinon.stub( console, "log" );
  
          try {
  
              return block();
  
          } finally {
  
              if ( typeof console === "object" ) console.log.restore();
  
          }
  
      }
  
      function helpers( chai, utils ) {
  
          const Assertion = chai.Assertion;
  
          Assertion.addMethod( "parse", function ( input, expected, options ) {
  
              options = typeof options !== "undefined" ? options : {};
  
              const result = withConsoleStub( () =>
                  utils.flag( this, "object" ).parse( input, options ) );
  
              if ( typeof expected !== "undefined" ) {
  
                  this.assert(
                      utils.eql( result, expected ),
                      "expected #{this} to parse input as #{exp} but got #{act}",
                      "expected #{this} to not parse input as #{exp}",
                      expected,
                      result,
                      ! utils.flag( this, "negate" ),
                  );
  
              }
  
          } );
  
          Assertion.addMethod( "failToParse", function ( input, props, options ) {
  
              options = typeof options !== "undefined" ? options : {};
  
              let passed, result;
  
              try {
  
                  result = withConsoleStub( () =>
                      utils.flag( this, "object" ).parse( input, options ) );
                  passed = true;
  
              } catch ( e ) {
  
                  result = e;
                  passed = false;
  
              }
  
              this.assert(
                  ! passed,
                  "expected #{this} to fail to parse input but got #{act}",
                  "expected #{this} to not fail to parse input but #{act} was thrown",
                  null,
                  result,
              );
  
              if ( ! passed && typeof props !== "undefined" ) {
  
                  Object.keys( props ).forEach( key => {
  
                      new Assertion( result ).to.have.property( key )
                          .that.is.deep.equal( props[ key ] );
  
                  } );
  
              }
  
          } );
  
      }
  
      // Helper activation needs to put inside a |beforeEach| block because the
      // helpers conflict with the ones in test/unit/parser.spec.js.
      beforeEach( function () {
  
          chai.use( helpers );
  
      } );
  
      varyOptimizationOptions( function ( options ) {
  
          describe( "initializer", function () {
  
              it( "executes the code before parsing starts", function () {
  
                  const parser = peg.generate( [
                      "{ var result = 42; }",
                      "start = 'a' { return result; }",
                  ].join( "\n" ), options );
  
                  expect( parser ).to.parse( "a", 42 );
  
              } );
  
              describe( "available variables and functions", function () {
  
                  it( "|options| contains options", function () {
  
                      const parser = peg.generate( [
                          "{ var result = options; }",
                          "start = 'a' { return result; }",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.parse( "a", { a: 42 }, { a: 42 } );
  
                  } );
  
              } );
  
          } );
  
          describe( "rule", function () {
  
              if ( options.cache ) {
  
                  it( "caches rule match results", function () {
  
                      const parser = peg.generate( [
                          "{ var n = 0; }",
                          "start = (a 'b') / (a 'c') { return n; }",
                          "a = 'a' { n++; }",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.parse( "ac", 1 );
  
                  } );
  
              } else {
  
                  it( "doesn't cache rule match results", function () {
  
                      const parser = peg.generate( [
                          "{ var n = 0; }",
                          "start = (a 'b') / (a 'c') { return n; }",
                          "a = 'a' { n++; }",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.parse( "ac", 2 );
  
                  } );
  
              }
  
              describe( "when the expression matches", function () {
  
                  it( "returns its match result", function () {
  
                      const parser = peg.generate( "start = 'a'" );
  
                      expect( parser ).to.parse( "a", "a" );
  
                  } );
  
              } );
  
              describe( "when the expression doesn't match", function () {
  
                  describe( "without display name", function () {
  
                      it( "reports match failure and doesn't record any expectation", function () {
  
                          const parser = peg.generate( "start = 'a'" );
  
                          expect( parser ).to.failToParse( "b", {
                              expected: [ { type: "literal", text: "a", ignoreCase: false } ],
                          } );
  
                      } );
  
                  } );
  
                  describe( "with display name", function () {
  
                      it( "reports match failure and records an expectation of type \"other\"", function () {
  
                          const parser = peg.generate( "start 'start' = 'a'" );
  
                          expect( parser ).to.failToParse( "b", {
                              expected: [ { type: "other", description: "start" } ],
                          } );
  
                      } );
  
                      it( "discards any expectations recorded when matching the expression", function () {
  
                          const parser = peg.generate( "start 'start' = 'a'" );
  
                          expect( parser ).to.failToParse( "b", {
                              expected: [ { type: "other", description: "start" } ],
                          } );
  
                      } );
  
                  } );
  
              } );
  
          } );
  
          describe( "literal", function () {
  
              describe( "matching", function () {
  
                  it( "matches empty literals", function () {
  
                      const parser = peg.generate( "start = ''", options );
  
                      expect( parser ).to.parse( "" );
  
                  } );
  
                  it( "matches one-character literals", function () {
  
                      const parser = peg.generate( "start = 'a'", options );
  
                      expect( parser ).to.parse( "a" );
                      expect( parser ).to.failToParse( "b" );
  
                  } );
  
                  it( "matches multi-character literals", function () {
  
                      const parser = peg.generate( "start = 'abcd'", options );
  
                      expect( parser ).to.parse( "abcd" );
                      expect( parser ).to.failToParse( "efgh" );
  
                  } );
  
                  it( "is case sensitive without the \"i\" flag", function () {
  
                      const parser = peg.generate( "start = 'a'", options );
  
                      expect( parser ).to.parse( "a" );
                      expect( parser ).to.failToParse( "A" );
  
                  } );
  
                  it( "is case insensitive with the \"i\" flag", function () {
  
                      const parser = peg.generate( "start = 'a'i", options );
  
                      expect( parser ).to.parse( "a" );
                      expect( parser ).to.parse( "A" );
  
                  } );
  
              } );
  
              describe( "when it matches", function () {
  
                  it( "returns the matched text", function () {
  
                      const parser = peg.generate( "start = 'a'", options );
  
                      expect( parser ).to.parse( "a", "a" );
  
                  } );
  
                  it( "consumes the matched text", function () {
  
                      const parser = peg.generate( "start = 'a' .", options );
  
                      expect( parser ).to.parse( "ab" );
  
                  } );
  
              } );
  
              describe( "when it doesn't match", function () {
  
                  it( "reports match failure and records an expectation of type \"literal\"", function () {
  
                      const parser = peg.generate( "start = 'a'", options );
  
                      expect( parser ).to.failToParse( "b", {
                          expected: [ { type: "literal", text: "a", ignoreCase: false } ],
                      } );
  
                  } );
  
              } );
  
          } );
  
          describe( "character class", function () {
  
              describe( "matching", function () {
  
                  it( "matches empty classes", function () {
  
                      const parser = peg.generate( "start = []", options );
  
                      expect( parser ).to.failToParse( "a" );
  
                  } );
  
                  it( "matches classes with a character list", function () {
  
                      const parser = peg.generate( "start = [abc]", options );
  
                      expect( parser ).to.parse( "a" );
                      expect( parser ).to.parse( "b" );
                      expect( parser ).to.parse( "c" );
                      expect( parser ).to.failToParse( "d" );
  
                  } );
  
                  it( "matches classes with a character range", function () {
  
                      const parser = peg.generate( "start = [a-c]", options );
  
                      expect( parser ).to.parse( "a" );
                      expect( parser ).to.parse( "b" );
                      expect( parser ).to.parse( "c" );
                      expect( parser ).to.failToParse( "d" );
  
                  } );
  
                  it( "matches inverted classes", function () {
  
                      const parser = peg.generate( "start = [^a]", options );
  
                      expect( parser ).to.failToParse( "a" );
                      expect( parser ).to.parse( "b" );
  
                  } );
  
                  it( "is case sensitive without the \"i\" flag", function () {
  
                      const parser = peg.generate( "start = [a]", options );
  
                      expect( parser ).to.parse( "a" );
                      expect( parser ).to.failToParse( "A" );
  
                  } );
  
                  it( "is case insensitive with the \"i\" flag", function () {
  
                      const parser = peg.generate( "start = [a]i", options );
  
                      expect( parser ).to.parse( "a" );
                      expect( parser ).to.parse( "A" );
  
                  } );
  
              } );
  
              describe( "when it matches", function () {
  
                  it( "returns the matched character", function () {
  
                      const parser = peg.generate( "start = [a]", options );
  
                      expect( parser ).to.parse( "a", "a" );
  
                  } );
  
                  it( "consumes the matched character", function () {
  
                      const parser = peg.generate( "start = [a] .", options );
  
                      expect( parser ).to.parse( "ab" );
  
                  } );
  
              } );
  
              describe( "when it doesn't match", function () {
  
                  it( "reports match failure and records an expectation of type \"class\"", function () {
  
                      const parser = peg.generate( "start = [a]", options );
  
                      expect( parser ).to.failToParse( "b", {
                          expected: [ { type: "class", parts: [ "a" ], inverted: false, ignoreCase: false } ],
                      } );
  
                  } );
  
              } );
  
          } );
  
          describe( "dot", function () {
  
              describe( "matching", function () {
  
                  it( "matches any character", function () {
  
                      const parser = peg.generate( "start = .", options );
  
                      expect( parser ).to.parse( "a" );
                      expect( parser ).to.parse( "b" );
                      expect( parser ).to.parse( "c" );
  
                  } );
  
              } );
  
              describe( "when it matches", function () {
  
                  it( "returns the matched character", function () {
  
                      const parser = peg.generate( "start = .", options );
  
                      expect( parser ).to.parse( "a", "a" );
  
                  } );
  
                  it( "consumes the matched character", function () {
  
                      const parser = peg.generate( "start = . .", options );
  
                      expect( parser ).to.parse( "ab" );
  
                  } );
  
              } );
  
              describe( "when it doesn't match", function () {
  
                  it( "reports match failure and records an expectation of type \"any\"", function () {
  
                      const parser = peg.generate( "start = .", options );
  
                      expect( parser ).to.failToParse( "", {
                          expected: [ { type: "any" } ],
                      } );
  
                  } );
  
              } );
  
          } );
  
          describe( "rule reference", function () {
  
              describe( "when referenced rule's expression matches", function () {
  
                  it( "returns its result", function () {
  
                      const parser = peg.generate( [
                          "start = a",
                          "a = 'a'",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.parse( "a", "a" );
  
                  } );
  
              } );
  
              describe( "when referenced rule's expression doesn't match", function () {
  
                  it( "reports match failure", function () {
  
                      const parser = peg.generate( [
                          "start = a",
                          "a = 'a'",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.failToParse( "b" );
  
                  } );
  
              } );
  
          } );
  
          describe( "positive semantic predicate", function () {
  
              describe( "when the code returns a truthy value", function () {
  
                  it( "returns |undefined|", function () {
  
                      // The |""| is needed so that the parser doesn't return just
                      // |undefined| which we can't compare against in |toParse| due to the
                      // way optional parameters work.
                      const parser = peg.generate( "start = &{ return true; } ''", options );
  
                      expect( parser ).to.parse( "", [ void 0, "" ] );
  
                  } );
  
              } );
  
              describe( "when the code returns a falsey value", function () {
  
                  it( "reports match failure", function () {
  
                      const parser = peg.generate( "start = &{ return false; }", options );
  
                      expect( parser ).to.failToParse( "" );
  
                  } );
  
              } );
  
              describe( "label variables", function () {
  
                  describe( "in containing sequence", function () {
  
                      it( "can access variables defined by preceding labeled elements", function () {
  
                          const parser = peg.generate(
                              "start = a:'a' &{ return a === 'a'; }",
                              options,
                          );
  
                          expect( parser ).to.parse( "a" );
  
                      } );
  
                      it( "cannot access variable defined by labeled predicate element", function () {
  
                          const parser = peg.generate(
                              "start = 'a' b:&{ return b === undefined; } 'c'",
                              options,
                          );
  
                          expect( parser ).to.failToParse( "ac" );
  
                      } );
  
                      it( "cannot access variables defined by following labeled elements", function () {
  
                          const parser = peg.generate(
                              "start = &{ return a === 'a'; } a:'a'",
                              options,
                          );
  
                          expect( parser ).to.failToParse( "a" );
  
                      } );
  
                      it( "cannot access variables defined by subexpressions", function () {
  
                          const testcases = [
                              {
                                  grammar: "start = (a:'a') &{ return a === 'a'; }",
                                  input: "a",
                              },
                              {
                                  grammar: "start = (a:'a')? &{ return a === 'a'; }",
                                  input: "a",
                              },
                              {
                                  grammar: "start = (a:'a')* &{ return a === 'a'; }",
                                  input: "a",
                              },
                              {
                                  grammar: "start = (a:'a')+ &{ return a === 'a'; }",
                                  input: "a",
                              },
                              {
                                  grammar: "start = $(a:'a') &{ return a === 'a'; }",
                                  input: "a",
                              },
                              {
                                  grammar: "start = &(a:'a') 'a' &{ return a === 'a'; }",
                                  input: "a",
                              },
                              {
                                  grammar: "start = !(a:'a') 'b' &{ return a === 'a'; }",
                                  input: "b",
                              },
                              {
                                  grammar: "start = b:(a:'a') &{ return a === 'a'; }",
                                  input: "a",
                              },
                              {
                                  grammar: "start = ('a' b:'b' 'c') &{ return b === 'b'; }",
                                  input: "abc",
                              },
                              {
                                  grammar: "start = (a:'a' { return a; }) &{ return a === 'a'; }",
                                  input: "a",
                              },
                              {
                                  grammar: "start = ('a' / b:'b' / 'c') &{ return b === 'b'; }",
                                  input: "b",
                              },
                          ];
  
                          testcases.forEach( testcase => {
  
                              const parser = peg.generate( testcase.grammar, options );
                              expect( parser ).to.failToParse( testcase.input );
  
                          } );
  
                      } );
  
                  } );
  
                  describe( "in outer sequence", function () {
  
                      it( "can access variables defined by preceding labeled elements", function () {
  
                          const parser = peg.generate(
                              "start = a:'a' ('b' &{ return a === 'a'; })",
                              options,
                          );
  
                          expect( parser ).to.parse( "ab" );
  
                      } );
  
                      it( "cannot access variable defined by labeled predicate element", function () {
  
                          const parser = peg.generate(
                              "start = 'a' b:('b' &{ return b === undefined; }) 'c'",
                              options,
                          );
  
                          expect( parser ).to.failToParse( "abc" );
  
                      } );
  
                      it( "cannot access variables defined by following labeled elements", function () {
  
                          const parser = peg.generate(
                              "start = ('a' &{ return b === 'b'; }) b:'b'",
                              options,
                          );
  
                          expect( parser ).to.failToParse( "ab" );
  
                      } );
  
                  } );
  
              } );
  
              describe( "initializer variables & functions", function () {
  
                  it( "can access variables defined in the initializer", function () {
  
                      const parser = peg.generate( [
                          "{ var v = 42 }",
                          "start = &{ return v === 42; }",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.parse( "" );
  
                  } );
  
                  it( "can access functions defined in the initializer", function () {
  
                      const parser = peg.generate( [
                          "{ function f() { return 42; } }",
                          "start = &{ return f() === 42; }",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.parse( "" );
  
                  } );
  
              } );
  
              describe( "available variables & functions", function () {
  
                  it( "|options| contains options", function () {
  
                      const parser = peg.generate( [
                          "{ var result; }",
                          "start = &{ result = options; return true; } { return result; }",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.parse( "", { a: 42 }, { a: 42 } );
  
                  } );
  
                  it( "|location| returns current location info", function () {
  
                      const parser = peg.generate( [
                          "{ var result; }",
                          "start = line (nl+ line)* { return result; }",
                          "line = thing (' '+ thing)*",
                          "thing = digit / mark",
                          "digit = [0-9]",
                          "mark = &{ result = location(); return true; } 'x'",
                          "nl = '\\r'? '\\n'",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.parse( "1\n2\n\n3\n\n\n4 5 x", {
                          start: { offset: 13, line: 7, column: 5 },
                          end: { offset: 13, line: 7, column: 5 },
                      } );
  
                      // Newline representations
                      expect( parser ).to.parse( "1\nx", {     // Unix
                          start: { offset: 2, line: 2, column: 1 },
                          end: { offset: 2, line: 2, column: 1 },
                      } );
                      expect( parser ).to.parse( "1\r\nx", {   // Windows
                          start: { offset: 3, line: 2, column: 1 },
                          end: { offset: 3, line: 2, column: 1 },
                      } );
  
                  } );
  
                  it( "|offset| returns current start offset", function () {
  
                      const parser = peg.generate( [
                          "start = [0-9]+ val:mark { return val; }",
                          "mark = 'xx' { return offset(); }",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.parse( "0123456xx", 7 );
  
                  } );
  
                  it( "|range| returns current range", function () {
  
                      const parser = peg.generate( [
                          "start = [0-9]+ val:mark { return val; }",
                          "mark = 'xx' { return range(); }",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.parse( "0123456xx", [ 7, 9 ] );
  
                  } );
  
              } );
  
          } );
  
          describe( "negative semantic predicate", function () {
  
              describe( "when the code returns a falsey value", function () {
  
                  it( "returns |undefined|", function () {
  
                      // The |""| is needed so that the parser doesn't return just
                      // |undefined| which we can't compare against in |toParse| due to the
                      // way optional parameters work.
                      const parser = peg.generate( "start = !{ return false; } ''", options );
  
                      expect( parser ).to.parse( "", [ void 0, "" ] );
  
                  } );
  
              } );
  
              describe( "when the code returns a truthy value", function () {
  
                  it( "reports match failure", function () {
  
                      const parser = peg.generate( "start = !{ return true; }", options );
  
                      expect( parser ).to.failToParse( "" );
  
                  } );
  
              } );
  
              describe( "label variables", function () {
  
                  describe( "in containing sequence", function () {
  
                      it( "can access variables defined by preceding labeled elements", function () {
  
                          const parser = peg.generate(
                              "start = a:'a' !{ return a !== 'a'; }",
                              options,
                          );
  
                          expect( parser ).to.parse( "a" );
  
                      } );
  
                      it( "cannot access variable defined by labeled predicate element", function () {
  
                          const parser = peg.generate(
                              "start = 'a' b:!{ return b !== undefined; } 'c'",
                              options,
                          );
  
                          expect( parser ).to.failToParse( "ac" );
  
                      } );
  
                      it( "cannot access variables defined by following labeled elements", function () {
  
                          const parser = peg.generate(
                              "start = !{ return a !== 'a'; } a:'a'",
                              options,
                          );
  
                          expect( parser ).to.failToParse( "a" );
  
                      } );
  
                      it( "cannot access variables defined by subexpressions", function () {
  
                          const testcases = [
                              {
                                  grammar: "start = (a:'a') !{ return a !== 'a'; }",
                                  input: "a",
                              },
                              {
                                  grammar: "start = (a:'a')? !{ return a !== 'a'; }",
                                  input: "a",
                              },
                              {
                                  grammar: "start = (a:'a')* !{ return a !== 'a'; }",
                                  input: "a",
                              },
                              {
                                  grammar: "start = (a:'a')+ !{ return a !== 'a'; }",
                                  input: "a",
                              },
                              {
                                  grammar: "start = $(a:'a') !{ return a !== 'a'; }",
                                  input: "a",
                              },
                              {
                                  grammar: "start = &(a:'a') 'a' !{ return a !== 'a'; }",
                                  input: "a",
                              },
                              {
                                  grammar: "start = !(a:'a') 'b' !{ return a !== 'a'; }",
                                  input: "b",
                              },
                              {
                                  grammar: "start = b:(a:'a') !{ return a !== 'a'; }",
                                  input: "a",
                              },
                              {
                                  grammar: "start = ('a' b:'b' 'c') !{ return b !== 'b'; }",
                                  input: "abc",
                              },
                              {
                                  grammar: "start = (a:'a' { return a; }) !{ return a !== 'a'; }",
                                  input: "a",
                              },
                              {
                                  grammar: "start = ('a' / b:'b' / 'c') !{ return b !== 'b'; }",
                                  input: "b",
                              },
                          ];
  
                          testcases.forEach( testcase => {
  
                              const parser = peg.generate( testcase.grammar, options );
                              expect( parser ).to.failToParse( testcase.input );
  
                          } );
  
                      } );
  
                  } );
  
                  describe( "in outer sequence", function () {
  
                      it( "can access variables defined by preceding labeled elements", function () {
  
                          const parser = peg.generate(
                              "start = a:'a' ('b' !{ return a !== 'a'; })",
                              options,
                          );
  
                          expect( parser ).to.parse( "ab" );
  
                      } );
  
                      it( "cannot access variable defined by labeled predicate element", function () {
  
                          const parser = peg.generate(
                              "start = 'a' b:('b' !{ return b !== undefined; }) 'c'",
                              options,
                          );
  
                          expect( parser ).to.failToParse( "abc" );
  
                      } );
  
                      it( "cannot access variables defined by following labeled elements", function () {
  
                          const parser = peg.generate(
                              "start = ('a' !{ return b !== 'b'; }) b:'b'",
                              options,
                          );
  
                          expect( parser ).to.failToParse( "ab" );
  
                      } );
  
                  } );
  
              } );
  
              describe( "initializer variables & functions", function () {
  
                  it( "can access variables defined in the initializer", function () {
  
                      const parser = peg.generate( [
                          "{ var v = 42 }",
                          "start = !{ return v !== 42; }",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.parse( "" );
  
                  } );
  
                  it( "can access functions defined in the initializer", function () {
  
                      const parser = peg.generate( [
                          "{ function f() { return 42; } }",
                          "start = !{ return f() !== 42; }",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.parse( "" );
  
                  } );
  
              } );
  
              describe( "available variables & functions", function () {
  
                  it( "|options| contains options", function () {
  
                      const parser = peg.generate( [
                          "{ var result; }",
                          "start = !{ result = options; return false; } { return result; }",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.parse( "", { a: 42 }, { a: 42 } );
  
                  } );
  
                  it( "|location| returns current location info", function () {
  
                      const parser = peg.generate( [
                          "{ var result; }",
                          "start = line (nl+ line)* { return result; }",
                          "line = thing (' '+ thing)*",
                          "thing = digit / mark",
                          "digit = [0-9]",
                          "mark = !{ result = location(); return false; } 'x'",
                          "nl = '\\r'? '\\n'",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.parse( "1\n2\n\n3\n\n\n4 5 x", {
                          start: { offset: 13, line: 7, column: 5 },
                          end: { offset: 13, line: 7, column: 5 },
                      } );
  
                      // Newline representations
                      expect( parser ).to.parse( "1\nx", {     // Unix
                          start: { offset: 2, line: 2, column: 1 },
                          end: { offset: 2, line: 2, column: 1 },
                      } );
                      expect( parser ).to.parse( "1\r\nx", {   // Windows
                          start: { offset: 3, line: 2, column: 1 },
                          end: { offset: 3, line: 2, column: 1 },
                      } );
  
                  } );
  
              } );
  
          } );
  
          describe( "group", function () {
  
              describe( "when the expression matches", function () {
  
                  it( "returns its match result", function () {
  
                      const parser = peg.generate( "start = ('a')", options );
  
                      expect( parser ).to.parse( "a", "a" );
  
                  } );
  
              } );
  
              describe( "when the expression doesn't match", function () {
  
                  it( "reports match failure", function () {
  
                      const parser = peg.generate( "start = ('a')", options );
  
                      expect( parser ).to.failToParse( "b" );
  
                  } );
  
              } );
  
          } );
  
          describe( "optional", function () {
  
              describe( "when the expression matches", function () {
  
                  it( "returns its match result", function () {
  
                      const parser = peg.generate( "start = 'a'?", options );
  
                      expect( parser ).to.parse( "a", "a" );
  
                  } );
  
              } );
  
              describe( "when the expression doesn't match", function () {
  
                  it( "returns |null|", function () {
  
                      const parser = peg.generate( "start = 'a'?", options );
  
                      expect( parser ).to.parse( "", null );
  
                  } );
  
              } );
  
          } );
  
          describe( "zero or more", function () {
  
              describe( "when the expression matches zero or more times", function () {
  
                  it( "returns an array of its match results", function () {
  
                      const parser = peg.generate( "start = 'a'*", options );
  
                      expect( parser ).to.parse( "", [] );
                      expect( parser ).to.parse( "a", [ "a" ] );
                      expect( parser ).to.parse( "aaa", [ "a", "a", "a" ] );
  
                  } );
  
              } );
  
          } );
  
          describe( "one or more", function () {
  
              describe( "when the expression matches one or more times", function () {
  
                  it( "returns an array of its match results", function () {
  
                      const parser = peg.generate( "start = 'a'+", options );
  
                      expect( parser ).to.parse( "a", [ "a" ] );
                      expect( parser ).to.parse( "aaa", [ "a", "a", "a" ] );
  
                  } );
  
              } );
  
              describe( "when the expression doesn't match", function () {
  
                  it( "reports match failure", function () {
  
                      const parser = peg.generate( "start = 'a'+", options );
  
                      expect( parser ).to.failToParse( "" );
  
                  } );
  
              } );
  
          } );
  
          describe( "text", function () {
  
              describe( "when the expression matches", function () {
  
                  it( "returns the matched text", function () {
  
                      const parser = peg.generate( "start = $('a' 'b' 'c')", options );
  
                      expect( parser ).to.parse( "abc", "abc" );
  
                  } );
  
              } );
  
              describe( "when the expression doesn't match", function () {
  
                  it( "reports match failure", function () {
  
                      const parser = peg.generate( "start = $('a')", options );
  
                      expect( parser ).to.failToParse( "b" );
  
                  } );
  
              } );
  
          } );
  
          describe( "positive simple predicate", function () {
  
              describe( "when the expression matches", function () {
  
                  it( "returns |undefined|", function () {
  
                      const parser = peg.generate( "start = &'a' 'a'", options );
  
                      expect( parser ).to.parse( "a", [ void 0, "a" ] );
  
                  } );
  
                  it( "resets parse position", function () {
  
                      const parser = peg.generate( "start = &'a' 'a'", options );
  
                      expect( parser ).to.parse( "a" );
  
                  } );
  
              } );
  
              describe( "when the expression doesn't match", function () {
  
                  it( "reports match failure", function () {
  
                      const parser = peg.generate( "start = &'a'", options );
  
                      expect( parser ).to.failToParse( "b" );
  
                  } );
  
                  it( "doesn't discard any expectations recorded when matching the expression", function () {
  
                      const parser = peg.generate( "start = 'a' / &'b' / 'c'", options );
  
                      expect( parser ).to.failToParse( "d", {
                          expected: [
                              { type: "literal", text: "a", ignoreCase: false },
                              { type: "literal", text: "b", ignoreCase: false },
                              { type: "literal", text: "c", ignoreCase: false },
                          ],
                      } );
  
                  } );
  
                  it( "records expectations from right place", function () {
  
                      const parser = peg.generate( "start = 'a' / &'b' .", options );
  
                      expect( parser ).to.failToParse( "d", {
                          expected: [
                              { type: "literal", text: "a", ignoreCase: false },
                              { type: "literal", text: "b", ignoreCase: false },
                          ],
                      } );
  
                  } );
  
              } );
  
          } );
  
          describe( "negative simple predicate", function () {
  
              describe( "when the expression matches", function () {
  
                  it( "reports match failure", function () {
  
                      const parser = peg.generate( "start = !'a'", options );
  
                      expect( parser ).to.failToParse( "a" );
  
                  } );
  
              } );
  
              describe( "when the expression doesn't match", function () {
  
                  it( "returns |undefined|", function () {
  
                      const parser = peg.generate( "start = !'a' 'b'", options );
  
                      expect( parser ).to.parse( "b", [ void 0, "b" ] );
  
                  } );
  
                  it( "resets parse position", function () {
  
                      const parser = peg.generate( "start = !'a' 'b'", options );
  
                      expect( parser ).to.parse( "b" );
  
                  } );
  
                  it( "inverts any expectations recorded when matching the expression", function () {
  
                      const parser = peg.generate( "start = 'a' / !'b' / 'c'", options );
  
                      expect( parser ).to.failToParse( "b", {
                          expected: [
                              { type: "literal", text: "a", ignoreCase: false },
                              { type: "not", expected: { type: "literal", text: "b", ignoreCase: false } },
                              { type: "literal", text: "c", ignoreCase: false },
                          ],
                      } );
  
                  } );
  
                  it( "records expectations from right place", function () {
  
                      const parser = peg.generate( "start = 'a' / !'b' .", options );
  
                      expect( parser ).to.failToParse( "b", {
                          expected: [
                              { type: "literal", text: "a", ignoreCase: false },
                              { type: "not", expected: { type: "literal", text: "b", ignoreCase: false } },
                          ],
                      } );
  
                  } );
  
                  it( "reports not inverted expectations when the expression inverted twice", function () {
  
                      const parser = peg.generate( "start = !(!'a')", options );
  
                      expect( parser ).to.failToParse( "b", {
                          expected: [
                              { type: "literal", text: "a", ignoreCase: false },
                          ],
                      } );
  
                  } );
  
              } );
  
          } );
  
          describe( "label", function () {
  
              describe( "when the expression matches", function () {
  
                  it( "returns its match result", function () {
  
                      const parser = peg.generate( "start = a:'a'", options );
  
                      expect( parser ).to.parse( "a", "a" );
  
                  } );
  
              } );
  
              describe( "when the expression doesn't match", function () {
  
                  it( "reports match failure", function () {
  
                      const parser = peg.generate( "start = a:'a'", options );
  
                      expect( parser ).to.failToParse( "b" );
  
                  } );
  
              } );
  
          } );
  
          describe( "sequence", function () {
  
              describe( "when all expressions match", function () {
  
                  function parser( description, edgecases ) {
  
                      it( description, () => {
  
                          edgecases.forEach( ( { grammar, input, output } ) => {
  
                              const parser = peg.generate( grammar, options );
                              expect( parser ).to.parse( input, output );
  
                          } );
  
                      } );
  
                  }
  
                  parser( "returns an array of their match results", [
                      {
                          grammar: "start = 'a' 'b' 'c'",
                          input: "abc",
                          output: [ "a", "b", "c" ],
                      },
                  ] );
  
                  parser( "plucking a single value", [
                      {
                          grammar: "start = @'a'",
                          input: "a",
                          output: "a",
                      },
                      {
                          grammar: "start = @'a' / @'b'",
                          input: "a",
                          output: "a",
                      },
                      {
                          grammar: "start = @'a' / @'b'",
                          input: "b",
                          output: "b",
                      },
                      {
                          grammar: "start = 'a' @'b' 'c'",
                          input: "abc",
                          output: "b",
                      },
                      {
                          grammar: "start = 'a' ( @'b' 'c' )",
                          input: "abc",
                          output: [ "a", "b" ],
                      },
                      {
                          grammar: "start = 'a' @( 'b' @'c' 'd' )",
                          input: "abcd",
                          output: "c",
                      },
                      {
                          grammar: "start = 'a' ( @'b' 'c' ) @'d'",
                          input: "abcd",
                          output: "d",
                      },
                      {
                          grammar: "start = 'a' @'b' 'c' / 'd' 'e' @'f'",
                          input: "def",
                          output: "f",
                      },
                  ] );
  
                  parser( "plucking multiple values", [
                      {
                          grammar: "start = 'a' @'b' @'c'",
                          input: "abc",
                          output: [ "b", "c" ],
                      },
                      {
                          grammar: "start = 'a' ( @'b' @'c' )",
                          input: "abc",
                          output: [ "a", [ "b", "c" ] ],
                      },
                      {
                          grammar: "start = 'a' @( 'b' @'c' @'d' )",
                          input: "abcd",
                          output: [ "c", "d" ],
                      },
                      {
                          grammar: "start = 'a' @( @'b' 'c' ) @'d' 'e'",
                          input: "abcde",
                          output: [ "b", "d" ],
                      },
                      {
                          grammar: "start = 'a' @'b' 'c' / @'d' 'e' @'f'",
                          input: "def",
                          output: [ "d", "f" ],
                      },
                  ] );
  
                  parser( "plucking a value if a predicate doesnt fail", [
                      {
                          grammar: "start = @'a' &{ return true; }",
                          input: "a",
                          output: "a",
                      },
                      {
                          grammar: "start = @'a' !{ return false; }",
                          input: "a",
                          output: "a",
                      },
                      {
                          grammar: "start = @n:[0-9] &{ return n > 0; }",
                          input: "2",
                          output: "2",
                      },
                  ] );
  
              } );
  
              describe( "when any expression doesn't match", function () {
  
                  it( "reports match failure", function () {
  
                      const parser = peg.generate( "start = 'a' 'b' 'c'", options );
  
                      expect( parser ).to.failToParse( "dbc" );
                      expect( parser ).to.failToParse( "adc" );
                      expect( parser ).to.failToParse( "abd" );
  
                  } );
  
                  it( "resets parse position", function () {
  
                      const parser = peg.generate( "start = 'a' 'b' / 'a'", options );
  
                      expect( parser ).to.parse( "a", "a" );
  
                  } );
  
              } );
  
          } );
  
          describe( "action", function () {
  
              describe( "when the expression matches", function () {
  
                  it( "returns the value returned by the code", function () {
  
                      const parser = peg.generate( "start = 'a' { return 42; }", options );
  
                      expect( parser ).to.parse( "a", 42 );
  
                  } );
  
                  describe( "label variables", function () {
  
                      describe( "in the expression", function () {
  
                          it( "can access variable defined by labeled expression", function () {
  
                              const parser = peg.generate( "start = a:'a' { return a; }", options );
  
                              expect( parser ).to.parse( "a", "a" );
  
                          } );
  
                          it( "can access variables defined by labeled sequence elements", function () {
  
                              const parser = peg.generate(
                                  "start = a:'a' b:'b' c:'c' { return [a, b, c]; }",
                                  options,
                              );
  
                              expect( parser ).to.parse( "abc", [ "a", "b", "c" ] );
  
                          } );
  
                          it( "cannot access variables defined by subexpressions", function () {
  
                              const testcases = [
                                  {
                                      grammar: "start = (a:'a') { return a; }",
                                      input: "a",
                                  },
                                  {
                                      grammar: "start = (a:'a')? { return a; }",
                                      input: "a",
                                  },
                                  {
                                      grammar: "start = (a:'a')* { return a; }",
                                      input: "a",
                                  },
                                  {
                                      grammar: "start = (a:'a')+ { return a; }",
                                      input: "a",
                                  },
                                  {
                                      grammar: "start = $(a:'a') { return a; }",
                                      input: "a",
                                  },
                                  {
                                      grammar: "start = &(a:'a') 'a' { return a; }",
                                      input: "a",
                                  },
                                  {
                                      grammar: "start = !(a:'a') 'b' { return a; }",
                                      input: "b",
                                  },
                                  {
                                      grammar: "start = b:(a:'a') { return a; }",
                                      input: "a",
                                  },
                                  {
                                      grammar: "start = ('a' b:'b' 'c') { return b; }",
                                      input: "abc",
                                  },
                                  {
                                      grammar: "start = (a:'a' { return a; }) { return a; }",
                                      input: "a",
                                  },
                                  {
                                      grammar: "start = ('a' / b:'b' / 'c') { return b; }",
                                      input: "b",
                                  },
                              ];
  
                              testcases.forEach( testcase => {
  
                                  const parser = peg.generate( testcase.grammar, options );
                                  expect( parser ).to.failToParse( testcase.input );
  
                              } );
  
                          } );
  
                      } );
  
                      describe( "in outer sequence", function () {
  
                          it( "can access variables defined by preceding labeled elements", function () {
  
                              const parser = peg.generate(
                                  "start = a:'a' ('b' { return a; })",
                                  options,
                              );
  
                              expect( parser ).to.parse( "ab", [ "a", "a" ] );
  
                          } );
  
                          it( "cannot access variable defined by labeled action element", function () {
  
                              const parser = peg.generate(
                                  "start = 'a' b:('b' { return b; }) c:'c'",
                                  options,
                              );
  
                              expect( parser ).to.failToParse( "abc" );
  
                          } );
  
                          it( "cannot access variables defined by following labeled elements", function () {
  
                              const parser = peg.generate(
                                  "start = ('a' { return b; }) b:'b'",
                                  options,
                              );
  
                              expect( parser ).to.failToParse( "ab" );
  
                          } );
  
                      } );
  
                  } );
  
                  describe( "initializer variables & functions", function () {
  
                      it( "can access variables defined in the initializer", function () {
  
                          const parser = peg.generate( [
                              "{ var v = 42 }",
                              "start = 'a' { return v; }",
                          ].join( "\n" ), options );
  
                          expect( parser ).to.parse( "a", 42 );
  
                      } );
  
                      it( "can access functions defined in the initializer", function () {
  
                          const parser = peg.generate( [
                              "{ function f() { return 42; } }",
                              "start = 'a' { return f(); }",
                          ].join( "\n" ), options );
  
                          expect( parser ).to.parse( "a", 42 );
  
                      } );
  
                  } );
  
                  describe( "available variables & functions", function () {
  
                      it( "|options| contains options", function () {
  
                          const parser = peg.generate(
                              "start = 'a' { return options; }",
                              options,
                          );
  
                          expect( parser ).to.parse( "a", { a: 42 }, { a: 42 } );
  
                      } );
  
                      it( "|text| returns text matched by the expression", function () {
  
                          const parser = peg.generate(
                              "start = 'a' { return text(); }",
                              options,
                          );
  
                          expect( parser ).to.parse( "a", "a" );
  
                      } );
  
                      it( "|location| returns location info of the expression", function () {
  
                          const parser = peg.generate( [
                              "{ var result; }",
                              "start = line (nl+ line)* { return result; }",
                              "line = thing (' '+ thing)*",
                              "thing = digit / mark",
                              "digit = [0-9]",
                              "mark = 'x' { result = location(); }",
                              "nl = '\\r'? '\\n'",
                          ].join( "\n" ), options );
  
                          expect( parser ).to.parse( "1\n2\n\n3\n\n\n4 5 x", {
                              start: { offset: 13, line: 7, column: 5 },
                              end: { offset: 14, line: 7, column: 6 },
                          } );
  
                          // Newline representations
                          expect( parser ).to.parse( "1\nx", {     // Unix
                              start: { offset: 2, line: 2, column: 1 },
                              end: { offset: 3, line: 2, column: 2 },
                          } );
                          expect( parser ).to.parse( "1\r\nx", {   // Windows
                              start: { offset: 3, line: 2, column: 1 },
                              end: { offset: 4, line: 2, column: 2 },
                          } );
  
                      } );
  
                      describe( "|expected|", function () {
  
                          it( "terminates parsing and throws an exception", function () {
  
                              const parser = peg.generate(
                                  "start = 'a' { expected('a'); }",
                                  options,
                              );
  
                              expect( parser ).to.failToParse( "a", {
                                  message: "Expected a but \"a\" found.",
                                  expected: [ { type: "other", description: "a" } ],
                                  found: "a",
                                  location: {
                                      start: { offset: 0, line: 1, column: 1 },
                                      end: { offset: 1, line: 1, column: 2 },
                                  },
                              } );
  
                          } );
  
                          it( "allows to set custom location info", function () {
  
                              const parser = peg.generate( [
                                  "start = 'a' {",
                                  "  expected('a', {",
                                  "    start: { offset: 1, line: 1, column: 2 },",
                                  "    end: { offset: 2, line: 1, column: 3 }",
                                  "  });",
                                  "}",
                              ].join( "\n" ), options );
  
                              expect( parser ).to.failToParse( "a", {
                                  message: "Expected a but \"a\" found.",
                                  expected: [ { type: "other", description: "a" } ],
                                  found: "a",
                                  location: {
                                      start: { offset: 1, line: 1, column: 2 },
                                      end: { offset: 2, line: 1, column: 3 },
                                  },
                              } );
  
                          } );
  
                      } );
  
                      describe( "|error|", function () {
  
                          it( "terminates parsing and throws an exception", function () {
  
                              const parser = peg.generate(
                                  "start = 'a' { error('a'); }",
                                  options,
                              );
  
                              expect( parser ).to.failToParse( "a", {
                                  message: "a",
                                  found: null,
                                  expected: null,
                                  location: {
                                      start: { offset: 0, line: 1, column: 1 },
                                      end: { offset: 1, line: 1, column: 2 },
                                  },
                              } );
  
                          } );
  
                          it( "allows to set custom location info", function () {
  
                              const parser = peg.generate( [
                                  "start = 'a' {",
                                  "  error('a', {",
                                  "    start: { offset: 1, line: 1, column: 2 },",
                                  "    end: { offset: 2, line: 1, column: 3 }",
                                  "  });",
                                  "}",
                              ].join( "\n" ), options );
  
                              expect( parser ).to.failToParse( "a", {
                                  message: "a",
                                  expected: null,
                                  found: null,
                                  location: {
                                      start: { offset: 1, line: 1, column: 2 },
                                      end: { offset: 2, line: 1, column: 3 },
                                  },
                              } );
  
                          } );
  
                      } );
  
                  } );
  
              } );
  
              describe( "when the expression doesn't match", function () {
  
                  it( "reports match failure", function () {
  
                      const parser = peg.generate( "start = 'a' { return 42; }", options );
  
                      expect( parser ).to.failToParse( "b" );
  
                  } );
  
                  it( "doesn't execute the code", function () {
  
                      const parser = peg.generate(
                          "start = 'a' { throw 'Boom!'; } / 'b'",
                          options,
                      );
  
                      expect( parser ).to.parse( "b" );
  
                  } );
  
              } );
  
          } );
  
          describe( "choice", function () {
  
              describe( "when any expression matches", function () {
  
                  it( "returns its match result", function () {
  
                      const parser = peg.generate( "start = 'a' / 'b' / 'c'", options );
  
                      expect( parser ).to.parse( "a", "a" );
                      expect( parser ).to.parse( "b", "b" );
                      expect( parser ).to.parse( "c", "c" );
  
                  } );
  
              } );
  
              describe( "when all expressions don't match", function () {
  
                  it( "reports match failure", function () {
  
                      const parser = peg.generate( "start = 'a' / 'b' / 'c'", options );
  
                      expect( parser ).to.failToParse( "d" );
  
                  } );
  
              } );
  
          } );
  
          describe( "error reporting", function () {
  
              describe( "behavior", function () {
  
                  it( "reports only the rightmost error", function () {
  
                      const parser = peg.generate( "start = 'a' 'b' / 'a' 'c' 'd'", options );
  
                      expect( parser ).to.failToParse( "ace", {
                          expected: [ { type: "literal", text: "d", ignoreCase: false } ],
                      } );
  
                  } );
  
              } );
  
              describe( "expectations reporting", function () {
  
                  it( "reports expectations correctly with no alternative", function () {
  
                      const parser = peg.generate( "start = 'a'", options );
  
                      expect( parser ).to.failToParse( "ab", {
                          expected: [ { type: "end" } ],
                      } );
  
                  } );
  
                  it( "reports expectations correctly with one alternative", function () {
  
                      const parser = peg.generate( "start = 'a'", options );
  
                      expect( parser ).to.failToParse( "b", {
                          expected: [ { type: "literal", text: "a", ignoreCase: false } ],
                      } );
  
                  } );
  
                  it( "reports expectations correctly with multiple alternatives", function () {
  
                      const parser = peg.generate( "start = 'a' / 'b' / 'c'", options );
  
                      expect( parser ).to.failToParse( "d", {
                          expected: [
                              { type: "literal", text: "a", ignoreCase: false },
                              { type: "literal", text: "b", ignoreCase: false },
                              { type: "literal", text: "c", ignoreCase: false },
                          ],
                      } );
  
                  } );
  
                  it( "reports expectations correctly with look ahead", function () {
  
                      // Case 1, by https://github.com/dmajda
  
                      let parser = peg.generate( [
                          "Statement = '{' __ !Statement Statement __ '}'",
                          "__ = [ ]*",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.failToParse( "{x}", {
                          expected: [
                              { type: "class", parts: [ " " ], inverted: false, ignoreCase: false },
                              { type: "not", expected: { type: "literal", text: "{", ignoreCase: false } },
                              { type: "literal", text: "{", ignoreCase: false },
                          ],
                      } );
  
                      // Case 2, by https://github.com/nikku
  
                      parser = peg.generate( [
                          "Start = Char+ End",
                          "End = 'e'",
                          "Char = !End [a-z]",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.failToParse( "a", {
                          expected: [
                              { type: "class", parts: [ [ "a", "z" ] ], inverted: false, ignoreCase: false },
                              { type: "literal", text: "e", ignoreCase: false },
                          ],
                      } );
  
                  } );
  
                  it( "reports expectations correctly with look ahead + grouped names", function () {
  
                      const parser = peg.generate( [
                          "Start = Char+ End",
                          "End 'end' = 'e'",
                          "Char = !End [a-z]",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.failToParse( "a", {
                          expected: [
                              { type: "class", parts: [ [ "a", "z" ] ], inverted: false, ignoreCase: false },
                              { type: "other", description: "end" },
                          ],
                      } );
  
                  } );
  
              } );
  
              describe( "found string reporting", function () {
  
                  it( "reports found string correctly at the end of input", function () {
  
                      const parser = peg.generate( "start = 'a'", options );
  
                      expect( parser ).to.failToParse( "", { found: null } );
  
                  } );
  
                  it( "reports found string correctly in the middle of input", function () {
  
                      const parser = peg.generate( "start = 'a'", options );
  
                      expect( parser ).to.failToParse( "b", { found: "b" } );
  
                  } );
  
              } );
  
              describe( "message building", function () {
  
                  it( "builds message correctly with no alternative", function () {
  
                      const parser = peg.generate( "start = 'a'", options );
  
                      expect( parser ).to.failToParse( "ab", {
                          message: "Expected end of input but \"b\" found.",
                      } );
  
                  } );
  
                  it( "builds message correctly with one alternative", function () {
  
                      const parser = peg.generate( "start = 'a'", options );
  
                      expect( parser ).to.failToParse( "b", {
                          message: "Expected \"a\" but \"b\" found.",
                      } );
  
                  } );
  
                  it( "builds message correctly with multiple alternatives", function () {
  
                      const parser = peg.generate( "start = 'a' / 'b' / 'c'", options );
  
                      expect( parser ).to.failToParse( "d", {
                          message: "Expected \"a\", \"b\", or \"c\" but \"d\" found.",
                      } );
  
                  } );
  
                  it( "builds message correctly at the end of input", function () {
  
                      const parser = peg.generate( "start = 'a'", options );
  
                      expect( parser ).to.failToParse( "", {
                          message: "Expected \"a\" but end of input found.",
                      } );
  
                  } );
  
                  it( "builds message correctly in the middle of input", function () {
  
                      const parser = peg.generate( "start = 'a'", options );
  
                      expect( parser ).to.failToParse( "b", {
                          message: "Expected \"a\" but \"b\" found.",
                      } );
  
                  } );
  
                  it( "removes duplicates from expectations", function () {
  
                      const parser = peg.generate( "start = 'a' / 'a'", options );
  
                      expect( parser ).to.failToParse( "b", {
                          message: "Expected \"a\" but \"b\" found.",
                      } );
  
                  } );
  
                  it( "sorts expectations", function () {
  
                      const parser = peg.generate( "start = 'c' / 'b' / 'a'", options );
  
                      expect( parser ).to.failToParse( "d", {
                          message: "Expected \"a\", \"b\", or \"c\" but \"d\" found.",
                      } );
  
                  } );
  
              } );
  
              describe( "position reporting", function () {
  
                  it( "reports position correctly at the end of input", function () {
  
                      const parser = peg.generate( "start = 'a'", options );
  
                      expect( parser ).to.failToParse( "", {
                          location: {
                              start: { offset: 0, line: 1, column: 1 },
                              end: { offset: 0, line: 1, column: 1 },
                          },
                      } );
  
                  } );
  
                  it( "reports position correctly in the middle of input", function () {
  
                      const parser = peg.generate( "start = 'a'", options );
  
                      expect( parser ).to.failToParse( "b", {
                          location: {
                              start: { offset: 0, line: 1, column: 1 },
                              end: { offset: 1, line: 1, column: 2 },
                          },
                      } );
  
                  } );
  
                  it( "reports position correctly with trailing input", function () {
  
                      const parser = peg.generate( "start = 'a'", options );
  
                      expect( parser ).to.failToParse( "aa", {
                          location: {
                              start: { offset: 1, line: 1, column: 2 },
                              end: { offset: 2, line: 1, column: 3 },
                          },
                      } );
  
                  } );
  
                  it( "reports position correctly in complex cases", function () {
  
                      const parser = peg.generate( [
                          "start = line (nl+ line)*",
                          "line = digit (' '+ digit)*",
                          "digit = [0-9]",
                          "nl = '\\r'? '\\n'",
                      ].join( "\n" ), options );
  
                      expect( parser ).to.failToParse( "1\n2\n\n3\n\n\n4 5 x", {
                          location: {
                              start: { offset: 13, line: 7, column: 5 },
                              end: { offset: 14, line: 7, column: 6 },
                          },
                      } );
  
                      // Newline representations
                      expect( parser ).to.failToParse( "1\nx", {     // Old Mac
                          location: {
                              start: { offset: 2, line: 2, column: 1 },
                              end: { offset: 3, line: 2, column: 2 },
                          },
                      } );
                      expect( parser ).to.failToParse( "1\r\nx", {   // Windows
                          location: {
                              start: { offset: 3, line: 2, column: 1 },
                              end: { offset: 4, line: 2, column: 2 },
                          },
                      } );
  
                  } );
  
              } );
  
          } );
  
          // Following examples are from Wikipedia, see
          // http://en.wikipedia.org/w/index.php?title=Parsing_expression_grammar&oldid=335106938.
          describe( "complex examples", function () {
  
              it( "handles arithmetics example correctly", function () {
  
                  // Value   ← [0-9]+ / '(' Expr ')'
                  // Product ← Value (('*' / '/') Value)*
                  // Sum     ← Product (('+' / '-') Product)*
                  // Expr    ← Sum
                  const parser = peg.generate( [
                      "Expr = Sum",
                      "Sum = head:Product tail:(('+' / '-') Product)* {",
                      "        return tail.reduce(function(result, element) {",
                      "          if (element[0] === '+') { return result + element[1]; }",
                      "          if (element[0] === '-') { return result - element[1]; }",
                      "        }, head);",
                      "      }",
                      "Product = head:Value tail:(('*' / '/') Value)* {",
                      "            return tail.reduce(function(result, element) {",
                      "              if (element[0] === '*') { return result * element[1]; }",
                      "              if (element[0] === '/') { return result / element[1]; }",
                      "            }, head);",
                      "          }",
                      "Value = digits:[0-9]+     { return parseInt(digits.join(''), 10); }",
                      "      / '(' expr:Expr ')' { return expr; }",
                  ].join( "\n" ), options );
  
                  // The "value" rule
                  expect( parser ).to.parse( "0", 0 );
                  expect( parser ).to.parse( "123", 123 );
                  expect( parser ).to.parse( "(42+43)", 42 + 43 );
  
                  // The "product" rule
                  expect( parser ).to.parse( "42", 42 );
                  expect( parser ).to.parse( "42*43", 42 * 43 );
                  expect( parser ).to.parse( "42*43*44*45", 42 * 43 * 44 * 45 );
                  expect( parser ).to.parse( "42/43", 42 / 43 );
                  expect( parser ).to.parse( "42/43/44/45", 42 / 43 / 44 / 45 );
  
                  // The "sum" rule
                  expect( parser ).to.parse( "42*43", 42 * 43 );
                  expect( parser ).to.parse( "42*43+44*45", 42 * 43 + 44 * 45 );
                  expect( parser ).to.parse( "42*43+44*45+46*47+48*49", 42 * 43 + 44 * 45 + 46 * 47 + 48 * 49 );
                  expect( parser ).to.parse( "42*43-44*45", 42 * 43 - 44 * 45 );
                  expect( parser ).to.parse( "42*43-44*45-46*47-48*49", 42 * 43 - 44 * 45 - 46 * 47 - 48 * 49 );
  
                  // The "expr" rule
                  expect( parser ).to.parse( "42+43", 42 + 43 );
  
                  // Complex test
                  expect( parser ).to.parse( "(1+2)*(3+4)", ( 1 + 2 ) * ( 3 + 4 ) );
  
              } );
  
              it( "handles non-context-free language correctly", 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
                  const parser = peg.generate( [
                      "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].join(''); }",
                      "B = b:'b' B:B? c:'c' { return [b, B, c].join(''); }",
                  ].join( "\n" ), options );
  
                  expect( parser ).to.parse( "abc", "abc" );
                  expect( parser ).to.parse( "aaabbbccc", "aaabbbccc" );
                  expect( parser ).to.failToParse( "aabbbccc" );
                  expect( parser ).to.failToParse( "aaaabbbccc" );
                  expect( parser ).to.failToParse( "aaabbccc" );
                  expect( parser ).to.failToParse( "aaabbbbccc" );
                  expect( parser ).to.failToParse( "aaabbbcc" );
                  expect( parser ).to.failToParse( "aaabbbcccc" );
  
              } );
  
              it( "handles nested comments example correctly", function () {
  
                  // Begin ← "(*"
                  // End ← "*)"
                  // C ← Begin N* End
                  // N ← C / (!Begin !End Z)
                  // Z ← any single character
                  const parser = peg.generate( [
                      "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 );
  
                  expect( parser ).to.parse( "(**)", "(**)" );
                  expect( parser ).to.parse( "(*abc*)", "(*abc*)" );
                  expect( parser ).to.parse( "(*(**)*)", "(*(**)*)" );
                  expect( parser ).to.parse(
                      "(*abc(*def*)ghi(*(*(*jkl*)*)*)mno*)",
                      "(*abc(*def*)ghi(*(*(*jkl*)*)*)mno*)",
                  );
  
              } );
  
          } );
  
      } );
  
  } );
  