@@ -40,11 +40,20 @@ public void Add<TCollection>(TCollection keys, Func<TKey, TValue> valueFunc) whe
4040 foreach ( var key in keys ) Add ( key , valueFunc ( key ) ) ;
4141 }
4242 }
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+
4351 /// <summary>
4452 /// A dictionary-based helper where the keys are classes of LaTeX <see cref="string"/>s, with special treatment
4553 /// 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.
4857 /// </summary>
4958 [ SuppressMessage ( "Naming" , "CA1710:Identifiers should have correct suffix" ,
5059 Justification = "This is conceptually a dictionary but has different lookup behavior" ) ]
@@ -60,17 +69,18 @@ public LaTeXCommandDictionary(DefaultDelegate defaultParser,
6069 if ( SplitCommand ( key . AsSpan ( ) ) != key . Length - 1 )
6170 commands . Add ( key , value ) ;
6271 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 ) ) ;
6473 } ;
6574 }
6675 readonly DefaultDelegate defaultParser ;
6776 readonly DefaultDelegate defaultParserForCommands ;
6877
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 > ( ) ) ;
7080 readonly Dictionary < string , TValue > commands = new Dictionary < string , TValue > ( ) ;
7181
7282 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 ) )
7484 . Concat ( commands . Select ( kvp => new KeyValuePair < string , TValue > ( kvp . Key , kvp . Value ) ) )
7585 . GetEnumerator ( ) ;
7686 IEnumerator IEnumerable . GetEnumerator ( ) => GetEnumerator ( ) ;
@@ -111,12 +121,11 @@ static int SplitCommand(ReadOnlySpan<char> chars) {
111121 return TryLookupNonCommand ( chars ) ;
112122 }
113123 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 ) ) ; }
118127 }
119- return commandFound == null ? defaultParser ( chars ) : Result . Ok ( ( nonCommands [ commandFound ] , commandFound . Length ) ) ;
128+ return defaultParser ( chars ) ;
120129 }
121130 }
122131
0 commit comments