mezzanine
Version:
Fantasy land union types with pattern matching
241 lines (222 loc) • 6.46 kB
JavaScript
//@flow
import { values } from 'ramda'
import toFastProps from '../to-fast-props'
import { isMezzanine } from './fixtures'
import { typeMark } from '../config'
import { createBuilder, createPred, transformInput } from './descriptor'
import { type Pred } from './descriptor'
import { type ContextMethod } from './index.h'
import { injector } from '../utils/props'
import { getInitialValue, applyStack } from '../virtual-stack'
import { instanceInjectableProps, fantasyInstance, fantasyOnClass } from './properties'
export type Descript =
| typeof String
| typeof Number
| typeof Array
| typeof Boolean
| typeof Function
| string
| number
| boolean
| Pred
| TypeRecord<Descript>
| {[id: string]: Descript }
export interface TypeRecord<Schema: Descript> {
+ಠ_ಠ: Symbol,
+keys: string[],
is(val: mixed): boolean,
toJSON(): Schema,
equals(val: mixed): boolean,
map<T>(fn: (val: Schema) => T): TypeRecord<T>,
chain<T>(fn: (val: Schema) => TypeRecord<T>): TypeRecord<T>,
// concat<R, D>(val: R | D): TypeRecord,
// extract(): Schema,
// ap<R>(m: R): R, //???
}
export interface TypeStatic<Schema: Descript> {
$call(val: Schema): TypeRecord<Schema>,
(val: Schema): TypeRecord<Schema>,
+keys: string[],
is(val: mixed): boolean,
typeName: string,
contramap<T>(prependFunction: (...vals: T[]) => Schema): TypeStatic<T>,
of<T: Schema | TypeRecord<Schema>>(val: T): TypeRecord<Schema>,
equals(a: TypeRecord<Schema>, b: TypeRecord<Schema>): boolean,
// equals<R, D>(val: R | D | mixed): boolean,
// concat<R, D>(val: R | D): TypeRecord,
// extract(): Schema,
// ap<R>(m: R): R, //???
}
export interface TypeStaticPrivate {
+ಠ_ಠ: Symbol,
desc: Descript,
func: {[name: string]: ContextMethod<*, *>},
stack: Array<(val: mixed) => mixed>
}
function isType(pred: Pred, uniqMark: Symbol, isMono: boolean, stack: Array<(val: mixed) => mixed>) {
const needTransform = stack.length !== 0
return function checkIsType(obj: any): boolean {
let val = obj
if (needTransform === true) {
const initial = getInitialValue(stack, val)
if (initial.succ === false) return false
val = initial.val
}
const data = transformInput(val, isMono)
if (data == null) return false
if (isMono && isMezzanine(data.value) && data.value.ಠ_ಠ === uniqMark)
return true
return pred(data)
}
}
const newUniqMark = (name: string) => Symbol(name)
const generalInjectableProps = {
clone: (_: any, Static: TypeStatic<any> & TypeStaticPrivate) => () => {
const { name, typeName, desc, func, stack } = Static
return makeContainer(name, typeName, desc, func, stack)
},
//$FlowIssue
[typeMark]: {
get : () => true,
enumerable: false,
},
}
const staticInjectableProps = {
//$FlowIssue
[Symbol.hasInstance]: (Static: TypeStatic<any>) => (val: mixed) => Static.is(val),
stackUpdate : (Static: TypeStatic<any> & TypeStaticPrivate) =>
(newStack: Array<(val: mixed) => mixed>) => {
const { name, typeName, desc, func } = Static
return makeContainer(name, typeName, desc, func, newStack)
},
}
const fullStaticProps = injector([
generalInjectableProps,
staticInjectableProps,
fantasyOnClass,
])
const fullInstanceProps = injector([
generalInjectableProps,
instanceInjectableProps,
fantasyInstance,
])
function makeContainer<F, Type: Descript>(
name: string,
typeName: string,
descriptor: Descript,
func: {[name: string]: ContextMethod<*, F>},
stack: Array<(val: mixed) => mixed> = []
//$FlowIssue
): TypeStatic<Type> {
const desc: * =
typeof descriptor !== 'object'
|| descriptor == null
? { value: descriptor }
: descriptor
const keys = Object.keys(desc)
const pred = createPred(desc)
const isMono = keys.length === 1 && keys[0] === 'value'
const uniqMark = newUniqMark(name)
const checkIs = isType(pred, uniqMark, isMono, stack)
function RecordFn<Type: Descript>(arg: any) {
//$FlowIssue
if (new.target == null)
return new RecordFn(arg)
const obj = applyStack(stack, arg)
const data = transformInput(obj, isMono)
if (data == null) {
throw new TypeError(`${name}{isMono: ${isMono.toString()}}: No value recieved`)
}
if (isMono && isMezzanine(data.value) && data.value.ಠ_ಠ === uniqMark) return data.value
if (!pred(data)) {
const mono = isMono.toString()
const keysList = Object.keys(data).toString()
const valuesList = values(data).toString()
const message = `${name}{isMono: ${mono}}: Unsafe pattern mismatch\nKeys: ${keysList}\nValues: ${valuesList}`
throw new TypeError(message)
}
const dataResult = createBuilder(desc, data)
// console.log(data, obj, dataResult)
for (const key of keys) {
this[key] = dataResult[key]
// console.log(this[key])
}
fullInnerInstProps(this, RecordFn)
fullInstanceProps(this, RecordFn)
toFastProps(this)
}
const generalProps = {
ಠ_ಠ: {
value : uniqMark,
enumerable: false,
},
type: {
value : name,
enumerable: true,
},
keys: {
value : keys,
enumerable: false,
},
isMono: {
get : () => isMono,
enumerable: false,
},
is: {
value : checkIs,
enumerable: true,
}
}
const staticProps = {
name: {
value : name,
enumerable: false,
},
typeName: {
value : typeName,
enumerable: false,
},
desc: {
value : descriptor,
enumerable: false,
},
func: {
value : func,
enumerable: false,
},
stack: {
value : stack,
enumerable: false,
},
}
const instProps = {
typeName: {
value : typeName,
enumerable: true,
},
}
const fullInnerStaticProps = injector([
generalProps,
staticProps,
])
const userMeth = getUserMethods(func)
const fullInnerInstProps = injector([
generalProps,
instProps,
userMeth,
])
fullInnerStaticProps(RecordFn, RecordFn)
fullStaticProps(RecordFn, RecordFn)
return RecordFn
}
const getUserMethods = (func: *) =>
Object.getOwnPropertyNames(func)
//$FlowIssue
.concat(Object.getOwnPropertySymbols(func))
.map(key => [key, {
value : func[key],
enumerable: true,
writable : true,
inject : true
}])
export default makeContainer