import { Labels } from '../labels/labels' ; import { Settings } from '../settings' ; import { Z80RegistersClass } from '../remotes/z80registers' ; import { Remote } from '../remotes/remotebase' ; import * as fs from 'fs' ; import { UnifiedPath } from './unifiedpath' ; import { Log } from '../log' ; import * as requireFromString from 'require-from-string' ; import * as vm from 'vm' ; import * as jsonc from 'jsonc-parser' ; /**
 * A collection of useful functions.
 */ export class Utility { protected static rootPath : string ; protected static extensionPath : string ; param value param boundary returns public static getBoundary ( value , boundary ) { if ( value < 0 ) return 0 ; const boundValue = value - ( value % boundary ) ; return boundValue ; } param value param size returns public static getHexString ( value , size ) { if ( value != undefined ) { const s = value . toString ( 16 ) . toUpperCase ( ) . padStart ( size , '0' ) ; return s ; } return "?" . repeat ( size ) ; } param value returns bank5 public static getLongAddressString ( value ) { let addrString = this . getHexString ( value & 0xFFFF , 4 ) + 'h' ; const bank = value >>> 16 ; if ( bank > 0 ) addrString += " @bank" + ( bank - 1 ) ; return addrString ; } param value param size public static getBitsString ( value , size ) { const s = value . toString ( 2 ) . padStart ( size , '0' ) ; return s ; } param line returns public static stripComment ( line ) { const k = line . indexOf ( ';' ) ; if ( k < 0 ) return line ; return line . substr ( 0 , k ) ; } param valueString returns public static parseValue ( valueString ) { const match = /^\s*((0x|\$)([0-9a-f]+)([^0-9a-f]*))?(([0-9a-f]+)h(.*))?(([01]+)b(.*))?(_([szhnpc]+)([^szhnpc])*)?((-?\d+)(\D*))?('([\S ]+)')?/i . exec ( valueString ) ; if ( ! match ) return NaN ; const ghex = match [ 3 ] ; const ghex_empty = match [ 4 ] ; const ghexh = match [ 6 ] ; const ghexh_empty = match [ 7 ] ; const gbit = match [ 9 ] ; const gbit_empty = match [ 10 ] ; let gflags = match [ 12 ] ; const gflags_empty = match [ 13 ] ; const gdec = match [ 15 ] ; const gdec_empty = match [ 16 ] ; const gchar = match [ 18 ] ; if ( ghex ) { if ( ghex_empty ) return NaN ; return parseInt ( ghex , 16 ) ; } if ( ghexh ) { if ( ghexh_empty ) return NaN ; return parseInt ( ghexh , 16 ) ; } if ( gdec ) { if ( gdec_empty ) return NaN ; return parseInt ( gdec , 10 ) ; } if ( gbit ) { if ( gbit_empty ) return NaN ; return parseInt ( gbit , 2 ) ; } if ( gflags ) { if ( gflags_empty ) return NaN ; gflags = gflags . toLowerCase ( ) ; let flags = 0 ; if ( gflags . includes ( 's' ) ) flags |= 0x80 ; if ( gflags . includes ( 'z' ) ) flags |= 0x40 ; if ( gflags . includes ( 'h' ) ) flags |= 0x10 ; if ( gflags . includes ( 'p' ) ) flags |= 0x04 ; if ( gflags . includes ( 'n' ) ) flags |= 0x02 ; if ( gflags . includes ( 'c' ) ) flags |= 0x01 ; return flags ; } if ( gchar ) { if ( gchar . length < 1 ) return NaN ; return gchar . charCodeAt ( 0 ) ; } return NaN ; } param expr returns public static exprContainsMainRegisters ( expr ) { const regString = 'pc|sp|af|bc|de|hl|ix|iy|a|f|b|c|d|e|h|l' ; const regex = new RegExp ( '\\b(' + regString + '|' + regString . toUpperCase ( ) + ')\\b' ) ; const match = regex . exec ( expr ) ; return ( match != undefined ) ; } param expr param evalRegisters param modulePrefix param lastLabel returns public static replaceVarsWithValues ( expr , evalRegisters = true , modulePrefix ? , lastLabel ? ) { const exprLabelled = expr . replace ( /(0x[a-fA-F0-9]+|[a-zA-Z_\.][a-zA-Z0-9_\.]*'?|[\$][0-9a-fA-F]+|[a-fA-F0-9]+h|[01]+b|\d+|'[\S ]+')/g , ( match , p1 ) => { let res ; if ( evalRegisters ) { if ( Z80RegistersClass . isRegister ( p1 ) ) { try { res = Remote . getRegisterValue ( p1 ) ; } catch { } } } if ( isNaN ( res ) ) { let lbl = p1 ; if ( lastLabel && lbl . startsWith ( '.' ) ) { lbl = lastLabel + lbl ; } if ( modulePrefix ) { res = Labels . getNumberFromString64k ( modulePrefix + lbl ) || NaN ; } if ( isNaN ( res ) ) { res = Labels . getNumberFromString64k ( lbl ) ; if ( isNaN ( res ) ) res = p1 ; } } return res . toString ( ) ; } ) ; return exprLabelled ; } returns public static evalExpression ( expr , evalRegisters = true , modulePrefix ? , lastLabel ? ) { try { const exprLabelled = this . replaceVarsWithValues ( expr , evalRegisters , modulePrefix , lastLabel ) ; const result = eval ( exprLabelled ) ; if ( typeof ( result ) == 'boolean' ) return ( result ) ? 1 : 0 ; return result ; } catch ( e ) { throw Error ( "Error evaluating '" + expr + "': " + e . message ) ; } } returns param label param modulePrefix param lastLabel returns public static createFullLabel ( label , modulePrefix ? , lastLabel ? ) { if ( lastLabel && label . startsWith ( '.' ) ) { label = lastLabel + label ; } if ( modulePrefix ) { label = modulePrefix + label ; } return label ; } param logString public static async evalLogString ( logString ) { const offsets = [ ] ; const promises = [ ] ; const regex = /\${(.*?)(:(.*?))?}/g ; const reAt = /([bw]@)?\((.*?)\)/i ; let offsCorrection = 0 ; logString = logString . replace ( regex , ( match , statement , p2 , format , offset ) => { let promise = new Promise < string > ( async ( resolve ) => { let size = 1 ; try { let value ; const reMatch = reAt . exec ( statement ) ; if ( reMatch ) { size = ( reMatch [ 1 ] ?. startsWith ( 'w' ) ) ? 2 : 1 ; const addrString = reMatch [ 2 ] ; const addr = Utility . evalExpression ( addrString ) ; const memValues = await Remote . readMemoryDump ( addr , size ) ; value = memValues [ 0 ] ; if ( size > 1 ) value += memValues [ 1 ] << 8 ; } else { value = Utility . evalExpression ( statement , true ) ; if ( Z80RegistersClass . isRegister ( statement ) && statement . length > 1 ) size = 2 ; } let formatString = format || 'unsigned' ; formatString = '${' + formatString + '}' ; const result = await this . numberFormatted ( '' , value , size , formatString , undefined ) ; resolve ( result ) ; } catch ( e ) { resolve ( e ) ; } } ) ; offset -= offsCorrection ; offsets . push ( offset ) ; promises . push ( promise ) ; offsCorrection += match . length ; return '' ; } ) ; const data = await Promise . all ( promises ) ; let result = '' ; let replacement ; let i = 0 ; while ( replacement = data . shift ( ) ) { const offset = offsets . shift ( ) as number ; const length = offset - i ; result += logString . substr ( i , length ) ; i = offset ; result += replacement ; } result += logString . substr ( i ) ; return result ; } param format param size returns public static calculateTabSizes ( format , size ) { if ( ! format . includes ( '\t' ) ) return null ; const result = format . replace ( /\${([^}]*?:)?([^:]*?)(:[\s\S]*?)?}/g , ( match , p1 , p2 , p3 ) => { let usedSize = size ; const modifier = ( p1 == null ) ? '' : p1 . substr ( 0 , p1 . length - 1 ) ; switch ( modifier ) { case 'b@' : usedSize = 1 ; break ; case 'w@' : usedSize = 2 ; break ; } switch ( p2 ) { case 'name' : return "nn" ; case 'hex' : return "h" . repeat ( 2 * usedSize ) ; case 'dhex' : if ( usedSize == 2 ) return "hhhhh" ; case 'hex' : return "h" . repeat ( 2 * usedSize ) ; case 'bits' : return "b" . repeat ( 8 * usedSize ) ; case 'unsigned' : return ( Math . pow ( 256 , usedSize ) - 1 ) . toString ( ) ; case 'signed' : return '-' + ( Math . pow ( 256 , usedSize ) / 2 ) . toString ( ) ; case 'char' : return "c" ; case 'flags' : return "SZHPNC" ; case 'labels' : case 'labelsplus' : return "ll" ; } return "" ; } ) ; const arr = result . split ( '\t' ) ; return arr ; } param value returns public static getASCIIChar ( value ) { if ( value == 0 ) return '0\u0332' ; if ( value >= 32 && value < 127 ) return String . fromCharCode ( value ) ; return '.' ; } param value returns public static getHTMLChar ( value ) { const res = ( value == ' ' . charCodeAt ( 0 ) ) ? '&nbsp;' : Utility . getASCIIChar ( value ) ; return res ; } param name param value param size param format param tabSizeArr param undefText returns public static async numberFormatted ( name , value , size , format , tabSizeArr , undefText = "undefined" ) { if ( value == undefined ) { return undefText ; } let memWord = 0 ; let regsAsWell = false ; if ( ! Z80RegistersClass . isRegister ( name ) ) { regsAsWell = true ; } const matchAddr = /(\${b@:|\${w@:)/ . exec ( format ) ; if ( matchAddr ) { const data = await Remote . readMemoryDump ( value , 2 ) ; const b1 = data [ 0 ] ; const b2 = data [ 1 ] ; memWord = ( b2 << 8 ) + b1 ; } const valString = Utility . numberFormattedSync ( value , size , format , regsAsWell , name , memWord , tabSizeArr ) ; return valString ; } param value param size param format param regsAsWell param paramName param paramWordAtAddress param tabSizeArr returns public static numberFormattedSync ( value , size , format , regsAsWell = false , paramName ? , paramWordAtAddress ? , tabSizeArr ? ) { const name = paramName || '' ; const wordAtAddress = paramWordAtAddress || 0 ; let valString = format . replace ( /\${([\s\S]*?)(?=\${|$)/g , ( match , p ) => { const k = p . lastIndexOf ( '}' ) ; if ( k < 0 ) { return p ; } const p1 = p . substr ( 0 , k ) ; const restP = p . substr ( k + 1 ) ; const innerMatch = /^([^\|]*?:)?([^\|]*?)(\|[\s\S]*?)?(\|[\s\S]*?)?$/ . exec ( p1 ) ; if ( innerMatch == undefined ) return '${' + p1 + '???}' + restP ; let usedValue ; let usedSize ; let modifier = innerMatch [ 1 ] ; modifier = ( modifier == null ) ? '' : modifier . substr ( 0 , modifier . length - 1 ) ; switch ( modifier ) { case 'b@' : usedValue = wordAtAddress & 0xFF ; usedSize = 1 ; break ; case 'w@' : usedValue = wordAtAddress ; usedSize = 2 ; break ; case '' : default : usedValue = value ; usedSize = size ; break ; } const formatting = innerMatch [ 2 ] ; let innerLabelSeparator = innerMatch [ 3 ] ; innerLabelSeparator = ( innerLabelSeparator == null ) ? '' : innerLabelSeparator . substr ( 1 ) ; let endLabelSeparator = innerMatch [ 4 ] ; endLabelSeparator = ( endLabelSeparator == null ) ? '' : endLabelSeparator . substr ( 1 ) ; switch ( formatting ) { case 'name' : return name + restP ; case 'dhex' : if ( usedSize == 2 ) { return Utility . getHexString ( usedValue >> 8 , 2 ) + ' ' + Utility . getHexString ( usedValue & 0xFF , 2 ) + restP ; } case 'hex' : return Utility . getHexString ( usedValue , 2 * usedSize ) + restP ; case 'bits' : return Utility . getBitsString ( usedValue , usedSize * 8 ) + restP ; case 'unsigned' : return usedValue . toString ( ) + restP ; case 'signed' : const maxValue = Math . pow ( 256 , usedSize ) ; const halfMaxValue = maxValue / 2 ; return ( ( usedValue >= halfMaxValue ) ? usedValue - maxValue : usedValue ) . toString ( ) + restP ; case 'char' : const s = Utility . getASCIIChar ( usedValue ) ; return s + restP ; case 'flags' : const res = this . getFlagsString ( usedValue ) ; return res + restP ; case 'labels' : { const labels = Labels . getLabelsForNumber64k ( value , regsAsWell ) ; if ( labels && labels . length > 0 ) return modifier + labels . join ( innerLabelSeparator ) + endLabelSeparator + restP ; return '' + restP ; } case 'labelsplus' : { const labels = Labels . getLabelsPlusIndexForNumber64k ( value , regsAsWell ) ; if ( labels && labels . length > 0 ) return modifier + labels . join ( innerLabelSeparator ) + endLabelSeparator + restP ; return '' + restP ; } default : return '${' + 1 + '???}' + restP ; } } ) ; if ( tabSizeArr ) { if ( tabSizeArr . length == valString . split ( '\t' ) . length ) { let index = 0 ; valString += '\t' ; valString = valString . replace ( /(.*?)\t/g , ( match , p1 , offset ) => { const tabSize = tabSizeArr [ index ] . length ; ++ index ; let result = p1 . padStart ( tabSize ) + " " ; return result ; } ) ; } } else { valString = valString . replace ( /\t/g , ' ' ) ; } return valString ; } /**
     * Convert value to flags string.
     * Useful to convert the F register number into a human readable string.
     */ public static getFlagsString ( flagValue ) { let res = ( flagValue & 0x80 ) ? 'S' : '-' ; res += ( flagValue & 0x40 ) ? 'Z' : '-' ; res += ( flagValue & 0x20 ) ? '1' : '-' ; res += ( flagValue & 0x10 ) ? 'H' : '-' ; res += ( flagValue & 0x08 ) ? '1' : '-' ; res += ( flagValue & 0x04 ) ? 'P' : '-' ; res += ( flagValue & 0x02 ) ? 'N' : '-' ; res += ( flagValue & 0x01 ) ? 'C' : '-' ; return res ; } param memory param index param count param little_endian returns public static getUintFromMemory ( memory , index , count = 1 , littleEndian = true ) { let memVal = 0 ; if ( littleEndian ) { for ( let i = index + count - 1 ; i >= index ; i -- ) memVal = 256 * memVal + memory [ i ] ; } else { const end = index + count ; for ( let i = index ; i < end ; i ++ ) memVal = 256 * memVal + memory [ i ] ; } return memVal ; } param memVal param memory param index param count param little_endian returns public static setUintToMemory ( memVal , memory , index , count = 1 , littleEndian = true ) { if ( memVal < 0 ) memVal += ( 0x1 ) << ( 8 * count ) ; const end = index + count ; if ( littleEndian ) { for ( let i = index ; i < end ; i ++ ) { memory [ i ] = memVal % 256 ; memVal = Math . trunc ( memVal / 256 ) ; } } else { const end = index + count ; for ( let i = end - 1 ; i >= index ; i -- ) { memory [ i ] = memVal % 0x100 ; memVal = Math . trunc ( memVal / 0x100 ) ; } } } param regIn param formatMap returns public static async getFormattedRegister ( regIn , formatMap ) { const reg = regIn . toUpperCase ( ) ; const format = formatMap . get ( reg ) ; Utility . assert ( format != undefined , 'Register ' + reg + ' does not exist.' ) ; const value = Remote . getRegisterValue ( reg ) ; let rLen ; if ( reg == "IXH" || reg == "IXL" || reg == "IYH" || reg == "IYL" ) { rLen = 1 ; } else { rLen = reg . length ; if ( reg [ rLen - 1 ] == '\'' ) -- rLen ; } const formattedRegister = await Utility . numberFormatted ( reg , value , rLen , format , undefined ) ; return formattedRegister ; } param absFilePath returns public static getRelFilePath ( absFilePath ) { let filePath = absFilePath ; let rootPath = Utility . rootPath ; if ( rootPath ) { if ( ! rootPath . endsWith ( '/' ) ) rootPath += '/' ; const lcFilePath = UnifiedPath . getUnifiedPath ( filePath ) ; if ( lcFilePath . startsWith ( rootPath ) ) filePath = filePath . substr ( rootPath . length ) ; } return filePath ; } param relFilePath returns public static getAbsFilePath ( relFilePath , rootPath ? ) { if ( UnifiedPath . isAbsolute ( relFilePath ) ) return relFilePath ; const usedRootPath = ( rootPath ) ? rootPath : Utility . rootPath || '' ; const filePath = UnifiedPath . join ( usedRootPath , relFilePath ) ; return filePath ; } param srcPath param srcDirs public static getAbsSourceFilePath ( srcPath , srcDirs ) { if ( UnifiedPath . isAbsolute ( srcPath ) ) return srcPath ; for ( let srcDir of srcDirs ) { const fPath = UnifiedPath . join ( srcDir , srcPath ) ; const absFPath = Utility . getAbsFilePath ( fPath ) ; if ( fs . existsSync ( absFPath ) ) return absFPath ; } return srcPath ; } param srcPath param srcDirs public static getRelSourceFilePath ( srcPath , srcDirs ) { srcPath = UnifiedPath . getUnifiedPath ( srcPath ) ; if ( UnifiedPath . isAbsolute ( srcPath ) ) return Utility . getRelFilePath ( srcPath ) ; for ( let srcDir of srcDirs ) { const fPath = UnifiedPath . join ( srcDir , srcPath ) ; const absFPath = Utility . getAbsFilePath ( fPath ) ; if ( fs . existsSync ( absFPath ) ) return fPath ; } return srcPath ; } param fileName returns public static getRelTmpFilePath ( fileName ) { const relFilePath = UnifiedPath . join ( Settings . launch . tmpDir , fileName ) ; return relFilePath ; } param stateName returns public static getAbsStateFileName ( stateName ) { const fPath = UnifiedPath . join ( 'states' , stateName ) ; const relPath = Utility . getRelTmpFilePath ( fPath ) ; return Utility . getAbsFilePath ( relPath ) ; } param rootPath public static setRootPath ( rootPath ) { Utility . assert ( rootPath ) ; ( Utility . rootPath as any ) = UnifiedPath . getUnifiedPath ( rootPath ) ; } param rootPath public static getRootPath ( ) { return Utility . rootPath ; } param extPath public static setExtensionPath ( extPath ) { Utility . extensionPath = UnifiedPath . getUnifiedPath ( extPath ) ; } return public static getExtensionPath ( ) { return Utility . extensionPath ; } /**
     * Removes all files in tmpDir that start with "TMP_".
     */ public static removeAllTmpFiles ( ) { const dir = Settings . launch . tmpDir ; if ( ! fs . existsSync ( dir ) ) return ; const fileNames = fs . readdirSync ( dir ) ; for ( let fName of fileNames ) { if ( fName . startsWith ( "TMP_" ) ) { const absFName = Utility . getAbsFilePath ( fName , dir ) ; fs . unlinkSync ( absFName ) ; } } } param handler param interval public static delayedCall ( handler , interval = 0.1 ) { let count = 0 ; const f = ( ) => { const time = count * interval ; const result = handler ( time ) ; if ( result ) return ; count ++ ; setTimeout ( ( ) => { f ( ) ; } , interval * 1000 ) ; } ; f ( ) ; } param buffer param index param value public static setWord ( buffer , index , value ) { buffer [ index ] = value & 0xFF ; buffer [ index + 1 ] = value >>> 8 ; } param buffer param index return public static getWord ( buffer , index ) { const value = buffer [ index ] + ( buffer [ index + 1 ] * 256 ) ; return value ; } param data param startIndex public static getStringFromBuffer ( data , startIndex ) { let result = '' ; const len = data . length ; for ( let i = startIndex ; i < len ; i ++ ) { const char = data [ i ] ; if ( char == 0 ) break ; result += String . fromCharCode ( char ) ; } return result ; } param data param start param count public static getStringFromData ( data , start = 0 , count = - 1 ) { if ( count == - 1 ) count = data . length ; if ( start + count > data . length ) count = data . length - start ; if ( count <= 0 ) return "---" ; let result = "" ; let printCount = count ; if ( printCount > 300 ) printCount = 300 ; for ( let i = 0 ; i < printCount ; i ++ ) result += data [ i + start ] . toString ( ) + " " ; if ( printCount != count ) result += "..." ; return result ; } param assertionExpression returns public static getConditionFromAssertion ( assertionExpression ) { if ( assertionExpression . trim ( ) . length == 0 ) assertionExpression = 'false' ; return '!(' + assertionExpression + ')' ; } param bpCondition returns public static getAssertionFromCondition ( bpCondition ) { if ( ! bpCondition ) return '' ; let assertionCond = bpCondition . substr ( 2 ) ; assertionCond = assertionCond . substr ( 0 , assertionCond . length - 1 ) ; return assertionCond ; } param code param filename throws public static runInContext ( code , context , filename ? , lineOffset = 0 , timeout ? ) { try { vm . createContext ( context ) ; vm . runInContext ( code , context , { timeout , filename , lineOffset } ) ; } catch ( e ) { if ( filename ) { const stackWo = e . stack . replace ( /\r/g , '' ) ; const stack = stackWo . split ( '\n' ) ; let errorText = '' ; for ( const stackLine of stack ) { if ( stackLine . startsWith ( '\tat ' ) ) { const regex = new RegExp ( filename + ':(\\d+):(\\d+)' ) ; const match = regex . exec ( stackLine ) ; if ( match ) { const line = parseInt ( match [ 1 ] ) - 1 ; const column = parseInt ( match [ 2 ] ) - 1 ; e . position = { filename , line , column } ; } else { const regexFirst = new RegExp ( filename + ':(\\d+)' ) ; const matchFirst = regexFirst . exec ( stack [ 0 ] ) ; if ( matchFirst ) { const line = parseInt ( matchFirst [ 1 ] ) - 1 ; e . position = { filename , line , column : 0 } ; } } break ; } errorText += stackLine + '\n' ; } e . message = errorText || "Unknown error" ; } throw e ; } } param code param fileName public static requireFromString ( code , fileName ? ) { try { return requireFromString ( code , fileName ) ; } catch ( e ) { const stackWo = e . stack . replace ( /\r/g , '' ) ; const stack = stackWo . split ( '\n' ) ; if ( stack . length > 1 ) { const firstAt = stack [ 1 ] ; const match = /.*?:(\d+):(\d+)/ . exec ( firstAt ) ; if ( match ) { const line = parseInt ( match [ 1 ] ) - 1 ; const column = parseInt ( match [ 2 ] ) - 1 ; e . position = { line , column } ; } else { const line0 = stack [ 0 ] ; const match2 = /^:(\d+)$/ . exec ( line0 ) ; if ( match2 ) { const line = parseInt ( match2 [ 1 ] ) - 1 ; e . position = { line , column : 0 } ; } } } throw e ; } } param depth param file returns { line public static getLineNumber ( depth = 0 , file ? ) { try { throw new Error ( 'getLineNumber' ) ; } catch ( e ) { return this . getLineNumberFromError ( e , depth , file ) ; } } param e param depth param file returns { line public static getLineNumberFromError ( e , depth = 0 , file ? ) { if ( e && e . stack ) { const stackWo = e . stack . replace ( /\r/g , '' ) ; const stackWhole = stackWo . split ( '\n' ) ; let stack ; if ( file ) stack = stackWhole . filter ( line => line . includes ( file ) ) ; else stack = stackWhole ; const index = depth + 1 ; const indexAt = stack [ index ] ; const match = /.*?:(\d+):(\d+)/ . exec ( indexAt ) ; if ( match ) { const line = parseInt ( match [ 1 ] ) - 1 ; const column = parseInt ( match [ 2 ] ) - 1 ; return { line , column } ; } } return undefined ; } param text returns public static getBufferFromString ( text ) { if ( text == undefined ) text = '' ; const zeroText = text + String . fromCharCode ( 0 ) ; const buf = Buffer . from ( zeroText , 'ascii' ) ; return buf ; } param regex param text returns public static getLineNumberInText ( regex , text ) { const match = regex . exec ( text ) ; if ( ! match ) return undefined ; const tmp = text . substring ( 0 , match . index ) ; const lineNr = this . countOccurencesOf ( '\n' , tmp ) ; return lineNr ; } param search param text returns public static countOccurencesOf ( search , text ) { let count = - 1 ; const len = search . length ; let pos = - len ; do { count ++ ; pos += len ; pos = text . indexOf ( search , pos ) ; } while ( pos > - 1 ) ; return count ; } param enumeration returns public static getEnumKeys ( enumeration ) { const arr = [ ] ; for ( const key in Object . keys ( enumeration ) ) { const val = enumeration [ key ] ; if ( typeof val == "string" ) arr . push ( val ) ; } return arr ; } /**
     * Like 'join'
    public static joinHuman(arr: string[], lastJoin = 'or', hyphen = "'"): string {
        const len = arr.length;
        if (len == 0)
            return 'nothing';
        if (len)
            return "";
        let joined = '';
        const lastIndex = len - 1;
        arr.forEach((value, index) => {
            if (index != 0) {
                if (index == lastIndex && lastJoin)
                    joined += ' ' + lastJoin + ' ';
                else
                    joined += ', ';
            }
            joined += hyphen + value + hyphen;
        });
        return joined;
    }
    */ param wsFolder returns public static getlaunchJsonPath ( wsFolder ) { return UnifiedPath . join ( wsFolder , '.vscode' , 'launch.json' ) ; } param launchJsonPath param launchData public static readLaunchJson ( launchJsonPath , launchData ? ) { if ( launchData == undefined ) { launchData = fs . readFileSync ( launchJsonPath , 'utf8' ) ; } const dotVscodeFolder = UnifiedPath . dirname ( launchJsonPath ) ; const workspaceFolder = UnifiedPath . dirname ( dotVscodeFolder ) ; const workspaceFolderBasename = UnifiedPath . basename ( workspaceFolder ) ; const substData = launchData . replace ( /\${.*}/g , variable => { switch ( variable ) { case '${workspaceFolder}' : return workspaceFolder ; case '${workspaceFolderBasename}' : return workspaceFolderBasename ; default : return variable ; } } ) ; const parseErrors = [ ] ; const launch = jsonc . parse ( substData , parseErrors , { allowTrailingComma : true } ) ; if ( parseErrors . length > 0 ) { throw Error ( "Parse error while reading " + launchJsonPath + "." ) ; } return launch ; } /**
     * Own assert function that additionally does a log
     * in case of a wrong assumption.
     */ public static assert ( test , message ? ) { if ( ! test ) { try { throw Error ( "'assert'" + ( message || "" ) ) ; } catch ( err ) { if ( message == undefined ) message = '' ; else message += '\n' ; err . message = message + err . stack ; Log . log ( '\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n' + err . message + '\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n' ) ; throw err ; } } } param ms public static async timeout ( ms ) { return new Promise ( resolve => setTimeout ( resolve , ms ) ) ; } returns public static timeDiff ( ) { const time = new Date ( ) . getMilliseconds ( ) ; const diff = time - this . previousTimeDiffValue ; this . previousTimeDiffValue = time ; return diff ; } static previousTimeDiffValue : number = 0 ; param algorithm param repetitions returns public static measure ( algorithm , repetitions = 100000 ) { const t0 = new Date ( ) . getTime ( ) ; for ( let i = repetitions ; i > 0 ; i -- ) { algorithm ( ) ; } const t1 = new Date ( ) . getTime ( ) ; const diff = ( t1 - t0 ) / repetitions ; const diffns = diff * 1000000 ; return diffns ; } }