import { assert } from "@here/harp-utils" ; import { CallExpr , LookupExpr , ObjectLiteralExpr , Value } from "../Expr" ; import { ExprEvaluatorContext , OperatorDescriptorMap } from "../ExprEvaluator" ; interface KeyValObj { [ key ] : Value ; } interface LookupEntry { keys : KeyValObj ; attributes : KeyValObj ; } type LookupArray = LookupEntry [ ] ; type LookupMap = Map < string , KeyValObj > ; function joinKeyValues ( keys ) { return keys . join ( "&" ) ; } function stringifyKeyValue ( key , value ) { return key + "=" + JSON . stringify ( value ) ; } param combinations returns function joinCombinations ( combinations ) { combinations . sort ( ( lhs , rhs ) => rhs . length - lhs . length ) ; const result = combinations . map ( ( keys ) => joinKeyValues ( keys ) ) ; result . push ( "" ) ; return result ; } param input param index returns function getAllCombinations ( input , index = 0 ) { if ( index >= input . length ) { return [ ] ; } const combinations = getAllCombinations ( input , index + 1 ) ; const initLength = combinations . length ; for ( let i = 0 ; i < initLength ; i += 1 ) { combinations . push ( [ ... combinations [ i ] , input [ index ] ] ) ; } combinations . push ( [ input [ index ] ] ) ; return combinations ; } param lookupExpr param context returns function getKeyCombinations ( lookupExpr , context ) { const keys = lookupExpr . args . slice ( 1 ) ; const result = [ ] ; for ( let i = 0 ; i < keys . length ; i += 2 ) { const value = context . evaluate ( keys [ i + 1 ] ) ; if ( value === null ) { continue ; } const key = context . evaluate ( keys [ i ] ) as string ; result . push ( stringifyKeyValue ( key , value ) ) ; } result . sort ( ) . reverse ( ) ; return joinCombinations ( getAllCombinations ( result ) ) ; } param lookupArray returns function createLookupMap ( lookupArray ) { const map = new Map ( ) ; for ( const entry of lookupArray ) { if ( typeof entry !== "object" ) { throw new Error ( `Invalid lookup table entry type (${ typeof entry })` ) ; } if ( ! entry . keys ) { throw new Error ( `Lookup table entry has no 'keys' property.` ) ; } if ( ! entry . attributes ) { throw new Error ( `Lookup table entry has no 'attributes' property.` ) ; } const key = joinKeyValues ( Object . getOwnPropertyNames ( entry . keys ) . sort ( ) . map ( key => stringifyKeyValue ( key , entry . keys [ key ] ) ) ) ; map . set ( key , entry . attributes ) ; } return map ; } param keys param map returns function searchLookupMap ( keys , map ) { for ( const key of keys ) { const matchAttributes = map . get ( key ) ; if ( matchAttributes ) { return matchAttributes ; } } return null ; } const operators = { length : { call : ( context , call ) => { const value = context . evaluate ( call . args [ 0 ] ) ; if ( Array . isArray ( value ) || typeof value === "string" ) { return value . length ; } throw new Error ( `invalid operand '${ value }' for operator 'length'` ) ; } } , coalesce : { call : ( context , call ) => { for ( const childExpr of call . args ) { const value = context . evaluate ( childExpr ) ; if ( value !== null ) { return value ; } } return null ; } } , lookup : { call : ( context , lookup ) => { assert ( lookup . args . length > 0 , "missing lookup table" ) ; const keyCombinations = getKeyCombinations ( lookup , context ) ; let table = context . evaluate ( lookup . args [ 0 ] ) as LookupArray | LookupMap ; assert ( Array . isArray ( table ) || table instanceof Map , "wrong lookup table type" ) ; if ( Array . isArray ( table ) ) { table = createLookupMap ( table ) ; const lookupMapExpr = new ObjectLiteralExpr ( table ) ; lookup . args [ 0 ] = lookupMapExpr ; } return searchLookupMap ( keyCombinations , table ) ; } } } ; export const MiscOperators = operators ; export type MiscOperatorNames = keyof typeof operators ;