make_uts46_mapping_table.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. # Copyright 2013-2014 The rust-url developers.
  2. #
  3. # Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
  4. # http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
  5. # <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
  6. # option. This file may not be copied, modified, or distributed
  7. # except according to those terms.
  8. # Run as: python make_uts46_mapping_table.py IdnaMappingTable.txt > uts46_mapping_table.rs
  9. # You can get the latest idna table from
  10. # http://www.unicode.org/Public/idna/latest/IdnaMappingTable.txt
  11. import collections
  12. import itertools
  13. print('''\
  14. // Copyright 2013-2020 The rust-url developers.
  15. //
  16. // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
  17. // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
  18. // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
  19. // option. This file may not be copied, modified, or distributed
  20. // except according to those terms.
  21. // Generated by make_idna_table.py
  22. ''')
  23. txt = open("IdnaMappingTable.txt")
  24. def escape_char(c):
  25. return "\\u{%x}" % ord(c[0])
  26. def char(s):
  27. return chr(int(s, 16))
  28. strtab = collections.OrderedDict()
  29. strtab_offset = 0
  30. def strtab_slice(s):
  31. global strtab, strtab_offset
  32. if s in strtab:
  33. return strtab[s]
  34. else:
  35. utf8_len = len(s.encode('utf8'))
  36. c = (strtab_offset, utf8_len)
  37. strtab[s] = c
  38. strtab_offset += utf8_len
  39. return c
  40. def rust_slice(s):
  41. start = s[0]
  42. length = s[1]
  43. start_lo = start & 0xff
  44. start_hi = start >> 8
  45. assert length <= 255
  46. assert start_hi <= 255
  47. return "(StringTableSlice { byte_start_lo: %d, byte_start_hi: %d, byte_len: %d })" % (start_lo, start_hi, length)
  48. ranges = []
  49. for line in txt:
  50. # remove comments
  51. line, _, _ = line.partition('#')
  52. # skip empty lines
  53. if len(line.strip()) == 0:
  54. continue
  55. fields = line.split(';')
  56. if fields[0].strip() == 'D800..DFFF':
  57. continue # Surrogates don't occur in Rust strings.
  58. first, _, last = fields[0].strip().partition('..')
  59. if not last:
  60. last = first
  61. mapping = fields[1].strip().replace('_', ' ').title().replace(' ', '')
  62. unicode_str = None
  63. if len(fields) > 2:
  64. if fields[2].strip():
  65. unicode_str = u''.join(char(c) for c in fields[2].strip().split(' '))
  66. elif mapping == "Deviation":
  67. unicode_str = u''
  68. ranges.append((first, last, mapping, unicode_str))
  69. def mergeable_key(r):
  70. mapping = r[2]
  71. # These types have associated data, so we should not merge them.
  72. if mapping in ('Mapped', 'Deviation', 'DisallowedStd3Mapped'):
  73. return r
  74. assert mapping in ('Valid', 'Ignored', 'Disallowed', 'DisallowedStd3Valid')
  75. return mapping
  76. grouped_ranges = itertools.groupby(ranges, key=mergeable_key)
  77. optimized_ranges = []
  78. for (k, g) in grouped_ranges:
  79. group = list(g)
  80. if len(group) == 1:
  81. optimized_ranges.append(group[0])
  82. continue
  83. # Assert that nothing in the group has an associated unicode string.
  84. for g in group:
  85. if g[3] is not None and len(g[3]) > 2:
  86. assert not g[3][2].strip()
  87. # Assert that consecutive members of the group don't leave gaps in
  88. # the codepoint space.
  89. a, b = itertools.tee(group)
  90. next(b, None)
  91. for (g1, g2) in zip(a, b):
  92. last_char = int(g1[1], 16)
  93. next_char = int(g2[0], 16)
  94. if last_char + 1 == next_char:
  95. continue
  96. # There's a gap where surrogates would appear, but we don't have to
  97. # worry about that gap, as surrogates never appear in Rust strings.
  98. # Assert we're seeing the surrogate case here.
  99. assert last_char == 0xd7ff
  100. assert next_char == 0xe000
  101. first = group[0][0]
  102. last = group[-1][1]
  103. mapping = group[0][2]
  104. unicode_str = group[0][3]
  105. optimized_ranges.append((first, last, mapping, unicode_str))
  106. def is_single_char_range(r):
  107. (first, last, _, _) = r
  108. return first == last
  109. # We can reduce the size of the character range table and the index table to about 1/4
  110. # by merging runs of single character ranges and using character offsets from the start
  111. # of that range to retrieve the correct `Mapping` value
  112. def merge_single_char_ranges(ranges):
  113. current = []
  114. for r in ranges:
  115. if not current or is_single_char_range(current[-1]) and is_single_char_range(r):
  116. current.append(r)
  117. continue
  118. if len(current) != 0:
  119. ret = current
  120. current = [r]
  121. yield ret
  122. continue
  123. current.append(r)
  124. ret = current
  125. current = []
  126. yield ret
  127. yield current
  128. optimized_ranges = list(merge_single_char_ranges(optimized_ranges))
  129. print("static TABLE: &[Range] = &[")
  130. for ranges in optimized_ranges:
  131. first = ranges[0][0]
  132. last = ranges[-1][1]
  133. print(" Range { from: '%s', to: '%s', }," % (escape_char(char(first)),
  134. escape_char(char(last))))
  135. print("];\n")
  136. print("static INDEX_TABLE: &[u16] = &[")
  137. SINGLE_MARKER = 1 << 15
  138. offset = 0
  139. for ranges in optimized_ranges:
  140. assert offset < SINGLE_MARKER
  141. block_len = len(ranges)
  142. single = SINGLE_MARKER if block_len == 1 else 0
  143. print(" %s," % (offset | single))
  144. offset += block_len
  145. print("];\n")
  146. print("static MAPPING_TABLE: &[Mapping] = &[")
  147. for ranges in optimized_ranges:
  148. for (first, last, mapping, unicode_str) in ranges:
  149. if unicode_str is not None:
  150. mapping += rust_slice(strtab_slice(unicode_str))
  151. print(" %s," % mapping)
  152. print("];\n")
  153. def escape_str(s):
  154. return [escape_char(c) for c in s]
  155. print("static STRING_TABLE: &str = \"%s\";"
  156. % '\\\n '.join(itertools.chain(*[escape_str(s) for s in strtab.keys()])))