Copyright 2020 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
License regexp compilation and execution. See https://swtch.com/~rsc/regexp/regexp2.html for detailed explanation of a similar machine. This machine differs from that one in a few important ways: word-at-a-time operation, word canonicalization, spell-checking, and early-cut wildcard matching. Word-at-a-Time Operation Most regexp matching engines, including the one linked above, process one input byte – or perhaps one Unicode code point – at a time. In contrast, this engine processes one word at a time. The license regular expressions (LREs) and the input texts themselves are first split into words, represented by integer word IDs in an automatically maintained word list (the Dict type). Then the NFA programs and DFA state sets are expressed in terms of word IDs, not characters. Operating on words lets us define wildcards like __10__, meaning "up to 10 words", which may be more useful than "up to 100 bytes". More importantly, it makes the match completely independent of to the exact punctuation and spacing of the input. It also greatly shortens the NFA and DFA representations. Word Canonicalization Splitting the input into words provides an opportunity to regularize the input further. Splitting folds letter case: "SHALL" and "shall" are given the same word ID. It also strips accents from vowels: "QUÉBEC", "Québec", and "quebec" are all given the same word ID. Although in general punctuation is ignored, canonicalization recognizes the pattern "word(s)" where word is any word and (s) is the literal three-byte sequence "(s)"; it canonicalizes that to "words". For example, "notice(s)" canonicalizes to "notices". Combined with spell checking (see below), "notice(s)" will therefore match "notices" or "notice". Another special case involving punctuation is "(c)" and "©", both of which canonicalize to "copyright". A pair of "copyright" words also canonicalizes to a single one, so that "Copyright © 2020" and "copyright 2020" are the same text. An unfortunate side effect of the "(c)" conversion is that the list bullets "c." and "c)" are different words from "(c)". That mismatch is handled by spell checking (see below). There are other common words that are often substituted for each other and worth canonicalizing, to simplify license patterns. These include - is are - it them they - the these this those - copy copies See the canonicalRewrites list in dict.go and its uses for the details. Regular singular/plural forms are handled by spell checking. Spell Checking At each state, the DFA knows which words would help move the match along. If it doesn't see one of those words, it would normally end the search. That moment provides an opportunity to do spelling correction: if the input word being processed is close enough to an expected word, we can treat it as having been corrected to the expected word. More specifically, if both the input and the target word are at least four bytes, and if the input can be edited to produce the target by a single byte insertion, deletion, or modification, then the words are considered close enough, and the misspelled word is treated as the target. This handles typos like "notwork" for "network". It also handles most singular/plural distinctions, such as "notice" for "notices". See the canMisspell function for the implementation. [One cost of word canonicalization (at least in the current implementation) is that spell checking does not consider non-canonical spellings of words. For example, "copies" canonicalizes to "copy", meaning that the word the DFA expects is "copy" (never "copies"), so an input word "copiesx" does NOT get spell-checked to "copies" (and from there to "copy"). This could be added if necessary.] Another possible spell-check fix is to join words incorrectly split apart. This happens sometimes in text that has been word-wrapped with hyphenation. If the next two input words can be joined together to produce an expected word, that pair is consumed as a misspelling of the expected one. For example, the hyphenation "nonin-" (new line) "fringement" might be reassembled as the expected word "noninfringement". This also handles non-standard over-hyphenation, such as "non-infringement" for "noninfringement" or "sub-license" for "sublicense". See canMisspellJoin for the implementation. Another possible spell-check fix is to split apart words incorrectly joined. This happens sometimes in text that has been un-word-wrapped by deleting newlines instead of turning them into spaces, or where spaces or other separating punctuation have been inadvertently deleted. If an expected word is a prefix of the next input word, and the remaining suffix is expected after that word, then the single word is consumed as a misspelling of the word pair. For example, "andor" can be consumed as a misspelling of "and/or". The "misspell split" comment marks the implementation. The inclusion of both the joining and splitting fixes means that license patterns can, for example, use either "non-infringement" or "noninfringement" and still match the other form without additional work. However, the former may be preferable in that it also accepts a wrapped and hyphenated "non-infringement" that turns into "non-in <newline> fringement", which "noninfringemnt" would not match. Spell checking also accepts "c" and "copyright" for each other. A plain "c" is not canonicalized to "copyright" to avoid making plain "c", especially in a file name like "file.c", appear to be the start of a copyright notice. (Spell checking only applies inside a potential match that is already started, but word canonicalization applies to every word in the file.) Early-Cut Wildcard Matching This implementation adds "cut" operations to reduce the number of states tracked when using counted wildcard patterns. To explain what that means exactly, first some background. DFAs are not good at counting: the standard regexp pattern /.{0,10}/ is much more expensive than /.. On the other hand, /.*/ can easily match far too much; the limit in /.{0,10}/ is semantically useful. LREs provide a counted wildcard __10__ equivalent to regexp /.{0,10}/. The counting can produce a multiplicative number of DFA states. For example, consider the pattern "The name __10__ may not be used" matched against "The name may not be may not be may not be may not be used". If __10__ means "up to 10 words, any words at all", then that text should be matched, assigning "may not be may not be may not be" to the wildcard. This never happens in practice, but the standard DFA construction must be prepared for it, along with variants like: The name may may may may may may may may may may may not be used. The name may not may not may not may not may not may not be used. The name may may not be may not be may not be may not be used. The name may not may not be used may not be used may not be used. In general the DFA must track (1) how many wildcard words have been used up so far, and (2) how far into the "may not be used" has been matched at the current input location. That's roughly 10 * 4 = 40 states. In a real license, the possible literal text that must be tracked is as many words as the wildcard itself allows, leading in this case to roughly 10 * 10 = 100 states. A __20__ produces roughly 400 states, and so on. This extra work is clearly useless: real inputs don't look like that, and people wouldn't recognize them if they did. After seeing a few of the words following the wildcard, we can safely assume that the wildcard part of the match is over. In the example above, after seeing The name Google may not be we might as well assume the wildcard is over and the next word must be "used", to complete the pattern. This is true even though technically the pattern might be interpreted to allow The name "Google may not be may not be" may not be used. Empirically, this kind of abuse does not come up in practice. This implementation cuts off wildcard matches by inserting an implicit "cut" operator (similar to and named after Prolog's cut operator) three literal words after each wildcard. The cut discards (cuts off) any NFA threads still attempting wildcard matches. That is, our example is interpreted implicitly as The name __10__ may not be (CUT) used. The effect of this cut after three words is that the 10 * 10 states drops to 10 * 3, 20 * 20 drops to 20 * 3, and so on: wildcards now have a footprint only linear in their size, not quadratic. As a larger example, the implicit cut in the longer pattern The name __20__ may not be (CUT) used to endorse or promote products derived from this software without specific prior written permission. reduces the total number of DFA states from 248 to 80. It is still possible to delay the cut by following the wildcard with optional words or phrases, so some state blowup is still possible, but not nearly as much. Overall, at time of writing, implicit cuts reduce the size of the DFA for the full license set from 5.8M states (240 MB and 39s to build) to 615k states (10 MB, under one second to build). The implicit cut is represented as an instCut that terminates any other NFA threads still matching a particular wildcard. The application of instCut happens in (*nfaState).trim.

package match

import (
	
	
	
	
)
A reProg is a regexp program: an instruction list.
type reProg []reInst
A reInst is a regexp instruction: an opcode and a numeric argument
type reInst struct {
	op  instOp
	arg int32
}
An instOp is the opcode for a regexp instruction.
type instOp int32

const (
	instInvalid instOp = iota

	instWord  // match specific word
	instAny   // match any word
	instAlt   // jump to both pc+1 and pc+1+arg
	instJump  // jump to pc+1+arg
	instMatch // completed match identified by arg
	instCut   // cut off the instAlt range starting at pc+1+arg
)
string returns a textual listing of the given program. The dictionary d supplies the actual words for the listing.
func ( reProg) ( *Dict) string {
	var  strings.Builder
	 := .Words()
	for ,  := range  {
		fmt.Fprintf(&, "%d\t", )
		switch .op {
		case instWord:
			fmt.Fprintf(&, "word %s\n", [.arg])
		case instAny:
			fmt.Fprintf(&, "any\n")
		case instAlt:
			fmt.Fprintf(&, "alt %d\n", +1+int(.arg))
		case instJump:
			fmt.Fprintf(&, "jump %d\n", +1+int(.arg))
		case instMatch:
			fmt.Fprintf(&, "match %d\n", int(.arg))
target is always an instAlt. Decode the target of the alt as well.
			 :=  + 1 + int(.arg)
			fmt.Fprintf(&, "cut [%d, %d]\n", , +1+int([].arg))
		}
	}
	return .String()
}
reCompile holds compilation state for a single regexp.
type reCompile struct {
	prog       reProg // program being constructed
	endPattern bool   // compiling the end of the pattern
	cut        []reCut
	err        error // first problem found; report delayed until end of compile
}
reCut holds the information about a pending cut.
type reCut struct {
	start   int // cut off the alt at pc = start
	trigger int // ... after trigger more literal word matches
}
compile appends a program for the regular expression re to init and returns the result. A successful match of the program for re will report the match value m.
func ( *reSyntax) ( reProg,  int32) (reProg, error) {
	 := &reCompile{prog: , endPattern: true}
	.compile()
	.compileCuts()
	return append(.prog, reInst{op: instMatch, arg: }), .err
}
compile appends the compiled program for re to c.prog.
func ( *reCompile) ( *reSyntax) {
	switch .op {
	default:
		panic(fmt.Sprintf("unexpected re.op %d", .op))

nothing

	case opWords:
		for ,  := range .w {
			.prog = append(.prog, reInst{op: instWord, arg: int32()})
			.reduceCut()
		}
		if .endPattern {
			.compileCuts()
		}

	case opConcat:
		 := len(.sub)
		if .endPattern {
			for  > 0 && canMatchEmpty(.sub[-1]) {
				--
			}
		}
		for ,  := range .sub {
			.endPattern =  >= 
			.()
		}

	case opQuest:
		 := len(.prog)
		.prog = append(.prog, reInst{op: instAlt})
		 := .cut
		 := .endPattern
		.(.sub[0])
		if  {
			.compileCuts()
		}
		.cut = .mergeCut(, .cut)
		.prog[].arg = int32(len(.prog) - ( + 1))

	case opAlternate:
		 := .cut
		 := .endPattern
		var  []reCut
		var ,  []int
		for ,  := range .sub {
			if +1 < len(.sub) {
				 = append(, len(.prog))
				.prog = append(.prog, reInst{op: instAlt})
			}
			.cut = 
			.endPattern = 
			.()
			 = .mergeCut(, .cut)
			if +1 < len(.sub) {
				 = append(, len(.prog))
				.prog = append(.prog, reInst{op: instJump})
			}
		}
		.cut = 
All alts jump to after jump.
		for ,  := range  {
			.prog[].arg = int32(([] + 1) - ( + 1))
		}
Patch all jumps to the end.
		 := len(.prog)
		for ,  := range  {
			.prog[].arg = int32( - ( + 1))
		}

All alts jump to the end of the expression, as if it were (.(.(.(.)?)?)?)? This results in smaller NFA state lists (max 2 states) than compiling like .?.?.?.? (max re.n states).
		.compileCuts()
		if .endPattern && .err == nil {
			.err = fmt.Errorf("__%d__ wildcard with no required text following", .n)
		}
		 := len(.prog)
		 := len(.prog) + int(.n)*2
		for  := int32(0);  < .n; ++ {
			.prog = append(.prog, reInst{op: instAlt, arg: int32( - (len(.prog) + 1))})
			.prog = append(.prog, reInst{op: instAny})
		}
		if .n > 3 {
			.cut = []reCut{{start: , trigger: 3}}
		}
	}
}
compileCuts emits instCut instructions for all pending cuts. See comment at top of file for information about cuts.
func ( *reCompile) () {
	for ,  := range .cut {
		.compileCut()
	}
	.cut = nil
}
compileCut emits an instCut instruction for cut.
func ( *reCompile) ( reCut) {
	.prog = append(.prog, reInst{op: instCut, arg: int32(.start - (len(.prog) + 1))})
}
reduceCut records that a new literal word has been matched, reducing the triggers in c.cut by 1 and emitting any triggered cuts.
func ( *reCompile) () {
	var  []reCut
	for ,  := range .cut {
		if .trigger--; .trigger == 0 {
			.compileCut()
			continue
		}
		 = append(, )
	}
	.cut = 
}
mergeCut merges the two cut lists cut1 and cut2 into a single cut list. Cuts with the same start but different triggers are merged into a single entry with the larger of the two triggers.
func ( *reCompile) (,  []reCut) []reCut {
	if len() == 0 {
		return 
	}
	if len() == 0 {
		return 
	}

	var  []reCut
	 = append(, ...)
	 = append(, ...)
	sort.Slice(, func(,  int) bool {
		if [].start != [].start {
			return [].start < [].start
		}
		return [].trigger > [].trigger
	})

	 := 0
	for ,  := range  {
		if  == 0 || [-1].start != .start {
			[] = 
			++
		}
	}
	return [:]
}
canMatchEmpty reports whether re can match an empty text.
func ( *reSyntax) bool {
	switch .op {
	case opAlternate:
		for ,  := range .sub {
			if () {
				return true
			}
		}
		return false

	case opConcat:
		for ,  := range .sub {
			if !() {
				return false
			}
		}

	case opWords:
		if len(.w) > 0 {
			return false
		}
	}

	return true
}
reCompileMulti returns a program that matches any of the listed regexps. The regexp list[i] returns match value i when it matches.
func ( []reProg) reProg {
	var  reProg
	for ,  := range  {
		 := -1
Insert Alt that can choose to jump over this program (to the next one).
			 = len()
			 = append(, reInst{op: instAlt})
		}

		for ,  := range  {
			if .op == instMatch {
				 = append(, reInst{op: instMatch, arg: int32()})
			} else {
				 = append(, )
			}
		}

		if  >= 0 {
			[].arg = int32(len() - ( + 1))
		}
	}
	return 
}
NFA state operations, in service of building a DFA. (Again, see https://swtch.com/~rsc/regexp/regexp2.html for background.)
An nfaState represents the state of the NFA - all possible instruction locations - after reading a particular input.
type nfaState []int32
nfaStart returns the start state for the NFA executing prog.
func ( reProg) nfaState {
	var  nfaState
	.add(, 0)
	.trim()
	return 
}
add adds pc and other states reachable from it to the set of possible instruction locations in *s.
Avoid adding same state twice. This scan is linear in the size of *s, which makes the overall nfaStart / s.next operation technically quadratic in the size of *s, but licenses are long texts of literal words, so the NFA states end up being very small - there's not much ambiguity about where we are in the list. If this ever showed up as expensive on a profile, we could switch to a sparse set instead; see https://research.swtch.com/sparse.
	for ,  := range * {
		if  ==  {
			return
		}
	}

	* = append(*, )
	switch [].op {
	case instAlt:
		.(, +1)
		.(, +1+[].arg)
	case instJump:
		.(, +1+[].arg)
	case instCut:
		.(, +1)
	}
}
trim canonicalizes *s by sorting it and removing unnecessary states. All that must be preserved between input tokens are the instruction locations that advance the input (instWord and instAny) or that report a match (instMatch).
Collect cut ranges and sort.
	var  []int32
	for ,  := range * {
		if [].op == instCut {
			 = append(, +1+[].arg)
		}
	}
	sortInt32s()
Sort and save just the word, any, match instructions, applying cuts.
	sortInt32s(*)
	 := (*)[:0]
	for ,  := range * {
		switch [].op {
		case instWord, instAny, instMatch:
			for len() > 0 &&  > [0]+1+[[0]].arg {
				 = [1:]
			}
			if len() > 0 && [0] <=  &&  <= [0]+1+[[0]].arg {
				break
			}
			 = append(, )
		}
	}
	* = 
}
next returns the new state that results from reading word w in state s, and whether a match has been belatedly detected just before w.
func ( nfaState) ( reProg,  WordID) nfaState {
	var  nfaState
	for ,  := range  {
		 := &[]
		switch .op {
		case instAny:
			.add(, +1)
		case instWord:
			if  == WordID(.arg) {
				.add(, +1)
			}
		}
	}
	.trim()
	return 
}
match returns the smallest match value of matches reached in state s, or -1 if there is no match.
func ( nfaState) ( reProg) int32 {
	 := int32(-1)
	for ,  := range  {
		 := &[]
		switch .op {
		case instMatch:
			if  == -1 ||  > .arg {
				 = .arg
			}
		}
	}
	return 
}
words returns the list of distinct words that can lead the NFA out of state s and into a new state. The returned list is sorted in increasing order. If the state can match any word (using instAny), the word ID AnyWord is first in the list.
func ( nfaState) ( reProg) []WordID {
	var  []WordID
	 := false
:
	for ,  := range  {
		 := &[]
		switch .op {
		case instAny:
			if ! {
				 = true
				 = append(, AnyWord)
			}
Dedup; linear scan but list should be small. If this is too slow, the caller should pass in a reusable map[WordID]bool.
			for ,  := range  {
				if  == WordID(.arg) {
					continue 
				}
			}
			 = append(, WordID(.arg))
		}
	}

	sortWordIDs()
	return 
}
appendEncoding appends a byte encoding of the state s to enc and returns the result.
func ( nfaState) ( []byte) []byte {
	 := len()
	for cap() < +len()*4 {
		 = append([:cap()], 0)
	}
	 = [:+len()*4]
	for ,  := range  {
		binary.BigEndian.PutUint32([+4*:], uint32())
	}
	return 
}
DFA building
A reDFA is an encoded DFA over word IDs. The encoded DFA is a sequence of encoded DFA states, packed together. Each DFA state is identified by the index where it starts in the slice. The initial DFA state is at the start of the slice, index 0. Each DFA state records whether reaching that state counts as matching the input, which of multiple regexps matched, and then the transition table for the possible words that lead to new states. (If a word is found that is not in the current state's transition table, the DFA stops immediately with no match.) The encoding of this state information is: - a one-word header M | N<<1, where M is 0 for a non-match, 1 for a match, and N is the number of words in the table. This header is conveniently also the number of words that follow in the encoding. - if M == 1, a one-word value V that is the match value to report, identifying which of a set of regexps has been matched. - N two-word pairs W:NEXT indicating that if word W is seen, the DFA should move to the state at offset NEXT. The pairs are sorted by W. An entry for W == AnyWord is treated as matching any input word; an exact match later in the list takes priority. The list is sorted by W, so AnyWord is always first if present.
type reDFA []int32
A dfaBuilder holds state for building a DFA from a reProg.
type dfaBuilder struct {
	prog reProg         // program being processed
	dfa  reDFA          // DFA so far
	have map[string]int // map from encoded NFA state to dfa array offset
	enc  []byte         // encoding buffer
}
reCompileDFA compiles prog into a DFA.
func ( reProg) reDFA {
	 := &dfaBuilder{
		prog: ,
		have: map[string]int{"": -1}, // dead (empty) NFA state encoding maps to DFA offset -1
	}
	.add(nfaStart())
	return .dfa
}
add returns the offset of the NFA state s in the DFA b.dfa, adding it to the end of the DFA if needed.
If we've processed this state already, return its known position.
	.enc = .appendEncoding(.enc[:0])
	,  := .have[string(.enc)]
	if  {
		return int32()
	}
New state; append to current end of b.dfa. Record position now, before filling in completely, in case a transition cycle leads back to s.
	 = len(.dfa)
	.have[string(.enc)] = 
Reserve room for this DFA state, so that new DFA states can be appended to it as we fill this one in. The total size of the state is 1+haveMatch+2*#words.
	 := .words(.prog)
	 := .match(.prog)
	 := 1 + 2*len()
	if  >= 0 {
		++
	}
	for cap(.dfa) < + {
		.dfa = append(.dfa[:cap(.dfa)], 0)
	}
	.dfa = .dfa[:+]
Fill in state.
	 := 
	.dfa[] = int32( - 1) // header: M | N<<1 == (match>=0) + 2*len(words)
	++
	if  >= 0 {
		.dfa[] =  // match value
		++
	}
	for ,  := range  {
		 := .next(.prog, )
		 := .()
		.dfa[] = int32()
		.dfa[+1] = 
		 += 2
	}

	return int32()
}
string returns a textual listing of the DFA. The dictionary d supplies the actual words for the listing.
func ( reDFA) ( *Dict) string {
	var  strings.Builder
	for  := 0;  < len(); {
		fmt.Fprintf(&, "%d", )
		 := []
		++
		if &1 != 0 {
			fmt.Fprintf(&, " m%d", [])
			++
		}
		 :=  >> 1
		for ;  > 0; -- {
			 := WordID([])
			 := [+1]
			 += 2
			var  string
			if  == AnyWord {
				 = "*"
			} else {
				 = .Words()[]
			}
			fmt.Fprintf(&, " %s:%d", , )
		}
		fmt.Fprintf(&, "\n")
	}
	return .String()
}
stateAt returns (partly) decoded information about the DFA state at the given offset. If the state is a matching state, stateAt returns match >= 0 specifies the match ID. If the state is not a matching state, stateAt returns match == -1. Either way, stateAt also returns the outgoing transition list interlaced in the delta slice. The caller can iterate over delta using: for i := 0; i < len(delta); i += 2 { dw, dnext := WordID(delta[i]), delta[i+1] if currentWord == dw { off = dnext } }
func ( reDFA) ( int32) ( int32,  []int32) {
	 := []
	++
	 = -1
	if &1 != 0 {
		 = []
		++
	}
	 :=  >> 1
	return , [ : +2*]
}
TraceDFA controls whether DFA execution prints debug tracing when stuck. If TraceDFA > 0 and the DFA has followed a path of at least TraceDFA symbols since the last matching state but hits a dead end, it prints out information about the dead end.
match looks for a match of DFA at the start of words, which are the result of dict.Split(text) or a subslice of it. match returns the match ID of the longest match, as well as the index in words immediately following the last matched word. If there is no match, match returns -1, 0.
func ( reDFA) ( *Dict,  string,  []Word) ( int32,  int) {
	,  = -1, 0
	 := int32(0) // offset of current state in DFA
	 := .Words()
No range loop here: misspellings can adjust i.
:
	for  := 0;  < len(); ++ {
		 := []
		 := .ID
Find next state in DFA for w.
		,  := .stateAt()
		if  >= 0 {
			 = 
			 = 
		}
Handle and remove AnyWord if present. Simplifes the remaining loops.
		 := int32(-1)
		if len() > 0 && WordID([0]) == AnyWord {
			 = [1]
			 = [2:]
		}

		for  := 0;  < len();  += 2 {
			if WordID([]) ==  {
				 = [+1]
				continue 
			}
		}
No exact word match. Try context-sensitive spell check. We know the words that could usefully come next. Do any of those look enough like the word we have? TODO: Should the misspellings reduce the match percent?
have is the current word; have2 is the word after that.
		 := toFold([[].Lo:[].Hi])
		 := ""
		if +1 < len() {
			 = toFold([[+1].Lo:[+1].Hi])
		}

		for  := 0;  < len();  += 2 {
			,  := WordID([]), [+1]
			 := []
Can we spell want by joining have and have2? This can happen with hyphenated line breaks.
			if canMisspellJoin(, , ) {
				 = 
				++ // for have; loop will i++ again for have2
				continue 
			}
misspell split Or can have be split into two words such that the pair is something we'd expect to see right now?
have[:len(want)] matches want. Look to see if have[len(want):] can be the word after want.
				 := [len():]
				,  := .stateAt()
				 := int32(-1)
				for  := 0;  < len();  += 2 {
					,  := WordID([]), [+1]
					if  == AnyWord || [] ==  {
						 = 
					}
				}
Successfully split have into two words to drive the DFA forward two steps.
					if  >= 0 {
						 = 
						 = 
					}
					 = 
					continue 
				}
			}
Can we misspell want as have?
			if canMisspell(, ) {
				 = 
				continue 
			}
		}

Stuck - match is about to abort. For help debugging why a match doesn't work, if we seemed to be in the middle of a promising match (at least 5 words that moved the DFA forward since the last time we saw a matching state), print information about it.
			if TraceDFA > 0 && - >= TraceDFA {
				 :=  - 10
				if  < 0 {
					 = 0
				}
				print("DFA mismatch at «",
					[[].Lo:[].Lo], "|",
					[[].Lo:[].Hi], "»\n")
				print("Possible next words:\n")
				for  := 0;  < len();  += 2 {
					print("\t", [[]], "\n")
				}
			}
Return best match we found.
			return , 
		}
		 = 
	}

	if ,  := .stateAt();  >= 0 {
		 = 
		 = len()
	}
	if  := len(); TraceDFA > 0 && - >= TraceDFA {
		 :=  - 10
		if  < 0 {
			 = 0
		}
		println("DFA ran out of input at «", [[-10].Lo:], "|", "EOF", "»\n")
	}
	return , 
}

func ( []int32) {
	sort.Slice(, func(,  int) bool {
		return [] < []
	})
}

func ( []WordID) {
	sort.Slice(, func(,  int) bool {
		return [] < []
	})
}
canMisspell reports whether want can be misspelled as have. Both words have been converted to lowercase already (want by the Dict, have by the caller).
Allow single-letter replacement, insertion, or deletion.
Count common bytes at start and end of strings.
		 := 0
		for  < len() &&  < len() && [] == [] {
			++
		}
		 := 0
		for  < len() &&  < len() && [len()-1-] == [len()-1-] {
			++
Total common bytes must be at least all but one of both strings.
		if + >= len()-1 && + >= len()-1 {
			return true
		}
	}
We have to canonicalize "(C)" and "(c)" to "copyright", but then that produces an unfortunate disconnect between list bullets "c.", "c)", and "(c)". The first two are both "c", but the third is "copyright". We can't canonicalize all "c" to "copyright", or else we'll see spurious "copyright" words in path names like "file.c", which might change the boundaries of an overall copyright notice match. Instead, we correct the ambiguity by treating "c" and "copyright" the same during spell check. (Spell checks only apply when a match has already started, so they don't affect the match boundaries.) The want string has been canonicalized, so it must be "c" or "copyright" (not "©"), but the have string has only been folded, so it can be any of the three.
	if ( == "c" ||  == "copyright") && ( == "c" ||  == "copyright" ||  == "©") {
		return true
	}

	return false
}
canMisspellJoin reports whether want can be misspelled as the word pair have1, have2. All three words have been converted to lowercase already (want by the Dict, have1, have2 by the caller).
want == have1+have2 but without allocating the concatenation
	return len() == len()+len() &&
		[:len()] ==  &&
		[len():] ==