import { wrapped , FunctionBuilder } from "./functions" ; import { parseType } from "./parse" ; import { lookup , Scope } from "./scope" ; import { Type } from "./types" ; import { concat , lookupForMap } from "./utils" ; import { array , call , conditional , conformance , expr , extractContentOfBox , literal , member , read , reuse , set , stringifyType , stringifyValue , typeFromValue , typeRequiresBox , typeValue , undefinedValue , Value } from "./values" ; import { isLiteral , Expression } from "@babel/types" ; export enum PossibleRepresentation { None , Undefined = 1 << 0 , Boolean = 1 << 1 , Number = 1 << 2 , String = 1 << 3 , Function = 1 << 4 , Object = 1 << 5 , Symbol = 1 << 6 , Null = 1 << 7 , Array = 1 << 8 , All = PossibleRepresentation . Undefined | PossibleRepresentation . Boolean | PossibleRepresentation . Number | PossibleRepresentation . String | PossibleRepresentation . Function | PossibleRepresentation . Object | PossibleRepresentation . Symbol | PossibleRepresentation . Null | PossibleRepresentation . Array } export interface ReifiedType { functions : ( name : string ) => FunctionBuilder | undefined ; conformances : ProtocolConformanceMap ; innerTypes : Readonly < TypeMap > ; cases ? : ReadonlyArray < EnumCase > ; defaultValue ? ( scope , consume ) : Value ; copy ? ( value , scope ) : Value ; store ? ( target , value , scope ) : Value ; } export type TypeParameterHost = < T extends Array < unknown > > ( ... args : T ) => { [ K in keyof T ] : T [ K ] extends string ? Value : T [ K ] ; } & Iterable < Value > ; export interface TypeMap { [ name ] : ( globalScope : Scope , typeParameters : TypeParameterHost ) => ReifiedType ; } export interface FunctionMap { [ name ] : FunctionBuilder ; } export interface ProtocolConformance { functions : { [ functionName ] : FunctionBuilder ; } ; requirements : string [ ] ; } export interface ProtocolConformanceMap { [ protocolName ] : ProtocolConformance ; } export interface EnumCase { name : string ; fieldTypes : ReadonlyArray < ReifiedType > ; } const emptyConformances = Object . create ( null ) ; const noFunctions = Object . create ( null ) ; const noInnerTypes = Object . create ( null ) ; export function withPossibleRepresentations ( conformances , possibleRepresentations ) { return { ... conformances , Object : { functions : { ":rep" : wrapped ( ( ) => literal ( possibleRepresentations ) , "() -> Int" ) , } , requirements : [ ] , } , } ; } export function primitive ( possibleRepresentations , defaultValue , functions = noFunctions , conformances = emptyConformances , innerTypes = noInnerTypes ) { return { functions : functions !== noFunctions ? lookupForMap ( functions ) : alwaysUndefined , conformances : withPossibleRepresentations ( conformances , possibleRepresentations ) , defaultValue ( ) { return defaultValue ; } , innerTypes , } ; } export function protocol ( protocolName , conformances = emptyConformances ) { return { functions ( functionName ) { return ( scope , arg , name , argTypes ) => { const typeArg = conformance ( arg ( 0 , "Self" ) , protocolName , scope ) ; const fn = typeFromValue ( typeArg , scope ) . functions ( functionName ) ; if ( typeof fn !== "function" ) { throw new TypeError ( `Could not find ${ functionName } on ${ stringifyValue ( typeArg ) }` ) ; } return fn ( scope , arg , name , argTypes ) ; } ; } , conformances : withPossibleRepresentations ( conformances , PossibleRepresentation . Object ) , innerTypes : noInnerTypes , } ; } export function inheritLayout ( type , functions = noFunctions , conformances = emptyConformances , innerTypes = noInnerTypes ) { return { functions : functions !== noFunctions ? lookupForMap ( functions ) : alwaysUndefined , conformances : { ... conformances , Object : type . conformances . Object , } , defaultValue : type . defaultValue , copy : type . copy , store : type . store , innerTypes , } ; } function cannotDefaultInstantiateClass ( ) { throw new TypeError ( `Cannot default instantiate a class` ) ; } export function newClass ( functions = noFunctions , conformances = emptyConformances , innerTypes = noInnerTypes , defaultValue = cannotDefaultInstantiateClass ) { return { functions : lookupForMap ( functions ) , conformances : withPossibleRepresentations ( conformances , PossibleRepresentation . Object ) , defaultValue , innerTypes , } ; } export function expressionSkipsCopy ( expression ) { switch ( expression . type ) { case "ObjectExpression" : case "ArrayExpression" : case "CallExpression" : return true ; case "ConditionalExpression" : return expressionSkipsCopy ( expression . consequent ) && expressionSkipsCopy ( expression . alternate ) ; default : return isLiteral ( expression ) ; } } export function store ( dest , source , type , scope ) { switch ( dest . kind ) { case "boxed" : return conditional ( typeRequiresBox ( dest . type , scope ) , store ( extractContentOfBox ( dest , scope ) , source , type , scope ) , store ( dest . contents , source , type , scope ) , scope ) ; case "direct" : const reified = typeFromValue ( type , scope ) ; if ( reified . store ) { return reified . store ( dest , source , scope ) ; } else { return set ( dest , source , scope , "=" ) ; } case "subscript" : return set ( dest , source , scope , "=" ) ; default : throw new TypeError ( `Unable to store to a ${ dest . kind } value` ) ; } } export function defaultInstantiateType ( type , scope , consume ) { const reified = typeFromValue ( type , scope ) ; if ( ! reified . defaultValue ) { throw new Error ( `Cannot default instantiate ${ stringifyValue ( type ) }` ) ; } return reified . defaultValue ( scope , consume ) ; } function typeArgumentsForArray ( args ) { return ( ( ... requested ) => { if ( requested . length > args . length ) { throw new TypeError ( `Requested ${ requested . length } type arguments from array that only contains ${ args . length } elements` ) ; } return args . slice ( 0 , args . length ) ; } ) as TypeParameterHost ; } export function reifyType ( typeOrTypeName , scope , typeArguments = [ ] , types ? ) { const type = typeof typeOrTypeName === "string" ? parseType ( typeOrTypeName ) : typeOrTypeName ; switch ( type . kind ) { case "name" : if ( typeof types !== "undefined" ) { for ( const map of types ) { if ( Object . hasOwnProperty . call ( map , type . name ) ) { return map [ type . name ] ( scope , typeArgumentsForArray ( typeArguments ) ) ; } } } else { let currentScope = scope ; while ( typeof currentScope !== "undefined" ) { const map = currentScope . types ; if ( Object . hasOwnProperty . call ( map , type . name ) ) { return map [ type . name ] ( scope , typeArgumentsForArray ( typeArguments ) ) ; } currentScope = currentScope . parent ; } } return typeFromValue ( lookup ( type . name , scope ) , scope ) ; case "array" : return reifyType ( { kind : "name" , name : "Array" } , scope , [ typeValue ( type . type ) ] ) ; case "modified" : return reifyType ( type . type , scope ) ; case "dictionary" : return reifyType ( { kind : "name" , name : "Dictionary" } , scope , [ typeValue ( type . keyType ) , typeValue ( type . valueType ) ] ) ; case "tuple" : const reifiedTypes = type . types . map ( ( inner ) => reifyType ( inner , scope ) ) ; switch ( type . types . length ) { case 0 : return primitive ( PossibleRepresentation . Undefined , undefinedValue ) ; case 1 : return reifiedTypes [ 0 ] ; default : return { functions : lookupForMap ( noFunctions ) , conformances : withPossibleRepresentations ( { } , PossibleRepresentation . Array ) , defaultValue ( innerScope ) { return array ( reifiedTypes . map ( ( inner , i ) => { const defaultValue = inner . defaultValue ; if ( typeof defaultValue === "undefined" ) { throw new TypeError ( `Tuple field ${ i } of type ${ stringifyType ( type . types [ i ] ) } is not default instantiable` ) ; } return defaultValue ( innerScope , alwaysUndefined ) ; } ) , innerScope ) ; } , copy ( value , innerScope ) { if ( value . kind === "tuple" ) { return value ; } const expression = read ( value , innerScope ) ; if ( expressionSkipsCopy ( expression ) ) { return expr ( expression ) ; } if ( ! reifiedTypes . some ( ( elementType ) => typeof elementType . copy !== "undefined" ) ) { return call ( member ( expr ( expression ) , "slice" , scope ) , [ ] , [ ] , scope ) ; } return reuse ( expr ( expression ) , innerScope , "copySource" , ( source ) => { return array ( reifiedTypes . map ( ( elementType , index ) => { const fieldValue = member ( source , index , innerScope ) ; return elementType . copy ? elementType . copy ( fieldValue , innerScope ) : fieldValue ; } ) , scope ) ; } ) ; } , innerTypes : noInnerTypes , } ; } case "generic" : return reifyType ( type . base , scope , concat ( typeArguments , type . arguments . map ( ( innerType ) => typeValue ( innerType ) ) ) ) ; case "metatype" : const reified = reifyType ( type . base , scope , typeArguments ) ; return reified ; case "function" : return primitive ( PossibleRepresentation . Function , undefinedValue ) ; case "namespaced" : return reifyType ( type . type , scope , [ ] , [ reifyType ( type . namespace , scope , typeArguments ) . innerTypes ] ) ; case "optional" : return reifyType ( { kind : "name" , name : "Optional" } , scope , [ typeValue ( type . type ) ] ) ; default : throw new TypeError ( `Received an unexpected type ${ ( type as Type ) . kind }` ) ; } } function alwaysUndefined ( ) { return undefined ; }