Reduce memory footprint of Item struct
This commit is contained in:
parent
4b59ced08f
commit
9e85cba0d0
@ -283,8 +283,9 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input util.C
|
|||||||
|
|
||||||
// Phase 1. Check if there's a match and calculate bonus for each point
|
// Phase 1. Check if there's a match and calculate bonus for each point
|
||||||
pidx, lastIdx, prevClass := 0, 0, charNonWord
|
pidx, lastIdx, prevClass := 0, 0, charNonWord
|
||||||
|
input.CopyRunes(T)
|
||||||
for idx := 0; idx < N; idx++ {
|
for idx := 0; idx < N; idx++ {
|
||||||
char := input.Get(idx)
|
char := T[idx]
|
||||||
var class charClass
|
var class charClass
|
||||||
if char <= unicode.MaxASCII {
|
if char <= unicode.MaxASCII {
|
||||||
class = charClassOfAscii(char)
|
class = charClassOfAscii(char)
|
||||||
@ -389,7 +390,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input util.C
|
|||||||
if i == 0 {
|
if i == 0 {
|
||||||
fmt.Print(" ")
|
fmt.Print(" ")
|
||||||
for j := int(F[i]); j <= lastIdx; j++ {
|
for j := int(F[i]); j <= lastIdx; j++ {
|
||||||
fmt.Printf(" " + string(input.Get(j)) + " ")
|
fmt.Printf(" " + string(T[j]) + " ")
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,8 @@ func (cc *ChunkCache) Add(chunk *Chunk, key string, list []*Result) {
|
|||||||
(*qc)[key] = list
|
(*qc)[key] = list
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find is called to lookup ChunkCache
|
// Lookup is called to lookup ChunkCache
|
||||||
func (cc *ChunkCache) Find(chunk *Chunk, key string) []*Result {
|
func (cc *ChunkCache) Lookup(chunk *Chunk, key string) []*Result {
|
||||||
if len(key) == 0 || !chunk.IsFull() {
|
if len(key) == 0 || !chunk.IsFull() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -14,27 +14,27 @@ func TestChunkCache(t *testing.T) {
|
|||||||
cache.Add(chunk2p, "bar", items2)
|
cache.Add(chunk2p, "bar", items2)
|
||||||
|
|
||||||
{ // chunk1 is not full
|
{ // chunk1 is not full
|
||||||
cached, found := cache.Find(chunk1p, "foo")
|
cached := cache.Lookup(chunk1p, "foo")
|
||||||
if found {
|
if cached != nil {
|
||||||
t.Error("Cached disabled for non-empty chunks", found, cached)
|
t.Error("Cached disabled for non-empty chunks", cached)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
cached, found := cache.Find(chunk2p, "foo")
|
cached := cache.Lookup(chunk2p, "foo")
|
||||||
if !found || len(cached) != 1 {
|
if cached == nil || len(cached) != 1 {
|
||||||
t.Error("Expected 1 item cached", found, cached)
|
t.Error("Expected 1 item cached", cached)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
cached, found := cache.Find(chunk2p, "bar")
|
cached := cache.Lookup(chunk2p, "bar")
|
||||||
if !found || len(cached) != 2 {
|
if cached == nil || len(cached) != 2 {
|
||||||
t.Error("Expected 2 items cached", found, cached)
|
t.Error("Expected 2 items cached", cached)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
cached, found := cache.Find(chunk1p, "foobar")
|
cached := cache.Lookup(chunk1p, "foobar")
|
||||||
if found {
|
if cached != nil {
|
||||||
t.Error("Expected 0 item cached", found, cached)
|
t.Error("Expected 0 item cached", cached)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,9 @@ func TestChunkList(t *testing.T) {
|
|||||||
sortCriteria = []criterion{byScore, byLength}
|
sortCriteria = []criterion{byScore, byLength}
|
||||||
|
|
||||||
cl := NewChunkList(func(s []byte, i int) Item {
|
cl := NewChunkList(func(s []byte, i int) Item {
|
||||||
return Item{text: util.ToChars(s), index: int32(i * 2)}
|
chars := util.ToChars(s)
|
||||||
|
chars.Index = int32(i * 2)
|
||||||
|
return Item{text: chars}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Snapshot
|
// Snapshot
|
||||||
@ -41,8 +43,8 @@ func TestChunkList(t *testing.T) {
|
|||||||
if len(*chunk1) != 2 {
|
if len(*chunk1) != 2 {
|
||||||
t.Error("Snapshot should contain only two items")
|
t.Error("Snapshot should contain only two items")
|
||||||
}
|
}
|
||||||
if (*chunk1)[0].text.ToString() != "hello" || (*chunk1)[0].index != 0 ||
|
if (*chunk1)[0].text.ToString() != "hello" || (*chunk1)[0].Index() != 0 ||
|
||||||
(*chunk1)[1].text.ToString() != "world" || (*chunk1)[1].index != 2 {
|
(*chunk1)[1].text.ToString() != "world" || (*chunk1)[1].Index() != 2 {
|
||||||
t.Error("Invalid data")
|
t.Error("Invalid data")
|
||||||
}
|
}
|
||||||
if chunk1.IsFull() {
|
if chunk1.IsFull() {
|
||||||
|
18
src/core.go
18
src/core.go
@ -98,11 +98,8 @@ func Run(opts *Options, revision string) {
|
|||||||
return nilItem
|
return nilItem
|
||||||
}
|
}
|
||||||
chars, colors := ansiProcessor(data)
|
chars, colors := ansiProcessor(data)
|
||||||
return Item{
|
chars.Index = int32(index)
|
||||||
index: int32(index),
|
return Item{text: chars, colors: colors}
|
||||||
trimLength: -1,
|
|
||||||
text: chars,
|
|
||||||
colors: colors}
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
chunkList = NewChunkList(func(data []byte, index int) Item {
|
chunkList = NewChunkList(func(data []byte, index int) Item {
|
||||||
@ -114,16 +111,9 @@ func Run(opts *Options, revision string) {
|
|||||||
return nilItem
|
return nilItem
|
||||||
}
|
}
|
||||||
textRunes := joinTokens(trans)
|
textRunes := joinTokens(trans)
|
||||||
item := Item{
|
|
||||||
index: int32(index),
|
|
||||||
trimLength: -1,
|
|
||||||
origText: &data,
|
|
||||||
colors: nil}
|
|
||||||
|
|
||||||
trimmed, colors := ansiProcessorRunes(textRunes)
|
trimmed, colors := ansiProcessorRunes(textRunes)
|
||||||
item.text = trimmed
|
trimmed.Index = int32(index)
|
||||||
item.colors = colors
|
return Item{text: trimmed, colors: colors, origText: &data}
|
||||||
return item
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
26
src/item.go
26
src/item.go
@ -4,33 +4,27 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Item represents each input line
|
// Item represents each input line. 56 bytes.
|
||||||
type Item struct {
|
type Item struct {
|
||||||
index int32
|
text util.Chars // 32 = 24 + 1 + 1 + 2 + 4
|
||||||
trimLength int32
|
transformed *[]Token // 8
|
||||||
text util.Chars
|
origText *[]byte // 8
|
||||||
origText *[]byte
|
colors *[]ansiOffset // 8
|
||||||
colors *[]ansiOffset
|
|
||||||
transformed []Token
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Index returns ordinal index of the Item
|
// Index returns ordinal index of the Item
|
||||||
func (item *Item) Index() int32 {
|
func (item *Item) Index() int32 {
|
||||||
return item.index
|
return item.text.Index
|
||||||
}
|
}
|
||||||
|
|
||||||
var nilItem = Item{index: -1}
|
var nilItem = Item{text: util.Chars{Index: -1}}
|
||||||
|
|
||||||
func (item *Item) Nil() bool {
|
func (item *Item) Nil() bool {
|
||||||
return item.index < 0
|
return item.Index() < 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (item *Item) TrimLength() int32 {
|
func (item *Item) TrimLength() uint16 {
|
||||||
if item.trimLength >= 0 {
|
return item.text.TrimLength()
|
||||||
return item.trimLength
|
|
||||||
}
|
|
||||||
item.trimLength = int32(item.text.TrimLength())
|
|
||||||
return item.trimLength
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Colors returns ansiOffsets of the Item
|
// Colors returns ansiOffsets of the Item
|
||||||
|
@ -247,7 +247,7 @@ func (p *Pattern) Match(chunk *Chunk, slab *util.Slab) []*Result {
|
|||||||
// ChunkCache: Exact match
|
// ChunkCache: Exact match
|
||||||
cacheKey := p.CacheKey()
|
cacheKey := p.CacheKey()
|
||||||
if p.cacheable {
|
if p.cacheable {
|
||||||
if cached := _cache.Find(chunk, cacheKey); cached != nil {
|
if cached := _cache.Lookup(chunk, cacheKey); cached != nil {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -352,18 +352,17 @@ func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Of
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) prepareInput(item *Item) []Token {
|
func (p *Pattern) prepareInput(item *Item) []Token {
|
||||||
if item.transformed != nil {
|
if len(p.nth) == 0 {
|
||||||
return item.transformed
|
return []Token{Token{text: &item.text, prefixLength: 0}}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret []Token
|
if item.transformed != nil {
|
||||||
if len(p.nth) == 0 {
|
return *item.transformed
|
||||||
ret = []Token{Token{text: &item.text, prefixLength: 0}}
|
|
||||||
} else {
|
|
||||||
tokens := Tokenize(item.text, p.delimiter)
|
|
||||||
ret = Transform(tokens, p.nth)
|
|
||||||
}
|
}
|
||||||
item.transformed = ret
|
|
||||||
|
tokens := Tokenize(item.text, p.delimiter)
|
||||||
|
ret := Transform(tokens, p.nth)
|
||||||
|
item.transformed = &ret
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,13 +142,13 @@ func TestOrigTextAndTransformed(t *testing.T) {
|
|||||||
Item{
|
Item{
|
||||||
text: util.RunesToChars([]rune("junegunn")),
|
text: util.RunesToChars([]rune("junegunn")),
|
||||||
origText: &origBytes,
|
origText: &origBytes,
|
||||||
transformed: trans},
|
transformed: &trans},
|
||||||
}
|
}
|
||||||
pattern.extended = extended
|
pattern.extended = extended
|
||||||
matches := pattern.matchChunk(&chunk, nil, slab) // No cache
|
matches := pattern.matchChunk(&chunk, nil, slab) // No cache
|
||||||
if !(matches[0].item.text.ToString() == "junegunn" &&
|
if !(matches[0].item.text.ToString() == "junegunn" &&
|
||||||
string(*matches[0].item.origText) == "junegunn.choi" &&
|
string(*matches[0].item.origText) == "junegunn.choi" &&
|
||||||
reflect.DeepEqual(matches[0].item.transformed, trans)) {
|
reflect.DeepEqual(*matches[0].item.transformed, trans)) {
|
||||||
t.Error("Invalid match result", matches)
|
t.Error("Invalid match result", matches)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +156,7 @@ func TestOrigTextAndTransformed(t *testing.T) {
|
|||||||
if !(match.item.text.ToString() == "junegunn" &&
|
if !(match.item.text.ToString() == "junegunn" &&
|
||||||
string(*match.item.origText) == "junegunn.choi" &&
|
string(*match.item.origText) == "junegunn.choi" &&
|
||||||
offsets[0][0] == 0 && offsets[0][1] == 5 &&
|
offsets[0][0] == 0 && offsets[0][1] == 5 &&
|
||||||
reflect.DeepEqual(match.item.transformed, trans)) {
|
reflect.DeepEqual(*match.item.transformed, trans)) {
|
||||||
t.Error("Invalid match result", match, offsets, extended)
|
t.Error("Invalid match result", match, offsets, extended)
|
||||||
}
|
}
|
||||||
if !((*pos)[0] == 4 && (*pos)[1] == 0) {
|
if !((*pos)[0] == 4 && (*pos)[1] == 0) {
|
||||||
|
@ -34,7 +34,7 @@ func buildResult(item *Item, offsets []Offset, score int) *Result {
|
|||||||
sort.Sort(ByOrder(offsets))
|
sort.Sort(ByOrder(offsets))
|
||||||
}
|
}
|
||||||
|
|
||||||
result := Result{item: item, rank: rank{index: item.index}}
|
result := Result{item: item, rank: rank{index: item.Index()}}
|
||||||
numChars := item.text.Length()
|
numChars := item.text.Length()
|
||||||
minBegin := math.MaxUint16
|
minBegin := math.MaxUint16
|
||||||
minEnd := math.MaxUint16
|
minEnd := math.MaxUint16
|
||||||
@ -57,7 +57,7 @@ func buildResult(item *Item, offsets []Offset, score int) *Result {
|
|||||||
// Higher is better
|
// Higher is better
|
||||||
val = math.MaxUint16 - util.AsUint16(score)
|
val = math.MaxUint16 - util.AsUint16(score)
|
||||||
case byLength:
|
case byLength:
|
||||||
val = util.AsUint16(int(item.TrimLength()))
|
val = item.TrimLength()
|
||||||
case byBegin, byEnd:
|
case byBegin, byEnd:
|
||||||
if validOffsetFound {
|
if validOffsetFound {
|
||||||
whitePrefixLen := 0
|
whitePrefixLen := 0
|
||||||
@ -86,7 +86,7 @@ var sortCriteria []criterion
|
|||||||
|
|
||||||
// Index returns ordinal index of the Item
|
// Index returns ordinal index of the Item
|
||||||
func (result *Result) Index() int32 {
|
func (result *Result) Index() int32 {
|
||||||
return result.item.index
|
return result.item.Index()
|
||||||
}
|
}
|
||||||
|
|
||||||
func minRank() rank {
|
func minRank() rank {
|
||||||
|
@ -11,6 +11,11 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func withIndex(i *Item, index int) *Item {
|
||||||
|
(*i).text.Index = int32(index)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
func TestOffsetSort(t *testing.T) {
|
func TestOffsetSort(t *testing.T) {
|
||||||
offsets := []Offset{
|
offsets := []Offset{
|
||||||
Offset{3, 5}, Offset{2, 7},
|
Offset{3, 5}, Offset{2, 7},
|
||||||
@ -52,12 +57,13 @@ func TestResultRank(t *testing.T) {
|
|||||||
sortCriteria = []criterion{byScore, byLength}
|
sortCriteria = []criterion{byScore, byLength}
|
||||||
|
|
||||||
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
|
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
|
||||||
item1 := buildResult(&Item{text: util.RunesToChars(strs[0]), index: 1, trimLength: -1}, []Offset{}, 2)
|
item1 := buildResult(
|
||||||
|
withIndex(&Item{text: util.RunesToChars(strs[0])}, 1), []Offset{}, 2)
|
||||||
if item1.rank.points[0] != math.MaxUint16-2 || // Bonus
|
if item1.rank.points[0] != math.MaxUint16-2 || // Bonus
|
||||||
item1.rank.points[1] != 3 || // Length
|
item1.rank.points[1] != 3 || // Length
|
||||||
item1.rank.points[2] != 0 || // Unused
|
item1.rank.points[2] != 0 || // Unused
|
||||||
item1.rank.points[3] != 0 || // Unused
|
item1.rank.points[3] != 0 || // Unused
|
||||||
item1.item.index != 1 {
|
item1.item.Index() != 1 {
|
||||||
t.Error(item1.rank)
|
t.Error(item1.rank)
|
||||||
}
|
}
|
||||||
// Only differ in index
|
// Only differ in index
|
||||||
@ -73,14 +79,18 @@ func TestResultRank(t *testing.T) {
|
|||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
if items[0] != item2 || items[1] != item2 ||
|
if items[0] != item2 || items[1] != item2 ||
|
||||||
items[2] != item1 || items[3] != item1 {
|
items[2] != item1 || items[3] != item1 {
|
||||||
t.Error(items, item1, item1.item.index, item2, item2.item.index)
|
t.Error(items, item1, item1.item.Index(), item2, item2.item.Index())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by relevance
|
// Sort by relevance
|
||||||
item3 := buildResult(&Item{index: 2}, []Offset{Offset{1, 3}, Offset{5, 7}}, 3)
|
item3 := buildResult(
|
||||||
item4 := buildResult(&Item{index: 2}, []Offset{Offset{1, 2}, Offset{6, 7}}, 4)
|
withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 3)
|
||||||
item5 := buildResult(&Item{index: 2}, []Offset{Offset{1, 3}, Offset{5, 7}}, 5)
|
item4 := buildResult(
|
||||||
item6 := buildResult(&Item{index: 2}, []Offset{Offset{1, 2}, Offset{6, 7}}, 6)
|
withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 4)
|
||||||
|
item5 := buildResult(
|
||||||
|
withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 5)
|
||||||
|
item6 := buildResult(
|
||||||
|
withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 6)
|
||||||
items = []*Result{item1, item2, item3, item4, item5, item6}
|
items = []*Result{item1, item2, item3, item4, item5, item6}
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
if !(items[0] == item6 && items[1] == item5 &&
|
if !(items[0] == item6 && items[1] == item5 &&
|
||||||
|
@ -3,63 +3,81 @@ package util
|
|||||||
import (
|
import (
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Chars struct {
|
type Chars struct {
|
||||||
runes []rune
|
slice []byte // or []rune
|
||||||
bytes []byte
|
inBytes bool
|
||||||
|
trimLengthKnown bool
|
||||||
|
trimLength uint16
|
||||||
|
|
||||||
|
// XXX Piggybacking item index here is a horrible idea. But I'm trying to
|
||||||
|
// minimize the memory footprint by not wasting padded spaces.
|
||||||
|
Index int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToChars converts byte array into rune array
|
// ToChars converts byte array into rune array
|
||||||
func ToChars(bytea []byte) Chars {
|
func ToChars(bytes []byte) Chars {
|
||||||
var runes []rune
|
var runes []rune
|
||||||
ascii := true
|
inBytes := true
|
||||||
numBytes := len(bytea)
|
numBytes := len(bytes)
|
||||||
for i := 0; i < numBytes; {
|
for i := 0; i < numBytes; {
|
||||||
if bytea[i] < utf8.RuneSelf {
|
if bytes[i] < utf8.RuneSelf {
|
||||||
if !ascii {
|
if !inBytes {
|
||||||
runes = append(runes, rune(bytea[i]))
|
runes = append(runes, rune(bytes[i]))
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
} else {
|
} else {
|
||||||
if ascii {
|
if inBytes {
|
||||||
ascii = false
|
inBytes = false
|
||||||
runes = make([]rune, i, numBytes)
|
runes = make([]rune, i, numBytes)
|
||||||
for j := 0; j < i; j++ {
|
for j := 0; j < i; j++ {
|
||||||
runes[j] = rune(bytea[j])
|
runes[j] = rune(bytes[j])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r, sz := utf8.DecodeRune(bytea[i:])
|
r, sz := utf8.DecodeRune(bytes[i:])
|
||||||
i += sz
|
i += sz
|
||||||
runes = append(runes, r)
|
runes = append(runes, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ascii {
|
if inBytes {
|
||||||
return Chars{bytes: bytea}
|
return Chars{slice: bytes, inBytes: inBytes}
|
||||||
}
|
}
|
||||||
return Chars{runes: runes}
|
return RunesToChars(runes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunesToChars(runes []rune) Chars {
|
func RunesToChars(runes []rune) Chars {
|
||||||
return Chars{runes: runes}
|
return Chars{slice: *(*[]byte)(unsafe.Pointer(&runes)), inBytes: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) optionalRunes() []rune {
|
||||||
|
if chars.inBytes {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return *(*[]rune)(unsafe.Pointer(&chars.slice))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) Get(i int) rune {
|
func (chars *Chars) Get(i int) rune {
|
||||||
if chars.runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return chars.runes[i]
|
return runes[i]
|
||||||
}
|
}
|
||||||
return rune(chars.bytes[i])
|
return rune(chars.slice[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) Length() int {
|
func (chars *Chars) Length() int {
|
||||||
if chars.runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return len(chars.runes)
|
return len(runes)
|
||||||
}
|
}
|
||||||
return len(chars.bytes)
|
return len(chars.slice)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrimLength returns the length after trimming leading and trailing whitespaces
|
// TrimLength returns the length after trimming leading and trailing whitespaces
|
||||||
func (chars *Chars) TrimLength() int {
|
func (chars *Chars) TrimLength() uint16 {
|
||||||
|
if chars.trimLengthKnown {
|
||||||
|
return chars.trimLength
|
||||||
|
}
|
||||||
|
chars.trimLengthKnown = true
|
||||||
var i int
|
var i int
|
||||||
len := chars.Length()
|
len := chars.Length()
|
||||||
for i = len - 1; i >= 0; i-- {
|
for i = len - 1; i >= 0; i-- {
|
||||||
@ -80,7 +98,8 @@ func (chars *Chars) TrimLength() int {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return i - j + 1
|
chars.trimLength = AsUint16(i - j + 1)
|
||||||
|
return chars.trimLength
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) TrailingWhitespaces() int {
|
func (chars *Chars) TrailingWhitespaces() int {
|
||||||
@ -96,28 +115,40 @@ func (chars *Chars) TrailingWhitespaces() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) ToString() string {
|
func (chars *Chars) ToString() string {
|
||||||
if chars.runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return string(chars.runes)
|
return string(runes)
|
||||||
}
|
}
|
||||||
return string(chars.bytes)
|
return string(chars.slice)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) ToRunes() []rune {
|
func (chars *Chars) ToRunes() []rune {
|
||||||
if chars.runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return chars.runes
|
return runes
|
||||||
}
|
}
|
||||||
runes := make([]rune, len(chars.bytes))
|
bytes := chars.slice
|
||||||
for idx, b := range chars.bytes {
|
runes := make([]rune, len(bytes))
|
||||||
|
for idx, b := range bytes {
|
||||||
runes[idx] = rune(b)
|
runes[idx] = rune(b)
|
||||||
}
|
}
|
||||||
return runes
|
return runes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) Slice(b int, e int) Chars {
|
func (chars *Chars) CopyRunes(dest []rune) {
|
||||||
if chars.runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return Chars{runes: chars.runes[b:e]}
|
copy(dest, runes)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return Chars{bytes: chars.bytes[b:e]}
|
for idx, b := range chars.slice {
|
||||||
|
dest[idx] = rune(b)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) Slice(b int, e int) Chars {
|
||||||
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
|
return RunesToChars(runes[b:e])
|
||||||
|
}
|
||||||
|
return Chars{slice: chars.slice[b:e], inBytes: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) Split(delimiter string) []Chars {
|
func (chars *Chars) Split(delimiter string) []Chars {
|
||||||
|
@ -2,27 +2,16 @@ package util
|
|||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestToCharsNil(t *testing.T) {
|
|
||||||
bs := Chars{bytes: []byte{}}
|
|
||||||
if bs.bytes == nil || bs.runes != nil {
|
|
||||||
t.Error()
|
|
||||||
}
|
|
||||||
rs := RunesToChars([]rune{})
|
|
||||||
if rs.bytes != nil || rs.runes == nil {
|
|
||||||
t.Error()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestToCharsAscii(t *testing.T) {
|
func TestToCharsAscii(t *testing.T) {
|
||||||
chars := ToChars([]byte("foobar"))
|
chars := ToChars([]byte("foobar"))
|
||||||
if chars.ToString() != "foobar" || chars.runes != nil {
|
if !chars.inBytes || chars.ToString() != "foobar" || !chars.inBytes {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCharsLength(t *testing.T) {
|
func TestCharsLength(t *testing.T) {
|
||||||
chars := ToChars([]byte("\tabc한글 "))
|
chars := ToChars([]byte("\tabc한글 "))
|
||||||
if chars.Length() != 8 || chars.TrimLength() != 5 {
|
if chars.inBytes || chars.Length() != 8 || chars.TrimLength() != 5 {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -36,7 +25,7 @@ func TestCharsToString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTrimLength(t *testing.T) {
|
func TestTrimLength(t *testing.T) {
|
||||||
check := func(str string, exp int) {
|
check := func(str string, exp uint16) {
|
||||||
chars := ToChars([]byte(str))
|
chars := ToChars([]byte(str))
|
||||||
trimmed := chars.TrimLength()
|
trimmed := chars.TrimLength()
|
||||||
if trimmed != exp {
|
if trimmed != exp {
|
||||||
@ -61,7 +50,8 @@ func TestSplit(t *testing.T) {
|
|||||||
input := ToChars([]byte(str))
|
input := ToChars([]byte(str))
|
||||||
result := input.Split(delim)
|
result := input.Split(delim)
|
||||||
if len(result) != len(tokens) {
|
if len(result) != len(tokens) {
|
||||||
t.Errorf("Invalid Split result for '%s': %d tokens found (expected %d): %s",
|
t.Errorf(
|
||||||
|
"Invalid Split result for '%s': %d tokens found (expected %d): %s",
|
||||||
str, len(result), len(tokens), result)
|
str, len(result), len(tokens), result)
|
||||||
}
|
}
|
||||||
for idx, token := range tokens {
|
for idx, token := range tokens {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user