mirror of
https://github.com/odin-lang/Odin.git
synced 2026-11-07 11:13:13 -05:00
It was decoding the first rune in the case when it was supposed to decode the rune before the last one. Also added some tests to verify its behavior.
157 lines
2.9 KiB
Odin
157 lines
2.9 KiB
Odin
// A convenient and efficient way to index strings by `Unicode` code point (`rune`) rather than byte.
|
|
package utf8string
|
|
|
|
import "core:unicode/utf8"
|
|
import "base:runtime"
|
|
import "base:builtin"
|
|
|
|
String :: struct {
|
|
contents: string,
|
|
rune_count: int,
|
|
|
|
// cached information
|
|
non_ascii: int, // index to non-ascii code points
|
|
width: int, // 0 if ascii
|
|
byte_pos: int,
|
|
rune_pos: int,
|
|
}
|
|
|
|
@(private)
|
|
_len :: builtin.len // helper procedure
|
|
|
|
init :: proc(s: ^String, contents: string) -> ^String {
|
|
s.contents = contents
|
|
s.byte_pos = 0
|
|
s.rune_pos = 0
|
|
|
|
for i in 0..<_len(contents) {
|
|
if contents[i] >= utf8.RUNE_SELF {
|
|
s.rune_count = utf8.rune_count_in_string(contents)
|
|
_, s.width = utf8.decode_rune_in_string(contents)
|
|
s.non_ascii = i
|
|
return s
|
|
}
|
|
}
|
|
|
|
s.rune_count = _len(contents)
|
|
s.width = 0
|
|
s.non_ascii = _len(contents)
|
|
return s
|
|
}
|
|
|
|
to_string :: proc(s: ^String) -> string {
|
|
return s.contents
|
|
}
|
|
|
|
len :: proc(s: ^String) -> int {
|
|
return s.rune_count
|
|
}
|
|
|
|
|
|
is_ascii :: proc(s: ^String) -> bool {
|
|
return s.width == 0
|
|
}
|
|
|
|
at :: proc(s: ^String, i: int, loc := #caller_location) -> (r: rune) {
|
|
runtime.bounds_check_error_loc(loc, i, s.rune_count)
|
|
|
|
if i < s.non_ascii {
|
|
return rune(s.contents[i])
|
|
}
|
|
|
|
switch i {
|
|
case 0:
|
|
r, s.width = utf8.decode_rune_in_string(s.contents)
|
|
s.rune_pos = 0
|
|
s.byte_pos = 0
|
|
return
|
|
|
|
case s.rune_count-1:
|
|
r, s.width = utf8.decode_last_rune(s.contents)
|
|
s.rune_pos = i
|
|
s.byte_pos = _len(s.contents) - s.width
|
|
return
|
|
|
|
case s.rune_pos-1:
|
|
r, s.width = utf8.decode_last_rune(s.contents[0:s.byte_pos])
|
|
s.rune_pos = i
|
|
s.byte_pos -= s.width
|
|
return
|
|
|
|
case s.rune_pos+1:
|
|
s.rune_pos = i
|
|
s.byte_pos += s.width
|
|
fallthrough
|
|
case s.rune_pos:
|
|
r, s.width = utf8.decode_rune_in_string(s.contents[s.byte_pos:])
|
|
return
|
|
}
|
|
|
|
// Linear scan
|
|
scan_forward := true
|
|
if i < s.rune_pos {
|
|
if i < (s.rune_pos-s.non_ascii)/2 {
|
|
s.byte_pos, s.rune_pos = s.non_ascii, s.non_ascii
|
|
} else {
|
|
scan_forward = false
|
|
}
|
|
} else if i-s.rune_pos < (s.rune_count-s.rune_pos)/2 {
|
|
// scan_forward = true
|
|
} else {
|
|
s.byte_pos, s.rune_pos = _len(s.contents), s.rune_count
|
|
scan_forward = false
|
|
}
|
|
|
|
if scan_forward {
|
|
for {
|
|
r, s.width = utf8.decode_rune_in_string(s.contents[s.byte_pos:])
|
|
if s.rune_pos == i {
|
|
return
|
|
}
|
|
s.rune_pos += 1
|
|
s.byte_pos += s.width
|
|
|
|
}
|
|
} else {
|
|
for {
|
|
r, s.width = utf8.decode_last_rune_in_string(s.contents[:s.byte_pos])
|
|
s.rune_pos -= 1
|
|
s.byte_pos -= s.width
|
|
if s.rune_pos == i {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
slice :: proc(s: ^String, i, j: int, loc := #caller_location) -> string {
|
|
runtime.slice_expr_error_lo_hi_loc(loc, i, j, s.rune_count)
|
|
|
|
if j < s.non_ascii {
|
|
return s.contents[i:j]
|
|
}
|
|
|
|
if i == j {
|
|
return ""
|
|
}
|
|
|
|
lo, hi: int
|
|
if i < s.non_ascii {
|
|
lo = i
|
|
} else if i == s.rune_count {
|
|
lo = _len(s.contents)
|
|
} else {
|
|
at(s, i, loc)
|
|
lo = s.byte_pos
|
|
}
|
|
|
|
if j == s.rune_count {
|
|
hi = _len(s.contents)
|
|
} else {
|
|
at(s, j, loc)
|
|
hi = s.byte_pos
|
|
}
|
|
|
|
return s.contents[lo:hi]
|
|
}
|