...
1
2
3
4
5
6 package utf8string
7
8 import (
9 "errors"
10 "unicode/utf8"
11 )
12
13
14
15
16
17
18
19
20
21
22 type String struct {
23 str string
24 numRunes int
25
26 width int
27 bytePos int
28 runePos int
29 nonASCII int
30 }
31
32
33 func NewString(contents string) *String {
34 return new(String).Init(contents)
35 }
36
37
38
39 func (s *String) Init(contents string) *String {
40 s.str = contents
41 s.bytePos = 0
42 s.runePos = 0
43 for i := 0; i < len(contents); i++ {
44 if contents[i] >= utf8.RuneSelf {
45
46 s.numRunes = utf8.RuneCountInString(contents)
47 _, s.width = utf8.DecodeRuneInString(contents)
48 s.nonASCII = i
49 return s
50 }
51 }
52
53 s.numRunes = len(contents)
54 s.width = 0
55 s.nonASCII = len(contents)
56 return s
57 }
58
59
60
61 func (s *String) String() string {
62 return s.str
63 }
64
65
66 func (s *String) RuneCount() int {
67 return s.numRunes
68 }
69
70
71 func (s *String) IsASCII() bool {
72 return s.width == 0
73 }
74
75
76 func (s *String) Slice(i, j int) string {
77
78 if j < s.nonASCII {
79 return s.str[i:j]
80 }
81 if i < 0 || j > s.numRunes || i > j {
82 panic(sliceOutOfRange)
83 }
84 if i == j {
85 return ""
86 }
87
88 var low, high int
89 switch {
90 case i < s.nonASCII:
91 low = i
92 case i == s.numRunes:
93 low = len(s.str)
94 default:
95 s.At(i)
96 low = s.bytePos
97 }
98 switch {
99 case j == s.numRunes:
100 high = len(s.str)
101 default:
102 s.At(j)
103 high = s.bytePos
104 }
105 return s.str[low:high]
106 }
107
108
109
110 func (s *String) At(i int) rune {
111
112 if i < s.nonASCII {
113 return rune(s.str[i])
114 }
115
116
117 if i < 0 || i >= s.numRunes {
118 panic(outOfRange)
119 }
120
121 var r rune
122
123
124
125 switch {
126
127 case i == s.runePos-1:
128 r, s.width = utf8.DecodeLastRuneInString(s.str[0:s.bytePos])
129 s.runePos = i
130 s.bytePos -= s.width
131 return r
132 case i == s.runePos+1:
133 s.runePos = i
134 s.bytePos += s.width
135 fallthrough
136 case i == s.runePos:
137 r, s.width = utf8.DecodeRuneInString(s.str[s.bytePos:])
138 return r
139 case i == 0:
140 r, s.width = utf8.DecodeRuneInString(s.str)
141 s.runePos = 0
142 s.bytePos = 0
143 return r
144
145 case i == s.numRunes-1:
146 r, s.width = utf8.DecodeLastRuneInString(s.str)
147 s.runePos = i
148 s.bytePos = len(s.str) - s.width
149 return r
150 }
151
152
153
154
155
156
157 forward := true
158 if i < s.runePos {
159
160
161
162 if i < (s.runePos-s.nonASCII)/2 {
163
164 s.bytePos, s.runePos = s.nonASCII, s.nonASCII
165 } else {
166
167 forward = false
168 }
169 } else {
170
171 if i-s.runePos < (s.numRunes-s.runePos)/2 {
172
173 } else {
174
175 s.bytePos, s.runePos = len(s.str), s.numRunes
176 forward = false
177 }
178 }
179 if forward {
180
181 for {
182 r, s.width = utf8.DecodeRuneInString(s.str[s.bytePos:])
183 if s.runePos == i {
184 break
185 }
186 s.runePos++
187 s.bytePos += s.width
188 }
189 } else {
190 for {
191 r, s.width = utf8.DecodeLastRuneInString(s.str[0:s.bytePos])
192 s.runePos--
193 s.bytePos -= s.width
194 if s.runePos == i {
195 break
196 }
197 }
198 }
199 return r
200 }
201
202 var outOfRange = errors.New("utf8string: index out of range")
203 var sliceOutOfRange = errors.New("utf8string: slice index out of range")
204
View as plain text