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

Last change on this file since 4186 was 4186, checked in by cameron, 5 years ago

Restructing.

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