aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/golang.org/x/text/internal/colltab/iter.go
blob: c1b1ba81ef0e012e62e36bc9a029578609d293fc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// Copyright 2015 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.

package colltab

// An Iter incrementally converts chunks of the input text to collation
// elements, while ensuring that the collation elements are in normalized order
// (that is, they are in the order as if the input text were normalized first).
type Iter struct {
	Weighter Weighter
	Elems    []Elem
	// N is the number of elements in Elems that will not be reordered on
	// subsequent iterations, N <= len(Elems).
	N int

	bytes []byte
	str   string
	// Because the Elems buffer may contain collation elements that are needed
	// for look-ahead, we need two positions in the text (bytes or str): one for
	// the end position in the text for the current iteration and one for the
	// start of the next call to appendNext.
	pEnd  int // end position in text corresponding to N.
	pNext int // pEnd <= pNext.
}

// Reset sets the position in the current input text to p and discards any
// results obtained so far.
func (i *Iter) Reset(p int) {
	i.Elems = i.Elems[:0]
	i.N = 0
	i.pEnd = p
	i.pNext = p
}

// Len returns the length of the input text.
func (i *Iter) Len() int {
	if i.bytes != nil {
		return len(i.bytes)
	}
	return len(i.str)
}

// Discard removes the collation elements up to N.
func (i *Iter) Discard() {
	// TODO: change this such that only modifiers following starters will have
	// to be copied.
	i.Elems = i.Elems[:copy(i.Elems, i.Elems[i.N:])]
	i.N = 0
}

// End returns the end position of the input text for which Next has returned
// results.
func (i *Iter) End() int {
	return i.pEnd
}

// SetInput resets i to input s.
func (i *Iter) SetInput(s []byte) {
	i.bytes = s
	i.str = ""
	i.Reset(0)
}

// SetInputString resets i to input s.
func (i *Iter) SetInputString(s string) {
	i.str = s
	i.bytes = nil
	i.Reset(0)
}

func (i *Iter) done() bool {
	return i.pNext >= len(i.str) && i.pNext >= len(i.bytes)
}

func (i *Iter) appendNext() bool {
	if i.done() {
		return false
	}
	var sz int
	if i.bytes == nil {
		i.Elems, sz = i.Weighter.AppendNextString(i.Elems, i.str[i.pNext:])
	} else {
		i.Elems, sz = i.Weighter.AppendNext(i.Elems, i.bytes[i.pNext:])
	}
	if sz == 0 {
		sz = 1
	}
	i.pNext += sz
	return true
}

// Next appends Elems to the internal array. On each iteration, it will either
// add starters or modifiers. In the majority of cases, an Elem with a primary
// value > 0 will have a CCC of 0. The CCC values of collation elements are also
// used to detect if the input string was not normalized and to adjust the
// result accordingly.
func (i *Iter) Next() bool {
	if i.N == len(i.Elems) && !i.appendNext() {
		return false
	}

	// Check if the current segment starts with a starter.
	prevCCC := i.Elems[len(i.Elems)-1].CCC()
	if prevCCC == 0 {
		i.N = len(i.Elems)
		i.pEnd = i.pNext
		return true
	} else if i.Elems[i.N].CCC() == 0 {
		// set i.N to only cover part of i.Elems for which prevCCC == 0 and
		// use rest for the next call to next.
		for i.N++; i.N < len(i.Elems) && i.Elems[i.N].CCC() == 0; i.N++ {
		}
		i.pEnd = i.pNext
		return true
	}

	// The current (partial) segment starts with modifiers. We need to collect
	// all successive modifiers to ensure that they are normalized.
	for {
		p := len(i.Elems)
		i.pEnd = i.pNext
		if !i.appendNext() {
			break
		}

		if ccc := i.Elems[p].CCC(); ccc == 0 || len(i.Elems)-i.N > maxCombiningCharacters {
			// Leave the starter for the next iteration. This ensures that we
			// do not return sequences of collation elements that cross two
			// segments.
			//
			// TODO: handle large number of combining characters by fully
			// normalizing the input segment before iteration. This ensures
			// results are consistent across the text repo.
			i.N = p
			return true
		} else if ccc < prevCCC {
			i.doNorm(p, ccc) // should be rare, never occurs for NFD and FCC.
		} else {
			prevCCC = ccc
		}
	}

	done := len(i.Elems) != i.N
	i.N = len(i.Elems)
	return done
}

// nextNoNorm is the same as next, but does not "normalize" the collation
// elements.
func (i *Iter) nextNoNorm() bool {
	// TODO: remove this function. Using this instead of next does not seem
	// to improve performance in any significant way. We retain this until
	// later for evaluation purposes.
	if i.done() {
		return false
	}
	i.appendNext()
	i.N = len(i.Elems)
	return true
}

const maxCombiningCharacters = 30

// doNorm reorders the collation elements in i.Elems.
// It assumes that blocks of collation elements added with appendNext
// either start and end with the same CCC or start with CCC == 0.
// This allows for a single insertion point for the entire block.
// The correctness of this assumption is verified in builder.go.
func (i *Iter) doNorm(p int, ccc uint8) {
	n := len(i.Elems)
	k := p
	for p--; p > i.N && ccc < i.Elems[p-1].CCC(); p-- {
	}
	i.Elems = append(i.Elems, i.Elems[p:k]...)
	copy(i.Elems[p:], i.Elems[k:])
	i.Elems = i.Elems[:n]
}