import { FunctionData , InstructionHolder } from 'js-yaml-loader!@/*' ; import { createFunctionWithInlineCode , createCallerFunction } from './SharedFunction' ; import { SharedFunctionCollection } from './SharedFunctionCollection' ; import { ISharedFunctionCollection } from './ISharedFunctionCollection' ; import { ISharedFunctionsParser } from './ISharedFunctionsParser' ; import { FunctionParameter } from './Parameter/FunctionParameter' ; import { FunctionParameterCollection } from './Parameter/FunctionParameterCollection' ; import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection' ; import { ISharedFunction } from './ISharedFunction' ; import { parseFunctionCalls } from './Call/FunctionCallParser' ; export class SharedFunctionsParser implements ISharedFunctionsParser { public static readonly instance : ISharedFunctionsParser = new SharedFunctionsParser ( ) ; public parseFunctions ( functions ) { const collection = new SharedFunctionCollection ( ) ; if ( ! functions || ! functions . length ) { return collection ; } ensureValidFunctions ( functions ) ; return functions . map ( ( func ) => parseFunction ( func ) ) . reduce ( ( acc , func ) => { acc . addFunction ( func ) ; return acc ; } , collection ) ; } } function parseFunction ( data ) { const { name } = data ; const parameters = parseParameters ( data ) ; if ( hasCode ( data ) ) { return createFunctionWithInlineCode ( name , parameters , data . code , data . revertCode ) ; } const calls = parseFunctionCalls ( data . call ) ; return createCallerFunction ( name , parameters , calls ) ; } function parseParameters ( data ) { return ( data . parameters || [ ] ) . map ( ( parameter ) => { try { return new FunctionParameter ( parameter . name , parameter . optional || false ) ; } catch ( err ) { throw new Error ( `"${ data . name }": ${ err . message }` ) ; } } ) . reduce ( ( parameters , parameter ) => { parameters . addParameter ( parameter ) ; return parameters ; } , new FunctionParameterCollection ( ) ) ; } function hasCode ( data ) { return Boolean ( data . code ) ; } function hasCall ( data ) { return Boolean ( data . call ) ; } function ensureValidFunctions ( functions ) { ensureNoUndefinedItem ( functions ) ; ensureNoDuplicatesInFunctionNames ( functions ) ; ensureNoDuplicateCode ( functions ) ; ensureEitherCallOrCodeIsDefined ( functions ) ; ensureExpectedParametersType ( functions ) ; } function printList ( list ) { return `"${ list . join ( '","' ) }"` ; } function ensureEitherCallOrCodeIsDefined ( holders ) { const withBothCallAndCode = holders . filter ( ( holder ) => hasCode ( holder ) && hasCall ( holder ) ) ; if ( withBothCallAndCode . length ) { throw new Error ( `both "code" and "call" are defined in ${ printNames ( withBothCallAndCode ) }` ) ; } const hasEitherCodeOrCall = holders . filter ( ( holder ) => ! hasCode ( holder ) && ! hasCall ( holder ) ) ; if ( hasEitherCodeOrCall . length ) { throw new Error ( `neither "code" or "call" is defined in ${ printNames ( hasEitherCodeOrCall ) }` ) ; } } function ensureExpectedParametersType ( functions ) { const unexpectedFunctions = functions . filter ( ( func ) => func . parameters && ! isArrayOfObjects ( func . parameters ) ) ; if ( unexpectedFunctions . length ) { const errorMessage = `parameters must be an array of objects in function(s) ${ printNames ( unexpectedFunctions ) }` ; throw new Error ( errorMessage ) ; } } function isArrayOfObjects ( value ) { return Array . isArray ( value ) && value . every ( ( item ) => typeof item === 'object' ) ; } function printNames ( holders ) { return printList ( holders . map ( ( holder ) => holder . name ) ) ; } function ensureNoDuplicatesInFunctionNames ( functions ) { const duplicateFunctionNames = getDuplicates ( functions . map ( ( func ) => func . name . toLowerCase ( ) ) ) ; if ( duplicateFunctionNames . length ) { throw new Error ( `duplicate function name: ${ printList ( duplicateFunctionNames ) }` ) ; } } function ensureNoUndefinedItem ( functions ) { if ( functions . some ( ( func ) => ! func ) ) { throw new Error ( 'some functions are undefined' ) ; } } function ensureNoDuplicateCode ( functions ) { const duplicateCodes = getDuplicates ( functions . map ( ( func ) => func . code ) . filter ( ( code ) => code ) ) ; if ( duplicateCodes . length > 0 ) { throw new Error ( `duplicate "code" in functions: ${ printList ( duplicateCodes ) }` ) ; } const duplicateRevertCodes = getDuplicates ( functions . filter ( ( func ) => func . revertCode ) . map ( ( func ) => func . revertCode ) ) ; if ( duplicateRevertCodes . length > 0 ) { throw new Error ( `duplicate "revertCode" in functions: ${ printList ( duplicateRevertCodes ) }` ) ; } } function getDuplicates ( texts ) { return texts . filter ( ( item , index ) => texts . indexOf ( item ) !== index ) ; }