| OLD | NEW |
| 1 # Copyright 2013 the V8 project authors. All rights reserved. | 1 # Copyright 2013 the V8 project authors. All rights reserved. |
| 2 # Redistribution and use in source and binary forms, with or without | 2 # Redistribution and use in source and binary forms, with or without |
| 3 # modification, are permitted provided that the following conditions are | 3 # modification, are permitted provided that the following conditions are |
| 4 # met: | 4 # met: |
| 5 # | 5 # |
| 6 # * Redistributions of source code must retain the above copyright | 6 # * Redistributions of source code must retain the above copyright |
| 7 # notice, this list of conditions and the following disclaimer. | 7 # notice, this list of conditions and the following disclaimer. |
| 8 # * Redistributions in binary form must reproduce the above | 8 # * Redistributions in binary form must reproduce the above |
| 9 # copyright notice, this list of conditions and the following | 9 # copyright notice, this list of conditions and the following |
| 10 # disclaimer in the documentation and/or other materials provided | 10 # disclaimer in the documentation and/or other materials provided |
| 11 # with the distribution. | 11 # with the distribution. |
| 12 # * Neither the name of Google Inc. nor the names of its | 12 # * Neither the name of Google Inc. nor the names of its |
| 13 # contributors may be used to endorse or promote products derived | 13 # contributors may be used to endorse or promote products derived |
| 14 # from this software without specific prior written permission. | 14 # from this software without specific prior written permission. |
| 15 # | 15 # |
| 16 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 16 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 17 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 17 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 18 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 18 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 19 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 19 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 20 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 20 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 23 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 23 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 24 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 24 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 25 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 25 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 26 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 26 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 27 | 27 |
| 28 from types import TupleType |
| 28 from string import printable | 29 from string import printable |
| 29 | 30 |
| 30 class TransitionKey: | 31 class KeyEncoding(object): |
| 32 |
| 33 __encodings = {} |
| 34 |
| 35 @staticmethod |
| 36 def get(name): |
| 37 if not KeyEncoding.__encodings: |
| 38 Latin1Encoding() |
| 39 Utf16Encoding() |
| 40 return KeyEncoding.__encodings[name] |
| 41 |
| 42 def __init__(self, name, primary_range, class_names): |
| 43 assert not name in KeyEncoding.__encodings |
| 44 KeyEncoding.__encodings[name] = self |
| 45 assert primary_range[0] <= primary_range[1] |
| 46 assert primary_range[0] >= 0 |
| 47 self.__name = name |
| 48 self.__primary_range = primary_range |
| 49 self.__lower_bound = primary_range[0] |
| 50 self.__upper_bound = primary_range[1] + len(class_names) |
| 51 f = lambda i : (i + primary_range[1] + 1, i + primary_range[1] + 1) |
| 52 self.__class_ranges = {name : f(i) for i, name in enumerate(class_names)} |
| 53 self.__predefined_ranges = {} |
| 54 |
| 55 def name(self): |
| 56 return self.__name |
| 57 |
| 58 def add_predefined_range(self, name, ranges): |
| 59 # TODO verify disjointness |
| 60 self.__predefined_ranges[name] = ranges |
| 61 |
| 62 def lower_bound(self): |
| 63 return self.__lower_bound |
| 64 |
| 65 def upper_bound(self): |
| 66 return self.__upper_bound |
| 67 |
| 68 def primary_range(self): |
| 69 return self.__primary_range |
| 70 |
| 71 def class_range(self, name): |
| 72 ranges = self.__class_ranges |
| 73 return None if not name in ranges else ranges[name] |
| 74 |
| 75 def class_range_iter(self): |
| 76 return self.__class_ranges.iteritems() |
| 77 |
| 78 def class_value_iter(self): |
| 79 return self.__class_ranges.itervalues() |
| 80 |
| 81 def predefined_range_iter(self, name): |
| 82 ranges = self.__predefined_ranges |
| 83 return None if not name in ranges else iter(ranges[name]) |
| 84 |
| 85 def is_primary_range(self, r): |
| 86 assert self.lower_bound() <= r[0] and r[1] <= self.upper_bound() |
| 87 primary_range = self.__primary_range |
| 88 if (primary_range[0] <= r[0] and r[1] <= primary_range[1]): |
| 89 return True |
| 90 assert r[0] == r[1] |
| 91 return False |
| 92 |
| 93 def in_primary_range(self, c): |
| 94 return self.is_primary_range((c, c)) |
| 95 |
| 96 def is_class_range(self, r): |
| 97 return not self.is_primary_range(r) |
| 98 |
| 99 class TransitionKey(object): |
| 31 '''Represents a transition from a state in DFA or NFA to another state. | 100 '''Represents a transition from a state in DFA or NFA to another state. |
| 32 | 101 |
| 33 A transition key has a list of character ranges and a list of class ranges | 102 A transition key has a list of character ranges and a list of class ranges |
| 34 (e.g., "whitespace"), defining for which characters the transition | 103 (e.g., "whitespace"), defining for which characters the transition |
| 35 happens. When we generate code based on the transition key, the character | 104 happens. When we generate code based on the transition key, the character |
| 36 ranges generate simple checks and the class ranges generate more complicated | 105 ranges generate simple checks and the class ranges generate more complicated |
| 37 conditions, e.g., function calls.''' | 106 conditions, e.g., function calls.''' |
| 38 | 107 |
| 39 __class_bounds = { | 108 __cached_keys = { |
| 40 'latin_1' : (1, 255), | 109 'no_encoding' : {}, |
| 41 # These are not real ranges; they just need to be separate from any real | 110 'latin1' : {}, |
| 42 # ranges. | 111 'utf8' : {}, |
| 43 'non_latin_1_whitespace' : (256, 256), | 112 'utf16' : {}, |
| 44 'non_latin_1_letter' : (257, 257), | |
| 45 'non_latin_1_identifier_part_not_letter' : (258, 258), | |
| 46 'non_latin_1_line_terminator' : (259, 259), | |
| 47 'eos' : (260, 260), | |
| 48 'zero' : (261, 261), | |
| 49 'byte_order_mark' : (262, 262), | |
| 50 'non_latin_1_everything_else' : (263, 263), | |
| 51 } | 113 } |
| 52 __lower_bound = min(__class_bounds.values(), key=lambda item: item[0])[0] | |
| 53 __upper_bound = max(__class_bounds.values(), key=lambda item: item[1])[1] | |
| 54 | |
| 55 __cached_keys = {} | |
| 56 | 114 |
| 57 __unique_key_counter = -1 | 115 __unique_key_counter = -1 |
| 58 | 116 |
| 59 __predefined_ranges = { | 117 @staticmethod |
| 60 'whitespace' : [ | 118 def __is_unique_range(r): |
| 61 (9, 9), (11, 12), (32, 32), (133, 133), (160, 160), | 119 return (r[0] == r[1] and |
| 62 __class_bounds['non_latin_1_whitespace']], | 120 r[0] < 0 and |
| 63 'letter' : [ | 121 r[1] > TransitionKey.__unique_key_counter) |
| 64 (65, 90), (97, 122), (170, 170), (181, 181), | |
| 65 (186, 186), (192, 214), (216, 246), (248, 255), | |
| 66 __class_bounds['non_latin_1_letter']], | |
| 67 'line_terminator' : [ | |
| 68 (10, 10), (13, 13), | |
| 69 __class_bounds['non_latin_1_line_terminator']], | |
| 70 'identifier_part_not_letter' : [ | |
| 71 (48, 57), (95, 95), | |
| 72 __class_bounds['non_latin_1_identifier_part_not_letter']], | |
| 73 } | |
| 74 | 122 |
| 75 @staticmethod | 123 @staticmethod |
| 76 def __in_latin_1(char): | 124 def __verify_ranges(encoding, ranges, check_merged): |
| 77 bound = TransitionKey.__class_bounds['latin_1'] | |
| 78 return (bound[0] <= char and char <= bound[1]) | |
| 79 | |
| 80 @staticmethod | |
| 81 def __is_class_range(r): | |
| 82 return r[0] == r[1] and not TransitionKey.__in_latin_1(r[0]) | |
| 83 | |
| 84 @staticmethod | |
| 85 def __is_unique_range(r): | |
| 86 return r[0] == r[1] and r[0] < TransitionKey.__lower_bound | |
| 87 | |
| 88 @staticmethod | |
| 89 def __verify_ranges(ranges, check_merged): | |
| 90 assert ranges | 125 assert ranges |
| 91 if len(ranges) == 1 and TransitionKey.__is_class_range(ranges[0]): | 126 if len(ranges) == 1 and TransitionKey.__is_unique_range(ranges[0]): |
| 92 return | 127 return |
| 93 last = None | 128 last = None |
| 94 for r in ranges: | 129 for r in ranges: |
| 95 assert TransitionKey.__lower_bound <= r[0] | 130 assert not TransitionKey.__is_unique_range(r) |
| 96 assert r[1] <= TransitionKey.__upper_bound | 131 r_is_class = encoding.is_class_range(r) |
| 97 assert r[0] <= r[1] | |
| 98 r_is_class = TransitionKey.__is_class_range(r) | |
| 99 # Assert that the ranges are in order. | 132 # Assert that the ranges are in order. |
| 100 if last != None and check_merged: | 133 if last != None and check_merged: |
| 101 assert last[1] + 1 < r[0] or r_is_class | 134 assert last[1] + 1 < r[0] or r_is_class |
| 102 if not TransitionKey.__in_latin_1(r[0]): | |
| 103 assert r_is_class | |
| 104 if not TransitionKey.__in_latin_1(r[1]): | |
| 105 assert r_is_class | |
| 106 last = r | 135 last = r |
| 107 | 136 |
| 108 def __is_unique(self): | 137 def __is_unique(self): |
| 109 return len(self.__ranges) == 1 and self.__is_unique_range(self.__ranges[0]) | 138 return len(self.__ranges) == 1 and self.__is_unique_range(self.__ranges[0]) |
| 110 | 139 |
| 111 @staticmethod | 140 @staticmethod |
| 112 def __cached_key(name, bounds_getter): | 141 def __cached_key(encoding, name, bounds_getter): |
| 113 if not name in TransitionKey.__cached_keys: | 142 encoding_name = encoding.name() if encoding else 'no_encoding' |
| 143 cache = TransitionKey.__cached_keys[encoding_name] |
| 144 if not name in cache: |
| 114 bounds = bounds_getter(name) | 145 bounds = bounds_getter(name) |
| 115 TransitionKey.__cached_keys[name] = TransitionKey(bounds, name) | 146 cache[name] = TransitionKey(encoding, bounds, name) |
| 116 return TransitionKey.__cached_keys[name] | 147 return cache[name] |
| 117 | 148 |
| 118 @staticmethod | 149 @staticmethod |
| 119 def epsilon(): | 150 def epsilon(): |
| 120 '''Returns a TransitionKey for the epsilon (empty) transition.''' | 151 '''Returns a TransitionKey for the epsilon (empty) transition.''' |
| 121 return TransitionKey.__cached_key('epsilon', lambda name : []) | 152 return TransitionKey.__cached_key(None, 'epsilon', lambda name : []) |
| 122 | 153 |
| 123 @staticmethod | 154 @staticmethod |
| 124 def any(): | 155 def any(encoding): |
| 125 '''Returns a TransitionKey which matches any character.''' | 156 '''Returns a TransitionKey which matches any character.''' |
| 126 return TransitionKey.__cached_key( | 157 return TransitionKey.__cached_key( |
| 158 encoding, |
| 127 'any', | 159 'any', |
| 128 lambda name : sorted(TransitionKey.__class_bounds.values())) | 160 lambda name : sorted( |
| 161 list(encoding.class_value_iter()) + [encoding.primary_range()])) |
| 129 | 162 |
| 130 @staticmethod | 163 @staticmethod |
| 131 def single_char(char): | 164 def single_char(encoding, char): |
| 132 '''Returns a TransitionKey for a single-character transition.''' | 165 '''Returns a TransitionKey for a single-character transition.''' |
| 133 char = ord(char) | 166 r = (ord(char), ord(char)) |
| 134 assert TransitionKey.__in_latin_1(char) | 167 assert encoding.is_primary_range(r) |
| 135 return TransitionKey([(char, char)]) | 168 return TransitionKey(encoding, [r]) |
| 136 | 169 |
| 137 @staticmethod | 170 @staticmethod |
| 138 def unique(name): | 171 def unique(name): |
| 139 '''Returns a unique TransitionKey for the given name (and creates it if it | 172 '''Returns a unique TransitionKey for the given name (and creates it if it |
| 140 doesn't exist yet). The TransitionKey won't have any real character range, | 173 doesn't exist yet). The TransitionKey won't have any real character range, |
| 141 but a newly-created "mock" character range which is separate from all other | 174 but a newly-created "mock" character range which is separate from all other |
| 142 character ranges.''' | 175 character ranges.''' |
| 143 def get_bounds(name): | 176 def get_bounds(name): |
| 144 bound = TransitionKey.__unique_key_counter | 177 bound = TransitionKey.__unique_key_counter |
| 145 TransitionKey.__unique_key_counter -= 1 | 178 TransitionKey.__unique_key_counter -= 1 |
| 146 return [(bound, bound)] | 179 return [(bound, bound)] |
| 147 name = '__' + name | 180 name = '__' + name |
| 148 return TransitionKey.__cached_key(name, get_bounds) | 181 return TransitionKey.__cached_key(None, name, get_bounds) |
| 149 | 182 |
| 150 @staticmethod | 183 @staticmethod |
| 151 def __process_graph(graph, ranges, key_map): | 184 def __process_graph(encoding, graph, ranges, key_map): |
| 152 key = graph[0] | 185 key = graph[0] |
| 153 if key == 'RANGE': | 186 if key == 'RANGE': |
| 154 ranges.append((ord(graph[1]), ord(graph[2]))) | 187 ranges.append((ord(graph[1]), ord(graph[2]))) |
| 155 elif key == 'LITERAL': | 188 elif key == 'LITERAL': |
| 156 ranges.append((ord(graph[1]), ord(graph[1]))) | 189 ranges.append((ord(graph[1]), ord(graph[1]))) |
| 157 elif key == 'CAT': | 190 elif key == 'CAT': |
| 158 for x in [graph[1], graph[2]]: | 191 for x in [graph[1], graph[2]]: |
| 159 TransitionKey.__process_graph(x, ranges, key_map) | 192 TransitionKey.__process_graph(encoding, x, ranges, key_map) |
| 160 elif key == 'CHARACTER_CLASS': | 193 elif key == 'CHARACTER_CLASS': |
| 161 class_name = graph[1] | 194 class_name = graph[1] |
| 162 if class_name in TransitionKey.__class_bounds: | 195 if encoding.class_range(class_name): |
| 196 r = encoding.class_range(class_name) |
| 163 if class_name in key_map: | 197 if class_name in key_map: |
| 164 assert (key_map[class_name] == | 198 assert key_map[class_name] == TransitionKey(encoding, [r]) |
| 165 TransitionKey([TransitionKey.__class_bounds[class_name]])) | 199 ranges.append(r) |
| 166 ranges.append(TransitionKey.__class_bounds[class_name]) | 200 elif encoding.predefined_range_iter(class_name): |
| 167 elif class_name in TransitionKey.__predefined_ranges: | 201 rs = list(encoding.predefined_range_iter(class_name)) |
| 168 if class_name in key_map: | 202 if class_name in key_map: |
| 169 assert (key_map[class_name] == | 203 assert key_map[class_name] == TransitionKey(encoding, rs) |
| 170 TransitionKey(TransitionKey.__predefined_ranges[class_name])) | 204 ranges += rs |
| 171 ranges += TransitionKey.__predefined_ranges[class_name] | |
| 172 elif class_name in key_map: | 205 elif class_name in key_map: |
| 173 ranges += key_map[class_name].__ranges | 206 ranges += key_map[class_name].__ranges |
| 174 else: | 207 else: |
| 175 raise Exception('unknown character class [%s]' % graph[1]) | 208 raise Exception('unknown character class [%s]' % graph[1]) |
| 176 else: | 209 else: |
| 177 raise Exception('bad key [%s]' % key) | 210 raise Exception('bad key [%s]' % key) |
| 178 | 211 |
| 179 @staticmethod | 212 @staticmethod |
| 180 def character_class(graph, key_map): | 213 def character_class(encoding, graph, key_map): |
| 181 '''Processes 'graph' (a representation of a character class in the rule | 214 '''Processes 'graph' (a representation of a character class in the rule |
| 182 file), and constructs a TransitionKey based on it. 'key_map' contains | 215 file), and constructs a TransitionKey based on it. 'key_map' contains |
| 183 already constructed aliases for character classes (they can be used in the | 216 already constructed aliases for character classes (they can be used in the |
| 184 new character class). It is a map from strings (character class names) to | 217 new character class). It is a map from strings (character class names) to |
| 185 TransitionKeys. For example, graph might represent the character class | 218 TransitionKeys. For example, graph might represent the character class |
| 186 [a-z:digit:] where 'digit' is a previously constructed character class found | 219 [a-z:digit:] where 'digit' is a previously constructed character class found |
| 187 in "key_map".''' | 220 in "key_map".''' |
| 188 ranges = [] | 221 ranges = [] |
| 189 assert graph[0] == 'CLASS' or graph[0] == 'NOT_CLASS' | 222 assert graph[0] == 'CLASS' or graph[0] == 'NOT_CLASS' |
| 190 invert = graph[0] == 'NOT_CLASS' | 223 invert = graph[0] == 'NOT_CLASS' |
| 191 TransitionKey.__process_graph(graph[1], ranges, key_map) | 224 TransitionKey.__process_graph(encoding, graph[1], ranges, key_map) |
| 192 return TransitionKey.__key_from_ranges(invert, ranges) | 225 return TransitionKey.__key_from_ranges(encoding, invert, ranges) |
| 193 | 226 |
| 194 def matches_char(self, char): | 227 def matches_char(self, char): |
| 195 char = ord(char) | 228 char = ord(char) |
| 196 # TODO class checks | 229 assert char < 128 |
| 197 for r in self.__ranges: | 230 for r in self.__ranges: |
| 198 if r[0] <= char and char <= r[1]: return True | 231 if r[0] <= char and char <= r[1]: return True |
| 199 return False | 232 return False |
| 200 | 233 |
| 201 def is_superset_of_key(self, key): | 234 def is_superset_of_key(self, key): |
| 202 '''Returns true if 'key' is a sub-key of this TransitionKey.''' | 235 '''Returns true if 'key' is a sub-key of this TransitionKey.''' |
| 203 assert isinstance(key, self.__class__) | 236 assert isinstance(key, self.__class__) |
| 204 assert key != TransitionKey.epsilon() and not key.__is_unique() | 237 assert key != TransitionKey.epsilon() and not key.__is_unique() |
| 205 assert len(key.__ranges) == 1 | 238 assert len(key.__ranges) == 1 |
| 206 subkey = key.__ranges[0] | 239 subkey = key.__ranges[0] |
| (...skipping 19 matching lines...) Expand all Loading... |
| 226 c = cmp(l, r) | 259 c = cmp(l, r) |
| 227 if c: | 260 if c: |
| 228 return c | 261 return c |
| 229 i += 1 | 262 i += 1 |
| 230 if i == left_length and i == right_length: | 263 if i == left_length and i == right_length: |
| 231 return 0 | 264 return 0 |
| 232 return 1 if i != left_length else -1 | 265 return 1 if i != left_length else -1 |
| 233 | 266 |
| 234 def __hash__(self): | 267 def __hash__(self): |
| 235 if self.__cached_hash == None: | 268 if self.__cached_hash == None: |
| 236 initial_hash = hash((-1, TransitionKey.__upper_bound + 1)) | 269 self.__cached_hash = hash(self.__ranges) |
| 237 f = lambda acc, r: acc ^ hash(r) | |
| 238 self.__cached_hash = reduce(f, self.__ranges, initial_hash) | |
| 239 return self.__cached_hash | 270 return self.__cached_hash |
| 240 | 271 |
| 241 def __eq__(self, other): | 272 def __eq__(self, other): |
| 242 return isinstance(other, self.__class__) and self.__ranges == other.__ranges | 273 return isinstance(other, self.__class__) and self.__ranges == other.__ranges |
| 243 | 274 |
| 244 @staticmethod | 275 @staticmethod |
| 245 def __class_name(r): | 276 def __class_name(encoding, r): |
| 246 for name, v in TransitionKey.__class_bounds.items(): | 277 for name, v in encoding.class_range_iter(): |
| 247 if r == v: return name | 278 if r == v: return name |
| 248 assert False | 279 assert False |
| 249 | 280 |
| 250 def range_iter(self): | 281 def range_iter(self, encoding): |
| 251 assert not self == TransitionKey.epsilon() and not self.__is_unique() | 282 assert not self == TransitionKey.epsilon() and not self.__is_unique() |
| 252 for r in self.__ranges: | 283 for r in self.__ranges: |
| 253 if self.__is_class_range(r): | 284 if encoding.is_class_range(r): |
| 254 yield ('CLASS', TransitionKey.__class_name(r)) | 285 yield ('CLASS', TransitionKey.__class_name(encoding, r)) |
| 255 else: | 286 else: |
| 256 yield ('LATIN_1', r) | 287 yield ('LATIN_1', r) |
| 257 | 288 |
| 258 __printable_cache = { | 289 __printable_cache = { |
| 259 ord('\t') : '\\t', | 290 ord('\t') : '\\t', |
| 260 ord('\n') : '\\n', | 291 ord('\n') : '\\n', |
| 261 ord('\r') : '\\r', | 292 ord('\r') : '\\r', |
| 262 } | 293 } |
| 263 | 294 |
| 264 @staticmethod | 295 @staticmethod |
| 265 def __range_str(r): | 296 def __range_str(encoding, r): |
| 266 if TransitionKey.__is_class_range(r): | 297 if encoding and encoding.is_class_range(r): |
| 267 return TransitionKey.__class_name(r) | 298 return TransitionKey.__class_name(encoding, r) |
| 268 def to_str(x): | 299 def to_str(x): |
| 269 assert TransitionKey.__in_latin_1(x) | 300 assert not encoding or encoding.in_primary_range(x) |
| 301 if x > 127: |
| 302 return str(x) |
| 270 if not x in TransitionKey.__printable_cache: | 303 if not x in TransitionKey.__printable_cache: |
| 271 res = "'%s'" % chr(x) if chr(x) in printable else str(x) | 304 res = "'%s'" % chr(x) if chr(x) in printable else str(x) |
| 272 TransitionKey.__printable_cache[x] = res | 305 TransitionKey.__printable_cache[x] = res |
| 273 return TransitionKey.__printable_cache[x] | 306 return TransitionKey.__printable_cache[x] |
| 274 if r[0] == r[1]: | 307 if r[0] == r[1]: |
| 275 return '%s' % to_str(r[0]) | 308 return '%s' % to_str(r[0]) |
| 276 else: | 309 else: |
| 277 return '[%s-%s]' % (to_str(r[0]), to_str(r[1])) | 310 return '[%s-%s]' % (to_str(r[0]), to_str(r[1])) |
| 278 | 311 |
| 279 def __init__(self, ranges, name = None): | 312 def __init__(self, encoding, ranges, name = None): |
| 280 if not ranges: | 313 if not ranges: |
| 281 assert name == 'epsilon' | 314 assert name == 'epsilon' |
| 282 assert not name in TransitionKey.__cached_keys | 315 assert not name in TransitionKey.__cached_keys['no_encoding'] |
| 283 else: | 316 else: |
| 284 TransitionKey.__verify_ranges(ranges, True) | 317 TransitionKey.__verify_ranges(encoding, ranges, True) |
| 285 self.__name = name | 318 self.__name = name |
| 286 self.__ranges = tuple(ranges) # immutable | 319 self.__ranges = tuple(ranges) # immutable |
| 287 self.__cached_hash = None | 320 self.__cached_hash = None |
| 288 | 321 |
| 289 def __str__(self): | 322 def to_string(self, encoding): |
| 290 if self.__name: | 323 if self.__name: |
| 291 return self.__name | 324 return self.__name |
| 292 return ', '.join(TransitionKey.__range_str(x) for x in self.__ranges) | 325 strings = [TransitionKey.__range_str(encoding, x) for x in self.__ranges] |
| 326 return ', '.join(strings) |
| 327 |
| 328 def __str__(self): |
| 329 self.to_string(None) |
| 293 | 330 |
| 294 @staticmethod | 331 @staticmethod |
| 295 def __disjoint_keys(range_map): | 332 def __disjoint_keys(encoding, range_map): |
| 296 '''Takes a set of possibly overlapping ranges, returns a list of ranges | 333 '''Takes a set of possibly overlapping ranges, returns a list of ranges |
| 297 which don't overlap and which cover the same points as the original | 334 which don't overlap and which cover the same points as the original |
| 298 set. range_map is a map from lower bounds to a list of upper bounds.''' | 335 set. range_map is a map from lower bounds to a list of upper bounds.''' |
| 299 sort = lambda x : sorted(set(x)) | 336 sort = lambda x : sorted(set(x)) |
| 300 range_map = sorted(map(lambda (k, v): (k, sort(v)), range_map.items())) | 337 range_map = sorted(map(lambda (k, v): (k, sort(v)), range_map.items())) |
| 301 ranges = [] | 338 ranges = [] |
| 302 upper_bound = TransitionKey.__upper_bound + 1 | 339 upper_bound = encoding.upper_bound() + 1 |
| 303 for i in range(len(range_map)): | 340 for i in range(len(range_map)): |
| 304 (left, left_values) = range_map[i] | 341 (left, left_values) = range_map[i] |
| 305 next = range_map[i + 1][0] if i != len(range_map) - 1 else upper_bound | 342 next = range_map[i + 1][0] if i != len(range_map) - 1 else upper_bound |
| 306 to_push = [] | 343 to_push = [] |
| 307 for v in left_values: | 344 for v in left_values: |
| 308 assert left <= next | 345 assert left <= next |
| 309 if v >= next: | 346 if v >= next: |
| 310 if not to_push and left < next: | 347 if not to_push and left < next: |
| 311 ranges.append((left, next - 1)) | 348 ranges.append((left, next - 1)) |
| 312 to_push.append(v) | 349 to_push.append(v) |
| 313 else: | 350 else: |
| 314 ranges.append((left, v)) | 351 ranges.append((left, v)) |
| 315 left = v + 1 | 352 left = v + 1 |
| 316 if to_push: | 353 if to_push: |
| 317 current = range_map[i + 1] | 354 current = range_map[i + 1] |
| 318 range_map[i + 1] = (current[0], sort(current[1] + to_push)) | 355 range_map[i + 1] = (current[0], sort(current[1] + to_push)) |
| 319 return ranges | 356 return ranges |
| 320 | 357 |
| 321 @staticmethod | 358 @staticmethod |
| 322 def __disjoint_ranges_from_key_set(key_set): | 359 def __disjoint_ranges_from_key_set(encoding, key_set): |
| 323 if not key_set: | 360 if not key_set: |
| 324 return [] | 361 return [] |
| 325 range_map = {} | 362 range_map = {} |
| 326 for x in key_set: | 363 for x in key_set: |
| 327 assert not x.__is_unique() | 364 assert not x.__is_unique() |
| 328 assert x != TransitionKey.epsilon | 365 assert x != TransitionKey.epsilon() |
| 329 for r in x.__ranges: | 366 for r in x.__ranges: |
| 330 if not r[0] in range_map: | 367 if not r[0] in range_map: |
| 331 range_map[r[0]] = [] | 368 range_map[r[0]] = [] |
| 332 range_map[r[0]].append(r[1]) | 369 range_map[r[0]].append(r[1]) |
| 333 ranges = TransitionKey.__disjoint_keys(range_map) | 370 ranges = TransitionKey.__disjoint_keys(encoding, range_map) |
| 334 TransitionKey.__verify_ranges(ranges, False) | 371 TransitionKey.__verify_ranges(encoding, ranges, False) |
| 335 return ranges | 372 return ranges |
| 336 | 373 |
| 337 @staticmethod | 374 @staticmethod |
| 338 def disjoint_keys(key_set): | 375 def disjoint_keys(encoding, key_set): |
| 339 '''Takes a set of possibly overlapping TransitionKeys, returns a list of | 376 '''Takes a set of possibly overlapping TransitionKeys, returns a list of |
| 340 TransitionKeys which don't overlap and whose union is the same as the union | 377 TransitionKeys which don't overlap and whose union is the same as the union |
| 341 of the original key_set. In addition, TransitionKeys are not merged, only | 378 of the original key_set. In addition, TransitionKeys are not merged, only |
| 342 split. | 379 split. |
| 343 | 380 |
| 344 For example, if key_set contains two TransitionKeys for ranges [1-10] and | 381 For example, if key_set contains two TransitionKeys for ranges [1-10] and |
| 345 [5-15], disjoint_keys returns a set of three TransitionKeys: [1-4], [5-10], | 382 [5-15], disjoint_keys returns a set of three TransitionKeys: [1-4], [5-10], |
| 346 [11-16].''' | 383 [11-16].''' |
| 347 ranges = TransitionKey.__disjoint_ranges_from_key_set(key_set) | 384 ranges = TransitionKey.__disjoint_ranges_from_key_set(encoding, key_set) |
| 348 return map(lambda x : TransitionKey([x]), ranges) | 385 return map(lambda x : TransitionKey(encoding, [x]), ranges) |
| 349 | 386 |
| 350 @staticmethod | 387 @staticmethod |
| 351 def inverse_key(key_set): | 388 def inverse_key(encoding, key_set): |
| 352 '''Returns a TransitionKey which matches represents the inverse of the union | 389 '''Returns a TransitionKey which matches represents the inverse of the union |
| 353 of 'key_set'. The TransitionKeys contain a set of character ranges and a set | 390 of 'key_set'. The TransitionKeys contain a set of character ranges and a set |
| 354 of classes. The character ranges are inverted in relation to the latin_1 | 391 of classes. The character ranges are inverted in relation to the latin_1 |
| 355 character range, and the character classes are inverted in relation to all | 392 character range, and the character classes are inverted in relation to all |
| 356 character classes in __class_bounds.''' | 393 character classes in __class_bounds.''' |
| 357 ranges = TransitionKey.__disjoint_ranges_from_key_set(key_set) | 394 ranges = TransitionKey.__disjoint_ranges_from_key_set(encoding, key_set) |
| 358 inverse = TransitionKey.__invert_ranges(ranges) | 395 inverse = TransitionKey.__invert_ranges(encoding, ranges) |
| 359 if not inverse: | 396 if not inverse: |
| 360 return None | 397 return None |
| 361 return TransitionKey(inverse) | 398 return TransitionKey(encoding, inverse) |
| 362 | 399 |
| 363 @staticmethod | 400 @staticmethod |
| 364 def __key_from_ranges(invert, ranges): | 401 def __key_from_ranges(encoding, invert, ranges): |
| 365 range_map = {} | 402 range_map = {} |
| 366 for r in ranges: | 403 for r in ranges: |
| 367 if not r[0] in range_map: | 404 if not r[0] in range_map: |
| 368 range_map[r[0]] = [] | 405 range_map[r[0]] = [] |
| 369 range_map[r[0]].append(r[1]) | 406 range_map[r[0]].append(r[1]) |
| 370 ranges = TransitionKey.__disjoint_keys(range_map) | 407 ranges = TransitionKey.__disjoint_keys(encoding, range_map) |
| 371 ranges = TransitionKey.__merge_ranges(ranges) | 408 ranges = TransitionKey.__merge_ranges(encoding, ranges) |
| 372 if invert: | 409 if invert: |
| 373 ranges = TransitionKey.__invert_ranges(ranges) | 410 ranges = TransitionKey.__invert_ranges(encoding, ranges) |
| 374 return TransitionKey(ranges) | 411 return TransitionKey(encoding, ranges) |
| 375 | 412 |
| 376 @staticmethod | 413 @staticmethod |
| 377 def __merge_ranges(ranges): | 414 def __merge_ranges(encoding, ranges): |
| 378 merged = [] | 415 merged = [] |
| 379 last = None | 416 last = None |
| 380 for r in ranges: | 417 for r in ranges: |
| 381 assert not TransitionKey.__is_unique_range(r) | 418 assert not TransitionKey.__is_unique_range(r) |
| 382 if last == None: | 419 if last == None: |
| 383 last = r | 420 last = r |
| 384 elif (last[1] + 1 == r[0] and not TransitionKey.__is_class_range(r)): | 421 elif (last[1] + 1 == r[0] and not encoding.is_class_range(r)): |
| 385 last = (last[0], r[1]) | 422 last = (last[0], r[1]) |
| 386 else: | 423 else: |
| 387 merged.append(last) | 424 merged.append(last) |
| 388 last = r | 425 last = r |
| 389 if last != None: | 426 if last != None: |
| 390 merged.append(last) | 427 merged.append(last) |
| 391 return merged | 428 return merged |
| 392 | 429 |
| 393 @staticmethod | 430 @staticmethod |
| 394 def merged_key(keys): | 431 def merged_key(encoding, keys): |
| 395 f = lambda acc, key: acc + list(key.__ranges) | 432 f = lambda acc, key: acc + list(key.__ranges) |
| 396 return TransitionKey.__key_from_ranges(False, reduce(f, keys, [])) | 433 return TransitionKey.__key_from_ranges(encoding, False, reduce(f, keys, [])) |
| 397 | 434 |
| 398 @staticmethod | 435 @staticmethod |
| 399 def __invert_ranges(ranges): | 436 def __invert_ranges(encoding, ranges): |
| 400 inverted = [] | 437 inverted = [] |
| 401 last = None | 438 last = None |
| 402 # Extract character classes (as opposed to character ranges) from | 439 classes = set(encoding.class_value_iter()) |
| 403 # __class_bounds. Since latin_1 is the only real character range, we can do | |
| 404 # this. | |
| 405 classes = set(TransitionKey.__class_bounds.values()) | |
| 406 latin_1 = TransitionKey.__class_bounds['latin_1'] | |
| 407 classes.remove(latin_1) | |
| 408 for r in ranges: | 440 for r in ranges: |
| 409 assert not TransitionKey.__is_unique_range(r) | 441 assert not TransitionKey.__is_unique_range(r) |
| 410 if TransitionKey.__is_class_range(r): | 442 if encoding.is_class_range(r): |
| 411 classes.remove(r) | 443 classes.remove(r) |
| 412 continue | 444 continue |
| 413 if last == None: | 445 if last == None: |
| 414 if r[0] != TransitionKey.__lower_bound: | 446 if r[0] != encoding.lower_bound(): |
| 415 inverted.append((TransitionKey.__lower_bound, r[0] - 1)) | 447 inverted.append((encoding.lower_bound(), r[0] - 1)) |
| 416 elif last[1] + 1 < r[0]: | 448 elif last[1] + 1 < r[0]: |
| 417 inverted.append((last[1] + 1, r[0] - 1)) | 449 inverted.append((last[1] + 1, r[0] - 1)) |
| 418 last = r | 450 last = r |
| 419 upper_bound = latin_1[1] | 451 upper_bound = encoding.primary_range()[1] |
| 420 if last == None: | 452 if last == None: |
| 421 inverted.append(latin_1) | 453 inverted.append(encoding.primary_range()) |
| 422 elif last[1] < upper_bound: | 454 elif last[1] < upper_bound: |
| 423 inverted.append((last[1] + 1, upper_bound)) | 455 inverted.append((last[1] + 1, upper_bound)) |
| 424 inverted += list(classes) | 456 inverted += list(classes) |
| 425 return inverted | 457 return inverted |
| 458 |
| 459 class Latin1Encoding(KeyEncoding): |
| 460 |
| 461 def __init__(self): |
| 462 super(Latin1Encoding, self).__init__( |
| 463 'latin1', |
| 464 (1, 255), |
| 465 ['eos', 'zero', 'byte_order_mark']) |
| 466 self.add_predefined_range( |
| 467 'whitespace', [(9, 9), (11, 12), (32, 32), (133, 133), (160, 160)]) |
| 468 self.add_predefined_range( |
| 469 'letter', [ |
| 470 (65, 90), (97, 122), (170, 170), (181, 181), |
| 471 (186, 186), (192, 214), (216, 246), (248, 255)]) |
| 472 self.add_predefined_range('line_terminator', [(10, 10), (13, 13)]) |
| 473 self.add_predefined_range( |
| 474 'identifier_part_not_letter', [(48, 57), (95, 95)]) |
| 475 |
| 476 class Utf16Encoding(KeyEncoding): |
| 477 |
| 478 def __init__(self): |
| 479 super(Utf16Encoding, self).__init__( |
| 480 'utf16', |
| 481 (1, 255), |
| 482 ['eos', 'zero', 'byte_order_mark', |
| 483 'non_latin_1_whitespace', |
| 484 'non_latin_1_letter', |
| 485 'non_latin_1_identifier_part_not_letter', |
| 486 'non_latin_1_line_terminator', |
| 487 'non_latin_1_everything_else']) |
| 488 self.add_predefined_range( |
| 489 'whitespace', |
| 490 [(9, 9), (11, 12), (32, 32), (133, 133), (160, 160), |
| 491 self.class_range('non_latin_1_whitespace')]) |
| 492 self.add_predefined_range( |
| 493 'letter', [ |
| 494 (65, 90), (97, 122), (170, 170), (181, 181), |
| 495 (186, 186), (192, 214), (216, 246), (248, 255), |
| 496 self.class_range('non_latin_1_letter')]) |
| 497 self.add_predefined_range( |
| 498 'line_terminator', |
| 499 [(10, 10), (13, 13), self.class_range('non_latin_1_line_terminator')]) |
| 500 self.add_predefined_range( |
| 501 'identifier_part_not_letter', |
| 502 [(48, 57), (95, 95), |
| 503 self.class_range('non_latin_1_identifier_part_not_letter')]) |
| OLD | NEW |