You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
pegjs/test/unit/parser.spec.js

930 lines
31 KiB
JavaScript

"use strict";
const chai = require( "chai" );
const {
parser,
util,
ast,
} = require( "pegjs" );
const expect = chai.expect;
// better diagnostics for deep eq failure
chai.config.truncateThreshold = 0;
function varyParserOptions( block ) {
const optionsVariants = [
{ },
{ extractComments: false },
{ extractComments: true },
];
optionsVariants.forEach( variant => {
describe(
"with options " + chai.util.inspect( variant ),
() => block( variant ),
);
} );
}
describe( "PEG.js grammar parser", function () {
const literalAbcd = { type: "literal", value: "abcd", ignoreCase: false };
const literalEfgh = { type: "literal", value: "efgh", ignoreCase: false };
const literalIjkl = { type: "literal", value: "ijkl", ignoreCase: false };
const literalMnop = { type: "literal", value: "mnop", ignoreCase: false };
const semanticAnd = { type: "semantic_and", code: " code " };
const semanticNot = { type: "semantic_not", code: " code " };
const optional = { type: "optional", expression: literalAbcd };
const zeroOrMore = { type: "zero_or_more", expression: literalAbcd };
const oneOrMore = { type: "one_or_more", expression: literalAbcd };
const textOptional = { type: "text", expression: optional };
const simpleNotAbcd = { type: "simple_not", expression: literalAbcd };
const simpleAndOptional = { type: "simple_and", expression: optional };
const simpleNotOptional = { type: "simple_not", expression: optional };
const labeledAbcd = { type: "labeled", label: "a", expression: literalAbcd };
const labeledEfgh = { type: "labeled", label: "b", expression: literalEfgh };
const labeledIjkl = { type: "labeled", label: "c", expression: literalIjkl };
const labeledMnop = { type: "labeled", label: "d", expression: literalMnop };
const labeledSimpleNot = { type: "labeled", label: "a", expression: simpleNotAbcd };
const sequence = {
type: "sequence",
elements: [ literalAbcd, literalEfgh, literalIjkl ],
};
const sequence2 = {
type: "sequence",
elements: [ labeledAbcd, labeledEfgh ],
};
const sequence4 = {
type: "sequence",
elements: [ labeledAbcd, labeledEfgh, labeledIjkl, labeledMnop ],
};
const groupLabeled = { type: "group", expression: labeledAbcd };
const groupSequence = { type: "group", expression: sequence };
const actionAbcd = { type: "action", expression: literalAbcd, code: " code " };
const actionEfgh = { type: "action", expression: literalEfgh, code: " code " };
const actionIjkl = { type: "action", expression: literalIjkl, code: " code " };
const actionMnop = { type: "action", expression: literalMnop, code: " code " };
const actionSequence = { type: "action", expression: sequence, code: " code " };
const choice = {
type: "choice",
alternatives: [ literalAbcd, literalEfgh, literalIjkl ],
};
const choice2 = {
type: "choice",
alternatives: [ actionAbcd, actionEfgh ],
};
const choice4 = {
type: "choice",
alternatives: [ actionAbcd, actionEfgh, actionIjkl, actionMnop ],
};
const named = { type: "named", name: "start rule", expression: literalAbcd };
const ruleA = { type: "rule", name: "a", expression: literalAbcd };
const ruleB = { type: "rule", name: "b", expression: literalEfgh };
const ruleC = { type: "rule", name: "c", expression: literalIjkl };
const ruleStart = { type: "rule", name: "start", expression: literalAbcd };
const initializer = { type: "initializer", code: " code " };
function oneRuleGrammar( expression ) {
return {
type: "grammar",
initializer: null,
comments: null,
rules: [ { type: "rule", name: "start", expression: expression } ],
};
}
function actionGrammar( code ) {
return oneRuleGrammar(
{ type: "action", expression: literalAbcd, code: code },
);
}
function literalGrammar( value, ignoreCase ) {
return oneRuleGrammar(
{ type: "literal", value: value, ignoreCase: ignoreCase },
);
}
function classGrammar( parts, inverted, ignoreCase ) {
return oneRuleGrammar( {
type: "class",
parts: parts,
inverted: inverted,
ignoreCase: ignoreCase,
} );
}
function anyGrammar() {
return oneRuleGrammar( { type: "any" } );
}
function ruleRefGrammar( name ) {
return oneRuleGrammar( { type: "rule_ref", name: name } );
}
function commented( grammar, comments, options ) {
function toObject( result, comment ) {
result[ comment.offset ] = {
text: comment.text,
multiline: comment.multiline,
};
return result;
}
grammar = util.clone( grammar );
grammar.comments = options.extractComments ? comments.reduce( toObject, {} ) : null;
return grammar;
}
const trivialGrammar = literalGrammar( "abcd", false );
const twoRuleGrammar = {
type: "grammar",
initializer: null,
comments: null,
rules: [ ruleA, ruleB ],
};
const stripProperties = ( function () {
let strip;
function stripLeaf( node ) {
delete node.location;
}
function stripExpression( node ) {
delete node.location;
strip( node.expression );
}
function stripChildren( property ) {
return function ( node ) {
delete node.location;
node[ property ].forEach( strip );
};
}
strip = ast.visitor.build( {
grammar( node ) {
delete node.location;
delete node._alwaysConsumesOnSuccess;
if ( node.initializer ) {
strip( node.initializer );
}
if ( node.comments ) {
util.each( node.comments, stripLeaf );
}
node.rules.forEach( strip );
},
initializer: stripLeaf,
rule: stripExpression,
named: stripExpression,
choice: stripChildren( "alternatives" ),
action: stripExpression,
sequence: stripChildren( "elements" ),
labeled: stripExpression,
text: stripExpression,
simple_and: stripExpression,
simple_not: stripExpression,
optional: stripExpression,
zero_or_more: stripExpression,
one_or_more: stripExpression,
group: stripExpression,
semantic_and: stripLeaf,
semantic_not: stripLeaf,
rule_ref: stripLeaf,
literal: stripLeaf,
class: stripLeaf,
any: stripLeaf,
} );
return strip;
} )();
function helpers( chai, utils ) {
const Assertion = chai.Assertion;
Assertion.addMethod( "parseAs", function ( expected, options ) {
options = typeof options === "undefined" ? {} : options;
const result = parser.parse( utils.flag( this, "object" ), options );
stripProperties( result );
this.assert(
utils.eql( result, expected ),
"expected #{this} to parse as #{exp} but got #{act}",
"expected #{this} to not parse as #{exp}",
expected,
result,
! utils.flag( this, "negate" ),
);
} );
Assertion.addMethod( "failToParse", function ( props ) {
let passed, result;
try {
result = parser.parse( utils.flag( this, "object" ) );
passed = true;
} catch ( e ) {
result = e;
passed = false;
}
if ( passed ) {
stripProperties( result );
}
this.assert(
! passed,
"expected #{this} to fail to parse but got #{act}",
"expected #{this} to not fail to parse but it failed with #{act}",
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/behavior/generated-parser-behavior.spec.js.
beforeEach( function () {
chai.use( helpers );
} );
// Grammars without any rules are not accepted.
it( "parses Rule+", function () {
expect( "start = a" ).to.parseAs( ruleRefGrammar( "a" ) );
const grammar = ruleRefGrammar( "a" );
grammar.initializer = {
"type": "initializer",
"code": "",
};
expect( "{}\nstart = a" ).to.parseAs( grammar );
expect( "" ).to.failToParse();
expect( "{}" ).to.failToParse();
} );
// Canonical Grammar is "a = 'abcd'; b = 'efgh'; c = 'ijkl';".
it( "parses Grammar", function () {
expect( "\na = 'abcd';\n" ).to.parseAs(
{ type: "grammar", comments: null, initializer: null, rules: [ ruleA ] },
);
expect( "\na = 'abcd';\nb = 'efgh';\nc = 'ijkl';\n" ).to.parseAs(
{ type: "grammar", comments: null, initializer: null, rules: [ ruleA, ruleB, ruleC ] },
);
expect( "\n{ code };\na = 'abcd';\n" ).to.parseAs(
{ type: "grammar", comments: null, initializer: initializer, rules: [ ruleA ] },
);
} );
// Canonical Initializer is "{ code }".
it( "parses Initializer", function () {
expect( "{ code };start = 'abcd'" ).to.parseAs(
{ type: "grammar", comments: null, initializer: initializer, rules: [ ruleStart ] },
);
} );
// Canonical Rule is "a = 'abcd';".
it( "parses Rule", function () {
expect( "start\n=\n'abcd';" ).to.parseAs(
oneRuleGrammar( literalAbcd ),
);
expect( "start\n'start rule'\n=\n'abcd';" ).to.parseAs(
oneRuleGrammar( named ),
);
} );
// Canonical Expression is "'abcd'".
it( "parses Expression", function () {
expect( "start = 'abcd' / 'efgh' / 'ijkl'" ).to.parseAs(
oneRuleGrammar( choice ),
);
} );
// Canonical ChoiceExpression is "'abcd' / 'efgh' / 'ijkl'".
it( "parses ChoiceExpression", function () {
expect( "start = 'abcd' { code }" ).to.parseAs(
oneRuleGrammar( actionAbcd ),
);
expect( "start = 'abcd' { code }\n/\n'efgh' { code }" ).to.parseAs(
oneRuleGrammar( choice2 ),
);
expect(
"start = 'abcd' { code }\n/\n'efgh' { code }\n/\n'ijkl' { code }\n/\n'mnop' { code }",
).to.parseAs(
oneRuleGrammar( choice4 ),
);
} );
// Canonical ActionExpression is "'abcd' { code }".
it( "parses ActionExpression", function () {
expect( "start = 'abcd' 'efgh' 'ijkl'" ).to.parseAs(
oneRuleGrammar( sequence ),
);
expect( "start = 'abcd' 'efgh' 'ijkl'\n{ code }" ).to.parseAs(
oneRuleGrammar( actionSequence ),
);
} );
// Canonical SequenceExpression is "'abcd' 'efgh' 'ijkl'".
it( "parses SequenceExpression", function () {
expect( "start = a:'abcd'" ).to.parseAs(
oneRuleGrammar( labeledAbcd ),
);
expect( "start = a:'abcd'\nb:'efgh'" ).to.parseAs(
oneRuleGrammar( sequence2 ),
);
expect( "start = a:'abcd'\nb:'efgh'\nc:'ijkl'\nd:'mnop'" ).to.parseAs(
oneRuleGrammar( sequence4 ),
);
} );
// Value Plucking
it( "parses `@` (value plucking)", function () {
function $S( ...elements ) {
return oneRuleGrammar( {
type: "sequence",
elements: elements,
} );
}
function $P( label, expression ) {
return {
type: "labeled",
pick: true,
label: label,
expression: expression,
};
}
expect( "start = @'abcd'" ).to.parseAs(
$S( $P( null, literalAbcd ) ),
);
expect( "start = @a:'abcd'" ).to.parseAs(
$S( $P( "a", literalAbcd ) ),
);
expect( "start = 'abcd' @'efgh'" ).to.parseAs(
$S( literalAbcd, $P( null, literalEfgh ) ),
);
expect( "start = a:'abcd' @b:'efgh'" ).to.parseAs(
$S( labeledAbcd, $P( "b", literalEfgh ) ),
);
expect( "start = @'abcd' b:'efgh'" ).to.parseAs(
$S( $P( null, literalAbcd ), labeledEfgh ),
);
expect( "start = a:'abcd' @'efgh' 'ijkl' @d:'mnop'" ).to.parseAs(
$S( labeledAbcd, $P( null, literalEfgh ), literalIjkl, $P( "d", literalMnop ) ),
);
} );
// Canonical LabeledExpression is "a:'abcd'".
it( "parses LabeledExpression", function () {
expect( "start = a\n:\n!'abcd'" ).to.parseAs( oneRuleGrammar( labeledSimpleNot ) );
expect( "start = !'abcd'" ).to.parseAs( oneRuleGrammar( simpleNotAbcd ) );
} );
// Canonical PrefixedExpression is "!'abcd'".
it( "parses PrefixedExpression", function () {
expect( "start = !\n'abcd'?" ).to.parseAs( oneRuleGrammar( simpleNotOptional ) );
expect( "start = 'abcd'?" ).to.parseAs( oneRuleGrammar( optional ) );
} );
// Canonical PrefixedOperator is "!".
it( "parses PrefixedOperator", function () {
expect( "start = $'abcd'?" ).to.parseAs( oneRuleGrammar( textOptional ) );
expect( "start = &'abcd'?" ).to.parseAs( oneRuleGrammar( simpleAndOptional ) );
expect( "start = !'abcd'?" ).to.parseAs( oneRuleGrammar( simpleNotOptional ) );
} );
// Canonical SuffixedExpression is "'abcd'?".
it( "parses SuffixedExpression", function () {
expect( "start = 'abcd'\n?" ).to.parseAs( oneRuleGrammar( optional ) );
expect( "start = 'abcd'" ).to.parseAs( oneRuleGrammar( literalAbcd ) );
} );
// Canonical SuffixedOperator is "?".
it( "parses SuffixedOperator", function () {
expect( "start = 'abcd'?" ).to.parseAs( oneRuleGrammar( optional ) );
expect( "start = 'abcd'*" ).to.parseAs( oneRuleGrammar( zeroOrMore ) );
expect( "start = 'abcd'+" ).to.parseAs( oneRuleGrammar( oneOrMore ) );
} );
// Canonical PrimaryExpression is "'abcd'".
it( "parses PrimaryExpression", function () {
expect( "start = 'abcd'" ).to.parseAs( trivialGrammar );
expect( "start = [a-d]" ).to.parseAs( classGrammar( [ [ "a", "d" ] ], false, false ) );
expect( "start = ." ).to.parseAs( anyGrammar() );
expect( "start = a" ).to.parseAs( ruleRefGrammar( "a" ) );
expect( "start = &{ code }" ).to.parseAs( oneRuleGrammar( semanticAnd ) );
expect( "start = (\na:'abcd'\n)" ).to.parseAs( oneRuleGrammar( groupLabeled ) );
expect( "start = (\n'abcd' 'efgh' 'ijkl'\n)" ).to.parseAs( oneRuleGrammar( groupSequence ) );
expect( "start = (\n'abcd'\n)" ).to.parseAs( trivialGrammar );
} );
// Canonical RuleReferenceExpression is "a".
it( "parses RuleReferenceExpression", function () {
expect( "start = a" ).to.parseAs( ruleRefGrammar( "a" ) );
expect( "start = a\n=" ).to.failToParse();
expect( "start = a\n'abcd'\n=" ).to.failToParse();
} );
// Canonical SemanticPredicateExpression is "!{ code }".
it( "parses SemanticPredicateExpression", function () {
expect( "start = !\n{ code }" ).to.parseAs( oneRuleGrammar( semanticNot ) );
} );
// Canonical SemanticPredicateOperator is "!".
it( "parses SemanticPredicateOperator", function () {
expect( "start = &{ code }" ).to.parseAs( oneRuleGrammar( semanticAnd ) );
expect( "start = !{ code }" ).to.parseAs( oneRuleGrammar( semanticNot ) );
} );
// The SourceCharacter rule is not tested.
// Canonical WhiteSpace is " ".
it( "parses WhiteSpace", function () {
expect( "start =\t'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\v'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\f'abcd'" ).to.parseAs( trivialGrammar );
expect( "start = 'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\u00A0'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\uFEFF'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\u1680'abcd'" ).to.parseAs( trivialGrammar );
} );
// Canonical LineTerminator is "\n".
it( "parses LineTerminator", function () {
expect( "start = '\n'" ).to.failToParse();
expect( "start = '\r'" ).to.failToParse();
expect( "start = '\u2028'" ).to.failToParse();
expect( "start = '\u2029'" ).to.failToParse();
} );
// Canonical LineTerminatorSequence is "\r\n".
it( "parses LineTerminatorSequence", function () {
expect( "start =\n'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\r\n'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\r'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\u2028'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\u2029'abcd'" ).to.parseAs( trivialGrammar );
} );
varyParserOptions( function ( options ) {
// Canonical Comment is "/* comment */".
it( "parses Comment", function () {
expect( "start =// comment\n'abcd'" ).to.parseAs( commented( trivialGrammar, [ { offset: 7, text: " comment", multiline: false } ], options ), options );
expect( "start =/* comment */'abcd'" ).to.parseAs( commented( trivialGrammar, [ { offset: 7, text: " comment ", multiline: true } ], options ), options );
} );
// Canonical MultiLineComment is "/* comment */".
it( "parses MultiLineComment", function () {
expect( "start =/**/'abcd'" ).to.parseAs( commented( trivialGrammar, [ { offset: 7, text: "", multiline: true } ], options ), options );
expect( "start =/*a*/'abcd'" ).to.parseAs( commented( trivialGrammar, [ { offset: 7, text: "a", multiline: true } ], options ), options );
expect( "start =/*abc*/'abcd'" ).to.parseAs( commented( trivialGrammar, [ { offset: 7, text: "abc", multiline: true } ], options ), options );
expect( "start =/**/*/'abcd'" ).to.failToParse();
} );
// Canonical MultiLineCommentNoLineTerminator is "/* comment */".
it( "parses MultiLineCommentNoLineTerminator", function () {
expect( "a = 'abcd'/**/\r\nb = 'efgh'" ).to.parseAs( commented( twoRuleGrammar, [ { offset: 10, text: "", multiline: true } ], options ), options );
expect( "a = 'abcd'/*a*/\r\nb = 'efgh'" ).to.parseAs( commented( twoRuleGrammar, [ { offset: 10, text: "a", multiline: true } ], options ), options );
expect( "a = 'abcd'/*abc*/\r\nb = 'efgh'" ).to.parseAs( commented( twoRuleGrammar, [ { offset: 10, text: "abc", multiline: true } ], options ), options );
expect( "a = 'abcd'/**/*/\r\nb = 'efgh'" ).to.failToParse();
expect( "a = 'abcd'/*\n*/\r\nb = 'efgh'" ).to.failToParse();
} );
// Canonical SingleLineComment is "// comment".
it( "parses SingleLineComment", function () {
expect( "start =//\n'abcd'" ).to.parseAs( commented( trivialGrammar, [ { offset: 7, text: "", multiline: false } ], options ), options );
expect( "start =//a\n'abcd'" ).to.parseAs( commented( trivialGrammar, [ { offset: 7, text: "a", multiline: false } ], options ), options );
expect( "start =//abc\n'abcd'" ).to.parseAs( commented( trivialGrammar, [ { offset: 7, text: "abc", multiline: false } ], options ), options );
expect( "start =//\n>\n'abcd'" ).to.failToParse();
} );
} );
// Canonical Identifier is "a".
it( "parses Identifier", function () {
expect( "start = a:'abcd'" ).to.parseAs( oneRuleGrammar( labeledAbcd ) );
} );
// Canonical IdentifierName is "a".
it( "parses IdentifierName", function () {
expect( "start = a" ).to.parseAs( ruleRefGrammar( "a" ) );
expect( "start = ab" ).to.parseAs( ruleRefGrammar( "ab" ) );
expect( "start = abcd" ).to.parseAs( ruleRefGrammar( "abcd" ) );
} );
// Canonical IdentifierStart is "a".
it( "parses IdentifierStart", function () {
expect( "start = a" ).to.parseAs( ruleRefGrammar( "a" ) );
expect( "start = $" ).to.parseAs( ruleRefGrammar( "$" ) );
expect( "start = _" ).to.parseAs( ruleRefGrammar( "_" ) );
expect( "start = \\u0061" ).to.parseAs( ruleRefGrammar( "a" ) );
} );
// Canonical IdentifierPart is "a".
it( "parses IdentifierPart", function () {
expect( "start = aa" ).to.parseAs( ruleRefGrammar( "aa" ) );
expect( "start = a\u0300" ).to.parseAs( ruleRefGrammar( "a\u0300" ) );
expect( "start = a0" ).to.parseAs( ruleRefGrammar( "a0" ) );
expect( "start = a\u203F" ).to.parseAs( ruleRefGrammar( "a\u203F" ) );
expect( "start = a\u200C" ).to.parseAs( ruleRefGrammar( "a\u200C" ) );
expect( "start = a\u200D" ).to.parseAs( ruleRefGrammar( "a\u200D" ) );
} );
// Unicode rules and reserved word rules are not tested.
// Canonical LiteralMatcher is "'abcd'".
it( "parses LiteralMatcher", function () {
expect( "start = 'abcd'" ).to.parseAs( literalGrammar( "abcd", false ) );
expect( "start = 'abcd'i" ).to.parseAs( literalGrammar( "abcd", true ) );
} );
// Canonical StringLiteral is "'abcd'".
it( "parses StringLiteral", function () {
expect( "start = \"\"" ).to.parseAs( literalGrammar( "", false ) );
expect( "start = \"a\"" ).to.parseAs( literalGrammar( "a", false ) );
expect( "start = \"abc\"" ).to.parseAs( literalGrammar( "abc", false ) );
expect( "start = ''" ).to.parseAs( literalGrammar( "", false ) );
expect( "start = 'a'" ).to.parseAs( literalGrammar( "a", false ) );
expect( "start = 'abc'" ).to.parseAs( literalGrammar( "abc", false ) );
} );
// Canonical DoubleStringCharacter is "a".
it( "parses DoubleStringCharacter", function () {
expect( "start = \"a\"" ).to.parseAs( literalGrammar( "a", false ) );
expect( "start = \"\\n\"" ).to.parseAs( literalGrammar( "\n", false ) );
expect( "start = \"\\\n\"" ).to.parseAs( literalGrammar( "", false ) );
expect( "start = \"\"\"" ).to.failToParse();
expect( "start = \"\\\"" ).to.failToParse();
expect( "start = \"\n\"" ).to.failToParse();
} );
// Canonical SingleStringCharacter is "a".
it( "parses SingleStringCharacter", function () {
expect( "start = 'a'" ).to.parseAs( literalGrammar( "a", false ) );
expect( "start = '\\n'" ).to.parseAs( literalGrammar( "\n", false ) );
expect( "start = '\\\n'" ).to.parseAs( literalGrammar( "", false ) );
expect( "start = '''" ).to.failToParse();
expect( "start = '\\'" ).to.failToParse();
expect( "start = '\n'" ).to.failToParse();
} );
// Canonical CharacterClassMatcher is "[a-d]".
it( "parses CharacterClassMatcher", function () {
expect( "start = []" ).to.parseAs(
classGrammar( [], false, false ),
);
expect( "start = [a-d]" ).to.parseAs(
classGrammar( [ [ "a", "d" ] ], false, false ),
);
expect( "start = [a]" ).to.parseAs(
classGrammar( [ "a" ], false, false ),
);
expect( "start = [a-de-hi-l]" ).to.parseAs(
classGrammar(
[ [ "a", "d" ], [ "e", "h" ], [ "i", "l" ] ],
false,
false,
),
);
expect( "start = [^a-d]" ).to.parseAs(
classGrammar( [ [ "a", "d" ] ], true, false ),
);
expect( "start = [a-d]i" ).to.parseAs(
classGrammar( [ [ "a", "d" ] ], false, true ),
);
expect( "start = [\\\n]" ).to.parseAs(
classGrammar( [], false, false ),
);
} );
// Canonical ClassCharacterRange is "a-d".
it( "parses ClassCharacterRange", function () {
expect( "start = [a-d]" ).to.parseAs( classGrammar( [ [ "a", "d" ] ], false, false ) );
expect( "start = [a-a]" ).to.parseAs( classGrammar( [ [ "a", "a" ] ], false, false ) );
expect( "start = [b-a]" ).to.failToParse( {
message: "Invalid character range: b-a.",
} );
} );
// Canonical ClassCharacter is "a".
it( "parses ClassCharacter", function () {
expect( "start = [a]" ).to.parseAs( classGrammar( [ "a" ], false, false ) );
expect( "start = [\\n]" ).to.parseAs( classGrammar( [ "\n" ], false, false ) );
expect( "start = [\\\n]" ).to.parseAs( classGrammar( [], false, false ) );
expect( "start = []]" ).to.failToParse();
expect( "start = [\\]" ).to.failToParse();
expect( "start = [\n]" ).to.failToParse();
} );
// Canonical LineContinuation is "\\\n".
it( "parses LineContinuation", function () {
expect( "start = '\\\r\n'" ).to.parseAs( literalGrammar( "", false ) );
} );
// Canonical EscapeSequence is "n".
it( "parses EscapeSequence", function () {
expect( "start = '\\n'" ).to.parseAs( literalGrammar( "\n", false ) );
expect( "start = '\\0'" ).to.parseAs( literalGrammar( "\x00", false ) );
expect( "start = '\\xFF'" ).to.parseAs( literalGrammar( "\xFF", false ) );
expect( "start = '\\uFFFF'" ).to.parseAs( literalGrammar( "\uFFFF", false ) );
expect( "start = '\\09'" ).to.failToParse();
} );
// Canonical CharacterEscapeSequence is "n".
it( "parses CharacterEscapeSequence", function () {
expect( "start = '\\n'" ).to.parseAs( literalGrammar( "\n", false ) );
expect( "start = '\\a'" ).to.parseAs( literalGrammar( "a", false ) );
} );
// Canonical SingleEscapeCharacter is "n".
it( "parses SingleEscapeCharacter", function () {
expect( "start = '\\''" ).to.parseAs( literalGrammar( "'", false ) );
expect( "start = '\\\"'" ).to.parseAs( literalGrammar( "\"", false ) );
expect( "start = '\\\\'" ).to.parseAs( literalGrammar( "\\", false ) );
expect( "start = '\\b'" ).to.parseAs( literalGrammar( "\b", false ) );
expect( "start = '\\f'" ).to.parseAs( literalGrammar( "\f", false ) );
expect( "start = '\\n'" ).to.parseAs( literalGrammar( "\n", false ) );
expect( "start = '\\r'" ).to.parseAs( literalGrammar( "\r", false ) );
expect( "start = '\\t'" ).to.parseAs( literalGrammar( "\t", false ) );
expect( "start = '\\v'" ).to.parseAs( literalGrammar( "\v", false ) );
} );
// Canonical NonEscapeCharacter is "a".
it( "parses NonEscapeCharacter", function () {
expect( "start = '\\a'" ).to.parseAs( literalGrammar( "a", false ) );
} );
// The negative predicate is impossible to test with PEG.js grammar structure.
// The EscapeCharacter rule is impossible to test with PEG.js grammar structure.
// Canonical HexEscapeSequence is "xFF".
it( "parses HexEscapeSequence", function () {
expect( "start = '\\xFF'" ).to.parseAs( literalGrammar( "\xFF", false ) );
} );
// Canonical UnicodeEscapeSequence is "uFFFF".
it( "parses UnicodeEscapeSequence", function () {
expect( "start = '\\uFFFF'" ).to.parseAs( literalGrammar( "\uFFFF", false ) );
} );
// Digit rules are not tested.
// Canonical AnyMatcher is ".".
it( "parses AnyMatcher", function () {
expect( "start = ." ).to.parseAs( anyGrammar() );
} );
// Canonical CodeBlock is "{ code }".
it( "parses CodeBlock", function () {
expect( "start = 'abcd' { code }" ).to.parseAs( actionGrammar( " code " ) );
} );
// Canonical Code is " code ".
it( "parses Code", function () {
expect( "start = 'abcd' {a}" ).to.parseAs( actionGrammar( "a" ) );
expect( "start = 'abcd' {abc}" ).to.parseAs( actionGrammar( "abc" ) );
expect( "start = 'abcd' {{a}}" ).to.parseAs( actionGrammar( "{a}" ) );
expect( "start = 'abcd' {{a}{b}{c}}" ).to.parseAs( actionGrammar( "{a}{b}{c}" ) );
expect( "start = 'abcd' {{}" ).to.failToParse();
expect( "start = 'abcd' {}}" ).to.failToParse();
} );
// Unicode character category rules and token rules are not tested.
// Canonical __ is "\n".
it( "parses __", function () {
expect( "start ='abcd'" ).to.parseAs( trivialGrammar );
expect( "start = 'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\r\n'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =/* comment */'abcd'" ).to.parseAs( trivialGrammar );
expect( "start = 'abcd'" ).to.parseAs( trivialGrammar );
} );
// Canonical _ is " ".
it( "parses _", function () {
expect( "a = 'abcd'\r\nb = 'efgh'" ).to.parseAs( twoRuleGrammar );
expect( "a = 'abcd' \r\nb = 'efgh'" ).to.parseAs( twoRuleGrammar );
expect( "a = 'abcd'/* comment */\r\nb = 'efgh'" ).to.parseAs( twoRuleGrammar );
expect( "a = 'abcd' \r\nb = 'efgh'" ).to.parseAs( twoRuleGrammar );
} );
// Canonical EOS is ";".
it( "parses EOS", function () {
expect( "a = 'abcd'\n;b = 'efgh'" ).to.parseAs( twoRuleGrammar );
expect( "a = 'abcd' \r\nb = 'efgh'" ).to.parseAs( twoRuleGrammar );
expect( "a = 'abcd' // comment\r\nb = 'efgh'" ).to.parseAs( twoRuleGrammar );
expect( "a = 'abcd'\nb = 'efgh'" ).to.parseAs( twoRuleGrammar );
} );
// Canonical EOF is the end of input.
it( "parses EOF", function () {
expect( "start = 'abcd'\n" ).to.parseAs( trivialGrammar );
} );
it( "reports unmatched brace", function () {
const text = "rule = \n 'x' { y \n z";
const errorLocation = {
start: { offset: 13, line: 2, column: 6 },
end: { offset: 14, line: 2, column: 7 },
};
expect( () => parser.parse( text ) )
.to.throw( "Unbalanced brace." )
.with.property( "location" )
.that.deep.equals( errorLocation );
} );
} );