< Summary - Igor Pro Universal Testing Framework

Information
Class: procedures.igortest-utils
Assembly: procedures
File(s): /builds/mirror/igortest/procedures/igortest-utils.ipf
Tag: 74147b3
Line coverage
5%
Covered lines: 43
Uncovered lines: 686
Coverable lines: 729
Total lines: 1057
Line coverage: 5.8%
Branch coverage
50%
Covered branches: 2
Total branches: 4
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/builds/mirror/igortest/procedures/igortest-utils.ipf

#LineLine coverage
 1#pragma rtGlobals=3
 2#pragma rtFunctionErrors=1
 3#pragma version=1.10
 4#pragma TextEncoding="UTF-8"
 5#pragma ModuleName=IUTF_Utils
 6
 7static Constant UTF_MAXDIFFCOUNT = 10
 8
 9///@cond HIDDEN_SYMBOL
 10
 11// Choosen so that we don't hit the IP6 limit of 1000 chars
 12// with two %s and some other text.
 13static Constant MAX_STRING_LENGTH = 250
 14
 15// The number of significant decimal digits a fp32 or fp64 can hold. This value depends on the
 16// length of the mantisse (23 bits for fp32 and 53 bits for fp64) and are calculated using this
 17// formula: mantisse * log(2). The values are rounded up to the next closest integer.
 18static Constant UTF_DECIMAL_DIGITS_FP32 = 7
 19static Constant UTF_DECIMAL_DIGITS_FP64 = 16
 20
 21// The range of printable ASCII characters.
 22static Constant ASCII_PRINTABLE_START = 32
 23static Constant ASCII_PRINTABLE_END   = 126
 24
 25// The maximum amount of bytes that should be printed as context before the difference in
 26// a string diff
 27static Constant MAX_STRING_DIFF_CONTEXT = 10
 28
 29// The comparison mode to use for CmpStr. This is binary for Igor >= 7.05 and case sensitive
 30// text comparison for older versions.
 31#if IgorVersion() >= 7.05
 32static Constant UTF_CMPSTR_MODE = 2
 33#else
 34static Constant UTF_CMPSTR_MODE = 1
 35#endif
 36
 37// The result of a string diff
 38Structure IUTF_StringDiffResult
 39  string v1
 40  string v2
 41EndStructure
 42
 43/// @brief Returns 1 if var is a finite/normal number, 0 otherwise
 44///
 45/// @hidecallgraph
 46/// @hidecallergraph
 547threadsafe static Function IsFinite(var)
 48  variable var
 49
 550  return numType(var) == 0
 551End
 52
 53/// @brief Returns 1 if var is a NaN, 0 otherwise
 54///
 55/// @hidecallgraph
 56/// @hidecallergraph
 4557threadsafe static Function IsNaN(var)
 58  variable var
 59
 4560  return numType(var) == 2
 4561End
 62
 63/// @brief Returns 1 if str is null, 0 otherwise
 64/// @param str must not be a SVAR
 65///
 66/// @hidecallgraph
 67/// @hidecallergraph
 568threadsafe static Function IsNull(str)
 69  string &str
 70
 571  variable len = strlen(str)
 572  return numtype(len) == 2
 573End
 74
 75/// @brief Returns one if str is empty, zero otherwise.
 76/// @param str any non-null string variable or text wave element
 77///
 78/// @hidecallgraph
 79/// @hidecallergraph
 261580threadsafe static Function IsEmpty(str)
 81  string str
 82
 261583  return !(strlen(str) > 0)
 261584End
 85
 86/// @brief Returns one if var is a nonfinite integer, zero otherwise
 587threadsafe static Function IsInteger(var)
 88  variable var
 89
 590  return trunc(var) == var && numtype(var) == 0
 591End
 92
 93/// @brief Convert a text wave to string list
 94///
 95/// @param[in] txtWave 1D text wave
 96/// @param[in] sep separator string
 97/// @returns string with list of former text wave elements
 2798static Function/S TextWaveToList(txtWave, sep)
 99  WAVE/T txtWave
 100  string sep
 101
 27102  string list = ""
 27103  variable i, numRows
 104
 27105  numRows = DimSize(txtWave, 0)
 27106  for(i = 0; i < numRows; i += 1)
 79107    list = AddListItem(txtWave[i], list, sep, Inf)
 79108  endfor
 109
 27110  return list
 27111End
 112
 113/// @brief Prepare the passed string for output
 114///
 115/// We return a fixed string if it is null and limit its size so that it can be used in a sprintf statement using plain
 116/// "%s". We also do that for IP9, where this limit does not exist anymore, to have a consistent output across all IP
 117/// versions.
 5118threadsafe Function/S IUTF_PrepareStringForOut(str, [maxLen])
 119  string  &str
 120  variable maxLen
 121
 5122  variable length
 5123  string   suffix
 124
 5125  if(IsNull(str))
 0126    return "(null)"
 5127  endif
 128
 5129  maxLen = ParamIsDefault(maxLen) ? MAX_STRING_LENGTH : maxLen
 130
 5131  length = strlen(str)
 132
 5133  if(length < maxLen)
 5134    return str
 0135  endif
 136
 0137  suffix = ".."
 0138  return str[0, maxLen - 1 - strlen(suffix)] + suffix
 5139End
 140
 141///@endcond // HIDDEN_SYMBOL
 142
 143/// @brief Returns 1 if all wave elements in wave reference wave wv are of type subType, 0 otherwise
 0144static Function HasConstantWaveTypes(wv, subType)
 145  WAVE/WAVE wv
 146  variable  subType
 147
 0148  Make/FREE/N=(DimSize(wv, UTF_ROW)) matches
 0149  MultiThread matches = WaveType(wv[p], 1) == subType
 150
 0151  FindValue/V=(0) matches
 0152  return V_Value == -1
 0153End
 154
 0155static Function/S GetTypeStrFromWaveType(wv)
 156  WAVE wv
 157
 0158  string str = ""
 0159  variable type1, type0
 160
 0161  type0 = WaveType(wv)
 0162  if(type0)
 0163    if(type0 & IUTF_WAVETYPE0_CMPL)
 0164      str += "complex "
 0165    endif
 0166    if(type0 & IUTF_WAVETYPE0_USGN)
 0167      str += "unsigned "
 0168    endif
 0169    if(type0 & IUTF_WAVETYPE0_FP32)
 0170      str += "32-bit float"
 0171    endif
 0172    if(type0 & IUTF_WAVETYPE0_FP64)
 0173      str += "64-bit float"
 0174    endif
 0175    if(type0 & IUTF_WAVETYPE0_INT8)
 0176      str += "8-bit integer"
 0177    endif
 0178    if(type0 & IUTF_WAVETYPE0_INT16)
 0179      str += "16-bit integer"
 0180    endif
 0181    if(type0 & IUTF_WAVETYPE0_INT32)
 0182      str += "32-bit integer"
 0183    endif
 0184    if(type0 & IUTF_WAVETYPE0_INT64)
 0185      str += "64-bit integer"
 0186    endif
 0187    return str
 0188  else
 0189    type1 = WaveType(wv, 1)
 190
 0191    if(type1 == IUTF_WAVETYPE1_NULL)
 0192      return "null wave"
 0193    endif
 0194    if(type1 == IUTF_WAVETYPE1_TEXT)
 0195      return "text wave"
 0196    endif
 0197    if(type1 == IUTF_WAVETYPE1_DFR)
 0198      return "DFREF wave"
 0199    endif
 0200    if(type1 == IUTF_WAVETYPE1_WREF)
 0201      return "WAVE REF wave"
 0202    endif
 0203  endif
 204
 0205  return "UNKNOWN wave type"
 0206End
 207
 208/// This function assumes that EqualWaves(wv1, wv2, WAVE_DATA, tol) != 1.
 0209static Function/S DetermineWaveDataDifference(wv1, wv2, tol)
 210  WAVE/Z wv1, wv2
 211  variable tol
 212
 0213  string msg
 0214  variable isComplex1, isComplex2
 0215  string wvId1, wvId2
 0216  string wvNamePrefix, tmpStr1, tmpStr2
 217
 0218  // Generate names for reference
 0219  wvId1 = GetWaveNameInDFStr(wv1)
 0220  wvId2 = GetWaveNameInDFStr(wv2)
 0221  sprintf wvNamePrefix, "Wave1: %s\rWave2: %s\r", wvId1, wvId2
 222
 0223  // Size Check
 0224  Make/FREE/D wv1Dims = {DimSize(wv1, UTF_ROW), DimSize(wv1, UTF_COLUMN), DimSize(wv1, UTF_LAYER), DimSize(wv1, UTF_CHUN
 0225  Make/FREE/D wv2Dims = {DimSize(wv2, UTF_ROW), DimSize(wv2, UTF_COLUMN), DimSize(wv2, UTF_LAYER), DimSize(wv2, UTF_CHUN
 0226  if(!EqualWaves(wv1Dims, wv2Dims, WAVE_DATA, 0))
 0227    Make/FREE/T/N=(2, 1) table
 0228    wfprintf tmpStr1, "[%d]", wv1Dims
 0229    wfprintf tmpStr2, "[%d]", wv2Dims
 0230    table[0][0] = tmpStr1
 0231    table[1][0] = tmpStr2
 0232    msg         = NicifyTableText(table, "Dimension Sizes;")
 0233    sprintf msg, "Waves differ in dimension sizes\r%s%s", wvNamePrefix, msg
 0234    return msg
 0235  endif
 236
 0237  // complex type
 0238  isComplex1 = WaveType(wv1) & IUTF_WAVETYPE0_CMPL
 0239  isComplex2 = WaveType(wv2) & IUTF_WAVETYPE0_CMPL
 0240  if(isComplex1 != isComplex2)
 0241    Make/FREE/T/N=(2, 1) table
 0242    table[0][0] = GetTypeStrFromWaveType(wv1)
 0243    table[1][0] = GetTypeStrFromWaveType(wv2)
 0244    msg         = NicifyTableText(table, "Wave Types;")
 0245    sprintf msg, "Waves differ in complex number type\r%s%s", wvNamePrefix, msg
 0246    return msg
 0247  endif
 248
 0249  // Data Check
 0250  Make/FREE/T/N=(2 * UTF_MAXDIFFCOUNT, 3) table
 0251  SetDimLabel UTF_COLUMN, 0, DIMS, table
 0252  SetDimLabel UTF_COLUMN, 1, DIMLABEL, table
 0253  SetDimLabel UTF_COLUMN, 2, ELEMENT, table
 0254  IterateOverWaves(table, wv1, wv2, wv1Dims[0], wv1Dims[1], wv1Dims[2], wv1Dims[3], tol)
 0255  if(WaveType(wv1, 1) & IUTF_WAVETYPE1_TEXT)
 0256    msg = NicifyTableText(table, "Dimensions;Labels;Text;")
 0257    return "Text waves difference:\r" + wvNamePrefix + msg
 0258  else
 0259    msg = NicifyTableText(table, "Dimensions;Labels;Value;")
 0260    if(tol != 0)
 0261      sprintf tmpStr1, "Waves difference (tolerance limit %15f):\r", tol
 0262    else
 0263      tmpStr1 = "Waves difference:\r"
 0264    endif
 0265    return tmpStr1 + wvNamePrefix + msg
 0266  endif
 267
 0268  return ""
 0269End
 270
 0271static Function IterateOverWaves(table, wv1, wv2, rows, cols, layers, chunks, tol)
 272  WAVE/T table
 273  WAVE wv1, wv2
 274  variable rows, cols, layers, chunks, tol
 275
 0276  variable i, j, k, l
 0277  variable locCount
 0278  variable runTol
 279
 0280  if(chunks)
 0281    for(l = 0; l < chunks; l += 1)
 0282      for(k = 0; k < layers; k += 1)
 0283        for(j = 0; j < cols; j += 1)
 0284          for(i = 0; i < rows; i += 1)
 0285            AddValueDiffImpl(table, wv1, wv2, locCount, i, j, k, l, 4, runTol, tol)
 0286            if(locCount == UTF_MAXDIFFCOUNT)
 0287              return NaN
 0288            endif
 0289          endfor
 0290        endfor
 0291      endfor
 0292    endfor
 0293  elseif(layers)
 0294    for(k = 0; k < layers; k += 1)
 0295      for(j = 0; j < cols; j += 1)
 0296        for(i = 0; i < rows; i += 1)
 0297          AddValueDiffImpl(table, wv1, wv2, locCount, i, j, k, l, 3, runTol, tol)
 0298          if(locCount == UTF_MAXDIFFCOUNT)
 0299            return NaN
 0300          endif
 0301        endfor
 0302      endfor
 0303    endfor
 0304  elseif(cols)
 0305    for(j = 0; j < cols; j += 1)
 0306      for(i = 0; i < rows; i += 1)
 0307        AddValueDiffImpl(table, wv1, wv2, locCount, i, j, k, l, 2, runTol, tol)
 0308        if(locCount == UTF_MAXDIFFCOUNT)
 0309          return NaN
 0310        endif
 0311      endfor
 0312    endfor
 0313  else
 0314    for(i = 0; i < rows; i += 1)
 0315      AddValueDiffImpl(table, wv1, wv2, locCount, i, j, k, l, 1, runTol, tol)
 0316      if(locCount == UTF_MAXDIFFCOUNT)
 0317        return NaN
 0318      endif
 0319    endfor
 0320  endif
 321
 0322  Redimension/N=(2 * locCount, -1) table
 0323  return NaN
 0324End
 325
 0326threadsafe static Function/S GetWavePointer_Impl(wv)
 327  WAVE wv
 328
 0329  variable err
 0330  string   str
 331
 0332  Make/FREE/WAVE refWave = {wv}
 333
 0334  WAVE ref = refWave
 335
 336#if IgorVersion() >= 7.0
 0337  sprintf str, "%#08x", ref[0]; err = GetRTError(1)
 338#else
 0339  sprintf str, "%#08x", ref[0]
 340#endif
 341
 0342  return str
 0343End
 344
 345/// @brief Return the memory address of the passed wave
 346///
 347/// Works on all Igor Pro versions without clearing lingering runtime errors.
 348/// The idea is that clearing RTE's in preemptive threads does not modify the
 349/// main thread's one.
 0350threadsafe static Function/S GetWavePointer(wv)
 351  WAVE wv
 352
 353#if IgorVersion() >= 7.0
 354
 0355  Make/FREE/T/N=2 address
 0356  MultiThread address = GetWavePointer_Impl(wv)
 357
 0358  return address[0]
 359#else
 0360  return GetWavePointer_Impl(wv)
 361#endif
 362
 0363End
 364
 0365static Function/S GetWaveNameInDFStr(w)
 366  WAVE/Z w
 367
 0368  string str
 369
 0370  if(!WaveExists(w))
 0371    return "_null_"
 0372  endif
 373
 0374  str = NameOfWave(w)
 0375  if(WaveType(w, 2) != IUTF_WAVETYPE2_FREE)
 0376    str += " in " + GetWavesDataFolder(w, 1)
 0377  else
 0378    str += " (" + GetWavePointer(w) + ")"
 0379  endif
 380
 0381  return str
 0382End
 383
 0384static Function ValueEqualInclNaN(v1, v2)
 385  variable v1, v2
 386
 0387  variable isNaN1, isNaN2, isInf1, isInf2, isnegInf1, isnegInf2
 388
 0389  isNaN1 = IsNaN(v1)
 0390  isNaN2 = IsNaN(v2)
 0391  if(isNaN1 && isNaN2)
 0392    return 1
 0393  endif
 0394  isInf1 = v1 == Inf
 0395  isInf2 = v2 == Inf
 0396  if(isInf1 && isInf2)
 0397    return 1
 0398  endif
 0399  isnegInf1 = v1 == -Inf
 0400  isnegInf2 = v2 == -Inf
 0401  if(isnegInf1 && isnegInf2)
 0402    return 1
 0403  endif
 0404  if(v1 == v2)
 0405    return 1
 0406  endif
 407
 0408  return 0
 0409End
 410
 0411static Function/S SPrintWaveElement(w, row, col, layer, chunk)
 412  WAVE w
 413  variable row, col, layer, chunk
 414
 0415  string str, tmpStr
 0416  variable err
 0417  variable majorType = WaveType(w, 1)
 0418  variable minorType = WaveType(w)
 419
 0420  if(majorType == IUTF_WAVETYPE1_NUM)
 0421    if(minorType & IUTF_WAVETYPE0_CMPL)
 0422      if(minorType & (IUTF_WAVETYPE0_INT8 | IUTF_WAVETYPE0_INT16 | IUTF_WAVETYPE0_INT32))
 0423        Make/FREE/N=1/C/Y=(WaveType(w)) wTmp
 0424        wTmp[0] = w[row][col][layer][chunk]
 0425        if(minorType & IUTF_WAVETYPE0_USGN)
 0426          wfprintf str, "(%u, %u)", wTmp
 0427        else
 0428          wfprintf str, "(%d, %d)", wTmp
 0429        endif
 0430      elseif(minorType & IUTF_WAVETYPE0_INT64)
 431#if IgorVersion() >= 7.00
 0432        if(minorType & IUTF_WAVETYPE0_USGN)
 0433          WAVE/C/U/L wCLU = w
 0434          Make/FREE/N=1/C/L/U wTmpCLU
 0435          wTmpCLU[0] = wCLU[row][col][layer][chunk]
 0436          wfprintf str, "(%u, %u)", wTmpCLU
 0437        else
 0438          WAVE/C/L wCLS = w
 0439          Make/FREE/N=1/C/L wTmpCLS
 0440          wTmpCLS[0] = wCLS[row][col][layer][chunk]
 0441          wfprintf str, "(%d, %d)", wTmpCLS
 0442        endif
 443#endif
 0444      elseif(minorType & IUTF_WAVETYPE0_FP64)
 0445        str = "(" + GetNiceStringForNumber(real(w[row][col][layer][chunk]), isDouble = 1) + ", " + GetNiceStringForNumbe
 0446      else
 0447        str = "(" + GetNiceStringForNumber(real(w[row][col][layer][chunk]), isDouble = 0) + ", " + GetNiceStringForNumbe
 0448      endif
 0449    else
 0450      if(minorType & (IUTF_WAVETYPE0_INT8 | IUTF_WAVETYPE0_INT16 | IUTF_WAVETYPE0_INT32))
 0451        if(minorType & IUTF_WAVETYPE0_USGN)
 0452          sprintf str, "%u", w[row][col][layer][chunk]
 0453        else
 0454          sprintf str, "%d", w[row][col][layer][chunk]
 0455        endif
 0456      elseif(minorType & IUTF_WAVETYPE0_INT64)
 457#if IgorVersion() >= 7.00
 0458        if(minorType & IUTF_WAVETYPE0_USGN)
 0459          WAVE/U/L wLU = w
 0460          sprintf str, "%u", wLU[row][col][layer][chunk]
 0461        else
 0462          WAVE/L wLS = w
 0463          sprintf str, "%d", wLS[row][col][layer][chunk]
 0464        endif
 465#endif
 0466      elseif(minorType & IUTF_WAVETYPE0_FP64)
 0467        str = GetNiceStringForNumber(w[row][col][layer][chunk], isDouble = 1)
 0468      else
 0469        str = GetNiceStringForNumber(w[row][col][layer][chunk], isDouble = 0)
 0470      endif
 0471    endif
 0472  elseif(majorType == IUTF_WAVETYPE1_TEXT)
 0473    // this should be done using DiffString
 0474    WAVE/T wtext = w
 0475    str = EscapeString(wtext[row][col][layer][chunk])
 0476  elseif(majorType == IUTF_WAVETYPE1_DFR)
 0477    WAVE/DF wdfref = w
 0478    tmpStr = GetDataFolder(1, wdfref[row][col][layer][chunk])
 0479    if(IsEmpty(tmpStr))
 0480      tmpStr = "_null DFR_"
 0481    elseif(DataFolderRefStatus(wdfref[row][col][layer][chunk]) == 3)
 0482      tmpStr = "_free DFR_"
 0483    endif
 484#if IgorVersion() < 9.00
 0485    sprintf str, "0x%08x : %s", w[row][col][layer][chunk], tmpStr
 486#else
 0487    if(GetRTError(0))
 0488      str = "_free_"
 0489    else
 0490      sprintf str, "0x%08x : %s", w[row][col][layer][chunk], tmpStr; err = GetRTError(1)
 0491    endif
 492#endif
 0493  elseif(majorType == IUTF_WAVETYPE1_WREF)
 0494    WAVE/WAVE wref = w
 0495    tmpStr = GetWavesDataFolder(wref[row][col][layer][chunk], 2)
 0496    if(IsEmpty(tmpStr))
 0497      tmpStr = "_free wave_"
 0498    endif
 499#if IgorVersion() < 9.00
 0500    sprintf str, "0x%08x : %s", w[row][col][layer][chunk], tmpStr
 501#else
 0502    if(GetRTError(0))
 0503      str = "_free_"
 0504    else
 0505      sprintf str, "0x%08x : %s", w[row][col][layer][chunk], tmpStr; err = GetRTError(1)
 0506    endif
 507#endif
 0508  else
 0509    sprintf str, "Unknown wave type"
 0510  endif
 511
 0512  return str
 0513End
 514
 0515static Function AddValueDiffImpl(table, wv1, wv2, locCount, row, col, layer, chunk, dimensions, runTol, tol)
 516  WAVE/T table
 517  WAVE wv1, wv2
 518  variable &locCount
 519  variable row, col, layer, chunk, dimensions
 520  variable &runTol
 521  variable  tol
 522
 0523  variable type1, type2, baseType1, baseType2
 0524  variable isInt1, isInt2, isInt641, isInt642, isComplex, v1, v2, isText1
 0525  variable bothWref, bothDFref
 0526  variable curTol
 0527  variable/C c1, c2
 0528  string s1, s2, str
 0529  STRUCT IUTF_StringDiffResult strDiffResult
 530#if IgorVersion() >= 7.0
 0531  int64 cmpI64R
 532#endif
 533
 0534  baseType1 = WaveType(wv1, 1)
 0535  baseType2 = WaveType(wv2, 1)
 0536  type1     = WaveType(wv1)
 0537  type2     = WaveType(wv2)
 538
 0539  bothWref  = (baseType1 == IUTF_WAVETYPE1_WREF) && (baseType2 == IUTF_WAVETYPE1_WREF)
 0540  bothDFref = (baseType1 == IUTF_WAVETYPE1_DFR) && (baseType2 == IUTF_WAVETYPE1_DFR)
 0541  isText1   = baseType1 == IUTF_WAVETYPE1_TEXT
 0542  if(istext1)
 0543    WAVE/T wv1t = wv1
 0544    WAVE/T wv2t = wv2
 545
 0546    s1 = wv1t[row][col][layer][chunk]
 0547    s2 = wv2t[row][col][layer][chunk]
 548
 549#if IgorVersion() >= 7.0
 0550    if(!CmpStr(s1, s2, 2))
 0551      return NaN
 0552    endif
 553#else
 0554    Make/FREE/T s1w = {s1}
 0555    Make/FREE/T s2w = {s2}
 556
 0557    if(EqualWaves(s1w, s2w, WAVE_DATA))
 0558      return NaN
 0559    endif
 560#endif
 0561  elseif(bothWref)
 0562    WAVE/WAVE wWRef1 = wv1
 0563    WAVE/WAVE wWRef2 = wv2
 0564    if(WaveRefsEqual(wWRef1[row][col][layer][chunk], wWRef2[row][col][layer][chunk]))
 0565      return NaN
 0566    endif
 0567  elseif(bothDFref)
 0568    WAVE/DF wDFRef1 = wv1
 0569    WAVE/DF wDFRef2 = wv2
 0570    if(DataFolderRefsEqual(wDFRef1[row][col][layer][chunk], wDFRef2[row][col][layer][chunk]))
 0571      return NaN
 0572    endif
 0573  else
 0574    isInt1    = type1 & (IUTF_WAVETYPE0_INT8 | IUTF_WAVETYPE0_INT16 | IUTF_WAVETYPE0_INT32 | IUTF_WAVETYPE0_INT64)
 0575    isInt2    = type2 & (IUTF_WAVETYPE0_INT8 | IUTF_WAVETYPE0_INT16 | IUTF_WAVETYPE0_INT32 | IUTF_WAVETYPE0_INT64)
 0576    isInt641  = type1 & IUTF_WAVETYPE0_INT64
 0577    isInt642  = type2 & IUTF_WAVETYPE0_INT64
 0578    isComplex = type1 & IUTF_WAVETYPE0_CMPL
 579
 0580    if(isInt1 && isInt2)
 0581      // Int compare
 0582      if(isInt641 || isInt642)
 583#if IgorVersion() >= 7.0
 0584        if(isComplex)
 0585          Make/FREE/C/L/N=1 wInt64Diff
 0586          wInt64Diff[0] = wv2[row][col][layer][chunk] - wv1[row][col][layer][chunk]
 0587          v1            = real(wInt64Diff[0])
 0588          v2            = imag(wInt64Diff[0])
 0589          if(!v1 && !v2)
 0590            return NaN
 0591          elseif(tol != 0)
 0592            curTol = v1 * v1 + v2 * v2
 0593          endif
 0594        else
 0595          cmpI64R = wv2[row][col][layer][chunk] - wv1[row][col][layer][chunk]
 0596          if(!cmpI64R)
 0597            return NaN
 0598          elseif(tol != 0)
 0599            curTol = cmpI64R * cmpI64R
 0600          endif
 0601        endif
 602#endif
 0603      else
 0604        if(isComplex)
 0605          c1 = wv2[row][col][layer][chunk] - wv1[row][col][layer][chunk]
 0606          v1 = real(c1)
 0607          v2 = imag(c1)
 0608          if(!v1 && !v2)
 0609            return NaN
 0610          elseif(tol != 0)
 0611            curTol = v1 * v1 + v2 * v2
 0612          endif
 0613        else
 0614          v1 = wv1[row][col][layer][chunk]
 0615          v2 = wv2[row][col][layer][chunk]
 0616          if(v1 == v2)
 0617            return NaN
 0618          elseif(tol != 0)
 0619            curTol = (v1 - v2) * (v1 - v2)
 0620          endif
 0621        endif
 0622      endif
 0623    else
 0624      // FP compare
 0625      if(isComplex)
 0626        c1 = wv1[row][col][layer][chunk]
 0627        c2 = wv2[row][col][layer][chunk]
 0628        if(ValueEqualInclNaN(real(c1), real(c2)) && ValueEqualInclNaN(imag(c1), imag(c2)))
 0629          return NaN
 0630        elseif(tol != 0)
 0631          v1     = GetToleranceValues(real(c1), real(c2))
 0632          v1     = IsFinite(v1) ? real(c1) - real(c2) : Inf
 0633          v2     = GetToleranceValues(imag(c1), imag(c2))
 0634          v2     = IsFinite(v1) ? imag(c1) - imag(c2) : Inf
 0635          curTol = v1 * v1 + v2 * v2
 0636        endif
 0637      else
 0638        v1 = wv1[row][col][layer][chunk]
 0639        v2 = wv2[row][col][layer][chunk]
 0640        if(ValueEqualInclNaN(v1, v2))
 0641          return NaN
 0642        elseif(tol != 0)
 0643          curTol = GetToleranceValues(v1, v2)
 0644        endif
 0645      endif
 0646    endif
 0647  endif
 648
 0649  if(!(istext1 | bothWref | bothDFref))
 0650    runTol += curTol
 0651    if(runTol < tol)
 0652      return NaN
 0653    endif
 0654  endif
 655
 0656  switch(dimensions)
 0657    case 1:
 0658      sprintf str, "[%d]", row
 0659      break
 0660    case 2:
 0661      sprintf str, "[%d][%d]", row, col
 0662      break
 0663    case 3:
 0664      sprintf str, "[%d][%d][%d]", row, col, layer
 0665      break
 0666    case 4:
 0667      sprintf str, "[%d][%d][%d][%d]", row, col, layer, chunk
 0668      break
 0669    default:
 0670      IUTF_Reporting#ReportErrorAndAbort("Unsupported number of dimensions")
 0671      break
 0672  endswitch
 0673  table[2 * locCount][%DIMS] = str
 0674  Make/FREE/T wDL1 = {GetDimLabel(wv1, UTF_ROW, row), GetDimLabel(wv1, UTF_COLUMN, col), GetDimLabel(wv1, UTF_LAYER, lay
 0675  str = wDL1[0] + wDL1[1] + wDL1[2] + wDL1[3]
 0676  if(!IsEmpty(str))
 0677    sprintf str, "%s;%s;%s;%s;", wDL1[0], wDL1[1], wDL1[2], wDL1[3]
 0678    table[2 * locCount][%DIMLABEL] = str
 0679  endif
 0680  Make/FREE/T wDL2 = {GetDimLabel(wv2, UTF_ROW, row), GetDimLabel(wv2, UTF_COLUMN, col), GetDimLabel(wv2, UTF_LAYER, lay
 0681  str = wDL2[0] + wDL2[1] + wDL2[2] + wDL2[3]
 0682  if(!IsEmpty(str))
 0683    sprintf str, "%s;%s;%s;%s;", wDL2[0], wDL2[1], wDL2[2], wDL2[3]
 0684    table[2 * locCount + 1][%DIMLABEL] = str
 0685  endif
 686
 0687  if(istext1)
 0688    WAVE/T wtext1 = wv1
 0689    WAVE/T wtext2 = wv2
 0690    string text1  = wtext1[row][col][layer][chunk]
 0691    string text2  = wtext2[row][col][layer][chunk]
 0692    DiffString(text1, text2, strDiffResult)
 0693    table[2 * locCount][%ELEMENT]     = strDiffResult.v1
 0694    table[2 * locCount + 1][%ELEMENT] = strDiffResult.v2
 0695  else
 0696    table[2 * locCount][%ELEMENT]     = SPrintWaveElement(wv1, row, col, layer, chunk)
 0697    table[2 * locCount + 1][%ELEMENT] = SPrintWaveElement(wv2, row, col, layer, chunk)
 0698  endif
 699
 0700  locCount += 1
 0701End
 702
 703// return the string representation of the number with no trailing zeros after the decimal point
 660704static Function/S GetNiceStringForNumber(n, [isDouble])
 705  variable n, isDouble
 706
 660707  variable precision
 660708  string   str
 709
 660710  isDouble = ParamIsDefault(isDouble) ? 0 : !!isDouble;
 711
 660712  precision = isDouble ? UTF_DECIMAL_DIGITS_FP64 : UTF_DECIMAL_DIGITS_FP32
 713
 660714  sprintf str, "%.*g", precision, n
 715
 660716  return str
 660717End
 718
 719// This function assumes that v1 != v2 and not both are NaN
 0720static Function GetToleranceValues(v1, v2)
 721  variable v1, v2
 722
 0723  variable isNaN1, isNaN2, isInf1, isInf2, isnegInf1, isnegInf2
 724
 0725  isNaN1 = IsNaN(v1)
 0726  isNaN2 = IsNaN(v2)
 0727  if(isNaN1 || isNaN2)
 0728    return Inf
 0729  endif
 0730  isInf1 = v1 == Inf
 0731  isInf2 = v2 == Inf
 0732  if(isInf1 != isInf2)
 0733    return Inf
 0734  endif
 0735  isnegInf1 = v1 == -Inf
 0736  isnegInf2 = v2 == -Inf
 0737  if(isnegInf1 != isnegInf2)
 0738    return Inf
 0739  endif
 740
 0741  return (v1 - v2) * (v1 - v2)
 0742End
 743
 0744static Function/S NicifyTableText(table, titleList)
 745  WAVE/T table
 746  string titleList
 747
 0748  variable numCols, numRows, i, j, padVal
 0749  string str
 750
 0751  InsertPoints/M=(UTF_ROW) 0, 2, table
 0752  numCols = min(DimSize(table, UTF_COLUMN), ItemsInList(titleList))
 0753  for(i = 0; i < numCols; i += 1)
 0754    table[0][i] = StringFromList(i, titleList)
 0755  endfor
 756
 0757  numCols = DimSize(table, UTF_COLUMN)
 0758  numRows = DimSize(table, UTF_ROW)
 0759  Make/FREE/N=(numRows, numCols)/D strSizes
 760
 0761  strSizes = strlen(table[p][q])
 762
 0763  for(j = 0; j < numRows; j += 1)
 0764    padVal = j == 1 ? 0x2d : 0x20
 0765    for(i = 0; i < numCols; i += 1)
 766#if IgorVersion() >= 7.00
 0767      WaveStats/Q/M=1/RMD=[][i, i] strSizes
 768#else
 0769      Make/FREE/N=(DimSize(strSizes, UTF_ROW)) strSizeCol
 0770      strSizeCol[] = strSizes[p][i]
 0771      WaveStats/Q/M=1 strSizeCol
 772#endif
 0773      table[j][i] = num2char(padVal) + PadString(table[j][i], V_Max, padVal) + num2char(padVal)
 0774    endfor
 0775  endfor
 776
 0777  str = ""
 0778  for(j = 0; j < numRows; j += 1)
 0779    for(i = 0; i < numCols; i += 1)
 0780      str += table[j][i] + "|"
 0781    endfor
 0782    str += "\r"
 0783  endfor
 784
 0785  return str
 0786End
 787
 788/// @brief Based on DisplayHelpTopic "Character-by-Character Operations"
 0789static Function NumBytesInUTF8Character(str, byteOffset)
 790  string   str
 791  variable byteOffset
 792
 0793  variable firstByte
 0794  variable numBytesInString = strlen(str)
 795
 0796  if(byteOffset < 0 || byteOffset >= numBytesInString)
 0797    return 0
 0798  endif
 799
 0800  firstByte = char2num(str[byteOffset]) & 0xFF
 801
 0802  if(firstByte < 0x80)
 0803    return 1
 0804  endif
 805
 0806  if(firstByte >= 0xC2 && firstByte <= 0xDF)
 0807    return 2
 0808  endif
 809
 0810  if(firstByte >= 0xE0 && firstByte <= 0xEF)
 0811    return 3
 0812  endif
 813
 0814  if(firstByte >= 0xF0 && firstByte <= 0xF4)
 0815    return 4
 0816  endif
 817
 0818  // Invalid UTF8 code point
 0819  return 1
 0820End
 821
 822/// @brief Generate a diff of str1 and str2. This will only look for the first difference in both
 823///        strings. The strings are expected to be different!
 824///
 825/// @param[in] str1 the first string
 826/// @param[in] str2 the second string
 827/// @param[out] result the diff of both strings
 828/// @param[in] case_sensitive (default: true) respecting the case during the diff
 0829static Function DiffString(str1, str2, result, [case_sensitive])
 830  string                       &str1
 831  string                       &str2
 832  STRUCT IUTF_StringDiffResult &result
 833  variable                      case_sensitive
 834
 0835  variable start, line, end1, end2, endmin, diffpos
 0836  variable str1len, str2len
 0837  string lineEnding1, lineEnding2, prefix
 838
 0839  start          = 0
 0840  line           = 0
 0841  str1len        = strlen(str1)
 0842  str2len        = strlen(str2)
 0843  case_sensitive = ParamIsDefault(case_sensitive) ? 1 : case_sensitive
 844
 0845  // handle null strings
 0846  if(IsNull(str1))
 0847    if(IsNull(str2))
 0848      IUTF_Reporting#ReportErrorAndAbort("Bug: Cannot create diff if both strings are null")
 0849    endif
 850
 0851    result.v1 = "-:-:-> <NULL STRING>"
 0852    end2      = DetectEndOfLine(str2, 0, lineEnding2)
 0853    result.v2 = "0:0:0>" + GetStringWithContext(str2, 0, 0, end2 - 1)
 0854    return NaN
 0855  elseif(IsNull(str2))
 0856    end1      = DetectEndOfLine(str1, 0, lineEnding2)
 0857    result.v1 = "0:0:0>" + GetStringWithContext(str1, 0, 0, end1 - 1)
 0858    result.v2 = "-:-:-> <NULL STRING>"
 0859    return NaN
 0860  endif
 861
 0862  // The following cases can happen during the diff:
 0863  // 1. text is different until line end
 0864  // 2. one line is shorter than the other
 0865  // 3. the line endings are different
 0866  // 4. no differences in the current line
 0867  // 5. one string is larger than the other one
 0868  do
 0869    end1   = DetectEndOfLine(str1, start, lineEnding1)
 0870    end2   = DetectEndOfLine(str2, start, lineEnding2)
 0871    endmin = min(end1, end2)
 872
 0873    diffpos = GetTextDiffPos(str1[start, endmin - 1], str2[start, endmin - 1], case_sensitive)
 874
 0875    // Case 1
 0876    if(diffpos >= 0)
 0877      sprintf prefix, "%d:%d:%d>", line, diffpos, start + diffpos
 0878      result.v1 = prefix + GetStringWithContext(str1, start, start + diffpos, end1 - 1)
 0879      result.v2 = prefix + GetStringWithContext(str2, start, start + diffpos, end2 - 1)
 0880      return NaN
 0881    endif
 882
 0883    // Case 2
 0884    if(end1 != end2)
 0885      sprintf prefix, "%d:%d:%d>", line, endmin - start, endmin
 0886      result.v1 = prefix + GetStringWithContext(str1, start, endmin, end1)
 0887      result.v2 = prefix + GetStringWithContext(str2, start, endmin, end2)
 0888      return NaN
 0889    endif
 890
 0891    // Case 3
 0892    if(CmpStr(lineEnding1, lineEnding2))
 0893      sprintf prefix, "%d:%d:%d>", line, endmin - start, endmin
 0894      result.v1 = prefix + GetStringWithContext(str1, start, endmin, endmin + strlen(lineEnding1) - 1)
 0895      result.v2 = prefix + GetStringWithContext(str2, start, endmin, endmin + strlen(lineEnding2) - 1)
 0896      return NaN
 0897    endif
 898
 0899    // Case 4
 0900    start = endmin + strlen(lineEnding1)
 0901    line += 1
 902
 0903  while(start < str1len && start < str2len)
 904
 0905  // Case 5
 0906  if(str1len != str2len)
 0907    sprintf prefix, "%d:0:%d>", line, start
 0908    if(str1len <= start)
 0909      result.v1 = prefix
 0910    else
 0911      end1      = DetectEndOfLine(str1, start, lineEnding1)
 0912      result.v1 = prefix + EscapeString(str1[start, end1])
 0913    endif
 0914    if(str2len <= start)
 0915      result.v2 = prefix
 0916    else
 0917      end2      = DetectEndOfLine(str2, start, lineEnding2)
 0918      result.v2 = prefix + EscapeString(str2[start, end2])
 0919    endif
 0920    return NaN
 0921  endif
 922
 0923  IUTF_Reporting#ReportErrorAndAbort("Bug: Cannot create diff of equal strings")
 0924End
 925
 926/// @brief Return a section of str which contains the character at diffpos and some context around.
 927///        The context will always be in the bounds of start and endpos.
 928///
 929/// @param[in] str the string for which a section has to generated
 930/// @param[in] start the left-most bound to generate the context from
 931/// @param[in] diffpos the position of interest. A context will be generated around this position respecting start and e
 932/// @param[in] endpos the right-most bound to generate the context from
 933/// @returns the context at diffpos in str
 0934static Function/S GetStringWithContext(str, start, diffpos, endpos)
 935  variable start, diffpos, endpos
 936  string str
 937
 0938  string strOut
 939
 0940  strOut  = EscapeString(str[max(start, diffpos - MAX_STRING_DIFF_CONTEXT), diffpos - 1])
 0941  strOut += EscapeString(str[diffpos, min(endpos, diffpos + MAX_STRING_DIFF_CONTEXT)])
 942
 0943  return strOut
 0944End
 945
 946/// @brief Get the first position with a difference in str1 and str2.
 947///        Since Igor 7.05 the case-sensitive check will be performed in byte mode.
 948///
 949/// @param[in] str1 the first string
 950/// @param[in] str2 the second string
 951/// @param[in] case_sensitive the mode for case check. If this is set to 0 this will enforce the
 952///            case-insensitive check. All other values will use the default case-sensitive check
 953///            and since Igor 7.05 in byte mode.
 954/// @returns the position of the first difference. -1 if there is no difference.
 0955static Function GetTextDiffPos(str1, str2, case_sensitive)
 956  string str1, str2
 957  variable case_sensitive
 958
 0959  variable i
 0960  variable length = strlen(str1)
 0961  variable mode   = case_sensitive ? UTF_CMPSTR_MODE : 0
 962
 0963  for(i = 0; i < length; i += 1)
 0964    if(CmpStr(str1[i], str2[i], mode))
 0965      return i
 0966    endif
 0967  endfor
 968
 0969  return -1
 0970End
 971
 972/// @brief Detect the next end-of-line marker from the current start position.
 973///        This function supports the common line endings for Windows, Unix and OSX.
 974///        If there are no more line endings in str it will output the length of the string and an empty marker.
 975///
 976/// @param[in] str a multi-line string for which a line ending has to be searched.
 977/// @param[in] start the start position inside str
 978/// @param[out] lineEnding the detected line ending marker. This can be "\r\n", "\r", "\n" or "".
 979/// returns the position where the line ending starts
 0980static Function DetectEndOfLine(str, start, lineEnding)
 981  variable start
 982  string   str
 983  string  &lineEnding
 984
 0985  variable i
 0986  variable length = strlen(str)
 987
 0988  lineEnding = ""
 989
 0990  for(i = start; i < length; i += 1)
 0991    if(!CmpStr(str[i], "\r"))
 0992      if(i + 1 < length && !CmpStr(str[i + 1], "\n"))
 0993        lineEnding = "\r\n"
 0994      else
 0995        lineEnding = "\r"
 0996      endif
 0997      return i
 0998    endif
 0999    if(!CmpStr(str[i], "\n"))
 01000      lineEnding = "\n"
 01001      return i
 01002    endif
 01003  endfor
 1004
 01005  // no line ending
 01006  return length
 01007End
 1008
 1009/// @brief Escaping a string by replacing any characters that are not printable in ASCII with a
 1010///        printable version. This has no support for other text encodings and is purely single
 1011///        byte aware.
 1012///
 1013/// @param[in] str the string to escape
 1014/// @returns the escaped string
 01015static Function/S EscapeString(str)
 1016  string str
 1017
 01018  variable i, charnum, length
 01019  string result, hex, char
 1020
 01021  result = ""
 01022  length = strlen(str)
 1023
 01024  for(i = 0; i < length; i += 1)
 01025    result += " "
 01026    char    = str[i]
 01027    charnum = char2num(char)
 01028    if(charnum < 0)
 01029      charnum += 256
 01030    endif
 01031    if(charnum >= ASCII_PRINTABLE_START && charnum <= ASCII_PRINTABLE_END)
 01032      result += str[i]
 01033      continue
 01034    endif
 01035    // non printable characters in ASCII
 01036    strswitch(char)
 01037      case "\000":
 01038        result += "<NUL>"
 01039        break
 01040      case "\n":
 01041        result += "<LF>"
 01042        break
 01043      case "\r":
 01044        result += "<CR>"
 01045        break
 01046      case "\t":
 01047        result += "<TAB>"
 01048        break
 01049      default:
 01050        sprintf hex, "<0x%02X>", charnum
 01051        result += hex
 01052        break
 01053    endswitch
 01054  endfor
 1055
 01056  return result
 01057End