55using SER . Code . Extensions ;
66using SER . Code . Helpers ;
77using SER . Code . Helpers . ResultSystem ;
8+ using SER . Code . MethodSystem ;
89using SER . Code . ScriptSystem ;
910using SER . Code . TokenSystem . Structures ;
1011using SER . Code . TokenSystem . Tokens ;
@@ -142,7 +143,9 @@ List<RunnableContext> contexts
142143
143144 if ( firstToken is not IContextableToken contextable )
144145 {
145- return rs + $ "'{ firstToken . RawRep } ' is not a valid way to start a line. Perhaps you made a typo?";
146+ var text = $ "'{ firstToken . RawRep } ' is not a valid way to start a line."
147+ + ( FindClosestMatch ( firstToken . RawRep ) is { } match ? $ " Did you mean to use '{ match } '?" : "" ) ;
148+ return rs + text . AsError ( ) ;
146149 }
147150
148151 var context = contextable . GetContext ( scr ) ;
@@ -242,4 +245,77 @@ private static Result HandleCurrentContext(BaseToken token, RunnableContext cont
242245 endLineContexting = true ;
243246 return true ;
244247 }
248+
249+ private static List < string > ? _suggestions = null ;
250+ public static string ? FindClosestMatch ( string input )
251+ {
252+ if ( _suggestions is null )
253+ {
254+ _suggestions = [ ] ;
255+ _suggestions . AddRange ( MethodIndex . GetMethods ( ) . Select ( m => m . Name ) ) ;
256+ _suggestions . AddRange ( KeywordToken . KeywordContexts . Select ( k => k . KeywordName ) ) ;
257+ }
258+
259+ var suggestion = _suggestions
260+ . Select ( name => new { Name = name , Score = GetJaroWinklerSimilarity ( input , name ) } )
261+ . Where ( x => x . Score > 0.5 )
262+ . OrderByDescending ( x => x . Score )
263+ . FirstOrDefault ( ) ;
264+
265+ return suggestion ? . Name ;
266+ }
267+
268+ public static double GetJaroWinklerSimilarity ( string s1 , string s2 )
269+ {
270+ double jaroDist = GetJaroSimilarity ( s1 , s2 ) ;
271+ if ( jaroDist < 0.7 ) return jaroDist ;
272+
273+ int prefixLength = 0 ;
274+ for ( int i = 0 ; i < Math . Min ( 4 , Math . Min ( s1 . Length , s2 . Length ) ) ; i ++ )
275+ {
276+ if ( s1 [ i ] == s2 [ i ] ) prefixLength ++ ;
277+ else break ;
278+ }
279+
280+ return jaroDist + prefixLength * 0.1 * ( 1.0 - jaroDist ) ;
281+ }
282+
283+ private static double GetJaroSimilarity ( string s1 , string s2 )
284+ {
285+ int s1Len = s1 . Length , s2Len = s2 . Length ;
286+ if ( s1Len == 0 && s2Len == 0 ) return 1.0 ;
287+
288+ int matchDistance = Math . Max ( s1Len , s2Len ) / 2 - 1 ;
289+ bool [ ] s1Matches = new bool [ s1Len ] ;
290+ bool [ ] s2Matches = new bool [ s2Len ] ;
291+
292+ int matches = 0 ;
293+ for ( int i = 0 ; i < s1Len ; i ++ )
294+ {
295+ int start = Math . Max ( 0 , i - matchDistance ) ;
296+ int end = Math . Min ( i + matchDistance + 1 , s2Len ) ;
297+ for ( int j = start ; j < end ; j ++ )
298+ {
299+ if ( s2Matches [ j ] || s1 [ i ] != s2 [ j ] ) continue ;
300+ s1Matches [ i ] = true ;
301+ s2Matches [ j ] = true ;
302+ matches ++ ;
303+ break ;
304+ }
305+ }
306+
307+ if ( matches == 0 ) return 0.0 ;
308+
309+ double transpositions = 0 ;
310+ int k = 0 ;
311+ for ( int i = 0 ; i < s1Len ; i ++ )
312+ {
313+ if ( ! s1Matches [ i ] ) continue ;
314+ while ( ! s2Matches [ k ] ) k ++ ;
315+ if ( s1 [ i ] != s2 [ k ] ) transpositions ++ ;
316+ k ++ ;
317+ }
318+
319+ return ( matches / ( double ) s1Len + matches / ( double ) s2Len + ( matches - transpositions / 2.0 ) / matches ) / 3.0 ;
320+ }
245321}
0 commit comments