Null, boolean, and numeric literals
This commit is contained in:
parent
f6ee58a4ce
commit
32964d8556
|
@ -4,33 +4,14 @@
|
|||
import * as peggy from 'peggy';
|
||||
|
||||
const grammar = `
|
||||
Start = LogicalOr
|
||||
Start
|
||||
= LogicalOr
|
||||
|
||||
LogicalOr
|
||||
= left:LogicalAnd WhiteSpace+ "||" WhiteSpace+ right:LogicalOr {
|
||||
return left || right;
|
||||
}
|
||||
/ LogicalAnd
|
||||
WhiteSpace
|
||||
= " "
|
||||
/ "\t"
|
||||
|
||||
LogicalAnd
|
||||
= left:Primary WhiteSpace+ "&&" WhiteSpace+ right:LogicalAnd {
|
||||
return left && right;
|
||||
}
|
||||
/ Factor
|
||||
|
||||
Factor
|
||||
= "!" WhiteSpace* operand:Factor {
|
||||
return !operand;
|
||||
}
|
||||
/ Primary
|
||||
|
||||
Primary
|
||||
= Variable
|
||||
/ "(" logicalOr:LogicalOr ")" {
|
||||
return logicalOr;
|
||||
}
|
||||
|
||||
Variable
|
||||
IdentifierPath
|
||||
= variable:Identifier accessor:("." Identifier)* {
|
||||
const path = variable.split( '.' );
|
||||
let result = path.reduce( ( nextObject, propertyName ) => nextObject[ propertyName ], options.context );
|
||||
|
@ -43,11 +24,124 @@ Variable
|
|||
}
|
||||
|
||||
Identifier
|
||||
= identifier:$[a-zA-Z0-9_]+
|
||||
= !ReservedWord name:IdentifierName {
|
||||
return name;
|
||||
}
|
||||
|
||||
WhiteSpace
|
||||
= " "
|
||||
/ "\t"
|
||||
IdentifierName
|
||||
= first:IdentifierStart rest:IdentifierPart* {
|
||||
return first + rest.join( '' );
|
||||
}
|
||||
|
||||
IdentifierStart
|
||||
= [a-zA-Z]
|
||||
/ "_"
|
||||
/ "$"
|
||||
|
||||
IdentifierPart
|
||||
= IdentifierStart
|
||||
|
||||
ReservedWord
|
||||
= NullLiteral
|
||||
/ BooleanLiteral
|
||||
|
||||
// Literals
|
||||
|
||||
Literal
|
||||
= NullLiteral
|
||||
/ BooleanLiteral
|
||||
/ NumericLiteral
|
||||
|
||||
NullLiteral
|
||||
= NullToken { return null; }
|
||||
|
||||
BooleanLiteral
|
||||
= "true" { return true; }
|
||||
/ "false" { return false; }
|
||||
|
||||
NumericLiteral
|
||||
= literal:HexIntegerLiteral !(IdentifierStart / DecimalDigit) {
|
||||
return literal;
|
||||
}
|
||||
/ literal:DecimalLiteral !(IdentifierStart / DecimalDigit) {
|
||||
return literal;
|
||||
}
|
||||
|
||||
HexIntegerLiteral
|
||||
= "0x"i digits:$HexDigit+ {
|
||||
return parseInt( digits, 16 );
|
||||
}
|
||||
|
||||
HexDigit
|
||||
= [0-9a-f]i
|
||||
|
||||
DecimalLiteral
|
||||
= DecimalIntegerLiteral "." DecimalDigit* ExponentPart? {
|
||||
return parseFloat( text() );
|
||||
}
|
||||
/ "." DecimalDigit+ ExponentPart? {
|
||||
return parseFloat( text() );
|
||||
}
|
||||
/ DecimalIntegerLiteral ExponentPart? {
|
||||
return parseFloat( text() );
|
||||
}
|
||||
|
||||
DecimalIntegerLiteral
|
||||
= "0"
|
||||
/ NonZeroDigit DecimalDigit*
|
||||
|
||||
DecimalDigit
|
||||
= [0-9]
|
||||
|
||||
NonZeroDigit
|
||||
= [1-9]
|
||||
|
||||
ExponentPart
|
||||
= ExponentIndicator SignedInteger
|
||||
|
||||
ExponentIndicator
|
||||
= "e"i
|
||||
|
||||
SignedInteger
|
||||
= [+-]? DecimalDigit+
|
||||
|
||||
// Tokens
|
||||
|
||||
NullToken
|
||||
= "null" !IdentifierPart
|
||||
|
||||
TrueToken
|
||||
= "true" !IdentifierPart
|
||||
|
||||
FalseToken
|
||||
= "false" !IdentifierPart
|
||||
|
||||
// Logical Expressions
|
||||
|
||||
LogicalOr
|
||||
= left:LogicalAnd WhiteSpace+ "||" WhiteSpace+ right:LogicalOr {
|
||||
return left || right;
|
||||
}
|
||||
/ LogicalAnd
|
||||
|
||||
LogicalAnd
|
||||
= left:PrimaryExpression WhiteSpace+ "&&" WhiteSpace+ right:LogicalAnd {
|
||||
return left && right;
|
||||
}
|
||||
/ Factor
|
||||
|
||||
Factor
|
||||
= "!" WhiteSpace* operand:Factor {
|
||||
return !operand;
|
||||
}
|
||||
/ PrimaryExpression
|
||||
|
||||
PrimaryExpression
|
||||
= IdentifierPath
|
||||
/ Literal
|
||||
/ "(" WhiteSpace* expression:LogicalOr WhiteSpace* ")" {
|
||||
return expression;
|
||||
}
|
||||
`;
|
||||
|
||||
export const parser = peggy.generate( grammar );
|
||||
|
|
|
@ -5,6 +5,54 @@
|
|||
import { parser } from '../parser';
|
||||
|
||||
describe( 'parser', () => {
|
||||
it( 'should parse a null literal', () => {
|
||||
const result = parser.parse( 'null' );
|
||||
|
||||
expect( result ).toEqual( null );
|
||||
} );
|
||||
|
||||
it( 'should parse a boolean true literal', () => {
|
||||
const result = parser.parse( 'true' );
|
||||
|
||||
expect( result ).toEqual( true );
|
||||
} );
|
||||
|
||||
it( 'should parse a boolean false literal', () => {
|
||||
const result = parser.parse( 'false' );
|
||||
|
||||
expect( result ).toEqual( false );
|
||||
} );
|
||||
|
||||
it( 'should parse a numeric integer literal', () => {
|
||||
const result = parser.parse( '23' );
|
||||
|
||||
expect( result ).toEqual( 23 );
|
||||
} );
|
||||
|
||||
it( 'should parse a numeric floating point literal', () => {
|
||||
const result = parser.parse( '5.23' );
|
||||
|
||||
expect( result ).toEqual( 5.23 );
|
||||
} );
|
||||
|
||||
it( 'should parse a numeric hexadecimal literal', () => {
|
||||
const result = parser.parse( '0x23' );
|
||||
|
||||
expect( result ).toEqual( 35 );
|
||||
} );
|
||||
|
||||
it( 'should parse a string literal with double quotes', () => {
|
||||
const result = parser.parse( '"foo"' );
|
||||
|
||||
expect( result ).toEqual( 'foo' );
|
||||
} );
|
||||
|
||||
it( 'should parse a string literal with single quotes', () => {
|
||||
const result = parser.parse( "'foo'" );
|
||||
|
||||
expect( result ).toEqual( 'foo' );
|
||||
} );
|
||||
|
||||
it( 'should parse a top-level context property', () => {
|
||||
const result = parser.parse( 'foo', {
|
||||
context: {
|
||||
|
@ -70,6 +118,6 @@ describe( 'parser', () => {
|
|||
} );
|
||||
|
||||
it( 'should throw an error if the expression is invalid', () => {
|
||||
expect( () => parser.parse( '1' ) ).toThrow();
|
||||
expect( () => parser.parse( '= 1' ) ).toThrow();
|
||||
} );
|
||||
} );
|
||||
|
|
Loading…
Reference in New Issue