iterator.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. package graphemes
  2. import "unicode/utf8"
  3. // FromString returns an iterator for the grapheme clusters in the input string.
  4. // Iterate while Next() is true, and access the grapheme via Value().
  5. func FromString(s string) *Iterator[string] {
  6. return &Iterator[string]{
  7. split: splitFuncString,
  8. data: s,
  9. }
  10. }
  11. // FromBytes returns an iterator for the grapheme clusters in the input bytes.
  12. // Iterate while Next() is true, and access the grapheme via Value().
  13. func FromBytes(b []byte) *Iterator[[]byte] {
  14. return &Iterator[[]byte]{
  15. split: splitFuncBytes,
  16. data: b,
  17. }
  18. }
  19. // Iterator is a generic iterator for grapheme clusters in strings or byte slices,
  20. // with an ASCII hot path optimization.
  21. type Iterator[T ~string | ~[]byte] struct {
  22. split func(T, bool) (int, T, error)
  23. data T
  24. pos int
  25. start int
  26. // AnsiEscapeSequences treats 7-bit ANSI escape sequences (ECMA-48) as
  27. // single grapheme clusters when true. The default is false.
  28. //
  29. // 8-bit controls are not enabled by this option. See [AnsiEscapeSequences8Bit].
  30. AnsiEscapeSequences bool
  31. // AnsiEscapeSequences8Bit treats 8-bit C1 ANSI escape sequences (ECMA-48) as single
  32. // grapheme clusters when true. The default is false.
  33. //
  34. // 8-bit control bytes are not UTF-8 encoded, i.e. not valid UTF-8. If you
  35. // choose this option, you are choosing to interpret non-UTF-8 data, caveat
  36. // emptor.
  37. AnsiEscapeSequences8Bit bool
  38. }
  39. var (
  40. splitFuncString = splitFunc[string]
  41. splitFuncBytes = splitFunc[[]byte]
  42. )
  43. const (
  44. esc = 0x1B
  45. cr = 0x0D
  46. bel = 0x07
  47. can = 0x18
  48. sub = 0x1A
  49. st = 0x9C
  50. )
  51. // Next advances the iterator to the next grapheme cluster.
  52. // Returns false when there are no more grapheme clusters.
  53. func (iter *Iterator[T]) Next() bool {
  54. if iter.pos >= len(iter.data) {
  55. return false
  56. }
  57. iter.start = iter.pos
  58. b := iter.data[iter.pos]
  59. if iter.AnsiEscapeSequences && b == esc {
  60. if a := ansiEscapeLength(iter.data[iter.pos:]); a > 0 {
  61. iter.pos += a
  62. return true
  63. }
  64. }
  65. if iter.AnsiEscapeSequences8Bit && b >= 0x80 && b <= 0x9F {
  66. if a := ansiEscapeLength8Bit(iter.data[iter.pos:]); a > 0 {
  67. iter.pos += a
  68. return true
  69. }
  70. }
  71. // ASCII hot path: any ASCII is one grapheme when next byte is ASCII or end.
  72. if b < utf8.RuneSelf && b != cr {
  73. if iter.pos+1 >= len(iter.data) || iter.data[iter.pos+1] < utf8.RuneSelf {
  74. iter.pos++
  75. return true
  76. }
  77. }
  78. // Fall back to UAX29 grapheme parsing
  79. remaining := iter.data[iter.pos:]
  80. advance, _, err := iter.split(remaining, true)
  81. if err != nil {
  82. panic(err)
  83. }
  84. if advance <= 0 {
  85. panic("splitFunc returned a zero or negative advance")
  86. }
  87. iter.pos += advance
  88. if iter.pos > len(iter.data) {
  89. panic("splitFunc advanced beyond end of data")
  90. }
  91. return true
  92. }
  93. // Value returns the current grapheme cluster.
  94. func (iter *Iterator[T]) Value() T {
  95. return iter.data[iter.start:iter.pos]
  96. }
  97. // Start returns the byte position of the current grapheme in the original data.
  98. func (iter *Iterator[T]) Start() int {
  99. return iter.start
  100. }
  101. // End returns the byte position after the current grapheme in the original data.
  102. func (iter *Iterator[T]) End() int {
  103. return iter.pos
  104. }
  105. // Reset resets the iterator to the beginning of the data.
  106. func (iter *Iterator[T]) Reset() {
  107. iter.start = 0
  108. iter.pos = 0
  109. }
  110. // SetText sets the data for the iterator to operate on, and resets all state.
  111. func (iter *Iterator[T]) SetText(data T) {
  112. iter.data = data
  113. iter.start = 0
  114. iter.pos = 0
  115. }
  116. // First returns the first grapheme cluster without advancing the iterator.
  117. func (iter *Iterator[T]) First() T {
  118. if len(iter.data) == 0 {
  119. return iter.data
  120. }
  121. // Use a copy to leverage Next()'s ASCII optimization
  122. cp := *iter
  123. cp.pos = 0
  124. cp.start = 0
  125. cp.Next()
  126. return cp.Value()
  127. }