@@ -53,29 +53,31 @@ public static string GetExtension (string fileName)
5353 : fileName [ ( i + 1 ) ..] ;
5454 }
5555
56-
5756 public static string GetFileSizeAsText ( long size )
5857 {
5958 return size < 1024
60- ? string . Empty + size + " bytes"
59+ ? $ " { size } bytes"
6160 : size < 1024 * 1024
62- ? string . Empty + ( size / 1024 ) + " KB"
63- : string . Empty + $ "{ size / 1048576.0 : 0.00} " + " MB";
61+ ? $ " { size / 1024 } KB"
62+ : $ "{ size / 1048576.0 : 0.00} MB";
6463 }
6564
66- //TOOD: check if the callers are checking for null before calling
67- public static bool TestFilterCondition ( FilterParams filterParams , ILogLine line , ILogLineColumnizerCallback columnizerCallback )
65+ public static bool TestFilterCondition ( FilterParams filterParams , ILogLineMemory logLine , ILogLineMemoryColumnizerCallback columnizerCallback )
6866 {
6967 ArgumentNullException . ThrowIfNull ( filterParams , nameof ( filterParams ) ) ;
70- ArgumentNullException . ThrowIfNull ( line , nameof ( line ) ) ;
68+ ArgumentNullException . ThrowIfNull ( logLine , nameof ( logLine ) ) ;
7169
72- if ( filterParams . LastLine . Equals ( line . FullLine , StringComparison . OrdinalIgnoreCase ) )
70+ // TODO: Once FilterParams.LastLine is converted to ReadOnlyMemory<char>, this can be simplified to:
71+ // if (MemoryExtensions.Equals(filterParams.LastLine.Span, logLine.FullLine.Span, StringComparison.OrdinalIgnoreCase))
72+ if ( MemoryExtensions . Equals ( filterParams . LastLine . AsSpan ( ) , logLine . FullLine . Span , StringComparison . OrdinalIgnoreCase ) )
7373 {
7474 return filterParams . LastResult ;
7575 }
7676
77- var match = TestFilterMatch ( filterParams , line , columnizerCallback ) ;
78- filterParams . LastLine = line . FullLine ;
77+ var match = TestFilterMatch ( filterParams , logLine , columnizerCallback ) ;
78+
79+ // TODO: This ToString() allocation will be eliminated when LastLine becomes ReadOnlyMemory<char>
80+ filterParams . LastLine = logLine . FullLine . ToString ( ) ;
7981
8082 if ( filterParams . IsRangeSearch )
8183 {
@@ -108,48 +110,68 @@ public static bool TestFilterCondition (FilterParams filterParams, ILogLine line
108110 return match ;
109111 }
110112
111- //TODO Add Null Checks (https://github.com/LogExperts/LogExpert/issues/403)
112- public static int DamerauLevenshteinDistance ( string src , string dest )
113+ public static int DamerauLevenshteinDistance ( ReadOnlySpan < char > source , ReadOnlySpan < char > destination , bool ignoreCase = false )
113114 {
114- var d = new int [ src . Length + 1 , dest . Length + 1 ] ;
115- int i , j , cost ;
116- var str1 = src . ToCharArray ( ) ;
117- var str2 = dest . ToCharArray ( ) ;
115+ var d = new int [ source . Length + 1 , destination . Length + 1 ] ;
118116
119- for ( i = 0 ; i <= str1 . Length ; i ++ )
117+ for ( var i = 0 ; i <= source . Length ; i ++ )
120118 {
121119 d [ i , 0 ] = i ;
122120 }
123121
124- for ( j = 0 ; j <= str2 . Length ; j ++ )
122+ for ( var j = 0 ; j <= destination . Length ; j ++ )
125123 {
126124 d [ 0 , j ] = j ;
127125 }
128126
129- for ( i = 1 ; i <= str1 . Length ; i ++ )
127+ for ( var i = 1 ; i <= source . Length ; i ++ )
130128 {
131- for ( j = 1 ; j <= str2 . Length ; j ++ )
129+ for ( var j = 1 ; j <= destination . Length ; j ++ )
132130 {
133- cost = str1 [ i - 1 ] == str2 [ j - 1 ]
131+ var char1 = ignoreCase
132+ ? char . ToUpperInvariant ( source [ i - 1 ] )
133+ : source [ i - 1 ] ;
134+
135+ var char2 = ignoreCase
136+ ? char . ToUpperInvariant ( destination [ j - 1 ] )
137+ : destination [ j - 1 ] ;
138+
139+ var cost = char1 == char2
134140 ? 0
135141 : 1 ;
136142
137- d [ i , j ] =
138- Math . Min ( d [ i - 1 , j ] + 1 , // Deletion
139- Math . Min ( d [ i , j - 1 ] + 1 , // Insertion
140- d [ i - 1 , j - 1 ] + cost ) ) ; // Substitution
141-
142- if ( i > 1 && j > 1 && str1 [ i - 1 ] == str2 [ j - 2 ] && str1 [ i - 2 ] == str2 [ j - 1 ] )
143+ d [ i , j ] = Math . Min
144+ (
145+ d [ i - 1 , j ] + 1 , // Deletion
146+ Math . Min
147+ (
148+ d [ i , j - 1 ] + 1 , // Insertion
149+ d [ i - 1 , j - 1 ] + cost // Substitution
150+ )
151+ ) ;
152+
153+ // Transposition
154+ if ( i > 1 && j > 1 )
143155 {
144- d [ i , j ] = Math . Min ( d [ i , j ] , d [ i - 2 , j - 2 ] + cost ) ;
156+ var prevChar1 = ignoreCase
157+ ? char . ToUpperInvariant ( source [ i - 2 ] )
158+ : source [ i - 2 ] ;
159+
160+ var prevChar2 = ignoreCase
161+ ? char . ToUpperInvariant ( destination [ j - 2 ] )
162+ : destination [ j - 2 ] ;
163+
164+ if ( char1 == prevChar2 && prevChar1 == char2 )
165+ {
166+ d [ i , j ] = Math . Min ( d [ i , j ] , d [ i - 2 , j - 2 ] + cost ) ;
167+ }
145168 }
146169 }
147170 }
148171
149- return d [ str1 . Length , str2 . Length ] ;
172+ return d [ source . Length , destination . Length ] ;
150173 }
151174
152- //TODO Add Null Checks (https://github.com/LogExperts/LogExpert/issues/403)
153175 public static unsafe int YetiLevenshtein ( string s1 , string s2 )
154176 {
155177 fixed ( char * p1 = s1 )
@@ -159,13 +181,13 @@ public static unsafe int YetiLevenshtein (string s1, string s2)
159181 }
160182 }
161183
162- public static unsafe int YetiLevenshtein ( string s1 , string s2 , int substitionCost )
184+ public static unsafe int YetiLevenshtein ( string s1 , string s2 , int substitutionCost )
163185 {
164- var xc = substitionCost - 1 ;
186+ var xc = substitutionCost - 1 ;
165187
166188 if ( xc is < 0 or > 1 )
167189 {
168- throw new ArgumentException ( "" , nameof ( substitionCost ) ) ;
190+ throw new ArgumentException ( "" , nameof ( substitutionCost ) ) ;
169191 }
170192
171193 fixed ( char * p1 = s1 )
@@ -385,26 +407,6 @@ public static unsafe int YetiLevenshtein (char* s1, int l1, char* s2, int l2, in
385407 return i ;
386408 }
387409
388- /// <summary>
389- /// Returns true, if the given string is null or empty
390- /// </summary>
391- /// <param name="toTest"></param>
392- /// <returns></returns>
393- public static bool IsNull ( string toTest )
394- {
395- return toTest == null || toTest . Length == 0 ;
396- }
397-
398- /// <summary>
399- /// Returns true, if the given string is null or empty or contains only spaces
400- /// </summary>
401- /// <param name="toTest"></param>
402- /// <returns></returns>
403- public static bool IsNullOrSpaces ( string toTest )
404- {
405- return toTest == null || toTest . Trim ( ) . Length == 0 ;
406- }
407-
408410 [ Conditional ( "DEBUG" ) ]
409411 public static void AssertTrue ( bool condition , string msg )
410412 {
@@ -418,7 +420,7 @@ public static void AssertTrue (bool condition, string msg)
418420
419421 //TODO Add Null Check (https://github.com/LogExperts/LogExpert/issues/403)
420422 [ SupportedOSPlatform ( "windows" ) ]
421- public string ? GetWordFromPos ( int xPos , string text , Graphics g , Font font )
423+ public static string ? GetWordFromPos ( int xPos , string text , Graphics g , Font font )
422424 {
423425 var words = text . Split ( [ ' ' , '.' , ':' , ';' ] ) ;
424426
@@ -469,7 +471,7 @@ public static void AssertTrue (bool condition, string msg)
469471
470472 #region Private Methods
471473
472- private static bool TestFilterMatch ( FilterParams filterParams , ILogLine line , ILogLineColumnizerCallback columnizerCallback )
474+ private static bool TestFilterMatch ( FilterParams filterParams , ILogLineMemory logLine , ILogLineMemoryColumnizerCallback columnizerCallback )
473475 {
474476 string normalizedSearchText ;
475477 string searchText ;
@@ -495,22 +497,19 @@ private static bool TestFilterMatch (FilterParams filterParams, ILogLine line, I
495497
496498 if ( filterParams . ColumnRestrict )
497499 {
498- var columns = filterParams . CurrentColumnizer . SplitLine ( columnizerCallback , line ) ;
500+ var columns = filterParams . CurrentColumnizer . SplitLine ( columnizerCallback , logLine ) ;
499501 var found = false ;
500502 foreach ( var colIndex in filterParams . ColumnList )
501503 {
502- if ( colIndex < columns . ColumnValues . Length
503- ) // just to be sure, maybe the columnizer has changed anyhow
504+ if ( colIndex < columns . ColumnValues . Length ) // just to be sure, maybe the columnizer has changed anyhow
504505 {
505506 if ( columns . ColumnValues [ colIndex ] . FullValue . Trim ( ) . Length == 0 )
506507 {
507508 if ( filterParams . EmptyColumnUsePrev )
508509 {
509- var prevValue = ( string ) filterParams . LastNonEmptyCols [ colIndex ] ;
510- if ( prevValue != null )
510+ if ( filterParams . LastNonEmptyCols . TryGetValue ( colIndex , out var prevValue ) )
511511 {
512- if ( TestMatchSub ( filterParams , prevValue , normalizedSearchText , searchText , rex ,
513- filterParams . ExactColumnMatch ) )
512+ if ( TestMatchSub ( filterParams , prevValue , normalizedSearchText . AsSpan ( ) , searchText . AsSpan ( ) , rex , filterParams . ExactColumnMatch ) )
514513 {
515514 found = true ;
516515 }
@@ -524,9 +523,7 @@ private static bool TestFilterMatch (FilterParams filterParams, ILogLine line, I
524523 else
525524 {
526525 filterParams . LastNonEmptyCols [ colIndex ] = columns . ColumnValues [ colIndex ] . FullValue ;
527- if ( TestMatchSub ( filterParams , columns . ColumnValues [ colIndex ] . FullValue , normalizedSearchText ,
528- searchText , rex ,
529- filterParams . ExactColumnMatch ) )
526+ if ( TestMatchSub ( filterParams , columns . ColumnValues [ colIndex ] . FullValue , normalizedSearchText . AsSpan ( ) , searchText . AsSpan ( ) , rex , filterParams . ExactColumnMatch ) )
530527 {
531528 found = true ;
532529 }
@@ -538,11 +535,17 @@ private static bool TestFilterMatch (FilterParams filterParams, ILogLine line, I
538535 }
539536 else
540537 {
541- return TestMatchSub ( filterParams , line . FullLine , normalizedSearchText , searchText , rex , false ) ;
538+ return TestMatchSub ( filterParams , logLine . FullLine , normalizedSearchText . AsSpan ( ) , searchText . AsSpan ( ) , rex , false ) ;
542539 }
543540 }
544541
545- private static bool TestMatchSub ( FilterParams filterParams , string line , string lowerSearchText , string searchText , Regex rex , bool exactMatch )
542+ private static bool TestMatchSub (
543+ FilterParams filterParams ,
544+ ReadOnlySpan < char > line ,
545+ ReadOnlySpan < char > normalizedSearchText , // Pre-normalized (uppercase) // lowerSearchText
546+ ReadOnlySpan < char > searchText ,
547+ Regex rex ,
548+ bool exactMatch )
546549 {
547550 if ( filterParams . IsRegex )
548551 {
@@ -557,14 +560,15 @@ private static bool TestMatchSub (FilterParams filterParams, string line, string
557560 {
558561 if ( exactMatch )
559562 {
560- if ( line . ToUpperInvariant ( ) . Trim ( ) . Equals ( lowerSearchText , StringComparison . Ordinal ) )
563+ var trimmedLine = line . Trim ( ) ;
564+ if ( MemoryExtensions . Equals ( trimmedLine , normalizedSearchText , StringComparison . OrdinalIgnoreCase ) )
561565 {
562566 return true ;
563567 }
564568 }
565569 else
566570 {
567- if ( line . Contains ( lowerSearchText , StringComparison . OrdinalIgnoreCase ) )
571+ if ( line . Contains ( normalizedSearchText , StringComparison . OrdinalIgnoreCase ) )
568572 {
569573 return true ;
570574 }
@@ -581,7 +585,7 @@ private static bool TestMatchSub (FilterParams filterParams, string line, string
581585 }
582586 else
583587 {
584- if ( line . Contains ( searchText , StringComparison . OrdinalIgnoreCase ) )
588+ if ( line . Contains ( searchText , StringComparison . Ordinal ) )
585589 {
586590 return true ;
587591 }
@@ -593,16 +597,11 @@ private static bool TestMatchSub (FilterParams filterParams, string line, string
593597 var range = line . Length - searchText . Length ;
594598 if ( range > 0 )
595599 {
596- for ( var i = 0 ; i < range ; ++ i )
600+ for ( var i = 0 ; i <= range ; ++ i )
597601 {
598- var src = line . Substring ( i , searchText . Length ) ;
599-
600- if ( ! filterParams . IsCaseSensitive )
601- {
602- src = src . ToLowerInvariant ( ) ;
603- }
602+ var src = line . Slice ( i , searchText . Length ) ;
604603
605- var dist = DamerauLevenshteinDistance ( src , searchText ) ;
604+ var dist = DamerauLevenshteinDistance ( src , searchText , ! filterParams . IsCaseSensitive ) ;
606605
607606 if ( ( searchText . Length + 1 ) / ( float ) ( dist + 1 ) >= 11F / ( float ) ( filterParams . FuzzyValue + 1F ) )
608607 {
@@ -618,6 +617,22 @@ private static bool TestMatchSub (FilterParams filterParams, string line, string
618617 return false ;
619618 }
620619
620+ private static bool TestMatchSub (
621+ FilterParams filterParams ,
622+ ReadOnlyMemory < char > line , // From ILogLineMemory.FullLine
623+ ReadOnlySpan < char > normalizedSearchText ,
624+ ReadOnlySpan < char > searchText ,
625+ Regex rex ,
626+ bool exactMatch )
627+ {
628+ return TestMatchSub ( filterParams , line . Span , normalizedSearchText , searchText , rex , exactMatch ) ;
629+ }
630+
631+ private static bool TestMatchSub ( FilterParams filterParams , string line , string lowerSearchText , string searchText , Regex rex , bool exactMatch )
632+ {
633+ return TestMatchSub ( filterParams , line . AsSpan ( ) , lowerSearchText . AsSpan ( ) , searchText . AsSpan ( ) , rex , exactMatch ) ;
634+ }
635+
621636 private static unsafe int MemchrRPLC ( char * buffer , char c , int count )
622637 {
623638 var p = buffer ;
0 commit comments