utf8.lua 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. -- $Id: testes/utf8.lua $
  2. -- See Copyright Notice in file lua.h
  3. -- UTF-8 file
  4. global <const> *
  5. print "testing UTF-8 library"
  6. local utf8 = require'utf8'
  7. local function checkerror (msg, f, ...)
  8. local s, err = pcall(f, ...)
  9. assert(not s and string.find(err, msg))
  10. end
  11. local function len (s)
  12. return #string.gsub(s, "[\x80-\xBF]", "")
  13. end
  14. local justone = "^" .. utf8.charpattern .. "$"
  15. -- 't' is the list of codepoints of 's'
  16. local function checksyntax (s, t)
  17. -- creates a string "return '\u{t[1]}...\u{t[n]}'"
  18. local ts = {"return '"}
  19. for i = 1, #t do ts[i + 1] = string.format("\\u{%x}", t[i]) end
  20. ts[#t + 2] = "'"
  21. ts = table.concat(ts)
  22. -- its execution should result in 's'
  23. assert(assert(load(ts))() == s)
  24. end
  25. assert(not utf8.offset("alo", 5))
  26. assert(not utf8.offset("alo", -4))
  27. -- 'check' makes several tests over the validity of string 's'.
  28. -- 't' is the list of codepoints of 's'.
  29. local function check (s, t, nonstrict)
  30. local l = utf8.len(s, 1, -1, nonstrict)
  31. assert(#t == l and len(s) == l)
  32. assert(utf8.char(table.unpack(t)) == s) -- 't' and 's' are equivalent
  33. assert(utf8.offset(s, 0) == 1)
  34. checksyntax(s, t)
  35. -- creates new table with all codepoints of 's'
  36. local t1 = {utf8.codepoint(s, 1, -1, nonstrict)}
  37. assert(#t == #t1)
  38. for i = 1, #t do assert(t[i] == t1[i]) end -- 't' is equal to 't1'
  39. for i = 1, l do -- for all codepoints
  40. local pi, pie = utf8.offset(s, i) -- position of i-th char
  41. local pi1 = utf8.offset(s, 2, pi) -- position of next char
  42. assert(pi1 == pie + 1)
  43. assert(string.find(string.sub(s, pi, pi1 - 1), justone))
  44. assert(utf8.offset(s, -1, pi1) == pi)
  45. assert(utf8.offset(s, i - l - 1) == pi)
  46. assert(pi1 - pi == #utf8.char(utf8.codepoint(s, pi, pi, nonstrict)))
  47. for j = pi, pi1 - 1 do
  48. local off1, off2 = utf8.offset(s, 0, j)
  49. assert(off1 == pi and off2 == pi1 - 1)
  50. end
  51. for j = pi + 1, pi1 - 1 do
  52. assert(not utf8.len(s, j))
  53. end
  54. assert(utf8.len(s, pi, pi, nonstrict) == 1)
  55. assert(utf8.len(s, pi, pi1 - 1, nonstrict) == 1)
  56. assert(utf8.len(s, pi, -1, nonstrict) == l - i + 1)
  57. assert(utf8.len(s, pi1, -1, nonstrict) == l - i)
  58. assert(utf8.len(s, 1, pi, nonstrict) == i)
  59. end
  60. local expected = 1 -- expected position of "current" character
  61. for i = 1, l + 1 do
  62. local p, e = utf8.offset(s, i)
  63. assert(p == expected)
  64. expected = e + 1
  65. end
  66. assert(expected - 1 == #s + 1)
  67. local i = 0
  68. for p, c in utf8.codes(s, nonstrict) do
  69. i = i + 1
  70. assert(c == t[i] and p == utf8.offset(s, i))
  71. assert(utf8.codepoint(s, p, p, nonstrict) == c)
  72. end
  73. assert(i == #t)
  74. i = 0
  75. for c in string.gmatch(s, utf8.charpattern) do
  76. i = i + 1
  77. assert(c == utf8.char(t[i]))
  78. end
  79. assert(i == #t)
  80. for i = 1, l do
  81. assert(utf8.offset(s, i) == utf8.offset(s, i - l - 1, #s + 1))
  82. end
  83. end
  84. do -- error indication in utf8.len
  85. local function checklen (s, p)
  86. local a, b = utf8.len(s)
  87. assert(not a and b == p)
  88. end
  89. checklen("abc\xE3def", 4)
  90. checklen("\xF4\x9F\xBF", 1)
  91. checklen("\xF4\x9F\xBF\xBF", 1)
  92. -- spurious continuation bytes
  93. checklen("汉字\x80", #("汉字") + 1)
  94. checklen("\x80hello", 1)
  95. checklen("hel\x80lo", 4)
  96. checklen("汉字\xBF", #("汉字") + 1)
  97. checklen("\xBFhello", 1)
  98. checklen("hel\xBFlo", 4)
  99. end
  100. -- errors in utf8.codes
  101. do
  102. local function errorcodes (s)
  103. checkerror("invalid UTF%-8 code",
  104. function ()
  105. for c in utf8.codes(s) do assert(c) end
  106. end)
  107. end
  108. errorcodes("ab\xff")
  109. errorcodes("\u{110000}")
  110. errorcodes("in\x80valid")
  111. errorcodes("\xbfinvalid")
  112. errorcodes("αλφ\xBFα")
  113. -- calling iteration function with invalid arguments
  114. local f = utf8.codes("")
  115. assert(f("", 2) == nil)
  116. assert(f("", -1) == nil)
  117. assert(f("", math.mininteger) == nil)
  118. end
  119. -- error in initial position for offset
  120. checkerror("position out of bounds", utf8.offset, "abc", 1, 5)
  121. checkerror("position out of bounds", utf8.offset, "abc", 1, -4)
  122. checkerror("position out of bounds", utf8.offset, "", 1, 2)
  123. checkerror("position out of bounds", utf8.offset, "", 1, -1)
  124. checkerror("continuation byte", utf8.offset, "𦧺", 1, 2)
  125. checkerror("continuation byte", utf8.offset, "𦧺", 1, 2)
  126. checkerror("continuation byte", utf8.offset, "\x80", 1)
  127. checkerror("continuation byte", utf8.offset, "\x9c", -1)
  128. -- error in indices for len
  129. checkerror("out of bounds", utf8.len, "abc", 0, 2)
  130. checkerror("out of bounds", utf8.len, "abc", 1, 4)
  131. do -- missing continuation bytes
  132. -- get what is available
  133. local p, e = utf8.offset("\xE0", 1)
  134. assert(p == 1 and e == 1)
  135. local p, e = utf8.offset("\xE0\x9e", -1)
  136. assert(p == 1 and e == 2)
  137. end
  138. local s = "hello World"
  139. local t = {string.byte(s, 1, -1)}
  140. for i = 1, utf8.len(s) do assert(t[i] == string.byte(s, i)) end
  141. check(s, t)
  142. check("汉字/漢字", {27721, 23383, 47, 28450, 23383,})
  143. do
  144. local s = "áéí\128"
  145. local t = {utf8.codepoint(s,1,#s - 1)}
  146. assert(#t == 3 and t[1] == 225 and t[2] == 233 and t[3] == 237)
  147. checkerror("invalid UTF%-8 code", utf8.codepoint, s, 1, #s)
  148. checkerror("out of bounds", utf8.codepoint, s, #s + 1)
  149. t = {utf8.codepoint(s, 4, 3)}
  150. assert(#t == 0)
  151. checkerror("out of bounds", utf8.codepoint, s, -(#s + 1), 1)
  152. checkerror("out of bounds", utf8.codepoint, s, 1, #s + 1)
  153. -- surrogates
  154. assert(utf8.codepoint("\u{D7FF}") == 0xD800 - 1)
  155. assert(utf8.codepoint("\u{E000}") == 0xDFFF + 1)
  156. assert(utf8.codepoint("\u{D800}", 1, 1, true) == 0xD800)
  157. assert(utf8.codepoint("\u{DFFF}", 1, 1, true) == 0xDFFF)
  158. assert(utf8.codepoint("\u{7FFFFFFF}", 1, 1, true) == 0x7FFFFFFF)
  159. end
  160. assert(utf8.char() == "")
  161. assert(utf8.char(0, 97, 98, 99, 1) == "\0abc\1")
  162. assert(utf8.codepoint(utf8.char(0x10FFFF)) == 0x10FFFF)
  163. assert(utf8.codepoint(utf8.char(0x7FFFFFFF), 1, 1, true) == (1<<31) - 1)
  164. checkerror("value out of range", utf8.char, 0x7FFFFFFF + 1)
  165. checkerror("value out of range", utf8.char, -1)
  166. local function invalid (s)
  167. checkerror("invalid UTF%-8 code", utf8.codepoint, s)
  168. assert(not utf8.len(s))
  169. end
  170. -- UTF-8 representation for 0x11ffff (value out of valid range)
  171. invalid("\xF4\x9F\xBF\xBF")
  172. -- surrogates
  173. invalid("\u{D800}")
  174. invalid("\u{DFFF}")
  175. -- overlong sequences
  176. invalid("\xC0\x80") -- zero
  177. invalid("\xC1\xBF") -- 0x7F (should be coded in 1 byte)
  178. invalid("\xE0\x9F\xBF") -- 0x7FF (should be coded in 2 bytes)
  179. invalid("\xF0\x8F\xBF\xBF") -- 0xFFFF (should be coded in 3 bytes)
  180. -- invalid bytes
  181. invalid("\x80") -- continuation byte
  182. invalid("\xBF") -- continuation byte
  183. invalid("\xFE") -- invalid byte
  184. invalid("\xFF") -- invalid byte
  185. -- empty string
  186. check("", {})
  187. -- minimum and maximum values for each sequence size
  188. s = "\0 \x7F\z
  189. \xC2\x80 \xDF\xBF\z
  190. \xE0\xA0\x80 \xEF\xBF\xBF\z
  191. \xF0\x90\x80\x80 \xF4\x8F\xBF\xBF"
  192. s = string.gsub(s, " ", "")
  193. check(s, {0,0x7F, 0x80,0x7FF, 0x800,0xFFFF, 0x10000,0x10FFFF})
  194. do
  195. -- original UTF-8 values
  196. local s = "\u{4000000}\u{7FFFFFFF}"
  197. assert(#s == 12)
  198. check(s, {0x4000000, 0x7FFFFFFF}, true)
  199. s = "\u{200000}\u{3FFFFFF}"
  200. assert(#s == 10)
  201. check(s, {0x200000, 0x3FFFFFF}, true)
  202. s = "\u{10000}\u{1fffff}"
  203. assert(#s == 8)
  204. check(s, {0x10000, 0x1FFFFF}, true)
  205. end
  206. local x = "日本語a-4\0éó"
  207. check(x, {26085, 26412, 35486, 97, 45, 52, 0, 233, 243})
  208. -- Supplementary Characters
  209. check("𣲷𠜎𠱓𡁻𠵼ab𠺢",
  210. {0x23CB7, 0x2070E, 0x20C53, 0x2107B, 0x20D7C, 0x61, 0x62, 0x20EA2,})
  211. check("𨳊𩶘𦧺𨳒𥄫𤓓\xF4\x8F\xBF\xBF",
  212. {0x28CCA, 0x29D98, 0x269FA, 0x28CD2, 0x2512B, 0x244D3, 0x10ffff})
  213. local i = 0
  214. for p, c in string.gmatch(x, "()(" .. utf8.charpattern .. ")") do
  215. i = i + 1
  216. assert(utf8.offset(x, i) == p)
  217. assert(utf8.len(x, p) == utf8.len(x) - i + 1)
  218. assert(utf8.len(c) == 1)
  219. for j = 1, #c - 1 do
  220. assert(utf8.offset(x, 0, p + j - 1) == p)
  221. end
  222. end
  223. print'ok'