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

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

Fixes for PropertyAliases?.h PropertyValueAliases?.h

File size: 10.1 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
13from unicode_set import *
14
15UCD_dir = "7.0.0"
16
17
18#
19#  Processing files of the UCD
20#
21#  General format for skippable comments, blank lines
22UCD_skip = re.compile("^#.*$|^\s*$")
23
24#
25#  UCD Property File Format 1: property aliases
26#  PropertyAliases.txt
27#
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   f = open(UCD_dir + "/" + 'PropertyAliases.txt')
35   lines = f.readlines()
36   for t in lines:
37      if UCD_skip.match(t): continue  # skip comment and blank lines
38      m = UCD_property_alias_regexp.match(t)
39      if not m: raise Exception("Unknown property alias syntax: %s" % t)
40      prop_enum = m.group(1)
41      prop_preferred_full_name = m.group(2)
42      prop_extra = m.group(3)
43      prop_aliases = re.findall("[-A-Za-z_0-9]+", prop_extra)
44      property_enum_name_list.append(prop_enum)
45      full_name_map[prop_enum] = prop_preferred_full_name
46      property_lookup_map[canonicalize(prop_enum)] = prop_enum
47      property_lookup_map[canonicalize(prop_preferred_full_name)] = prop_enum
48      for a in prop_aliases: property_lookup_map[canonicalize(a)] = prop_enum
49   return (property_enum_name_list, full_name_map, property_lookup_map)
50
51trivial_name_char_re = re.compile('[-_\s]')
52def canonicalize(property_string):
53   c = trivial_name_char_re.sub('', property_string.lower())
54   if len(c) > 2 and c[0:2] == "is": return c[2:]
55   else: return c
56
57
58PropertyAliases_template = r"""
59#include <unordered_map>
60namespace UCD {
61  enum property_t {
62%s
63  };
64  const std::string property_full_name[] = {
65%s
66  };
67  const std::unordered_map<std::string, property_t> alias_map = {
68%s
69  };
70}
71"""
72
73def multiline_join(item_list, items_per_line, separator = ",", closer='', indent = 4):
74  lines = ""
75  sep_with_space = separator + " "
76  while len(item_list) > items_per_line:
77    line_items = item_list[:items_per_line]
78    lines += (" " * indent) + sep_with_space.join(line_items) + separator + "\n"
79    item_list = item_list[items_per_line:]
80  lines += (" " * indent) + sep_with_space.join(item_list) + closer
81  return lines
82
83enums_per_line = 4
84def generate_PropertyAliases_h():
85   (property_enum_name_list, full_name_map, property_lookup_map) = parse_PropertyAlias_txt()
86   f = open('PropertyAliases.h', 'w')
87   enum_text = multiline_join([e.lower() for e in property_enum_name_list], enums_per_line, ',')
88   full_name_text = multiline_join(['"%s"' % full_name_map[e] for e in property_enum_name_list], 2, ',')
89   map_text = multiline_join(['{"%s", %s}' % (k, property_lookup_map[k].lower()) for k in sorted(property_lookup_map.keys())], 2, ',')
90   f.write(PropertyAliases_template % (enum_text, full_name_text, map_text))
91   f.close()
92   
93#
94#  UCD Property File Format 2: property value aliases
95#  PropertyValueAliases.txt
96#
97#  This file records value aliases for property values for
98#  each enumerated property, with the following additional notes:
99#  (1) The corresponding integer value of the enum constant is
100#      also specified for ccc (second field).
101#  (2) The Age property is a numeric type which has decimal float
102#      values as the enum constants: these won't be legal in enum syntax.
103#  (3) Binary properties also have enumerated values and aliases listed,
104#      although this is redundant, because all binary properties have the
105#      same value space.
106#
107
108UCD_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.]+)([^#]*)")
109
110def parse_PropertyValueAlias_txt():
111    property_value_list = {}
112    property_value_enum_integer = {}
113    property_value_full_name_map = {}
114    property_value_lookup_map = {}
115    f = open(UCD_dir + "/" + 'PropertyValueAliases.txt')
116    lines = f.readlines()
117    for t in lines:
118        if UCD_skip.match(t): continue  # skip comment and blank lines
119        m = UCD_property_value_alias_regexp.match(t)
120        if not m: raise Exception("Unknown property value alias syntax: %s" % t)
121        prop_code = m.group(1)
122        if not property_value_list.has_key(prop_code):
123          property_value_list[prop_code] = []
124          property_value_enum_integer[prop_code] = {}
125          property_value_full_name_map[prop_code] = {}
126          property_value_lookup_map[prop_code] = {}
127          enum_integer = 0
128        # Special case for ccc: second field is enum integer value
129        if prop_code == 'ccc':
130          enum_integer = int(m.group(2))
131          value_enum = m.group(3)
132          extra = m.group(4)
133          extra_list = re.findall("[-A-Za-z_0-9.]+", extra)
134          value_preferred_full_name = extra_list[0]
135          value_aliases = extra_list[1:]
136        # Special case for age: second field is numeric, third field is enum
137        # treat numeric value as an alias string
138        elif prop_code == 'age':
139          value_enum = m.group(3)
140          value_preferred_full_name = m.group(3)
141          extra = m.group(4)
142          value_aliases = [m.group(2)] + re.findall("[-A-Za-z_0-9]+", extra)
143        else:
144          value_enum = m.group(2)
145          value_preferred_full_name = m.group(3)
146          extra = m.group(4)
147          value_aliases = re.findall("[-A-Za-z_0-9]+", extra)
148        property_value_list[prop_code].append(value_enum)
149        property_value_enum_integer[prop_code][value_enum] = enum_integer
150        enum_integer += 1
151        property_value_full_name_map[prop_code][value_enum] = value_preferred_full_name
152        property_value_lookup_map[prop_code][canonicalize(value_enum)] = value_enum
153        property_value_lookup_map[prop_code][canonicalize(value_preferred_full_name)] = value_enum
154        for a in value_aliases: property_value_lookup_map[prop_code][canonicalize(a)] = value_enum
155    return (property_value_list, property_value_enum_integer, property_value_full_name_map, property_value_lookup_map)
156
157
158PropertyValueAliases_template = r"""
159#include <unordered_map>
160namespace UCD {
161  namespace %s {
162    enum value_t {
163%s
164    };
165    const std::string value_name[] = {
166%s
167    };
168    const std::unordered_map<std::string, value_t> alias_map = {
169%s
170    };
171  }
172}
173"""
174
175def generate_PropertyValueAliases_h():
176   (property_enum_name_list, full_name_map, property_lookup_map) = parse_PropertyAlias_txt()
177   (property_value_list, property_value_enum_integer, property_value_full_name_map, property_value_lookup_map) = parse_PropertyValueAlias_txt()
178   f = open('PropertyValueAliases.h', 'w')
179   for p in property_enum_name_list:
180     if property_value_list.has_key(p):
181       if property_value_list[p] == ['N', 'Y']: continue # skip boolean properties
182       if p == 'ccc':
183         enum_text = multiline_join(["%s = %s" % (e, property_value_enum_integer[p][e]) for e in property_value_list['ccc']], 4, ',', '', 6)
184       else: enum_text = multiline_join(property_value_list[p], 4, ',')
185       full_name_text = multiline_join(['"%s"' % (property_value_full_name_map[p][e]) for e in property_value_list[p]], 4, ',',  '', 6)
186       map_text = multiline_join(['{"%s", %s}' % (k, property_value_lookup_map[p][k]) for k in sorted(property_value_lookup_map[p].keys())], 4, ',', '', 6)
187       f.write(PropertyValueAliases_template % (p.upper(), enum_text, full_name_text, map_text))
188   f.close()
189   
190
191
192#
193#  UCD Property File Format 3:  codepoint -> name maps
194#
195UCD_skip = re.compile("^#.*$|^\s*$")
196UCD_point_name_regexp = re.compile("^([0-9A-F]{4,6})\s*;\s*((?:[-A-Za-z0-9_]+\s+)*[-A-Za-z0-9_]+)\s*(?:[;#]|$)")
197UCD_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*(?:[;#]|$)")
198
199def parse_UCD_codepoint_name_map(mapfile):
200   name_map = {}
201   name_list_order = []
202   f = open(UCD_dir + "/" + mapfile)
203   lines = f.readlines()
204   for t in lines:
205      if UCD_skip.match(t): continue  # skip comment and blank lines
206      m = UCD_point_name_regexp.match(t)
207      if m:
208        (codepoint, name) = (int(m.group(1), 16), m.group(2))
209        newset = singleton_set(codepoint)
210      else: 
211        m = UCD_range_name_regexp.match(t)
212        if not m: raise Exception("Unknown syntax: %s" % t)
213        (cp_lo, cp_hi, name) = (int(m.group(1), 16), int(m.group(2), 16), m.group(3))
214        newset = make_range_set(cp_lo, cp_hi)
215      if not name_map.has_key(name):
216        name_map[name] = newset
217        name_list_order.append(name)
218      else: name_map[name] = union(name_map[name], newset)
219   return (name_list_order, name_map)
220
221def generate_PropList_h():
222   (props, prop_map) = parse_UCD_codepoint_name_map('PropList.txt')
223   f = open('PropList.h', 'w')
224   for k in props:
225     f.write(prop_map[k].showC(k))
226   f.close()
227
228def generate_Blocks_h():
229   (blocks, block_map) = parse_UCD_codepoint_name_map('Blocks.txt')
230   f = open('Blocks.h', 'w')
231   for k in blocks:
232     f.write(block_map[k].showC('block["%s"]' % k))
233   f.close()
234
235def generate_Scripts_h():
236   (scripts, script_map) = parse_UCD_codepoint_name_map('Scripts.txt')
237   f = open('Scripts.h', 'w')
238   for k in scripts:
239     f.write(script_map[k].showC('script["%s"]' % k))
240   f.close()
241   
242def generate_ScriptExtensions_h():
243   (scx_sets, scx_map) = parse_UCD_codepoint_name_map('ScriptExtensions.txt')
244   map2 = {}
245   f = open('ScriptExtensions.h', 'w')
246   for scx_list in scx_sets:
247     scx_items = scx_list.split(" ")
248     for scx in scx_items:
249        if map2.has_key(scx): 
250           map2[scx] = union(map2[scx], scx_map[scx_list])
251        else: map2[scx] = scx_map[scx_list]
252   for k in sorted(map2.keys()):
253     f.write(map2[k].showC('scx["%s"]' % k))
254   f.close()
255
256def generate_DerivedGeneralCategory_h():
257   (categories, cat_map) = parse_UCD_codepoint_name_map('extracted/DerivedGeneralCategory.txt')
258   f = open('DerivedGeneralCategory.h', 'w')
259   for k in categories:
260     f.write(cat_map[k].showC('GC["%s"]' % k))
261   f.close()
262
263def generate_DerivedCoreProperties_h():
264   (properties, prop_map) = parse_UCD_codepoint_name_map('DerivedCoreProperties.txt')
265   f = open('DerivedCoreProperties.h', 'w')
266   for k in properties:
267     f.write(prop_map[k].showC(k))
268   f.close()
269
270
Note: See TracBrowser for help on using the repository browser.