import { hexToBin , utf8ToBin } from '../../format/format' ; import { bigIntToScriptNumber } from '../../vm/instruction-sets/instruction-sets' ; import { AnyCompilationEnvironment , CompilationData , CompilationEnvironment , CompilerOperation , CompilerOperationResult , } from '../compiler-types' ; import { AuthenticationTemplateVariable } from '../template-types' ; import { compileScriptRaw } from './compile' ; import { BtlScriptSegment , CompilationResultSuccess , IdentifierResolutionErrorType , IdentifierResolutionFunction , IdentifierResolutionType , MarkedNode , Range , ResolvedScript , ResolvedSegment , } from './language-types' ; import { stringifyErrors } from './language-utils' ; const pluckRange = ( node ) : Range => ( { endColumn : node . end . column , endLineNumber : node . end . line , startColumn : node . start . column , startLineNumber : node . start . line , } ) ; const removeNumericSeparators = ( numericLiteral ) => numericLiteral . replace ( /_/gu , '' ) ; export const resolveScriptSegment = ( segment , resolveIdentifiers ) : ResolvedScript => { const resolved = segment . value . map < ResolvedSegment > ( ( child ) => { const range = pluckRange ( child ) ; switch ( child . name ) { case 'Identifier' : { const identifier = child . value ; const result = resolveIdentifiers ( identifier ) ; const ret = result . status ? { range , type : 'bytecode' as const , value : result . bytecode , ... ( result . type === IdentifierResolutionType . opcode ? { opcode : identifier , } : result . type === IdentifierResolutionType . variable ? { ... ( 'debug' in result ? { debug : result . debug } : { } ) , ... ( 'signature' in result ? { signature : result . signature } : { } ) , variable : identifier , } : result . type === IdentifierResolutionType . script ? { script : identifier , source : result . source } : ( { unknown : identifier } as never ) ) , } : { ... ( 'debug' in result ? { debug : result . debug } : { } ) , ... ( 'recoverable' in result && result . recoverable ? { missingIdentifier : identifier , owningEntity : result . entityOwnership , } : { } ) , range , type : 'error' as const , value : result . error , } ; return ret ; } case 'Push' : return { range , type : 'push' as const , value : resolveScriptSegment ( child . value , resolveIdentifiers ) , } ; case 'Evaluation' : return { range , type : 'evaluation' as const , value : resolveScriptSegment ( child . value , resolveIdentifiers ) , } ; case 'BigIntLiteral' : return { literal : child . value , literalType : 'BigIntLiteral' as const , range , type : 'bytecode' as const , value : bigIntToScriptNumber ( BigInt ( removeNumericSeparators ( child . value ) ) ) , } ; case 'BinaryLiteral' : return { literal : child . value , literalType : 'BinaryLiteral' as const , range , type : 'bytecode' as const , value : bigIntToScriptNumber ( BigInt ( `0b${ removeNumericSeparators ( child . value ) }` ) ) , } ; case 'HexLiteral' : return { literal : child . value , literalType : 'HexLiteral' as const , range , type : 'bytecode' as const , value : hexToBin ( removeNumericSeparators ( child . value ) ) , } ; case 'UTF8Literal' : return { literal : child . value , literalType : 'UTF8Literal' as const , range , type : 'bytecode' as const , value : utf8ToBin ( child . value ) , } ; case 'Comment' : return { range , type : 'comment' as const , value : child . value , } ; default : return { range , type : 'error' as const , value : `Unrecognized segment: ${ ( child as { name : string ; } ) . name }` , } ; } } ) ; return resolved . length === 0 ? [ { range : pluckRange ( segment ) , type : 'comment' as const , value : '' } ] : resolved ; } ; export enum BuiltInVariables { currentBlockTime = 'current_block_time' , currentBlockHeight = 'current_block_height' , signingSerialization = 'signing_serialization' } const attemptCompilerOperation = < TransactionContext , Environment extends AnyCompilationEnvironment < TransactionContext > > ( { data , environment , identifier , matchingOperations , operationExample = 'operation_identifier' , operationId , variableId , variableType , } ) : CompilerOperationResult < true > => { if ( matchingOperations === undefined ) { return { error : `The "${ variableId }" variable type can not be resolved because the "${ variableType }" operation has not been included in this compiler's CompilationEnvironment.` , status : 'error' , } ; } if ( typeof matchingOperations === 'function' ) { const operation = matchingOperations ; return operation ( identifier , data , environment ) ; } if ( operationId === undefined ) { return { error : `This "${ variableId }" variable could not be resolved because this compiler's "${ variableType }" operations require an operation identifier, e.g. '${ variableId }.${ operationExample }'.` , status : 'error' , } ; } const operation = ( matchingOperations as { [ x ] : CompilerOperation < TransactionContext > | undefined ; } ) [ operationId ] ; if ( operation === undefined ) { return { error : `The identifier "${ identifier }" could not be resolved because the "${ variableId }.${ operationId }" operation is not available to this compiler.` , status : 'error' , } ; } return operation ( identifier , data , environment ) ; } ; param identifier param data param environment export const resolveVariableIdentifier = < TransactionContext , Environment extends AnyCompilationEnvironment < TransactionContext > > ( { data , environment , identifier , } ) : CompilerOperationResult < true > => { const [ variableId , operationId ] = identifier . split ( '.' ) as [ string , string | undefined ] ; switch ( variableId ) { case BuiltInVariables . currentBlockHeight : return attemptCompilerOperation ( { data , environment , identifier , matchingOperations : environment . operations ?. currentBlockHeight , operationId , variableId , variableType : 'currentBlockHeight' , } ) ; case BuiltInVariables . currentBlockTime : return attemptCompilerOperation ( { data , environment , identifier , matchingOperations : environment . operations ?. currentBlockTime , operationId , variableId , variableType : 'currentBlockTime' , } ) ; case BuiltInVariables . signingSerialization : return attemptCompilerOperation ( { data , environment , identifier , matchingOperations : environment . operations ?. signingSerialization , operationExample : 'version' , operationId , variableId , variableType : 'signingSerialization' , } ) ; default : { const expectedVariable = environment . variables ?. [ variableId ] ; if ( expectedVariable === undefined ) { return { status : 'skip' } ; } return attemptCompilerOperation ( { data , environment , identifier , operationId , variableId , ... { AddressData : { matchingOperations : environment . operations ?. addressData , variableType : 'addressData' , } , HdKey : { matchingOperations : environment . operations ?. hdKey , operationExample : 'public_key' , variableType : 'hdKey' , } , Key : { matchingOperations : environment . operations ?. key , operationExample : 'public_key' , variableType : 'key' , } , WalletData : { matchingOperations : environment . operations ?. walletData , variableType : 'walletData' , } , } [ expectedVariable . type ] , } ) ; } } } ; remarks param identifier param data param environment param parentIdentifier export const resolveScriptIdentifier = < TransactionContext , ProgramState > ( { data , environment , identifier , } ) : CompilationResultSuccess < ProgramState > | string | false => { if ( ( environment . scripts [ identifier ] as string | undefined ) === undefined ) { return false ; } const result = compileScriptRaw ( { data , environment , scriptId : identifier } ) ; if ( result . success ) { return result ; } return `Compilation error in resolved script "${ identifier }": ${ stringifyErrors ( result . errors ) }` ; } ; param scriptId param environment param data export const createIdentifierResolver = < TransactionContext > ( { data , environment , } ) : IdentifierResolutionFunction => ( identifier ) : ReturnType < IdentifierResolutionFunction > => { const opcodeResult = environment . opcodes ?. [ identifier ] ; if ( opcodeResult !== undefined ) { return { bytecode : opcodeResult , status : true , type : IdentifierResolutionType . opcode , } ; } const variableResult = resolveVariableIdentifier ( { data , environment , identifier , } ) ; if ( variableResult . status !== 'skip' ) { return variableResult . status === 'error' ? { ... ( 'debug' in variableResult ? { debug : variableResult . debug } : { } ) , error : variableResult . error , ... ( environment . entityOwnership === undefined ? { } : { entityOwnership : environment . entityOwnership [ identifier . split ( '.' ) [ 0 ] ] , } ) , recoverable : 'recoverable' in variableResult , status : false , type : IdentifierResolutionErrorType . variable , } : { ... ( 'debug' in variableResult ? { debug : variableResult . debug } : { } ) , bytecode : variableResult . bytecode , ... ( 'signature' in variableResult ? { signature : variableResult . signature , } : { } ) , status : true , type : IdentifierResolutionType . variable , } ; } const scriptResult = resolveScriptIdentifier ( { data , environment , identifier , } ) ; if ( scriptResult !== false ) { return typeof scriptResult === 'string' ? { error : scriptResult , scriptId : identifier , status : false , type : IdentifierResolutionErrorType . script , } : { bytecode : scriptResult . bytecode , source : scriptResult . resolve , status : true , type : IdentifierResolutionType . script , } ; } return { error : `Unknown identifier "${ identifier }".` , status : false , type : IdentifierResolutionErrorType . unknown , } ; } ;