source: proto/charsetcompiler/UCD/UCD_properties.py @ 4184

Last change on this file since 4184 was 4184, checked in by cameron, 4 years ago

Generating PropertyObjectArray?.h

File size: 16.4 KB
Line 
1#
2# UCD_properties.py - parsing Unicode Character Database (UCD) files
3# and generating C headers for property data using a compact bitset
4# representation.
5#
6# Robert D. Cameron
7# September 10, 2014
8#
9# Licensed under Open Software License 3.0.
10#
11#
12import re, string, os.path, cformat
13from unicode_set import *
14
15UCD_dir = "7.0.0"
16
17#
18#  Processing files of the UCD
19#
20#  General format for skippable comments, blank lines
21UCD_skip = re.compile("^#.*$|^\s*$")
22
23#
24#  UCD Property File Format 1: property aliases
25#  PropertyAliases.txt
26#
27UCD_property_section_regexp = re.compile("^#\s*([-A-Za-z_0-9]+)\s*Properties\s*$")
28UCD_property_alias_regexp = re.compile("^([-A-Za-z_0-9]+)\s*;\s*([-A-Za-z_0-9]+)([^#]*)")
29
30def parse_PropertyAlias_txt():
31   property_enum_name_list = []
32   full_name_map = {}
33   property_lookup_map = {}
34   property_kind_map = {}
35   property_kind = "unspecified"
36   f = open(UCD_dir + "/" + 'PropertyAliases.txt')
37   lines = f.readlines()
38   for t in lines:
39      m = UCD_property_section_regexp.match(t)
40      if m:
41        property_kind = m.group(1)
42      if UCD_skip.match(t): continue  # skip comment and blank lines
43      m = UCD_property_alias_regexp.match(t)
44      if not m: raise Exception("Unknown property alias syntax: %s" % t)
45      prop_enum = m.group(1).lower()
46      prop_preferred_full_name = m.group(2)
47      prop_extra = m.group(3)
48      prop_aliases = re.findall("[-A-Za-z_0-9]+", prop_extra)
49      property_enum_name_list.append(prop_enum)
50      full_name_map[prop_enum] = prop_preferred_full_name
51      property_lookup_map[canonicalize(prop_enum)] = prop_enum
52      property_lookup_map[canonicalize(prop_preferred_full_name)] = prop_enum
53      for a in prop_aliases: property_lookup_map[canonicalize(a)] = prop_enum
54      property_kind_map[prop_enum] = property_kind
55   return (property_enum_name_list, full_name_map, property_lookup_map, property_kind_map)
56
57trivial_name_char_re = re.compile('[-_\s]')
58def canonicalize(property_string):
59   c = trivial_name_char_re.sub('', property_string.lower())
60   if len(c) > 2 and c[0:2] == "is": return c[2:]
61   else: return c
62
63PropertyAliases_template = r"""
64namespace UCD {
65  enum property_t {
66%s
67  };
68  const std::string property_full_name[] = {
69%s
70  };
71  const std::unordered_map<std::string, property_t> alias_map = {
72%s
73  };
74}
75"""
76
77
78def generate_PropertyAliases_h(property_enum_name_list, full_name_map, property_lookup_map):
79   f = cformat.open_header_file_for_write('PropertyAliases')
80   cformat.write_imports(f, ["<string>", "<unordered_map>"])
81   #enum_text = multiline_join(property_enum_name_list, 4, ',')
82   enum_text = cformat.multiline_fill(property_enum_name_list, ',')
83   #full_name_text = multiline_join(['"%s"' % full_name_map[e] for e in property_enum_name_list], 2, ',')
84   full_name_text = cformat.multiline_fill(['"%s"' % full_name_map[e] for e in property_enum_name_list], ',')
85   #map_text = multiline_join(['{"%s", %s}' % (k, property_lookup_map[k]) for k in sorted(property_lookup_map.keys())], 2,',')
86   map_text = cformat.multiline_fill(['{"%s", %s}' % (k, property_lookup_map[k]) for k in sorted(property_lookup_map.keys())], ',')
87   f.write(PropertyAliases_template % (enum_text, full_name_text, map_text))
88   cformat.close_header_file(f)
89
90#
91#  UCD Property File Format 2: property value aliases
92#  PropertyValueAliases.txt
93#
94#  This file records value aliases for property values for
95#  each enumerated property, with the following additional notes:
96#  (1) The corresponding integer value of the enum constant is
97#      also specified for ccc (second field).
98#  (2) The Age property is a numeric type which has decimal float
99#      values as the enum constants: these won't be legal in enum syntax.
100#  (3) Binary properties also have enumerated values and aliases listed,
101#      although this is redundant, because all binary properties have the
102#      same value space.
103#
104
105UCD_property_value_alias_regexp = re.compile("^([-A-Za-z_0-9.]+)\s*;\s*([-A-Za-z_0-9.]+)\s*;\s*([-A-Za-z_0-9.]+)([^#]*)")
106
107def parse_PropertyValueAlias_txt(property_lookup_map):
108    property_value_list = {}
109    property_value_enum_integer = {}
110    property_value_full_name_map = {}
111    property_value_lookup_map = {}
112    f = open(UCD_dir + "/" + 'PropertyValueAliases.txt')
113    lines = f.readlines()
114    for t in lines:
115        if UCD_skip.match(t): continue  # skip comment and blank lines
116        m = UCD_property_value_alias_regexp.match(t)
117        if not m: raise Exception("Unknown property value alias syntax: %s" % t)
118        prop_code = canonicalize(m.group(1))
119        if not property_lookup_map.has_key(prop_code): raise Exception("Property code: '%s' is unknown" % prop_code)
120        else: prop_code = property_lookup_map[prop_code]
121        if not property_value_list.has_key(prop_code):
122          property_value_list[prop_code] = []
123          property_value_enum_integer[prop_code] = {}
124          property_value_full_name_map[prop_code] = {}
125          property_value_lookup_map[prop_code] = {}
126          enum_integer = 0
127        # Special case for ccc: second field is enum integer value
128        if prop_code == 'ccc':
129          enum_integer = int(m.group(2))
130          value_enum = m.group(3)
131          extra = m.group(4)
132          extra_list = re.findall("[-A-Za-z_0-9.]+", extra)
133          value_preferred_full_name = extra_list[0]
134          value_aliases = extra_list[1:]
135        # Special case for age: second field is numeric, third field is enum
136        # treat numeric value as an alias string
137        elif prop_code == 'age':
138          value_enum = m.group(3)
139          value_preferred_full_name = m.group(3)
140          extra = m.group(4)
141          value_aliases = [m.group(2)] + re.findall("[-A-Za-z_0-9]+", extra)
142        else:
143          value_enum = m.group(2)
144          value_preferred_full_name = m.group(3)
145          extra = m.group(4)
146          value_aliases = re.findall("[-A-Za-z_0-9]+", extra)
147        property_value_list[prop_code].append(value_enum)
148        property_value_enum_integer[prop_code][value_enum] = enum_integer
149        enum_integer += 1
150        property_value_full_name_map[prop_code][value_enum] = value_preferred_full_name
151        property_value_lookup_map[prop_code][canonicalize(value_enum)] = value_enum
152        property_value_lookup_map[prop_code][canonicalize(value_preferred_full_name)] = value_enum
153        for a in value_aliases: property_value_lookup_map[prop_code][canonicalize(a)] = value_enum
154    return (property_value_list, property_value_enum_integer, property_value_full_name_map, property_value_lookup_map)
155
156
157PropertyValueAliases_template = r"""
158namespace UCD {
159  enum binary_value_t {N, Y};
160%s
161
162  const std::vector<std::string> value_name[] = {
163%s};
164
165  const std::unordered_map<std::string, int> property_value_alias_map[] = {
166%s};
167
168}
169"""
170
171
172
173PropertyValues_template = r"""
174using std::vector;
175
176namespace UCD {
177  vector<UnicodeSet> value_sets[] = {
178%s
179  };
180}
181"""
182
183
184
185def generate_PropertyValueAliases_h(property_enum_name_list, property_value_list, property_value_enum_integer, property_value_full_name_map, property_value_lookup_map):
186   f = cformat.open_header_file_for_write('PropertyValueAliases')
187   cformat.write_imports(f, ["<string>", "<unordered_map>", '"unicode_set.h"', '"PropertyAliases.h"'])
188   #  Generate the aliases for all Binary properties.
189   full_name_text = cformat.multiline_fill(['"No"', '"Yes"'], ',', 6)
190   binary_map_text = cformat.multiline_fill(['{"n", N}', '{"y", Y}', '{"no", N}', '{"yes", Y}', '{"f", N}', '{"t", Y}', '{"false", N}', '{"true", Y}'], ',', 6)
191   #
192   enum_text = ""
193   name_vectors = []
194   alias_maps = []
195   for p in property_enum_name_list:
196     if property_value_list.has_key(p):
197       if property_value_list[p] == ['N', 'Y']:
198         name_vectors.append('    {"No", "Yes"}')
199         alias_maps.append("    {%s}" % binary_map_text)
200       else:
201         enum_text += "  namespace %s {\n    enum value_t {\n" % p.upper()
202         #enum_text += multiline_join(property_value_list[p], 4, ',','', 6)
203         enum_text += cformat.multiline_fill(property_value_list[p], ',', 6)
204         if p == 'ccc': # Special case: add numeric value information for ccc.
205           enum_text += r"""
206    };
207    const uint8_t enum_val[] = {
208"""
209           #enum_text += multiline_join(["%s" % (property_value_enum_integer[p][e]) for e in property_value_list['ccc']], 4, ',', '', 6)
210           enum_text += cformat.multiline_fill(["%s" % (property_value_enum_integer[p][e]) for e in property_value_list['ccc']], ',', 6)
211         enum_text += "};\n  }\n"
212         #full_name_text = multiline_join(['"%s"' % (property_value_full_name_map[p][e]) for e in property_value_list[p]], 4, ',',  '', 6)
213         full_name_text = cformat.multiline_fill(['"%s"' % (property_value_full_name_map[p][e]) for e in property_value_list[p]], ',', 6)
214         name_vectors.append("    {%s}" % full_name_text)
215         #map_text = multiline_join(['{"%s", %s::%s}' % (k, p.upper(), property_value_lookup_map[p][k]) for k in sorted(property_value_lookup_map[p].keys())], 4, ',', '', 6)
216         map_text = cformat.multiline_fill(['{"%s", %s::%s}' % (k, p.upper(), property_value_lookup_map[p][k]) for k in sorted(property_value_lookup_map[p].keys())], ',', 6)
217         alias_maps.append("    {%s}" % map_text)
218     else:
219       name_vectors.append("    {}")
220       alias_maps.append("    {}")
221   f.write(PropertyValueAliases_template % (enum_text, ",\n".join(name_vectors), ",\n".join(alias_maps)))
222   cformat.close_header_file(f)
223
224
225def generate_PropertyValueSets_h(property_enum_name_list, property_value_list, property_value_enum_integer, property_value_full_name_map, property_value_lookup_map):
226   f = cformat.open_header_file_for_write('PropertyValueSets')
227   cformat.write_imports(f, ["<vector>", '"unicode_set.h"'])
228   vec_decl_list = []
229   for p in property_enum_name_list:
230     if not property_value_list.has_key(p):
231       vec_decl_list.append("vector<UnicodeSet>(0)")
232     elif property_value_list[p] == ['N', 'Y']:
233       vec_decl_list.append("vector<UnicodeSet>(1)")
234     elif p == 'scx': 
235       vec_decl_list.append("vector<UnicodeSet>(%i)" % len(property_value_list['sc']))
236     else: 
237       vec_decl_list.append("vector<UnicodeSet>(%i)" % len(property_value_list[p]))
238   f.write(PropertyValues_template % (cformat.multiline_fill(vec_decl_list, ',', 6)))
239   cformat.close_header_file(f)
240
241
242#
243#  UCD Property File Format 3:  codepoint -> name maps
244#
245UCD_skip = re.compile("^#.*$|^\s*$")
246UCD_point_name_regexp = re.compile("^([0-9A-F]{4,6})\s*;\s*((?:[-A-Za-z0-9_]+\s+)*[-A-Za-z0-9_]+)\s*(?:[;#]|$)")
247UCD_range_name_regexp = re.compile("^([0-9A-F]{4,6})[.][.]([0-9A-F]{4,6})\s*;\s*((?:[-A-Za-z0-9_]+\s+)*[-A-Za-z0-9_]+)\s*(?:[;#]|$)")
248
249def parse_UCD_codepoint_name_map(mapfile, canonical_name_lookup_map = None):
250   value_map = {}
251   name_list_order = []
252   f = open(UCD_dir + "/" + mapfile)
253   lines = f.readlines()
254   for t in lines:
255      if UCD_skip.match(t): continue  # skip comment and blank lines
256      m = UCD_point_name_regexp.match(t)
257      if m:
258        (codepoint, name) = (int(m.group(1), 16), m.group(2))
259        newset = singleton_uset(codepoint)
260      else: 
261        m = UCD_range_name_regexp.match(t)
262        if not m: raise Exception("Unknown syntax: %s" % t)
263        (cp_lo, cp_hi, name) = (int(m.group(1), 16), int(m.group(2), 16), m.group(3))
264        newset = range_uset(cp_lo, cp_hi)
265      if not canonical_name_lookup_map == None:
266        cname = canonicalize(name)
267        if not canonical_name_lookup_map.has_key(cname):  raise Exception("Unknown property or property value name '%s'" % cname)
268        name = canonical_name_lookup_map[cname]
269      if not value_map.has_key(name):
270        value_map[name] = newset
271        name_list_order.append(name)
272      else: value_map[name] = uset_union(value_map[name], newset)
273   return (name_list_order, value_map)
274
275def generate_property_value_file(filename_root, property_code, canonical_property_value_map):
276   (prop_values, value_map) = parse_UCD_codepoint_name_map(filename_root + '.txt', canonical_property_value_map)
277   f = cformat.open_header_file_for_write(os.path.basename(filename_root))
278   cformat.write_imports(f, ["<vector>", '"unicode_set.h"', '"PropertyAliases.h"', '"PropertyValueAliases.h"', '"PropertyValueSets.h"'])
279   f.write("\nusing namespace UCD;\n\n")
280   print "%s bytes" % sum([value_map[v].bytes() for v in value_map.keys()])
281   for v in prop_values:
282     f.write(value_map[v].showC('value_sets[%s][%s::%s]' % (property_code, property_code.upper(), v)))
283   cformat.close_header_file(f)
284   
285def generate_binary_properties_file(filename_root, canonical_property_name_map):
286   (props, prop_map) = parse_UCD_codepoint_name_map(filename_root + '.txt', canonical_property_name_map)
287   f = cformat.open_header_file_for_write(os.path.basename(filename_root))
288   cformat.write_imports(f, ["<vector>", '"unicode_set.h"', '"PropertyAliases.h"', '"PropertyValueSets.h"'])
289   f.write("\nusing namespace UCD;\n\n")
290   print "%s bytes" % sum([prop_map[p].bytes() for p in prop_map.keys()])
291   for p in sorted(props):
292     f.write(prop_map[p].showC('value_sets[%s][0]' % (p)))
293   cformat.close_header_file(f)
294     
295def generate_PropertyObjectArray_h(property_enum_name_list, property_kind_map):
296   f = cformat.open_header_file_for_write('PropertyObjectArray')
297   cformat.write_imports(f, ['"PropertyObjects.h"', '"PropertyAliases.h"', '"PropertyValueAliases.h"'])
298   objlist = []
299   for p in property_enum_name_list:
300     k = property_kind_map[p]
301     if k == 'Enumerated' or k == 'Catalog':
302        objlist.append("EnumeratedPropertyObject(UCD::%s, UCD::%s::full_name, UCD::%s::alias_map)" % (p, p.upper(), p.upper()))
303     else:
304        objlist.append("%sPropertyObject(UCD::%s)" % (k, p))
305   f.write("\nPropertyObject property_object_array[] = {\n    ")
306   f.write(",\n    ".join(objlist) + '};\n')
307   cformat.close_header_file(f)
308
309def generate_ScriptExtensions_h():
310   (scx_sets, scx_map) = parse_UCD_codepoint_name_map('ScriptExtensions.txt')
311   map2 = {}
312   f = cformat.open_header_file_for_write('ScriptExtensions')
313   cformat.write_imports(f, ["<vector>", '"PropertyAliases.h"', '"PropertyValueAliases.h"', '"unicode_set.h"'])
314   f.write("\nusing namespace UCD;\n\n")
315   for scx_list in scx_sets:
316     scx_items = scx_list.split(" ")
317     for scx in scx_items:
318        if map2.has_key(scx): 
319           map2[scx] = uset_union(map2[scx], scx_map[scx_list])
320        else: map2[scx] = scx_map[scx_list]
321   print "%s bytes" % sum([map2[k].bytes() for k in map2.keys()])
322   for k in sorted(map2.keys()):
323     f.write(map2[k].showC('value_sets[scx][SC::%s]' % k.lower()))
324   cformat.close_header_file(f)
325
326
327
328def UCD_main():
329   # First parse all property names and their aliases
330   (property_enum_name_list, full_name_map, property_lookup_map, property_kind_map) = parse_PropertyAlias_txt()
331   generate_PropertyAliases_h(property_enum_name_list, full_name_map, property_lookup_map)
332   # Next parse all property value names and their aliases
333   (property_value_list, property_value_enum_integer, property_value_full_name_map, property_value_lookup_map) = parse_PropertyValueAlias_txt(property_lookup_map)
334   generate_PropertyValueAliases_h(property_enum_name_list, property_value_list, property_value_enum_integer, property_value_full_name_map, property_value_lookup_map)
335   #
336   generate_PropertyValueSets_h(property_enum_name_list, property_value_list, property_value_enum_integer, property_value_full_name_map, property_value_lookup_map)
337   #
338   generate_PropertyObjectArray_h(property_enum_name_list, property_kind_map)
339   #
340   # Blocks
341   generate_property_value_file('Blocks', 'blk', property_value_lookup_map['blk'])
342   #
343   # Scripts
344   generate_property_value_file('Scripts', 'sc', property_value_lookup_map['sc'])
345   #
346   # Script Extensions
347   generate_ScriptExtensions_h()
348   #
349   # General Category
350   generate_property_value_file('extracted/DerivedGeneralCategory', 'gc', property_value_lookup_map['gc'])
351
352   #
353   # Binary properties from PropList.txt
354   generate_binary_properties_file('PropList', property_lookup_map)
355   #
356   # Binary properties from DerivedCoreProperties.txt
357   generate_binary_properties_file('DerivedCoreProperties', property_lookup_map)
358   #
359   # LineBreak types
360   generate_property_value_file('LineBreak', 'lb', property_value_lookup_map['lb'])
361   #
362   # East Asian Width
363   generate_property_value_file('EastAsianWidth', 'ea', property_value_lookup_map['ea'])
364   #
365   # Hangul Syllable Type
366   generate_property_value_file('HangulSyllableType', 'hst', property_value_lookup_map['hst'])
367
368   #
369   # Jamo Short Name - AAARGH - property value for 110B is an empty string!!!!!  - Not in PropertyValueAliases.txt
370   # generate_property_value_file('Jamo', 'jsn', property_value_lookup_map['jsn'])
371
372
373if __name__ == "__main__":
374  UCD_main()
Note: See TracBrowser for help on using the repository browser.