/**
 * Copyright (c) 2016-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */ import * as AlgebraicType from './algebraic-type' ; import * as Maybe from './maybe' ; import * as ObjC from './objc' ; import * as StringUtils from './string-utils' ; export interface MatchingBlockType { name : string ; underlyingType : string ; modifiers : ObjC . KeywordArgumentModifier [ ] ; defaultValue : string ; } export function nameForInstanceVariableStoringSubtype ( ) { return 'subtype' ; } export function valueAccessorForInstanceVariableStoringSubtype ( ) { return '_' + nameForInstanceVariableStoringSubtype ( ) ; } export function attributesFromSubtype ( subtype ) { return subtype . match ( function ( namedAttributeCollectionSubtype ) { return namedAttributeCollectionSubtype . attributes ; } , function ( attribute ) { return [ attribute ] ; } ) ; } export function subtypeNameFromSubtype ( subtype ) { return subtype . match ( function ( namedAttributeCollectionSubtype ) { return StringUtils . capitalize ( namedAttributeCollectionSubtype . name ) ; } , function ( attribute ) { return StringUtils . capitalize ( attribute . name ) ; } ) ; } function buildAttributesFromSubtype ( soFar , subtype ) { return soFar . concat ( attributesFromSubtype ( subtype ) ) ; } export function allAttributesFromSubtypes ( subtypes ) { return subtypes . reduce ( buildAttributesFromSubtype , [ ] ) ; } export function mapAttributesWithSubtypeFromSubtypes < T > ( subtypes , mapper ) { return subtypes . reduce ( function ( soFar , subtype ) { return soFar . concat ( attributesFromSubtype ( subtype ) . map ( ( attribute ) => mapper ( subtype , attribute ) ) ) ; } , [ ] ) ; } function typeForUnderlyingType ( underlyingType ) { return { name : underlyingType , reference : underlyingType === 'NSObject' ? 'NSObject*' : underlyingType } ; } export function computeTypeOfAttribute ( attribute ) { return Maybe . match ( typeForUnderlyingType , function ( ) : ObjC . Type { return { name : attribute . type . name , reference : attribute . type . reference } ; } , attribute . type . underlyingType ) ; } export function computeOriginalTypeOfAttribute ( attribute ) { return { name : attribute . type . name , reference : attribute . type . reference } ; } export function nameOfInstanceVariableForAttribute ( subtype , attribute ) { return subtype . match ( function ( namedAttributeCollectionSubtype ) { return ( StringUtils . lowercased ( namedAttributeCollectionSubtype . name ) + '_' + StringUtils . lowercased ( attribute . name ) ) ; } , function ( attribute ) { return StringUtils . lowercased ( attribute . name ) ; } ) ; } export function valueAccessorForInstanceVariableForAttribute ( subtype , attribute ) { return '_' + nameOfInstanceVariableForAttribute ( subtype , attribute ) ; } export function EnumerationNameForAlgebraicType ( algebraicType ) { return algebraicType . name + 'Subtypes' ; } export function EnumerationValueNameForSubtype ( algebraicType , subtype ) { return ( EnumerationNameForAlgebraicType ( algebraicType ) + subtypeNameFromSubtype ( subtype ) ) ; } function caseStatementForSubtypeWithSubtypeMapper ( algebraicType , subtypeMapper , soFar , subtype ) { const internalCode = subtypeMapper ( algebraicType , subtype ) ; const code = [ 'case ' + EnumerationValueNameForSubtype ( algebraicType , subtype ) + ': {' , ] . concat ( internalCode . map ( StringUtils . indent ( 2 ) ) ) . concat ( [ '  break;' , '}' ] ) ; return soFar . concat ( code ) ; } export function codeForSwitchingOnSubtypeWithSubtypeMapper ( algebraicType , subtypeValueAccessor , subtypeMapper ) { const caseStatements = algebraicType . subtypes . reduce < string [ ] > ( ( soFar , subtype ) => caseStatementForSubtypeWithSubtypeMapper ( algebraicType , subtypeMapper , soFar , subtype ) , [ ] ) ; return [ 'switch (' + subtypeValueAccessor + ') {' ] . concat ( caseStatements . map ( StringUtils . indent ( 2 ) ) ) . concat ( '}' ) ; } function blockTypeNameForSubtype ( algebraicType , subtype , matchingBlockType ) { return Maybe . match ( function Just ( matchingBlockType ) { return ( algebraicType . name + StringUtils . capitalize ( matchingBlockType . name ) + StringUtils . capitalize ( subtypeNameFromSubtype ( subtype ) ) + 'MatchHandler' ) ; } , function Nothing ( ) { return ( algebraicType . name + StringUtils . capitalize ( subtypeNameFromSubtype ( subtype ) ) + 'MatchHandler' ) ; } , matchingBlockType ) ; } function blockTypeParameterForSubtypeAttribute ( attribute ) { return { name : attribute . name , type : { name : attribute . type . name , reference : attribute . type . reference } , nullability : attribute . nullability } ; } function blockParametersForSubtype ( subtype ) { const attributes = attributesFromSubtype ( subtype ) ; if ( attributes . length > 0 ) { return attributes . map ( blockTypeParameterForSubtypeAttribute ) ; } else { return [ ] ; } } export function returnTypeForMatchingBlockType ( matchingBlockType ) { return matchingBlockType != null ? { type : typeForUnderlyingType ( matchingBlockType . underlyingType ) , modifiers : matchingBlockType . modifiers } : { type : null , modifiers : [ ] } ; } export function blockTypeForSubtype ( algebraicType , matchingBlockType , subtype ) { return { comments : [ ] , name : blockTypeNameForSubtype ( algebraicType , subtype , matchingBlockType ) , parameters : blockParametersForSubtype ( subtype ) , returnType : returnTypeForMatchingBlockType ( matchingBlockType ) , isPublic : true , nullability : algebraicType . includes . indexOf ( 'RMAssumeNonnull' ) >= 0 ? ObjC . ClassNullability . assumeNonnull : ObjC . ClassNullability . default } ; } export function blockParameterNameForMatchMethodFromSubtype ( subtype ) { return StringUtils . lowercased ( subtypeNameFromSubtype ( subtype ) + 'MatchHandler' ) ; } export function keywordForMatchMethodFromSubtype ( algebraicType , matchingBlockType , assumesNonnull , subtype ) { const blockType = blockTypeForSubtype ( algebraicType , matchingBlockType , subtype ) ; return { name : StringUtils . lowercased ( subtypeNameFromSubtype ( subtype ) ) , argument : { name : blockParameterNameForMatchMethodFromSubtype ( subtype ) , modifiers : ( assumesNonnull ? [ ObjC . KeywordArgumentModifier . Nullable ( ) ] : [ ] ) . concat ( [ ObjC . KeywordArgumentModifier . Noescape ( ) , ObjC . KeywordArgumentModifier . UnsafeUnretained ( ) , ] ) , type : { name : blockType . name , reference : blockType . name } } } ; } export function firstKeywordForMatchMethodFromSubtype ( algebraicType , matchingBlockType , assumesNonnull , subtype ) { const normalKeyword = keywordForMatchMethodFromSubtype ( algebraicType , matchingBlockType , assumesNonnull , subtype ) ; return { argument : normalKeyword . argument , name : Maybe . match ( function Just ( matchingBlockType ) { return ( 'match' + StringUtils . capitalize ( matchingBlockType . name ) + StringUtils . capitalize ( normalKeyword . name ) ) ; } , function Nothing ( ) { return 'match' + StringUtils . capitalize ( normalKeyword . name ) ; } , matchingBlockType ) } ; } function swiftNameForAlgebraicTypeMatcher ( algebraicType ) { const keywords = algebraicType . subtypes . map ( ( subtype ) => StringUtils . swiftCaseForString ( subtypeNameFromSubtype ( subtype ) ) + ':' ) . join ( '' ) ; return `NS_SWIFT_NAME(match(${ keywords }))` ; } function instanceMethodKeywordsForMatchingSubtypesOfAlgebraicType ( algebraicType , matchingBlockType , nullable ) { const firstKeyword = firstKeywordForMatchMethodFromSubtype ( algebraicType , matchingBlockType , nullable , algebraicType . subtypes [ 0 ] ) ; const additionalKeywords = algebraicType . subtypes . slice ( 1 ) . map ( ( subtype ) => keywordForMatchMethodFromSubtype ( algebraicType , matchingBlockType , nullable , subtype ) ) ; return [ firstKeyword ] . concat ( additionalKeywords ) ; } function blockInvocationWithNilCheckForSubtype ( algebraicType , subtype ) { return [ 'if (' + blockParameterNameForMatchMethodFromSubtype ( subtype ) + ') {' , StringUtils . indent ( 2 ) ( blockInvocationForSubtype ( algebraicType , subtype ) ) , '}' , ] ; } function resultReturningBlockInvocationWithNilCheckForSubtype ( algebraicType , subtype ) { return [ 'if (' + blockParameterNameForMatchMethodFromSubtype ( subtype ) + ') {' , StringUtils . indent ( 2 ) ( 'result = ' + blockInvocationForSubtype ( algebraicType , subtype ) ) , '}' , ] ; } function blockInvocationForSubtype ( algebraicType , subtype ) { return ( blockParameterNameForMatchMethodFromSubtype ( subtype ) + '(' + attributesFromSubtype ( subtype ) . map ( ( attribute ) => valueAccessorForInstanceVariableForAttribute ( subtype , attribute ) ) . join ( ', ' ) + ');' ) ; } function matcherCodeForAlgebraicType ( algebraicType , matchingBlockType ) { return Maybe . match ( function Just ( matchingBlockType ) { const switchStatement = codeForSwitchingOnSubtypeWithSubtypeMapper ( algebraicType , valueAccessorForInstanceVariableStoringSubtype ( ) , resultReturningBlockInvocationWithNilCheckForSubtype ) ; return [ '__block ' + matchingBlockType . underlyingType + ' result = ' + matchingBlockType . defaultValue + ';' , ] . concat ( switchStatement ) . concat ( 'return result;' ) ; } , function Nothing ( ) { return codeForSwitchingOnSubtypeWithSubtypeMapper ( algebraicType , valueAccessorForInstanceVariableStoringSubtype ( ) , blockInvocationWithNilCheckForSubtype ) ; } , matchingBlockType ) ; } export function instanceMethodForMatchingSubtypesOfAlgebraicType ( algebraicType , matchingBlockType , assumesNonnull ) { return { preprocessors : [ ] , belongsToProtocol : null , code : matcherCodeForAlgebraicType ( algebraicType , matchingBlockType ) , comments : [ ] , compilerAttributes : [ swiftNameForAlgebraicTypeMatcher ( algebraicType ) , ] . concat ( matchingBlockType == null ? [ ] : '__attribute__((warn_unused_result))' ) , keywords : instanceMethodKeywordsForMatchingSubtypesOfAlgebraicType ( algebraicType , matchingBlockType , assumesNonnull ) , returnType : returnTypeForMatchingBlockType ( matchingBlockType ) } ; }