go.starlark.net@v0.0.0-20231101134539-556fd59b42f6/internal/spell/spell.go (about)

     1  // Package spell file defines a simple spelling checker for use in attribute errors
     2  // such as "no such field .foo; did you mean .food?".
     3  package spell
     4  
     5  import (
     6  	"strings"
     7  	"unicode"
     8  )
     9  
    10  // Nearest returns the element of candidates
    11  // nearest to x using the Levenshtein metric,
    12  // or "" if none were promising.
    13  func Nearest(x string, candidates []string) string {
    14  	// Ignore underscores and case when matching.
    15  	fold := func(s string) string {
    16  		return strings.Map(func(r rune) rune {
    17  			if r == '_' {
    18  				return -1
    19  			}
    20  			return unicode.ToLower(r)
    21  		}, s)
    22  	}
    23  
    24  	x = fold(x)
    25  
    26  	var best string
    27  	bestD := (len(x) + 1) / 2 // allow up to 50% typos
    28  	for _, c := range candidates {
    29  		d := levenshtein(x, fold(c), bestD)
    30  		if d < bestD {
    31  			bestD = d
    32  			best = c
    33  		}
    34  	}
    35  	return best
    36  }
    37  
    38  // levenshtein returns the non-negative Levenshtein edit distance
    39  // between the byte strings x and y.
    40  //
    41  // If the computed distance exceeds max,
    42  // the function may return early with an approximate value > max.
    43  func levenshtein(x, y string, max int) int {
    44  	// This implementation is derived from one by Laurent Le Brun in
    45  	// Bazel that uses the single-row space efficiency trick
    46  	// described at bitbucket.org/clearer/iosifovich.
    47  
    48  	// Let x be the shorter string.
    49  	if len(x) > len(y) {
    50  		x, y = y, x
    51  	}
    52  
    53  	// Remove common prefix.
    54  	for i := 0; i < len(x); i++ {
    55  		if x[i] != y[i] {
    56  			x = x[i:]
    57  			y = y[i:]
    58  			break
    59  		}
    60  	}
    61  	if x == "" {
    62  		return len(y)
    63  	}
    64  
    65  	if d := abs(len(x) - len(y)); d > max {
    66  		return d // excessive length divergence
    67  	}
    68  
    69  	row := make([]int, len(y)+1)
    70  	for i := range row {
    71  		row[i] = i
    72  	}
    73  
    74  	for i := 1; i <= len(x); i++ {
    75  		row[0] = i
    76  		best := i
    77  		prev := i - 1
    78  		for j := 1; j <= len(y); j++ {
    79  			a := prev + b2i(x[i-1] != y[j-1]) // substitution
    80  			b := 1 + row[j-1]                 // deletion
    81  			c := 1 + row[j]                   // insertion
    82  			k := min(a, min(b, c))
    83  			prev, row[j] = row[j], k
    84  			best = min(best, k)
    85  		}
    86  		if best > max {
    87  			return best
    88  		}
    89  	}
    90  	return row[len(y)]
    91  }
    92  
    93  func b2i(b bool) int {
    94  	if b {
    95  		return 1
    96  	} else {
    97  		return 0
    98  	}
    99  }
   100  
   101  func min(x, y int) int {
   102  	if x < y {
   103  		return x
   104  	} else {
   105  		return y
   106  	}
   107  }
   108  
   109  func abs(x int) int {
   110  	if x >= 0 {
   111  		return x
   112  	} else {
   113  		return -x
   114  	}
   115  }