Bug 1862332 [wpt PR 42877] - WebKit export of https://bugs.webkit.org/show_bug.cgi...
[gecko.git] / gfx / harfbuzz / src / gen-indic-table.py
blob4ef970265d124e2d83a1137e7ba53a86dc78b4a1
1 #!/usr/bin/env python3
3 """usage: ./gen-indic-table.py IndicSyllabicCategory.txt IndicPositionalCategory.txt Blocks.txt
5 Input files:
6 * https://unicode.org/Public/UCD/latest/ucd/IndicSyllabicCategory.txt
7 * https://unicode.org/Public/UCD/latest/ucd/IndicPositionalCategory.txt
8 * https://unicode.org/Public/UCD/latest/ucd/Blocks.txt
9 """
11 import sys
13 if len (sys.argv) != 4:
14 sys.exit (__doc__)
16 ALLOWED_SINGLES = [0x00A0, 0x25CC]
17 ALLOWED_BLOCKS = [
18 'Basic Latin',
19 'Latin-1 Supplement',
20 'Devanagari',
21 'Bengali',
22 'Gurmukhi',
23 'Gujarati',
24 'Oriya',
25 'Tamil',
26 'Telugu',
27 'Kannada',
28 'Malayalam',
29 'Myanmar',
30 'Khmer',
31 'Vedic Extensions',
32 'General Punctuation',
33 'Superscripts and Subscripts',
34 'Devanagari Extended',
35 'Myanmar Extended-B',
36 'Myanmar Extended-A',
39 files = [open (x, encoding='utf-8') for x in sys.argv[1:]]
41 headers = [[f.readline () for i in range (2)] for f in files]
43 unicode_data = [{} for _ in files]
44 for i, f in enumerate (files):
45 for line in f:
47 j = line.find ('#')
48 if j >= 0:
49 line = line[:j]
51 fields = [x.strip () for x in line.split (';')]
52 if len (fields) == 1:
53 continue
55 uu = fields[0].split ('..')
56 start = int (uu[0], 16)
57 if len (uu) == 1:
58 end = start
59 else:
60 end = int (uu[1], 16)
62 t = fields[1]
64 for u in range (start, end + 1):
65 unicode_data[i][u] = t
67 # Merge data into one dict:
68 defaults = ('Other', 'Not_Applicable', 'No_Block')
69 combined = {}
70 for i,d in enumerate (unicode_data):
71 for u,v in d.items ():
72 if i == 2 and not u in combined:
73 continue
74 if not u in combined:
75 combined[u] = list (defaults)
76 combined[u][i] = v
77 combined = {k:v for k,v in combined.items() if k in ALLOWED_SINGLES or v[2] in ALLOWED_BLOCKS}
80 # Convert categories & positions types
82 categories = {
83 'indic' : [
84 'X',
85 'C',
86 'V',
87 'N',
88 'H',
89 'ZWNJ',
90 'ZWJ',
91 'M',
92 'SM',
93 'A',
94 'VD',
95 'PLACEHOLDER',
96 'DOTTEDCIRCLE',
97 'RS',
98 'MPst',
99 'Repha',
100 'Ra',
101 'CM',
102 'Symbol',
103 'CS',
105 'khmer' : [
106 'VAbv',
107 'VBlw',
108 'VPre',
109 'VPst',
111 'Robatic',
112 'Xgroup',
113 'Ygroup',
115 'myanmar' : [
116 'VAbv',
117 'VBlw',
118 'VPre',
119 'VPst',
121 'IV',
122 'As',
123 'DB',
124 'GB',
125 'MH',
126 'MR',
127 'MW',
128 'MY',
129 'PT',
130 'VS',
131 'ML',
135 category_map = {
136 'Other' : 'X',
137 'Avagraha' : 'Symbol',
138 'Bindu' : 'SM',
139 'Brahmi_Joining_Number' : 'PLACEHOLDER', # Don't care.
140 'Cantillation_Mark' : 'A',
141 'Consonant' : 'C',
142 'Consonant_Dead' : 'C',
143 'Consonant_Final' : 'CM',
144 'Consonant_Head_Letter' : 'C',
145 'Consonant_Initial_Postfixed' : 'C', # TODO
146 'Consonant_Killer' : 'M', # U+17CD only.
147 'Consonant_Medial' : 'CM',
148 'Consonant_Placeholder' : 'PLACEHOLDER',
149 'Consonant_Preceding_Repha' : 'Repha',
150 'Consonant_Prefixed' : 'X', # Don't care.
151 'Consonant_Subjoined' : 'CM',
152 'Consonant_Succeeding_Repha' : 'CM',
153 'Consonant_With_Stacker' : 'CS',
154 'Gemination_Mark' : 'SM', # https://github.com/harfbuzz/harfbuzz/issues/552
155 'Invisible_Stacker' : 'H',
156 'Joiner' : 'ZWJ',
157 'Modifying_Letter' : 'X',
158 'Non_Joiner' : 'ZWNJ',
159 'Nukta' : 'N',
160 'Number' : 'PLACEHOLDER',
161 'Number_Joiner' : 'PLACEHOLDER', # Don't care.
162 'Pure_Killer' : 'M', # Is like a vowel matra.
163 'Register_Shifter' : 'RS',
164 'Syllable_Modifier' : 'SM',
165 'Tone_Letter' : 'X',
166 'Tone_Mark' : 'N',
167 'Virama' : 'H',
168 'Visarga' : 'SM',
169 'Vowel' : 'V',
170 'Vowel_Dependent' : 'M',
171 'Vowel_Independent' : 'V',
173 position_map = {
174 'Not_Applicable' : 'END',
176 'Left' : 'PRE_C',
177 'Top' : 'ABOVE_C',
178 'Bottom' : 'BELOW_C',
179 'Right' : 'POST_C',
181 # These should resolve to the position of the last part of the split sequence.
182 'Bottom_And_Right' : 'POST_C',
183 'Left_And_Right' : 'POST_C',
184 'Top_And_Bottom' : 'BELOW_C',
185 'Top_And_Bottom_And_Left' : 'BELOW_C',
186 'Top_And_Bottom_And_Right' : 'POST_C',
187 'Top_And_Left' : 'ABOVE_C',
188 'Top_And_Left_And_Right' : 'POST_C',
189 'Top_And_Right' : 'POST_C',
191 'Overstruck' : 'AFTER_MAIN',
192 'Visual_order_left' : 'PRE_M',
195 category_overrides = {
197 # These are the variation-selectors. They only appear in the Myanmar grammar
198 # but are not Myanmar-specific
199 0xFE00: 'VS',
200 0xFE01: 'VS',
201 0xFE02: 'VS',
202 0xFE03: 'VS',
203 0xFE04: 'VS',
204 0xFE05: 'VS',
205 0xFE06: 'VS',
206 0xFE07: 'VS',
207 0xFE08: 'VS',
208 0xFE09: 'VS',
209 0xFE0A: 'VS',
210 0xFE0B: 'VS',
211 0xFE0C: 'VS',
212 0xFE0D: 'VS',
213 0xFE0E: 'VS',
214 0xFE0F: 'VS',
216 # These appear in the OT Myanmar spec, but are not Myanmar-specific
217 0x2015: 'PLACEHOLDER',
218 0x2022: 'PLACEHOLDER',
219 0x25FB: 'PLACEHOLDER',
220 0x25FC: 'PLACEHOLDER',
221 0x25FD: 'PLACEHOLDER',
222 0x25FE: 'PLACEHOLDER',
225 # Indic
227 0x0930: 'Ra', # Devanagari
228 0x09B0: 'Ra', # Bengali
229 0x09F0: 'Ra', # Bengali
230 0x0A30: 'Ra', # Gurmukhi No Reph
231 0x0AB0: 'Ra', # Gujarati
232 0x0B30: 'Ra', # Oriya
233 0x0BB0: 'Ra', # Tamil No Reph
234 0x0C30: 'Ra', # Telugu Reph formed only with ZWJ
235 0x0CB0: 'Ra', # Kannada
236 0x0D30: 'Ra', # Malayalam No Reph, Logical Repha
238 # The following act more like the Bindus.
239 0x0953: 'SM',
240 0x0954: 'SM',
242 # U+0A40 GURMUKHI VOWEL SIGN II may be preceded by U+0A02 GURMUKHI SIGN BINDI.
243 0x0A40: 'MPst',
245 # The following act like consonants.
246 0x0A72: 'C',
247 0x0A73: 'C',
248 0x1CF5: 'C',
249 0x1CF6: 'C',
251 # TODO: The following should only be allowed after a Visarga.
252 # For now, just treat them like regular tone marks.
253 0x1CE2: 'A',
254 0x1CE3: 'A',
255 0x1CE4: 'A',
256 0x1CE5: 'A',
257 0x1CE6: 'A',
258 0x1CE7: 'A',
259 0x1CE8: 'A',
261 # TODO: The following should only be allowed after some of
262 # the nasalization marks, maybe only for U+1CE9..U+1CF1.
263 # For now, just treat them like tone marks.
264 0x1CED: 'A',
266 # The following take marks in standalone clusters, similar to Avagraha.
267 0xA8F2: 'Symbol',
268 0xA8F3: 'Symbol',
269 0xA8F4: 'Symbol',
270 0xA8F5: 'Symbol',
271 0xA8F6: 'Symbol',
272 0xA8F7: 'Symbol',
273 0x1CE9: 'Symbol',
274 0x1CEA: 'Symbol',
275 0x1CEB: 'Symbol',
276 0x1CEC: 'Symbol',
277 0x1CEE: 'Symbol',
278 0x1CEF: 'Symbol',
279 0x1CF0: 'Symbol',
280 0x1CF1: 'Symbol',
282 0x0A51: 'M', # https://github.com/harfbuzz/harfbuzz/issues/524
284 # According to ScriptExtensions.txt, these Grantha marks may also be used in Tamil,
285 # so the Indic shaper needs to know their categories.
286 0x11301: 'SM',
287 0x11302: 'SM',
288 0x11303: 'SM',
289 0x1133B: 'N',
290 0x1133C: 'N',
292 0x0AFB: 'N', # https://github.com/harfbuzz/harfbuzz/issues/552
293 0x0B55: 'N', # https://github.com/harfbuzz/harfbuzz/issues/2849
295 0x09FC: 'PLACEHOLDER', # https://github.com/harfbuzz/harfbuzz/pull/1613
296 0x0C80: 'PLACEHOLDER', # https://github.com/harfbuzz/harfbuzz/pull/623
297 0x0D04: 'PLACEHOLDER', # https://github.com/harfbuzz/harfbuzz/pull/3511
299 0x25CC: 'DOTTEDCIRCLE',
302 # Khmer
304 0x179A: 'Ra',
306 0x17CC: 'Robatic',
307 0x17C9: 'Robatic',
308 0x17CA: 'Robatic',
310 0x17C6: 'Xgroup',
311 0x17CB: 'Xgroup',
312 0x17CD: 'Xgroup',
313 0x17CE: 'Xgroup',
314 0x17CF: 'Xgroup',
315 0x17D0: 'Xgroup',
316 0x17D1: 'Xgroup',
318 0x17C7: 'Ygroup',
319 0x17C8: 'Ygroup',
320 0x17DD: 'Ygroup',
321 0x17D3: 'Ygroup', # Just guessing. Uniscribe doesn't categorize it.
323 0x17D9: 'PLACEHOLDER', # https://github.com/harfbuzz/harfbuzz/issues/2384
326 # Myanmar
328 # https://docs.microsoft.com/en-us/typography/script-development/myanmar#analyze
330 0x104E: 'C', # The spec says C, IndicSyllableCategory says Consonant_Placeholder
332 0x1004: 'Ra',
333 0x101B: 'Ra',
334 0x105A: 'Ra',
336 0x1032: 'A',
337 0x1036: 'A',
339 0x103A: 'As',
341 #0x1040: 'D0', # XXX The spec says D0, but Uniscribe doesn't seem to do.
343 0x103E: 'MH',
344 0x1060: 'ML',
345 0x103C: 'MR',
346 0x103D: 'MW',
347 0x1082: 'MW',
348 0x103B: 'MY',
349 0x105E: 'MY',
350 0x105F: 'MY',
352 0x1063: 'PT',
353 0x1064: 'PT',
354 0x1069: 'PT',
355 0x106A: 'PT',
356 0x106B: 'PT',
357 0x106C: 'PT',
358 0x106D: 'PT',
359 0xAA7B: 'PT',
361 0x1038: 'SM',
362 0x1087: 'SM',
363 0x1088: 'SM',
364 0x1089: 'SM',
365 0x108A: 'SM',
366 0x108B: 'SM',
367 0x108C: 'SM',
368 0x108D: 'SM',
369 0x108F: 'SM',
370 0x109A: 'SM',
371 0x109B: 'SM',
372 0x109C: 'SM',
374 0x104A: 'PLACEHOLDER',
376 position_overrides = {
378 0x0A51: 'BELOW_C', # https://github.com/harfbuzz/harfbuzz/issues/524
380 0x0B01: 'BEFORE_SUB', # Oriya Bindu is BeforeSub in the spec.
383 def matra_pos_left(u, block):
384 return "PRE_M"
385 def matra_pos_right(u, block):
386 if block == 'Devanagari': return 'AFTER_SUB'
387 if block == 'Bengali': return 'AFTER_POST'
388 if block == 'Gurmukhi': return 'AFTER_POST'
389 if block == 'Gujarati': return 'AFTER_POST'
390 if block == 'Oriya': return 'AFTER_POST'
391 if block == 'Tamil': return 'AFTER_POST'
392 if block == 'Telugu': return 'BEFORE_SUB' if u <= 0x0C42 else 'AFTER_SUB'
393 if block == 'Kannada': return 'BEFORE_SUB' if u < 0x0CC3 or u > 0x0CD6 else 'AFTER_SUB'
394 if block == 'Malayalam': return 'AFTER_POST'
395 return 'AFTER_SUB'
396 def matra_pos_top(u, block):
397 # BENG and MLYM don't have top matras.
398 if block == 'Devanagari': return 'AFTER_SUB'
399 if block == 'Gurmukhi': return 'AFTER_POST' # Deviate from spec
400 if block == 'Gujarati': return 'AFTER_SUB'
401 if block == 'Oriya': return 'AFTER_MAIN'
402 if block == 'Tamil': return 'AFTER_SUB'
403 if block == 'Telugu': return 'BEFORE_SUB'
404 if block == 'Kannada': return 'BEFORE_SUB'
405 return 'AFTER_SUB'
406 def matra_pos_bottom(u, block):
407 if block == 'Devanagari': return 'AFTER_SUB'
408 if block == 'Bengali': return 'AFTER_SUB'
409 if block == 'Gurmukhi': return 'AFTER_POST'
410 if block == 'Gujarati': return 'AFTER_POST'
411 if block == 'Oriya': return 'AFTER_SUB'
412 if block == 'Tamil': return 'AFTER_POST'
413 if block == 'Telugu': return 'BEFORE_SUB'
414 if block == 'Kannada': return 'BEFORE_SUB'
415 if block == 'Malayalam': return 'AFTER_POST'
416 return "AFTER_SUB"
417 def indic_matra_position(u, pos, block): # Reposition matra
418 if pos == 'PRE_C': return matra_pos_left(u, block)
419 if pos == 'POST_C': return matra_pos_right(u, block)
420 if pos == 'ABOVE_C': return matra_pos_top(u, block)
421 if pos == 'BELOW_C': return matra_pos_bottom(u, block)
422 assert (False)
424 def position_to_category(pos):
425 if pos == 'PRE_C': return 'VPre'
426 if pos == 'ABOVE_C': return 'VAbv'
427 if pos == 'BELOW_C': return 'VBlw'
428 if pos == 'POST_C': return 'VPst'
429 assert(False)
432 defaults = (category_map[defaults[0]], position_map[defaults[1]], defaults[2])
434 indic_data = {}
435 for k, (cat, pos, block) in combined.items():
436 cat = category_map[cat]
437 pos = position_map[pos]
438 indic_data[k] = (cat, pos, block)
440 for k,new_cat in category_overrides.items():
441 (cat, pos, _) = indic_data.get(k, defaults)
442 indic_data[k] = (new_cat, pos, unicode_data[2][k])
444 # We only expect position for certain types
445 positioned_categories = ('CM', 'SM', 'RS', 'H', 'M', 'MPst')
446 for k, (cat, pos, block) in indic_data.items():
447 if cat not in positioned_categories:
448 pos = 'END'
449 indic_data[k] = (cat, pos, block)
451 # Position overrides are more complicated
453 # Keep in sync with CONSONANT_FLAGS in the shaper
454 consonant_categories = ('C', 'CS', 'Ra','CM', 'V', 'PLACEHOLDER', 'DOTTEDCIRCLE')
455 matra_categories = ('M', 'MPst')
456 smvd_categories = ('SM', 'VD', 'A', 'Symbol')
457 for k, (cat, pos, block) in indic_data.items():
458 if cat in consonant_categories:
459 pos = 'BASE_C'
460 elif cat in matra_categories:
461 if block.startswith('Khmer') or block.startswith('Myanmar'):
462 cat = position_to_category(pos)
463 else:
464 pos = indic_matra_position(k, pos, block)
465 elif cat in smvd_categories:
466 pos = 'SMVD';
467 indic_data[k] = (cat, pos, block)
469 for k,new_pos in position_overrides.items():
470 (cat, pos, _) = indic_data.get(k, defaults)
471 indic_data[k] = (cat, new_pos, unicode_data[2][k])
474 values = [{_: 1} for _ in defaults]
475 for vv in indic_data.values():
476 for i,v in enumerate(vv):
477 values[i][v] = values[i].get (v, 0) + 1
482 # Move the outliers NO-BREAK SPACE and DOTTED CIRCLE out
483 singles = {}
484 for u in ALLOWED_SINGLES:
485 singles[u] = indic_data[u]
486 del indic_data[u]
488 print ("/* == Start of generated table == */")
489 print ("/*")
490 print (" * The following table is generated by running:")
491 print (" *")
492 print (" * ./gen-indic-table.py IndicSyllabicCategory.txt IndicPositionalCategory.txt Blocks.txt")
493 print (" *")
494 print (" * on files with these headers:")
495 print (" *")
496 for h in headers:
497 for l in h:
498 print (" * %s" % (l.strip()))
499 print (" */")
500 print ()
501 print ('#include "hb.hh"')
502 print ()
503 print ('#ifndef HB_NO_OT_SHAPE')
504 print ()
505 print ('#include "hb-ot-shaper-indic.hh"')
506 print ()
507 print ('#pragma GCC diagnostic push')
508 print ('#pragma GCC diagnostic ignored "-Wunused-macros"')
509 print ()
511 # Print categories
512 for shaper in categories:
513 print ('#include "hb-ot-shaper-%s-machine.hh"' % shaper)
514 print ()
515 done = {}
516 for shaper, shaper_cats in categories.items():
517 print ('/* %s */' % shaper)
518 for cat in shaper_cats:
519 v = shaper[0].upper()
520 if cat not in done:
521 print ("#define OT_%s %s_Cat(%s)" % (cat, v, cat))
522 done[cat] = v
523 else:
524 print ('static_assert (OT_%s == %s_Cat(%s), "");' % (cat, v, cat))
525 print ()
527 # Shorten values
528 short = [{
529 "Repha": 'Rf',
530 "PLACEHOLDER": 'GB',
531 "DOTTEDCIRCLE": 'DC',
532 "VPst": 'VR',
533 "VPre": 'VL',
534 "Robatic": 'Rt',
535 "Xgroup": 'Xg',
536 "Ygroup": 'Yg',
537 "As": 'As',
539 "END": 'X',
540 "BASE_C": 'C',
541 "ABOVE_C": 'T',
542 "BELOW_C": 'B',
543 "POST_C": 'R',
544 "PRE_C": 'L',
545 "PRE_M": 'LM',
546 "AFTER_MAIN": 'A',
547 "AFTER_SUB": 'AS',
548 "BEFORE_SUB": 'BS',
549 "AFTER_POST": 'AP',
550 "SMVD": 'SM',
552 all_shorts = [{},{}]
554 # Add some of the values, to make them more readable, and to avoid duplicates
556 for i in range (2):
557 for v,s in short[i].items ():
558 all_shorts[i][s] = v
560 what = ["OT", "POS"]
561 what_short = ["_OT", "_POS"]
562 cat_defs = []
563 for i in range (2):
564 vv = sorted (values[i].keys ())
565 for v in vv:
566 v_no_and = v.replace ('_And_', '_')
567 if v in short[i]:
568 s = short[i][v]
569 else:
570 s = ''.join ([c for c in v_no_and if ord ('A') <= ord (c) <= ord ('Z')])
571 if s in all_shorts[i]:
572 raise Exception ("Duplicate short value alias", v, all_shorts[i][s])
573 all_shorts[i][s] = v
574 short[i][v] = s
575 cat_defs.append ((what_short[i] + '_' + s, what[i] + '_' + (v.upper () if i else v), str (values[i][v]), v))
577 maxlen_s = max ([len (c[0]) for c in cat_defs])
578 maxlen_l = max ([len (c[1]) for c in cat_defs])
579 maxlen_n = max ([len (c[2]) for c in cat_defs])
580 for s in what_short:
581 print ()
582 for c in [c for c in cat_defs if s in c[0]]:
583 print ("#define %s %s /* %s chars; %s */" %
584 (c[0].ljust (maxlen_s), c[1].ljust (maxlen_l), c[2].rjust (maxlen_n), c[3]))
585 print ()
586 print ('#pragma GCC diagnostic pop')
587 print ()
588 print ("#define INDIC_COMBINE_CATEGORIES(S,M) ((S) | ((M) << 8))")
589 print ()
590 print ("#define _(S,M) INDIC_COMBINE_CATEGORIES (%s_##S, %s_##M)" % tuple(what_short))
591 print ()
592 print ()
594 total = 0
595 used = 0
596 last_block = None
597 def print_block (block, start, end, data):
598 global total, used, last_block
599 if block and block != last_block:
600 print ()
601 print ()
602 print (" /* %s */" % block)
603 num = 0
604 assert start % 8 == 0
605 assert (end+1) % 8 == 0
606 for u in range (start, end+1):
607 if u % 8 == 0:
608 print ()
609 print (" /* %04X */" % u, end="")
610 if u in data:
611 num += 1
612 d = data.get (u, defaults)
613 print ("%9s" % ("_(%s,%s)," % (short[0][d[0]], short[1][d[1]])), end="")
615 total += end - start + 1
616 used += num
617 if block:
618 last_block = block
620 uu = sorted (indic_data)
622 last = -100000
623 num = 0
624 offset = 0
625 starts = []
626 ends = []
627 print ("static const uint16_t indic_table[] = {")
628 for u in uu:
629 if u <= last:
630 continue
631 block = indic_data[u][2]
633 start = u//8*8
634 end = start+1
635 while end in uu and block == indic_data[end][2]:
636 end += 1
637 end = (end-1)//8*8 + 7
639 if start != last + 1:
640 if start - last <= 1+16*2:
641 print_block (None, last+1, start-1, indic_data)
642 else:
643 if last >= 0:
644 ends.append (last + 1)
645 offset += ends[-1] - starts[-1]
646 print ()
647 print ()
648 print ("#define indic_offset_0x%04xu %d" % (start, offset))
649 starts.append (start)
651 print_block (block, start, end, indic_data)
652 last = end
653 ends.append (last + 1)
654 offset += ends[-1] - starts[-1]
655 print ()
656 print ()
657 occupancy = used * 100. / total
658 page_bits = 12
659 print ("}; /* Table items: %d; occupancy: %d%% */" % (offset, occupancy))
660 print ()
661 print ("uint16_t")
662 print ("hb_indic_get_categories (hb_codepoint_t u)")
663 print ("{")
664 print (" switch (u >> %d)" % page_bits)
665 print (" {")
666 pages = set ([u>>page_bits for u in starts+ends+list (singles.keys ())])
667 for p in sorted(pages):
668 print (" case 0x%0Xu:" % p)
669 for u,d in singles.items ():
670 if p != u>>page_bits: continue
671 print (" if (unlikely (u == 0x%04Xu)) return _(%s,%s);" % (u, short[0][d[0]], short[1][d[1]]))
672 for (start,end) in zip (starts, ends):
673 if p not in [start>>page_bits, end>>page_bits]: continue
674 offset = "indic_offset_0x%04xu" % start
675 print (" if (hb_in_range<hb_codepoint_t> (u, 0x%04Xu, 0x%04Xu)) return indic_table[u - 0x%04Xu + %s];" % (start, end-1, start, offset))
676 print (" break;")
677 print ("")
678 print (" default:")
679 print (" break;")
680 print (" }")
681 print (" return _(X,X);")
682 print ("}")
683 print ()
684 print ("#undef _")
685 print ("#undef INDIC_COMBINE_CATEGORIES")
686 for i in range (2):
687 print ()
688 vv = sorted (values[i].keys ())
689 for v in vv:
690 print ("#undef %s_%s" %
691 (what_short[i], short[i][v]))
692 print ()
693 print ('#endif')
694 print ()
695 print ("/* == End of generated table == */")
697 # Maintain at least 50% occupancy in the table */
698 if occupancy < 50:
699 raise Exception ("Table too sparse, please investigate: ", occupancy)