UNPKG

odatafy-parser

Version:

generate odatafy ast from odata query parameters

16 lines (15 loc) 19.3 kB
import peggy from 'peggy'; import { FilterNode } from '../types/nodes'; export declare const filterGrammar = "\n\n{\n function filterExprHelper(left, right){\n if (right) {\n return {\n nodeType: 'OperatorNode',\n op: right.op,\n left: left,\n right: right.value\n }\n } else {\n return left;\n }\n }\n\n function constantNodeHelper(type, value) {\n return {\n nodeType: \"ConstantNode\",\n type: type,\n value: value\n }\n }\n\n function removeEmpty(obj) {\n return Object.fromEntries(Object.entries(obj).filter(([_, v]) => v != null));\n}\n }\n\n\nboolCommonExpr = commonExpr\n\n\n//basic types\nUINT = $DIGIT+\n// INT covers value ranges of sbyte, byte, int16, int32, int64\nINT = value:$(SIGN? DIGIT+)\n\n/*\n* 4. Expressions\n*/\n\n//TODO ADD rootExpr/firstMemberExpr/functionExpr\n//ggf methodCall auf boolMethodCalls restricten\ncommonExpr = (\n andOrExpr\n / booleanValue\n )\n //{if(opAndRight) {return {left:left, op: opAndRight.op, right: opAndRight.right}} return left}\n\n part = arrayOrObject / negateExpr / methodCallExpr / value:$mathExpr {return {type: 'mathExpr', value: value}} / primitiveLiteral / qualifiedEnumTypeName / memberExpr/ odataIdentifier/ enum\n boolExpr = compareExpr / hasExpr / inExpr / methodCallExpr / notExpr\n notExpr = \"not\" RWS right:compareExpr {return {nodeType: \"OperatorNode\", op: \"not\", right: right}} / \"not\" RWS OPEN BWS right:commonExpr BWS CLOSE {return {nodeType: \"OperatorNode\", op: \"not\", right: right}}\n\n memberExpr = head:$odataIdentifier tail:( \"/\" @$odataIdentifier)+ {return {nodeType:\"SymbolNode\", type: \"MemberExpression\", value: [head, ...tail]}}\n\n methodCallExpr = noArgFunctionCallExpr \n / oneArgFunctionCallExpr\n / twoArgFunctionCallExpr \n / substringMethodCallExpr \n / caseMethodCallExpr\n / castExpr \n / isofExpr \n \n \n\ntwoArgFunctionCallExpr = func:twoArgFunc OPEN BWS arg1:part BWS COMMA BWS arg2:part BWS CLOSE {return {nodeType: 'FuncNode2Args', func:func, args:[arg1, arg2]}}\ntwoArgFunc = \"concat\" / \"contains\" / \"endswith\" / \"indexof\" / \"matchesPattern\" / \"startswith\" / \"geo.distance\" / \"geo.intersects\" / \"hassubset\" / \"hassubsequence\"\n\noneArgFunctionCallExpr = func:oneArgFunc OPEN BWS arg1:part BWS CLOSE {return {nodeType: 'FuncNode1Args', func:func, args:[arg1]}}\noneArgFunc = \"length\" / \"tolower\"/\"toupper\"/\"trim\"/\"year\"/\"month\"/\"day\"/\"hour\"/\"minute\"/\"second\"/\"fractionalseconds\"/\"totalseconds\"/\"date\"/\"time\"/\"totaloffsetminutes\"/\"round\"/\"floor\"/\"ceiling\"/\"geo.length\"\n\nnoArgFunctionCallExpr = func:noArgFunc OPEN BWS CLOSE {return {nodeType: 'FuncNode0Args', func:func}}\nnoArgFunc = \"mindatetime\" / \"maxdatetime\" / \"now\"\n\n//FuncNodeVarArgs\nsubstringMethodCallExpr = \"substring\" OPEN BWS part1:part BWS COMMA BWS part2:part BWS part3:( COMMA BWS @part BWS )? CLOSE {return{nodeType: \"FuncNodeVarArgs\", func: \"substring\", args:part3?[part1,part2,part3]:[part1,part2]}}\ncaseMethodCallExpr = \"case\" OPEN BWS head:(cond:boolCommonExpr BWS COLON BWS value:part BWS {return{cond:cond, value:value}})\n tail:( COMMA BWS cond:boolCommonExpr BWS COLON BWS value:part BWS {return{cond:cond, value:value}})* CLOSE {return{nodeType: \"FuncNodeCase\", args:[head, ...tail]}}\n \n \n \n \nparenExpr = OPEN BWS commonExpr:commonExpr BWS CLOSE {return commonExpr}\nlistExpr = OPEN BWS head:primitiveLiteral BWS tail:( COMMA BWS @primitiveLiteral BWS )* CLOSE {return [head, ...tail]}\n \n\n//unary minus and plus are not needed according to odata ABNF\nmathOp = \"add\" / \"sub\" / \"mul\" / \"divby\" / \"div\" / \"mod\"\nmathExpr = ((primitiveLiteral / odataIdentifier / methodCallExpr / OPEN BWS mathExpr2 BWS CLOSE ) RWS mathOp RWS mathExpr2) / OPEN BWS mathExpr2 BWS CLOSE\nmathExpr2 = (primitiveLiteral / odataIdentifier / methodCallExpr / OPEN BWS mathExpr2 BWS CLOSE ) (RWS mathOp RWS mathExpr2)?\n\n\nandOrExpr = OPEN BWS left:commonExpr BWS CLOSE right:(RWS op:(\"and\"/\"or\") RWS value:commonExpr BWS {return {op:op, value:value}})? {return filterExprHelper(left, right)} \n / BWS left:boolExpr right:(RWS op:(\"and\"/\"or\") RWS value:commonExpr BWS {return {op:op, value:value}})? {return filterExprHelper(left, right)} \n\ncompareExpr = left:part RWS op:compareOp RWS right:part {return {nodeType: \"OperatorNode\", op: op, left: left, right:right }}\ncompareOp = \"eq\" / \"ne\" / \"lt\" / \"le\" / \"gt\" / \"ge\"\n\n\ninExpr = left:part RWS \"in\" RWS right:( value:listExpr {return {type: \"list\", value:value}} / qualifiedEnumTypeName / odataIdentifier ) {return {nodeType: \"OperatorNode\", op: \"in\", left:left, right:right }}\nhasExpr = left:part RWS \"has\" RWS right:enum {return {nodeType: \"OperatorNode\", op: \"has\", left:left, right:right }}\n\nnegateExpr = \"-\" BWS right:commonExpr {return {op: \"-\", value:right}}\n\n//FuncNodeVarArgs\nisofExpr = \"isof\" OPEN BWS part:( @part BWS COMMA BWS )? typeName:optionallyQualifiedTypeName BWS CLOSE { return {nodeType: \"FuncNodeVarArgs\", func: \"isof\", args:part?[part,typeName]:[typeName] }}\ncastExpr = \"cast\" OPEN BWS part:( @part BWS COMMA BWS )? typeName:optionallyQualifiedTypeName BWS CLOSE { return {nodeType: \"FuncNodeVarArgs\", func: \"cast\", args:part?[part,typeName]:[typeName] }}\n \n/*\n* 5. JSON format for queries\n*/\n\narrayOrObject = value:$array {return constantNodeHelper(\"Array\",JSON.parse(value))} / value:$object {return constantNodeHelper(\"Object\",JSON.parse(value))}\n\n array = begin_array \n ( valueInUrl ( value_separator valueInUrl )* )?\n end_array\n\n object = begin_object\n ( member ( value_separator member )* )?\n end_object\n\n member = stringInUrl name_separator valueInUrl\n \n begin_object = BWS ( \"{\" / \"%7B\" ) BWS\n end_object = BWS ( \"}\" / \"%7D\" )\n \n begin_array = BWS ( \"[\" / \"%5B\" ) BWS \n end_array = BWS ( \"]\" / \"%5D\" )\n\n name_separator = BWS COLON BWS\n value_separator = BWS COMMA BWS\n quotation_mark = DQUOTE / \"%22\"\n valueInUrl = stringInUrl / arrayOrObject / primitiveLiteral \n stringInUrl = quotation_mark charInJSON* quotation_mark \n charInJSON = [^\"]\n \n\n\n/*\n* 6. Names and Identifiers\n*/\n\noptionallyQualifiedTypeName = singleQualifiedTypeName \n / 'Collection' OPEN value:$singleQualifiedTypeName CLOSE {return{ nodeType: \"SymbolNode\", type: \"Collection\", value: value}}\n / odataIdentifier\n / 'Collection' OPEN value:$odataIdentifier CLOSE {return{ nodeType: \"SymbolNode\", type: \"Collection\", value: value}}\n\nsingleQualifiedTypeName = value:$(odataIdentifier (\".\" odataIdentifier)+ / primitiveTypeName) {return{ nodeType: \"SymbolNode\", type: \"TypeName\", value: value }}\n\nprimitiveTypeName = 'Edm.' ( 'Binary'\n / 'Boolean'\n / 'Byte'\n / 'Date' \n / 'DateTimeOffset'\n / 'Decimal'\n / 'Double'\n / 'Duration' \n / 'Guid' \n / 'Int16'\n / 'Int32'\n / 'Int64'\n / 'ObjectId'\n / 'SByte'\n / 'Single'\n / 'Stream'\n / 'String'\n / 'TimeOfDay'\n / abstractSpatialTypeName ( concreteSpatialTypeName )? \n )\nabstractSpatialTypeName = 'Geography'\n / 'Geometry'\nconcreteSpatialTypeName = 'Collection'\n / 'LineString'\n / 'MultiLineString'\n / 'MultiPoint'\n / 'MultiPolygon'\n / 'Point'\n / 'Polygon'\n\nodataIdentifier = value:$(identifierLeadingCharacter identifierCharacter*) {return{nodeType: \"SymbolNode\",type: \"Identifier\", value: value }}\nidentifierLeadingCharacter = ALPHA / \"_\" \nidentifierCharacter = ALPHA / \"_\" / DIGIT \nqualifiedEnumTypeName = value:$(odataIdentifier (\".\" odataIdentifier)+) {return {nodeType: \"ConstantNode\",type: 'EnumTypeName', value:value}}\nnamespace = namespacePart ( \".\" namespacePart )*\nnamespacePart = odataIdentifier\nenumerationTypeName = odataIdentifier\n\n\n\n\n/*\n* 7. Literal Data Values\n*/\n// INT covers value ranges of sbyte, byte, int16, int32, int64\n//left out geography and geometry stuff bcs probably not relevant\nprimitiveLiteral = nullValue {return constantNodeHelper(\"Null\", null)} \n / booleanValue\n / value:$guidValue {return constantNodeHelper(\"GUID\",value)}\n / value:$dateTimeOffsetValueInUrl {return constantNodeHelper(\"DateTimeOffsetValueInUrl\",value)}\n / value:$dateValue {return constantNodeHelper(\"DateValue\",value)}\n / value:$timeOfDayValueInUrl {return constantNodeHelper(\"TimeOfDayValueInUrl\",value)}\n / value:$decimalValue {return {nodeType: \"ConstantNode\", type: \"Decimal\", value:parseFloat(value)}}\n / value:$INT {return{nodeType: \"ConstantNode\", type: \"Integer\", value:parseInt(value)}} \n / value:$duration {return constantNodeHelper(\"Duration\", value)}\n / value:string {return{nodeType: \"ConstantNode\", type: \"String\", value: value}} \n / enum\n / value:$binary {return constantNodeHelper(\"Binary\",value)} \n / geographyLiteral\n / geometryLiteral\n \n geographyLiteral = value:(geographyCollection \n / geographyLineString \n / geographyMultiLineString \n / geographyMultiPoint \n / geographyMultiPolygon \n / geographyPoint \n / geographyPolygon) {return {nodeType: \"ConstantSpatialNode\", abstractSpatialType: \"Geography\", value: value}}\n\n geometryLiteral = value:(geometryCollection \n / geometryLineString \n / geometryMultiLineString \n / geometryMultiPoint \n / geometryMultiPolygon \n / geometryPoint \n / geometryPolygon ) {return {nodeType: \"ConstantSpatialNode\", abstractSpatialType: \"Geometry\", value: value}} \n\n\nnullValue = 'null' \nbooleanValue = value:$(\"true\" / \"false\") {return {nodeType: \"ConstantNode\", type: \"Boolean\", value: value === \"true\" ? true : false}}\n//peggyjs does not support specific repetition\nguidValue = HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG \"-\" HEXDIG HEXDIG HEXDIG HEXDIG \"-\" HEXDIG HEXDIG HEXDIG HEXDIG \"-\" HEXDIG HEXDIG HEXDIG HEXDIG \"-\" HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG \n\ndateTimeOffsetValueInUrl = year \"-\" month \"-\" day \"T\" timeOfDayValueInUrl ( \"Z\" / SIGN hour COLON minute )\ndateValue = year \"-\" month \"-\" day\ntimeOfDayValueInUrl = hour COLON minute ( COLON second ( \".\" fractionalSeconds )? )?\n\n year = ( \"-\" )? ( \"0\" DIGIT+ / oneToNine DIGIT+ ) \n month = \"0\" oneToNine\n / \"1\" ( \"0\" / \"1\" / \"2\" )\n day = \"0\" oneToNine\n / ( \"1\" / \"2\" ) DIGIT\n / \"3\" ( \"0\" / \"1\" )\n hour = ( \"0\" / \"1\" ) DIGIT\n / \"2\" ( \"0\" / \"1\" / \"2\" / \"3\" ) \n minute = zeroToFiftyNine\n second = zeroToFiftyNine / \"60\"\n\n oneToNine = \"1\" / \"2\" / \"3\" / \"4\" / \"5\" / \"6\" / \"7\" / \"8\" / \"9\" \n zeroToFiftyNine = ( \"0\" / \"1\" / \"2\" / \"3\" / \"4\" / \"5\" ) DIGIT\n fractionalSeconds = DIGIT+\n\n//decimalValue also covers doubleValue, singleValue\ndecimalValue = ( SIGN )? DIGIT+ ( \".\" DIGIT+ ) ( \"e\" ( SIGN )? DIGIT+ )? / nanInfinity\n nanInfinity = 'NaN' / '-INF' / 'INF'\n\nstring = SQUOTE string:$( SQUOTE_in_string / [^'] )* SQUOTE {return string}\n SQUOTE_in_string = SQUOTE SQUOTE\n\nduration = ( \"duration\" )? SQUOTE durationValue SQUOTE\n durationValue = ( SIGN )? \"P\" ( DIGIT+ \"D\" )? ( \"T\" ( DIGIT+ \"H\" )? ( DIGIT+ \"M\" )? ( DIGIT+ ( \".\" DIGIT+ )? \"S\" )? )?\n\nenum = enumTypeName:$( qualifiedEnumTypeName )? SQUOTE enumValue:$enumValue SQUOTE {return enumTypeName ? {nodeType: 'EnumValueNode', type: 'EnumValue', enumTypeName: enumTypeName, enumValue: enumValue} : {nodeType: 'EnumValueNode', type: 'EnumValue', enumValue: enumValue}}\n enumValue = singleEnumValue ( COMMA singleEnumValue )*\n singleEnumValue = enumerationMember / enumMemberValue\n enumMemberValue = INT\n enumerationMember = odataIdentifier\n\nbinary = \"binary\" SQUOTE binaryValue SQUOTE\n binaryValue = (base64char base64char base64char base64char)* ( base64b16 / base64b8 )?\n base64b16 = base64char base64char ( 'A' / 'E' / 'I' / 'M' / 'Q' / 'U' / 'Y' / 'c' / 'g' / 'k' / 'o' / 's' / 'w' / '0' / '4' / '8' ) ( \"=\" )?\n base64b8 = base64char ( 'A' / 'Q' / 'g' / 'w' ) ( \"==\" )?\n base64char = ALPHA / DIGIT / \"-\" / \"_\"\n\ngeographyCollection = geographyPrefix SQUOTE node:fullCollectionLiteral SQUOTE {return node}\nfullCollectionLiteral = srid: sridLiteral node:collectionLiteral {return {...srid, ...node}}\ncollectionLiteral = \"Collection(\" head:geoLiteral tail:( COMMA @geoLiteral )* CLOSE {return {nodeType: \"CollectionNode\",collection: [head, ...tail]}}\ngeoLiteral = collectionLiteral\n / lineStringLiteral\n / multiPointLiteral\n / multiLineStringLiteral\n / multiPolygonLiteral\n / pointLiteral\n / polygonLiteral\n\ngeographyLineString = geographyPrefix SQUOTE node:fullLineStringLiteral SQUOTE {return node}\nfullLineStringLiteral = srid:sridLiteral node:lineStringLiteral {return {...node, ...srid}}\nlineStringLiteral = \"LineString\" node:lineStringData {return node}\nlineStringData = OPEN positions:(head:positionLiteral tail:( COMMA @positionLiteral )* {return [head, ...tail]}) CLOSE {return {nodeType: \"LineStringNode\", positions: positions}}\n\ngeographyMultiLineString = geographyPrefix SQUOTE node:fullMultiLineStringLiteral SQUOTE {return node}\nfullMultiLineStringLiteral = srid:sridLiteral node:multiLineStringLiteral {return {...srid, ...node}}\nmultiLineStringLiteral = \"MultiLineString(\" lineStrings:( head:lineStringData tail:( COMMA @lineStringData )* {return [head, ...tail]} )? CLOSE {return {nodeType: \"MultiLineStringNode\", lineStrings: lineStrings}}\n\ngeographyMultiPoint = geographyPrefix SQUOTE node:fullMultiPointLiteral SQUOTE {return node}\nfullMultiPointLiteral = srid:sridLiteral node:multiPointLiteral {return {...srid, ...node}}\nmultiPointLiteral = \"MultiPoint(\" points:( head:pointData tail:( COMMA @pointData )* {return [head, ...tail]})? CLOSE {return {nodeType: \"MultiPointNode\", points: points ? points: []}}\n\ngeographyMultiPolygon = geographyPrefix SQUOTE node:fullMultiPolygonLiteral SQUOTE {return node}\nfullMultiPolygonLiteral = srid:sridLiteral node:multiPolygonLiteral {return {...srid, ...node}}\nmultiPolygonLiteral = \"MultiPolygon(\" polygons:( head:polygonData tail:( COMMA @polygonData )* {return [head, ...tail]} )? CLOSE {return {nodeType: \"MultiPolygonNode\", polygons: polygons? polygons: []}}\n\ngeographyPoint = geographyPrefix SQUOTE node:fullPointLiteral SQUOTE {return node}\nfullPointLiteral = srid:sridLiteral node:pointLiteral {return {...srid, ...node}}\nsridLiteral = \"SRID\" EQ srid:$DIGIT+ SEMI {return {srid: parseInt(srid)}}\npointLiteral =\"Point\" point:pointData {return {nodeType: \"PointNode\", point: point}}\npointData = OPEN pos:positionLiteral CLOSE {return pos}\npositionLiteral = lon:$(decimalValue/INT) SP lat:$(decimalValue/INT) alt:( SP @$(decimalValue/INT) )? lrm:( SP @$(decimalValue/INT) )? {return {lon:Number(lon), lat:Number(lat), alt: Number(alt), lrm: Number(lrm)}}\n\ngeographyPolygon = geographyPrefix SQUOTE node:fullPolygonLiteral SQUOTE {return node}\nfullPolygonLiteral = srid:sridLiteral node:polygonLiteral {return {...srid, ...node}}\npolygonLiteral = \"Polygon\" node:polygonData {return node}\npolygonData = OPEN head:ringLiteral tail:( COMMA @ringLiteral )* CLOSE {return {nodeType: \"PolygonNode\", rings: [head, ...tail]}}\nringLiteral = OPEN head:positionLiteral tail:( COMMA @positionLiteral )* CLOSE {return {positions:[head, ...tail]}}\n\ngeometryCollection = geometryPrefix SQUOTE node:fullCollectionLiteral SQUOTE {return node}\ngeometryLineString = geometryPrefix SQUOTE node:fullLineStringLiteral SQUOTE {return node}\ngeometryMultiLineString = geometryPrefix SQUOTE node:fullMultiLineStringLiteral SQUOTE {return node}\ngeometryMultiPoint = geometryPrefix SQUOTE node:fullMultiPointLiteral SQUOTE {return node}\ngeometryMultiPolygon = geometryPrefix SQUOTE node:fullMultiPolygonLiteral SQUOTE {return node}\ngeometryPoint = geometryPrefix SQUOTE node:fullPointLiteral SQUOTE {return node}\ngeometryPolygon = geometryPrefix SQUOTE node:fullPolygonLiteral SQUOTE {return node}\n\ngeographyPrefix = \"geography\"\ngeometryPrefix = \"geometry\" \n\n\n/*\n* 9. Punctuation\n*/\n\n//rws = required whitespace, bws = bad whitespace\nRWS = ( SP / HTAB / \"%20\" / \"%09\" )+\nBWS = ( SP / HTAB / \"%20\" / \"%09\" )* \n\nAT = \"@\" / \"%40\"\nCOLON = \":\" / \"%3A\"\nCOMMA = \",\" / \"%2C\"\nEQ = \"=\"\n// the # character is not allowed in the query part\nHASH = \"%23\" \nSIGN = \"+\" / \"%2B\" / \"-\"\nSEMI = \";\" / \"%3B\"\nSTAR = \"*\" / \"%2A\"\nSQUOTE = \"'\" / \"%27\"\n\nOPEN = \"(\" / \"%28\"\nCLOSE = \")\" / \"%29\"\n\n/*\n* abnf core rules (vchar missing)\n*/\nALPHA = [a-zA-Z] / \"\u00E4\" / \"\u00F6\" / \"\u00FC\" / \"\u00C4\" / \"\u00D6\" / \"\u00DC\" / \"\u00DF\"\nDIGIT = [0-9] \nHEXDIG = DIGIT / A_to_F\nA_to_F = \"A\" / \"B\" / \"C\" / \"D\" / \"E\" / \"F\" \nDQUOTE = '\"'\nSP = ' '\nHTAB = ' '\n"; export declare const rawParser: peggy.Parser; declare const _default: { /** * Parser for filter expressions * @param expr filter expression as string * @param options options for peggy.js parser * @example filterParser.parse("Name eq 'Max'"); * @returns Abstract Syntax Tree (AST) of type FilterNode */ parse: (expr: string, options?: peggy.ParserOptions | undefined) => FilterNode; }; export default _default;