@@ -40,11 +40,20 @@ public void Add<TCollection>(TCollection keys, Func<TKey, TValue> valueFunc) whe
40
40
foreach ( var key in keys ) Add ( key , valueFunc ( key ) ) ;
41
41
}
42
42
}
43
+ class DescendingStringLengthIComparer < TValue > : IComparer < Tuple < string , TValue > > {
44
+ int IComparer < Tuple < string , TValue > > . Compare ( Tuple < string , TValue > x , Tuple < string , TValue > y ) {
45
+ if ( x . Item1 . Length > y . Item1 . Length ) { return - 1 ; }
46
+ else if ( x . Item1 . Length < y . Item1 . Length ) { return 1 ; }
47
+ else { return string . CompareOrdinal ( x . Item1 , y . Item1 ) ; }
48
+ }
49
+ }
50
+
43
51
/// <summary>
44
52
/// A dictionary-based helper where the keys are classes of LaTeX <see cref="string"/>s, with special treatment
45
53
/// for commands (starting "\"). The start of an inputted <see cref="Span{Char}"/> is parsed, and an arbitrary object
46
- /// <typeparamref name="TValue"/> is returned, along with the number of matching characters. Processing is based on dictionary lookup
47
- /// with fallack to specified default functions for command and non-commands when lookup fails.
54
+ /// <typeparamref name="TValue"/> is returned, along with the number of matching characters. Processing is based on
55
+ /// dictionary lookup with fallack to specified default functions for command and non-commands when lookup fails.
56
+ /// For non-commands, dictionary lookup finds the longest matching non-command.
48
57
/// </summary>
49
58
[ SuppressMessage ( "Naming" , "CA1710:Identifiers should have correct suffix" ,
50
59
Justification = "This is conceptually a dictionary but has different lookup behavior" ) ]
@@ -60,17 +69,18 @@ public LaTeXCommandDictionary(DefaultDelegate defaultParser,
60
69
if ( SplitCommand ( key . AsSpan ( ) ) != key . Length - 1 )
61
70
commands . Add ( key , value ) ;
62
71
else throw new ArgumentException ( "Key is unreachable: " + key , nameof ( key ) ) ;
63
- else nonCommands . Add ( key , value ) ;
72
+ else nonCommands . Add ( new Tuple < string , TValue > ( key , value ) ) ;
64
73
} ;
65
74
}
66
75
readonly DefaultDelegate defaultParser ;
67
76
readonly DefaultDelegate defaultParserForCommands ;
68
77
69
- readonly Dictionary < string , TValue > nonCommands = new Dictionary < string , TValue > ( ) ;
78
+ readonly SortedSet < Tuple < string , TValue > > nonCommands =
79
+ new SortedSet < Tuple < string , TValue > > ( new DescendingStringLengthIComparer < TValue > ( ) ) ;
70
80
readonly Dictionary < string , TValue > commands = new Dictionary < string , TValue > ( ) ;
71
81
72
82
public IEnumerator < KeyValuePair < string , TValue > > GetEnumerator ( ) =>
73
- nonCommands . Select ( kvp => new KeyValuePair < string , TValue > ( kvp . Key , kvp . Value ) )
83
+ nonCommands . Select ( t => new KeyValuePair < string , TValue > ( t . Item1 , t . Item2 ) )
74
84
. Concat ( commands . Select ( kvp => new KeyValuePair < string , TValue > ( kvp . Key , kvp . Value ) ) )
75
85
. GetEnumerator ( ) ;
76
86
IEnumerator IEnumerable . GetEnumerator ( ) => GetEnumerator ( ) ;
@@ -111,12 +121,11 @@ static int SplitCommand(ReadOnlySpan<char> chars) {
111
121
return TryLookupNonCommand ( chars ) ;
112
122
}
113
123
Result < ( TValue Result , int SplitIndex ) > TryLookupNonCommand ( ReadOnlySpan < char > chars ) {
114
- string ? commandFound = null ; // TODO:short-circuit when found
115
- foreach ( string command in nonCommands . Keys ) {
116
- if ( chars . StartsWith ( command . AsSpan ( ) , StringComparison . Ordinal ) ) {
117
- commandFound = command ; }
124
+ foreach ( Tuple < string , TValue > t in nonCommands ) {
125
+ if ( chars . StartsWith ( t . Item1 . AsSpan ( ) , StringComparison . Ordinal ) ) {
126
+ return Result . Ok ( ( t . Item2 , t . Item1 . Length ) ) ; }
118
127
}
119
- return commandFound == null ? defaultParser ( chars ) : Result . Ok ( ( nonCommands [ commandFound ] , commandFound . Length ) ) ;
128
+ return defaultParser ( chars ) ;
120
129
}
121
130
}
122
131
0 commit comments