grammar Plan;

expr:
  Identifier (op1=(ADD | SUB) INTERVAL interval_string=StringLiteral)? op2=(LT | LE | GT | GE | EQ | NE) ISO compare_string=StringLiteral # TimestamptzCompareForward
	| ISO compare_string=StringLiteral op2=(LT | LE | GT | GE | EQ | NE) Identifier (op1=(ADD | SUB) INTERVAL interval_string=StringLiteral)? # TimestamptzCompareReverse
	| IntegerConstant											                                            # Integer
	| FloatingConstant										                                                # Floating
	| BooleanConstant										                                                # Boolean
	| StringLiteral											                                                # String
	| RawStringLiteral										                                                # RawString
	| (Identifier|Meta)           			      							                                # Identifier
	| JSONIdentifier                                                                                        # JSONIdentifier
	| StructFieldIdentifier                                                                                 # StructField
	| StructIndexFieldIdentifier                                                                            # StructIndexField
	| StructSubFieldIdentifier                                                                              # StructSubField
	| LBRACE Identifier RBRACE                                                                              # TemplateVariable
	| '(' expr ')'											                                                # Parens
	| '[' expr (',' expr)* ','? ']'                                                                         # Array
	| EmptyArray                                                                                            # EmptyArray
	| EXISTS expr                                                                                           # Exists
	| expr LIKE expr                                                                                        # Like
	| expr REGEXMATCH expr                                                                                  # RegexMatch
	| expr REGEXNOTMATCH expr                                                                               # RegexNotMatch
	| TEXTMATCH'('Identifier',' expr (',' textMatchOption)? ')'                                             # TextMatch
	| TEXTMATCHFUZZY'('Identifier',' expr ',' Identifier ASSIGN IntegerConstant ')'                         # TextMatchFuzzy
	| PHRASEMATCH'('Identifier',' expr (',' expr)? ')'       			                                    # PhraseMatch
	| RANDOMSAMPLE'(' expr ')'						     						                            # RandomSample
	| ElementFilter'('Identifier',' expr')'                                	                                # ElementFilter
	| op=(MATCH_ALL | MATCH_ANY) '(' Identifier ',' expr ')'                                                 # MatchSimple
	| op=(MATCH_LEAST | MATCH_MOST | MATCH_EXACT) '(' Identifier ',' expr ',' THRESHOLD ASSIGN IntegerConstant ')'  # MatchThreshold
	| expr POW expr											                                                # Power
	| op = (ADD | SUB | BNOT | NOT) expr					                                                # Unary
//	| '(' typeName ')' expr									                                                # Cast
	| expr op = (MUL | DIV | MOD) expr						                                                # MulDivMod
	| expr op = (ADD | SUB) expr							                                                # AddSub
	| expr op = (SHL | SHR) expr							                                                # Shift
	| expr op = NOT? IN expr                                                                                # Term
	| (JSONContains | ArrayContains)'('expr',' expr')'                                                      # JSONContains
	| (JSONContainsAll | ArrayContainsAll)'('expr',' expr')'                                                # JSONContainsAll
	| (JSONContainsAny | ArrayContainsAny)'('expr',' expr')'                                                # JSONContainsAny
	| op=(STEuqals | STTouches | STOverlaps | STCrosses | STContains | STIntersects | STWithin) '(' Identifier ',' expr ')'  # SpatialBinary
	| STDWithin'('Identifier',' expr',' expr')'                                                             # STDWithin
	| STIsValid'('Identifier')'                                  			 	                            # STIsValid
	| ArrayLength'('(Identifier | JSONIdentifier | StructFieldIdentifier)')'                                 # ArrayLength
	| Identifier '(' ( expr (',' expr )* ','? )? ')'                                                        # Call
	| expr op1 = (LT | LE) (Identifier | JSONIdentifier | StructSubFieldIdentifier | StructIndexFieldIdentifier) op2 = (LT | LE) expr	# Range
	| expr op1 = (GT | GE) (Identifier | JSONIdentifier | StructSubFieldIdentifier | StructIndexFieldIdentifier) op2 = (GT | GE) expr    # ReverseRange
	| expr op = (LT | LE | GT | GE) expr					                                                # Relational
	| expr op = (EQ | NE) expr								                                                # Equality
	| expr BAND expr										                                                # BitAnd
	| expr BXOR expr										                                                # BitXor
	| expr BOR expr											                                                # BitOr
	| expr AND expr											                                                # LogicalAnd
	| expr OR expr											                                                # LogicalOr
	| (Identifier | JSONIdentifier) ISNULL                                                                  # IsNull
	| (Identifier | JSONIdentifier) ISNOTNULL                                                               # IsNotNull;

textMatchOption:
	MINIMUM_SHOULD_MATCH ASSIGN IntegerConstant;

LBRACE: '{';
RBRACE: '}';

LT: '<';
LE: '<=';
GT: '>';
GE: '>=';
EQ: '==';
NE: '!=';

LIKE: 'like' | 'LIKE';
EXISTS: 'exists' | 'EXISTS';
TEXTMATCH: 'text_match'|'TEXT_MATCH';
TEXTMATCHFUZZY: 'text_match_fuzzy'|'TEXT_MATCH_FUZZY';
PHRASEMATCH: 'phrase_match'|'PHRASE_MATCH';
RANDOMSAMPLE: 'random_sample' | 'RANDOM_SAMPLE';
MATCH_ALL: 'match_all' | 'MATCH_ALL';
MATCH_ANY: 'match_any' | 'MATCH_ANY';
MATCH_LEAST: 'match_least' | 'MATCH_LEAST';
MATCH_MOST: 'match_most' | 'MATCH_MOST';
MATCH_EXACT: 'match_exact' | 'MATCH_EXACT';
INTERVAL: 'interval' | 'INTERVAL';
ISO: 'iso' | 'ISO';
MINIMUM_SHOULD_MATCH: 'minimum_should_match' | 'MINIMUM_SHOULD_MATCH';
THRESHOLD: 'threshold' | 'THRESHOLD';
REGEXMATCH: '=~';
REGEXNOTMATCH: '!~';
ASSIGN: '=';

ADD: '+';
SUB: '-';
MUL: '*';
DIV: '/';
MOD: '%';
POW: '**';
SHL: '<<';
SHR: '>>';
BAND: '&';
BOR: '|';
BXOR: '^';

AND: '&&' | 'and' | 'AND';
OR: '||' | 'or' | 'OR';

ISNULL: 'is null' | 'IS NULL';
ISNOTNULL: 'is not null' | 'IS NOT NULL';

BNOT: '~';
NOT: '!' | 'not' | 'NOT';

IN: 'in' | 'IN';
EmptyArray: '[' (Whitespace | Newline)* ']';

JSONContains: 'json_contains' | 'JSON_CONTAINS';
JSONContainsAll: 'json_contains_all' | 'JSON_CONTAINS_ALL';
JSONContainsAny: 'json_contains_any' | 'JSON_CONTAINS_ANY';

ArrayContains: 'array_contains' | 'ARRAY_CONTAINS';
ArrayContainsAll: 'array_contains_all' | 'ARRAY_CONTAINS_ALL';
ArrayContainsAny: 'array_contains_any' | 'ARRAY_CONTAINS_ANY';
ArrayLength: 'array_length' | 'ARRAY_LENGTH';
ElementFilter: 'element_filter' | 'ELEMENT_FILTER';

STEuqals:'st_equals' | 'ST_EQUALS';
STTouches:'st_touches' | 'ST_TOUCHES';
STOverlaps: 'st_overlaps' | 'ST_OVERLAPS';
STCrosses: 'st_crosses' | 'ST_CROSSES';
STContains: 'st_contains' | 'ST_CONTAINS';
STIntersects : 'st_intersects' | 'ST_INTERSECTS';
STWithin :'st_within' | 'ST_WITHIN';
STDWithin: 'st_dwithin' | 'ST_DWITHIN';
STIsValid: 'st_isvalid' | 'ST_ISVALID';

BooleanConstant: 'true' | 'True' | 'TRUE' | 'false' | 'False' | 'FALSE';

IntegerConstant:
	DecimalConstant
	| OctalConstant
	| HexadecimalConstant
	| BinaryConstant;

FloatingConstant:
	DecimalFloatingConstant
	| HexadecimalFloatingConstant;

Identifier: Nondigit (Nondigit | Digit)*;
Meta: '$meta';

StringLiteral: EncodingPrefix? ('"' DoubleSCharSequence? '"' | '\'' SingleSCharSequence? '\'');
RawStringLiteral: [rR] ('"' DoubleRChar* '"' | '\'' SingleRChar* '\'');
JSONIdentifier: (Identifier | Meta)('[' (StringLiteral | RawStringLiteral | DecimalConstant) ']')+;
StructIndexFieldIdentifier: Identifier '[' DecimalConstant ']' '[' Identifier ']';
StructFieldIdentifier: Identifier '[' Identifier ']';
StructSubFieldIdentifier: '$[' Identifier ']';

fragment EncodingPrefix: 'u8' | 'u' | 'U' | 'L';

fragment DoubleSCharSequence: DoubleSChar+;
fragment SingleSCharSequence: SingleSChar+;

fragment DoubleSChar: ~["\\\r\n] | EscapeSequence | '\\\n' | '\\\r\n';
fragment SingleSChar: ~['\\\r\n] | EscapeSequence | '\\\n' | '\\\r\n';
// Raw string chars: a backslash is kept verbatim (no unescaping). A backslash
// before the delimiter only prevents termination; both bytes stay in the token.
fragment DoubleRChar: ~["\\\r\n] | '\\' ~[\r\n];
fragment SingleRChar: ~['\\\r\n] | '\\' ~[\r\n];
fragment Nondigit: [a-zA-Z_];
fragment Digit: [0-9];
fragment BinaryConstant: '0' [bB] [0-1]+;
fragment DecimalConstant: NonzeroDigit Digit* | '0';
fragment OctalConstant: '0' OctalDigit*;
fragment HexadecimalConstant: '0' [xX] HexadecimalDigitSequence;
fragment NonzeroDigit: [1-9];
fragment OctalDigit: [0-7];
fragment HexadecimalDigit: [0-9a-fA-F];
fragment HexQuad:
	HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit;
fragment UniversalCharacterName:
	'\\u' HexQuad
	| '\\U' HexQuad HexQuad;
fragment DecimalFloatingConstant:
	FractionalConstant ExponentPart?
	| DigitSequence ExponentPart;
fragment HexadecimalFloatingConstant:
	'0' [xX] (
		HexadecimalFractionalConstant
		| HexadecimalDigitSequence
	) BinaryExponentPart;
fragment FractionalConstant:
	DigitSequence? '.' DigitSequence
	| DigitSequence '.';
fragment ExponentPart: [eE] [+-]? DigitSequence;
fragment DigitSequence: Digit+;
fragment HexadecimalFractionalConstant:
	HexadecimalDigitSequence? '.' HexadecimalDigitSequence
	| HexadecimalDigitSequence '.';
fragment HexadecimalDigitSequence: HexadecimalDigit+;
fragment BinaryExponentPart: [pP] [+-]? DigitSequence;
fragment EscapeSequence:
	'\\' ['"?abfnrtv\\]
	| '\\' OctalDigit OctalDigit? OctalDigit?
	| '\\x' HexadecimalDigitSequence
	| UniversalCharacterName
	| '\\' ~[\r\n];

Whitespace: [ \t]+ -> skip;

Newline: ( '\r' '\n'? | '\n') -> skip;
