import { flattenBinArray } from '../../format/format' ; import { AuthenticationErrorCommon , encodeDataPush , } from '../../vm/instruction-sets/instruction-sets' ; import { AuthenticationVirtualMachine } from '../../vm/virtual-machine' ; import { AuthenticationProgramStateError , AuthenticationProgramStateExecutionStack , AuthenticationProgramStateStack , } from '../../vm/vm-types' ; import { CompilationError , Range , ResolvedScript , ScriptReductionTraceChildNode , ScriptReductionTraceScriptNode , } from './language-types' ; import { mergeRanges } from './language-utils' ; const emptyReductionTraceNode = ( range ) => ( { bytecode : Uint8Array . of ( ) , range , } ) ; param state export const verifyBtlEvaluationState = < ProgramState extends AuthenticationProgramStateStack & AuthenticationProgramStateExecutionStack & AuthenticationProgramStateError < unknown > > ( state ) => { if ( state . error !== undefined ) { return state . error ; } if ( state . executionStack . length !== 0 ) { return AuthenticationErrorCommon . nonEmptyExecutionStack ; } if ( state . stack . length !== 1 ) { return AuthenticationErrorCommon . requiresCleanStack ; } return true ; } ; param resolvedScript param vm param createEvaluationProgram export const reduceScript = < ProgramState extends AuthenticationProgramStateStack & AuthenticationProgramStateExecutionStack & AuthenticationProgramStateError < unknown > , AuthenticationProgram > ( resolvedScript , vm ? , createEvaluationProgram ? ) : ScriptReductionTraceScriptNode < ProgramState > => { const script = resolvedScript . map < ScriptReductionTraceChildNode < ProgramState > > ( ( segment ) => { switch ( segment . type ) { case 'bytecode' : return { bytecode : segment . value , range : segment . range } ; case 'push' : { const push = reduceScript ( segment . value , vm , createEvaluationProgram ) ; const bytecode = encodeDataPush ( push . bytecode ) ; return { bytecode , ... ( push . errors === undefined ? undefined : { errors : push . errors } ) , push , range : segment . range , } ; } case 'evaluation' : { if ( typeof vm === 'undefined' || typeof createEvaluationProgram === 'undefined' ) { return { errors : [ { error : 'Both a VM and a createState method are required to reduce evaluations.' , range : segment . range , } , ] , ... emptyReductionTraceNode ( segment . range ) , } ; } const reductionTrace = reduceScript ( segment . value , vm , createEvaluationProgram ) ; if ( reductionTrace . errors !== undefined ) { return { ... emptyReductionTraceNode ( segment . range ) , errors : reductionTrace . errors , source : reductionTrace , trace : [ ] , } ; } const trace = vm . debug ( createEvaluationProgram ( reductionTrace . bytecode ) ) ; /**
                 * `vm.debug` should always return at least one state.
                 */ const lastState = trace [ trace . length - 1 ] ; const result = verifyBtlEvaluationState ( lastState ) ; const bytecode = lastState . stack [ lastState . stack . length - 1 ] ; return { ... ( typeof result === 'string' ? { bytecode : Uint8Array . of ( ) , errors : [ { error : `Failed to reduce evaluation: ${ result }` , range : segment . range , } , ] , } : { bytecode , } ) , range : segment . range , source : reductionTrace , trace , } ; } case 'comment' : return emptyReductionTraceNode ( segment . range ) ; case 'error' : return { errors : [ { error : `Tried to reduce a BTL script with resolution errors: ${ segment . value }` , range : segment . range , } , ] , ... emptyReductionTraceNode ( segment . range ) , } ; default : throw new Error ( `"${ ( segment as { type : string ; } ) . type }" is not a known segment type.` ) as never ; } } ) ; const reduction = script . reduce < { bytecode : Uint8Array [ ] ; errors ? : CompilationError [ ] | undefined ; ranges : Range [ ] ; } > ( ( all , segment ) => ( { bytecode : [ ... all . bytecode , segment . bytecode ] , ranges : [ ... all . ranges , segment . range ] , ... ( all . errors !== undefined || segment . errors !== undefined ? { errors : [ ... ( all . errors === undefined ? [ ] : all . errors ) , ... ( segment . errors === undefined ? [ ] : segment . errors ) , ] , } : undefined ) , } ) , { bytecode : [ ] , ranges : [ ] } ) ; return { ... ( reduction . errors === undefined ? undefined : { errors : reduction . errors } ) , bytecode : flattenBinArray ( reduction . bytecode ) , range : mergeRanges ( reduction . ranges , resolvedScript . length === 0 ? undefined : resolvedScript [ 0 ] . range ) , script , } ; } ;