import { EnumShape , LiteralShape , ShapeGuards , ShapeVisitor , UnionShape , Value } from '@punchcard/shape' ; import { array , ArrayShape , MapShape , SetShape } from '@punchcard/shape/lib/collection' ; import { FunctionArgs , FunctionShape } from '@punchcard/shape/lib/function' ; import { AnyShape , BinaryShape , bool , BoolShape , IntegerShape , NeverShape , NothingShape , number , NumberShape , string , StringShape , TimestampShape } from '@punchcard/shape/lib/primitive' ; import { Shape } from '@punchcard/shape/lib/shape' ; import { TypeShape } from '@punchcard/shape/lib/type' ; import { Writer } from './writer' ; const Objekt = Object ; export namespace JsonPath { export type Tag = typeof Tag ; export const Tag = Symbol . for ( '@punchcard/shape-jsonpath.JsonPath.Tag' ) ; export type Of < T extends Shape > = T extends BinaryShape ? JsonPath . Binary : T extends BoolShape ? JsonPath . Bool : T extends AnyShape ? JsonPath . Any < T > : T extends NumberShape ? JsonPath . Number : T extends StringShape ? JsonPath . String : T extends EnumShape ? JsonPath . String : T extends LiteralShape < infer S > ? { [ k in keyof T ] : Of < S > ; } [ keyof T ] : T extends ArrayShape < infer I > ? JsonPath . Array < I > : T extends MapShape < infer V > ? JsonPath . Map < V > : T extends TypeShape < any > ? JsonPath . Struct < T > & { [ fieldName in keyof T [ 'Members' ] ] : Of < T [ 'Members' ] [ fieldName ] > ; } : T extends SetShape < infer V > ? JsonPath . Array < V > : T extends UnionShape < infer U > ? U extends 1 ? { [ i in Extract < keyof U , number > ] : Of < U [ i ] > ; } [ 1 ] : NothingShape extends Extract < U [ Extract < keyof U , number > ] , NothingShape > ? U [ 'length' ] extends 1 ? JsonPath . Object < NothingShape > : U [ 'length' ] extends 2 ? Exclude < { [ i in Extract < keyof U , number > ] : Of < U [ i ] > ; } [ Extract < keyof U , number > ] , JsonPath . Object < NothingShape > > : JsonPath . Union < T > : JsonPath . Union < T > : T extends { [ Tag ] : infer Q ; } ? Q : JsonPath . Object < T > ; export type Root < T extends TypeShape > = Struct < T > [ Fields ] ; export function of < T extends TypeShape > ( shape ) { const result = { } ; for ( const [ name , member ] of Objekt . entries ( shape . Members ) as [ string , Shape ] [ ] ) { result [ name ] = member . visit ( visitor as any , new Id ( member , `$['${ name }']` ) ) ; } return result ; } export function compile ( node ) { const writer = new Writer ( ) ; node [ Synthesize ] ( writer ) ; return writer . toJsonPath ( ) ; } export const isNode = ( a ) : a is Node => a [ NodeType ] !== undefined ; function resolveExpression < T extends StringShape | NumberShape | IntegerShape > ( type , expression ) { return isNode ( expression ) ? expression : new Id ( type , typeof expression === 'number' ? expression . toString ( ) : `'${ expression }'` ) as any ; } export type Expression < T extends Shape > = ExpressionNode < T > | Value . Of < T > ; export const NodeType = Symbol . for ( '@punchcard/shape-jsonpath.JsonPath.NodeType' ) ; export const SubNodeType = Symbol . for ( '@punchcard/shape-jsonpath.JsonPath.SubNodeType' ) ; export const DataType = Symbol . for ( '@punchcard/shape-jsonpath.JsonPath.DataType' ) ; export const InstanceExpression = Symbol . for ( '@punchcard/shape-jsonpath.JsonPath.InstanceExpression' ) ; export const Synthesize = Symbol . for ( '@punchcard/shape-jsonpath.JsonPath.Synthesize' ) ; export const ExpressionTag = Symbol . for ( '@punchcard/shape-jsonpath.JsonPath.ExpressionTag' ) ; export abstract class Node < T extends string = string > { public readonly [ NodeType ] : T ; constructor ( nodeType ) { this [ NodeType ] = nodeType ; } public abstract [ Synthesize ] ( writer ) ; } export abstract class StatementNode extends Node < 'statement' > { public abstract [ SubNodeType ] : string ; constructor ( ) { super ( 'statement' ) ; } } export abstract class ExpressionNode < S extends Shape > extends Node < 'expression' > { public readonly [ DataType ] : S ; public abstract readonly [ SubNodeType ] : string ; constructor ( shape ) { super ( 'expression' ) ; this [ DataType ] = shape ; } } export class Id < T extends Shape > extends ExpressionNode < T > { public readonly [ SubNodeType ] : 'identifier' = 'identifier' ; constructor ( shape , public readonly value ) { super ( shape ) ; } public [ Synthesize ] ( writer ) { writer . writeToken ( this . value ) ; } } export class Object < T extends Shape = Shape > extends ExpressionNode < T > { public readonly [ SubNodeType ] : 'object' = 'object' ; public readonly [ ExpressionTag ] : ExpressionNode < T > ; constructor ( shape , expression ) { super ( shape ) ; this [ ExpressionTag ] = expression ; } public [ Synthesize ] ( writer ) { this [ ExpressionTag ] [ Synthesize ] ( writer ) ; } } export namespace Object { export abstract class Comparison < T extends Shape , U extends Shape > extends ExpressionNode < BoolShape > { protected abstract operator : string ; constructor ( public readonly left , public readonly right ) { super ( bool ) ; } public [ Synthesize ] ( writer ) { this . left [ Synthesize ] ( writer ) ; writer . writeToken ( this . operator ) ; this . right [ Synthesize ] ( writer ) ; } } export class Equals < T extends Shape > extends Object . Comparison < T , T > { protected readonly operator : '==' = '==' ; public readonly [ SubNodeType ] = 'equals' ; } export class NotEquals < T extends Shape > extends Object . Comparison < T , T > { protected readonly operator : '!=' = '!=' ; public readonly [ SubNodeType ] = 'not-equals' ; } } export class Any < T extends AnyShape > extends Object < T > { public as < S extends Shape > ( shape ) { return shape . visit ( visitor as any , this ) ; } } export class Union < T extends UnionShape < Shape [ ] > > extends Object < T > { public as < S extends T [ 'Items' ] [ Extract < keyof T [ 'Items' ] , number > ] > ( shape ) { return shape . visit ( visitor as any , this ) ; } } export class Binary extends Object < BinaryShape > { } export class Bool extends Object < BoolShape > { constructor ( expression , boolShape ? ) { super ( boolShape || bool , expression ) ; } public and ( ... conditions ) { return new Bool ( new Bool . And ( [ this , ... conditions ] ) ) ; } public or ( ... conditions ) { return new Bool ( new Bool . Or ( [ this , ... conditions ] ) ) ; } public not ( ) { return new Bool ( new Bool . Not ( this ) , this [ DataType ] ) ; } } export namespace Bool { export abstract class Operands extends ExpressionNode < BoolShape > { public abstract readonly operator : string ; constructor ( public readonly operands ) { super ( bool ) ; } public [ Synthesize ] ( writer ) { writer . writeToken ( '(' ) ; for ( const op of this . operands ) { op [ Synthesize ] ( writer ) ; writer . writeToken ( ` ${ this . operator } ` ) ; } writer . pop ( ) ; writer . writeToken ( ')' ) ; } } export class And extends Operands { public readonly operator = '&&' ; public [ SubNodeType ] = 'and' ; } export class Or extends Operands { public readonly operator = '||' ; public [ SubNodeType ] = 'or' ; } export class Not extends ExpressionNode < BoolShape > { public [ SubNodeType ] = 'or' ; constructor ( public readonly operand ) { super ( bool ) ; } public [ Synthesize ] ( writer ) { writer . writeToken ( '(!' ) ; this . operand [ Synthesize ] ( writer ) ; writer . writeToken ( ')' ) ; } } } export function or ( ... operands ) { return new Bool ( new Bool . Or ( operands ) ) ; } export function and ( ... operands ) { return new Bool ( new Bool . And ( operands ) ) ; } export function not ( operand ) { return new Bool ( new Bool . Not ( operand ) ) ; } export class Number extends Object < Number . Shape > { public greaterThan ( other ) { return new Bool ( new Number . Gt ( this , resolveExpression ( number , other ) ) ) ; } public greaterThanOrEqual ( other ) { return new Bool ( new Number . Gte ( this , resolveExpression ( number , other ) ) ) ; } public lessThan ( other ) { return new Bool ( new Number . Lt ( this , resolveExpression ( number , other ) ) ) ; } public lessThanOrEqual ( other ) { return new Bool ( new Number . Lte ( this , resolveExpression ( number , other ) ) ) ; } public equals ( other ) { return new Bool ( new Object . Equals ( this , resolveExpression ( number , other as any ) ) ) ; } public notEquals ( other ) { return new Bool ( new Object . NotEquals ( this , resolveExpression ( number , other as any ) ) ) ; } } export namespace Number { export type Shape = NumberShape | IntegerShape ; export class Gt < T extends Shape > extends Object . Comparison < T , Number . Shape > { protected readonly operator : '>' = '>' ; public readonly [ SubNodeType ] = 'greaterThan' ; } export class Gte < T extends Shape > extends Object . Comparison < T , Number . Shape > { protected readonly operator : '>=' = '>=' ; public readonly [ SubNodeType ] = 'greaterThanOrEqual' ; } export class Lt < T extends Shape > extends Object . Comparison < T , Number . Shape > { protected readonly operator : '<' = '<' ; public readonly [ SubNodeType ] = 'lessThan' ; } export class Lte < T extends Shape > extends Object . Comparison < T , Number . Shape > { protected readonly operator : '<=' = '<=' ; public readonly [ SubNodeType ] = 'lessThanOrEqual' ; } } export class String extends Object < StringShape > { public equals ( other ) { return new Bool ( new Object . Equals ( this , resolveExpression ( string , other as any ) ) ) ; } public notEquals ( other ) { return new Bool ( new Object . NotEquals ( this , resolveExpression ( string , other as any ) ) ) ; } public match ( regex ) { return new Bool ( new String . Match ( this , regex ) ) ; } } export namespace String { export class Match extends ExpressionNode < BoolShape > { public readonly [ SubNodeType ] : 'string-match' = 'string-match' ; constructor ( private readonly string , public readonly regex ) { super ( bool ) ; } public [ Synthesize ] ( writer ) { this . string [ Synthesize ] ( writer ) ; writer . writeToken ( ' =~ ' ) ; writer . writeToken ( this . regex . source ) ; } } } export class Filter < T extends Shape > extends ExpressionNode < ArrayShape < T > > { public readonly [ SubNodeType ] : 'filter' = 'filter' ; constructor ( private readonly parent , public readonly condition ) { super ( parent [ DataType ] ) ; } public [ Synthesize ] ( writer ) { this . parent [ Synthesize ] ( writer ) ; writer . writeToken ( `[?(` ) ; this . condition [ Synthesize ] ( writer ) ; writer . writeToken ( `)]` ) ; } } export namespace Filter { export class Item < T extends Shape > extends ExpressionNode < T > { public readonly [ SubNodeType ] : 'filter-item' = 'filter-item' ; public [ Synthesize ] ( writer ) { writer . writeToken ( '@' ) ; } } } export type Item = typeof Item ; export const Item = Symbol . for ( '@punchcard/shape-jsonpath.JsonPath.Item' ) ; export class Array < T extends Shape > extends Object < ArrayShape < T > > { public readonly [ Item ] : Of < T > ; constructor ( shape , expression ) { super ( shape , expression ) ; this [ Item ] = expression [ DataType ] . Items . visit ( visitor as any , new Filter . Item ( expression [ DataType ] ) ) as any ; } public get ( key ) { return this [ DataType ] . Items . visit ( visitor as any , new Array . Get ( this , key ) ) as any ; } public filter ( f ) { return this [ DataType ] . Items . visit ( visitor as any , new Filter ( this , f ( this [ Item ] ) ) ) as any ; } } export namespace Array { export class Get < T extends Shape > extends ExpressionNode < T > { public readonly [ SubNodeType ] : 'array-get-value' = 'array-get-value' ; constructor ( public readonly array , public readonly key ) { super ( array [ DataType ] . Items ) ; } public [ Synthesize ] ( writer ) { this . array [ Synthesize ] ( writer ) ; writer . writeToken ( `[${ this . key }]` ) ; } } } export class Map < T extends Shape > extends Object < MapShape < T > > { public readonly [ Item ] : Of < T > ; constructor ( shape , expression ) { super ( shape , expression ) ; this [ Item ] = shape . Items . visit ( visitor as any , new Filter . Item ( expression [ DataType ] ) ) as any ; } public get ( key ) { return this [ DataType ] . Items . visit ( visitor as any , new Map . Get ( this , key ) ) as any ; } public filter ( f ) { return this [ DataType ] . Items . visit ( visitor as any , new Filter ( this , f ( this [ Item ] ) ) ) as any ; } } export namespace Map { export class Get < T extends Shape > extends ExpressionNode < T > { public readonly [ SubNodeType ] : 'map-get-value' = 'map-get-value' ; constructor ( public readonly map , public readonly key ) { super ( map [ DataType ] . Items ) ; } public [ Synthesize ] ( writer ) { this . map [ Synthesize ] ( writer ) ; writer . writeToken ( `['${ this . key }']` ) ; } } } export const Fields = Symbol . for ( '@punchcard/shape-jsonpath.JsonPath.Fields' ) ; export type Fields = typeof Fields ; export class Struct < T extends TypeShape < any > > extends Object < T > { public readonly [ Fields ] : { [ fieldName in keyof T [ 'Members' ] ] : Of < T [ 'Members' ] [ fieldName ] > ; } ; constructor ( type , expression ) { super ( type , expression ) ; this [ Fields ] = { } as any ; for ( const [ name , prop ] of Objekt . entries ( type . Members ) as [ string , Shape ] [ ] ) { ( this [ Fields ] as any ) [ name ] = prop . visit ( visitor as any , new Struct . Field ( this , prop , name ) ) ; } } } export namespace Struct { export class Field < T extends Shape > extends ExpressionNode < T > { public readonly [ SubNodeType ] = 'struct-field' ; constructor ( public readonly struct , type , public readonly name ) { super ( type ) ; } public [ Synthesize ] ( writer ) { this . struct [ Synthesize ] ( writer ) ; writer . writeToken ( `['${ this . name }']` ) ; } } } } class Visitor implements ShapeVisitor < any , JsonPath . ExpressionNode < any > > { public enumShape ( shape , expr ) { return this . stringShape ( string , expr ) ; } public literalShape ( shape , expr ) { return shape . Type . visit ( this , expr ) ; } public unionShape ( shape , expr ) { const items = shape . Items . filter ( i => ! ShapeGuards . isNothingShape ( i ) ) ; if ( items . length === 1 ) { return items [ 0 ] . visit ( this , expr ) ; } throw new Error ( "Method not implemented." ) ; } public neverShape ( shape , context ) { throw new Error ( "NeverShape is not supported by JSON Path" ) ; } public functionShape ( shape ) { throw new Error ( "FunctionShape is not supported by JSON Path" ) ; } public nothingShape ( shape , expression ) { return new JsonPath . Object ( shape , expression ) ; } public anyShape ( shape , expression ) { return new JsonPath . Any ( shape , expression ) ; } public binaryShape ( shape , expression ) { return new JsonPath . Binary ( shape , expression ) ; } public arrayShape ( shape , expression ) { return new Proxy ( new JsonPath . Array ( shape , expression ) , { get : ( target , prop ) => { if ( typeof prop === 'string' ) { if ( ! isNaN ( prop as any ) ) { return target . get ( parseInt ( prop , 10 ) ) ; } } else if ( typeof prop === 'number' && prop % 1 === 0 ) { return target . get ( prop ) ; } return ( target as any ) [ prop ] ; } } ) ; } public boolShape ( shape , expression ) { return new JsonPath . Bool ( expression , shape ) ; } public recordShape ( shape , expression ) { return new Proxy ( new JsonPath . Struct ( shape , expression ) , { get : ( target , prop ) => { if ( typeof prop === 'string' ) { return target [ JsonPath . Fields ] [ prop ] ; } return ( target as any ) [ prop ] ; } } ) ; } public mapShape ( shape , expression ) { return new Proxy ( new JsonPath . Map ( shape , expression ) , { get : ( target , prop ) => { if ( typeof prop === 'string' ) { if ( typeof ( target as any ) [ prop ] === 'function' ) { return ( target as any ) [ prop ] ; } return target . get ( prop ) ; } return ( target as any ) [ prop ] ; } } ) ; } public integerShape ( shape , expression ) { return new JsonPath . Number ( shape , expression ) ; } public numberShape ( shape , expression ) { return new JsonPath . Number ( shape , expression ) ; } public setShape ( shape , expression ) { return new JsonPath . Array ( array ( shape . Items ) , expression ) ; } public stringShape ( shape , expression ) { return new JsonPath . String ( shape , expression ) ; } public timestampShape ( shape , expression ) { return new JsonPath . String ( string , expression ) ; } } const visitor = new Visitor ( ) ;