Null, boolean, and numeric literals

This commit is contained in:
Matt Sherman 2023-10-02 20:50:59 -04:00
parent f6ee58a4ce
commit 32964d8556
2 changed files with 172 additions and 30 deletions

View File

@ -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 );

View File

@ -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();
} );
} );