export class NoMatchingPattern extends Error { constructor ( value , patterns ) { console . info ( 'value' , value ) ; console . info ( 'patterns have been tried' ) ; for ( const pattern of patterns ) { console . debug ( pattern ) ; } super ( `No matching pattern` ) ; } } export const PlaceholderSymbol = Symbol ( "Placeholder" ) ; export const NextSymbol = Symbol ( "next" ) ; export type NextStatement = { [ NextSymbol ] : true ; } ; export const next = { [ NextSymbol ] : true , } ; export type Placeholder = { [ PlaceholderSymbol ] : true ; predicate : ( value : any ) => boolean ; } ; export const placeholder = { [ PlaceholderSymbol ] : true , predicate : ( ) => true , } ; export const predicate = ( _predicate ) : Placeholder => { return { [ PlaceholderSymbol ] : true , predicate : ( value ) => _predicate ( value ) , } ; } ; export type Pattern < Type > = { [ Property in keyof Type ] ? : Type [ Property ] | { [ P in keyof Type [ Property ] ] : Placeholder | Pattern < Type > ; } | Placeholder | any [ ] ; } ; export type Statement < S > = { case : ( pattern : Pattern < S > , output : ( match : S ) => any ) => Statement < S > ; match : ( ) => S | never ; } ; export type WrappedTupple < T > = { [ K in keyof T ] : T [ K ] | Placeholder | Pattern < T [ K ] > ; } ; export const pattern = < S extends any [ ] > ( ... args ) => { let patternFunction = object ( args ) ; const statement = { case : ( ... patternArgs ) => { return ( callback ) => { if ( NextSymbol in callback ) { patternFunction = patternFunction . case ( patternArgs ) ; } else { patternFunction = patternFunction . case ( patternArgs , ( ) => ( callback as CallableFunction ) ( ... args ) ) ; } return statement ; } ; } , match : ( ) => { return patternFunction . match ( ) ; } } ; return statement ; } ; export const object = < S > ( value ) => { let matched ; let result ; let triedPatterns = [ ] ; let fallThrough = false ; const match = ( pattern , matchWith ) : boolean => { let matches = true ; switch ( pattern ) { case undefined : case null : matches = pattern === matchWith ; break ; default : if ( typeof pattern === "object" ) { if ( PlaceholderSymbol in pattern ) { if ( pattern . predicate ( matchWith ) ) { matches = true ; break ; } } else { if ( Array . isArray ( pattern ) || Array . isArray ( matchWith ) ) { if ( pattern . length !== matchWith . length ) { matches = false ; break ; } } } for ( const key in pattern ) { if ( ! Object . prototype . hasOwnProperty . call ( pattern , key ) ) { continue ; } const element = pattern [ key ] ; matches = match ( element , matchWith [ key ] ) ; if ( ! matches ) { break ; } } } else { matches = pattern === matchWith ; } break ; } return matches ; } ; const breakNext = { match ( ) { return result ; } , case ( pattern , output ) { return breakNext ; } , } ; const continueNext = { case ( pattern , output ) { if ( ! fallThrough ) { matched = match ( pattern , value ) ; } if ( matched ) { if ( ! output ) { fallThrough = true ; } else { result = output ( value ) ; fallThrough = false ; } } if ( matched && ! fallThrough ) { return breakNext ; } else { triedPatterns . push ( pattern ) ; return continueNext ; } } , match ( ) { throw new NoMatchingPattern ( value , triedPatterns ) ; } , } ; return continueNext ; } ;