Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(66)

Unified Diff: tools/lexer_generator/transition_keys.py

Issue 82983002: Experimental parser: KeyEncoding class added (Closed) Base URL: https://v8.googlecode.com/svn/branches/experimental/parser
Patch Set: Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tools/lexer_generator/transition_key_test.py ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/lexer_generator/transition_keys.py
diff --git a/tools/lexer_generator/transition_keys.py b/tools/lexer_generator/transition_keys.py
index d28fac941e80db9cdb8ebb6eb0b1e0cc20b4e153..5401c2fe9b212a419f657f5b239e829ab003cb90 100644
--- a/tools/lexer_generator/transition_keys.py
+++ b/tools/lexer_generator/transition_keys.py
@@ -25,9 +25,78 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+from types import TupleType
from string import printable
-class TransitionKey:
+class KeyEncoding(object):
+
+ __encodings = {}
+
+ @staticmethod
+ def get(name):
+ if not KeyEncoding.__encodings:
+ Latin1Encoding()
+ Utf16Encoding()
+ return KeyEncoding.__encodings[name]
+
+ def __init__(self, name, primary_range, class_names):
+ assert not name in KeyEncoding.__encodings
+ KeyEncoding.__encodings[name] = self
+ assert primary_range[0] <= primary_range[1]
+ assert primary_range[0] >= 0
+ self.__name = name
+ self.__primary_range = primary_range
+ self.__lower_bound = primary_range[0]
+ self.__upper_bound = primary_range[1] + len(class_names)
+ f = lambda i : (i + primary_range[1] + 1, i + primary_range[1] + 1)
+ self.__class_ranges = {name : f(i) for i, name in enumerate(class_names)}
+ self.__predefined_ranges = {}
+
+ def name(self):
+ return self.__name
+
+ def add_predefined_range(self, name, ranges):
+ # TODO verify disjointness
+ self.__predefined_ranges[name] = ranges
+
+ def lower_bound(self):
+ return self.__lower_bound
+
+ def upper_bound(self):
+ return self.__upper_bound
+
+ def primary_range(self):
+ return self.__primary_range
+
+ def class_range(self, name):
+ ranges = self.__class_ranges
+ return None if not name in ranges else ranges[name]
+
+ def class_range_iter(self):
+ return self.__class_ranges.iteritems()
+
+ def class_value_iter(self):
+ return self.__class_ranges.itervalues()
+
+ def predefined_range_iter(self, name):
+ ranges = self.__predefined_ranges
+ return None if not name in ranges else iter(ranges[name])
+
+ def is_primary_range(self, r):
+ assert self.lower_bound() <= r[0] and r[1] <= self.upper_bound()
+ primary_range = self.__primary_range
+ if (primary_range[0] <= r[0] and r[1] <= primary_range[1]):
+ return True
+ assert r[0] == r[1]
+ return False
+
+ def in_primary_range(self, c):
+ return self.is_primary_range((c, c))
+
+ def is_class_range(self, r):
+ return not self.is_primary_range(r)
+
+class TransitionKey(object):
'''Represents a transition from a state in DFA or NFA to another state.
A transition key has a list of character ranges and a list of class ranges
@@ -36,103 +105,67 @@ class TransitionKey:
ranges generate simple checks and the class ranges generate more complicated
conditions, e.g., function calls.'''
- __class_bounds = {
- 'latin_1' : (1, 255),
- # These are not real ranges; they just need to be separate from any real
- # ranges.
- 'non_latin_1_whitespace' : (256, 256),
- 'non_latin_1_letter' : (257, 257),
- 'non_latin_1_identifier_part_not_letter' : (258, 258),
- 'non_latin_1_line_terminator' : (259, 259),
- 'eos' : (260, 260),
- 'zero' : (261, 261),
- 'byte_order_mark' : (262, 262),
- 'non_latin_1_everything_else' : (263, 263),
+ __cached_keys = {
+ 'no_encoding' : {},
+ 'latin1' : {},
+ 'utf8' : {},
+ 'utf16' : {},
}
- __lower_bound = min(__class_bounds.values(), key=lambda item: item[0])[0]
- __upper_bound = max(__class_bounds.values(), key=lambda item: item[1])[1]
-
- __cached_keys = {}
__unique_key_counter = -1
- __predefined_ranges = {
- 'whitespace' : [
- (9, 9), (11, 12), (32, 32), (133, 133), (160, 160),
- __class_bounds['non_latin_1_whitespace']],
- 'letter' : [
- (65, 90), (97, 122), (170, 170), (181, 181),
- (186, 186), (192, 214), (216, 246), (248, 255),
- __class_bounds['non_latin_1_letter']],
- 'line_terminator' : [
- (10, 10), (13, 13),
- __class_bounds['non_latin_1_line_terminator']],
- 'identifier_part_not_letter' : [
- (48, 57), (95, 95),
- __class_bounds['non_latin_1_identifier_part_not_letter']],
- }
-
- @staticmethod
- def __in_latin_1(char):
- bound = TransitionKey.__class_bounds['latin_1']
- return (bound[0] <= char and char <= bound[1])
-
- @staticmethod
- def __is_class_range(r):
- return r[0] == r[1] and not TransitionKey.__in_latin_1(r[0])
-
@staticmethod
def __is_unique_range(r):
- return r[0] == r[1] and r[0] < TransitionKey.__lower_bound
+ return (r[0] == r[1] and
+ r[0] < 0 and
+ r[1] > TransitionKey.__unique_key_counter)
@staticmethod
- def __verify_ranges(ranges, check_merged):
+ def __verify_ranges(encoding, ranges, check_merged):
assert ranges
- if len(ranges) == 1 and TransitionKey.__is_class_range(ranges[0]):
+ if len(ranges) == 1 and TransitionKey.__is_unique_range(ranges[0]):
return
last = None
for r in ranges:
- assert TransitionKey.__lower_bound <= r[0]
- assert r[1] <= TransitionKey.__upper_bound
- assert r[0] <= r[1]
- r_is_class = TransitionKey.__is_class_range(r)
+ assert not TransitionKey.__is_unique_range(r)
+ r_is_class = encoding.is_class_range(r)
# Assert that the ranges are in order.
if last != None and check_merged:
assert last[1] + 1 < r[0] or r_is_class
- if not TransitionKey.__in_latin_1(r[0]):
- assert r_is_class
- if not TransitionKey.__in_latin_1(r[1]):
- assert r_is_class
last = r
def __is_unique(self):
return len(self.__ranges) == 1 and self.__is_unique_range(self.__ranges[0])
@staticmethod
- def __cached_key(name, bounds_getter):
- if not name in TransitionKey.__cached_keys:
+ def __cached_key(encoding, name, bounds_getter):
+ encoding_name = encoding.name() if encoding else 'no_encoding'
+ cache = TransitionKey.__cached_keys[encoding_name]
+ if not name in cache:
bounds = bounds_getter(name)
- TransitionKey.__cached_keys[name] = TransitionKey(bounds, name)
- return TransitionKey.__cached_keys[name]
+ cache[name] = TransitionKey(encoding, bounds, name)
+ return cache[name]
@staticmethod
def epsilon():
'''Returns a TransitionKey for the epsilon (empty) transition.'''
- return TransitionKey.__cached_key('epsilon', lambda name : [])
+ return TransitionKey.__cached_key(None, 'epsilon', lambda name : [])
@staticmethod
- def any():
+ def any(encoding):
'''Returns a TransitionKey which matches any character.'''
return TransitionKey.__cached_key(
+ encoding,
'any',
- lambda name : sorted(TransitionKey.__class_bounds.values()))
+ lambda name : sorted(
+ list(encoding.class_value_iter()) + [encoding.primary_range()]))
@staticmethod
- def single_char(char):
+ def single_char(encoding, char):
'''Returns a TransitionKey for a single-character transition.'''
- char = ord(char)
- assert TransitionKey.__in_latin_1(char)
- return TransitionKey([(char, char)])
+ r = (ord(char), ord(char))
+ assert encoding.is_primary_range(r)
+ return TransitionKey(encoding, [r])
@staticmethod
def unique(name):
@@ -145,10 +178,10 @@ class TransitionKey:
TransitionKey.__unique_key_counter -= 1
return [(bound, bound)]
name = '__' + name
- return TransitionKey.__cached_key(name, get_bounds)
+ return TransitionKey.__cached_key(None, name, get_bounds)
@staticmethod
- def __process_graph(graph, ranges, key_map):
+ def __process_graph(encoding, graph, ranges, key_map):
key = graph[0]
if key == 'RANGE':
ranges.append((ord(graph[1]), ord(graph[2])))
@@ -156,19 +189,19 @@ class TransitionKey:
ranges.append((ord(graph[1]), ord(graph[1])))
elif key == 'CAT':
for x in [graph[1], graph[2]]:
- TransitionKey.__process_graph(x, ranges, key_map)
+ TransitionKey.__process_graph(encoding, x, ranges, key_map)
elif key == 'CHARACTER_CLASS':
class_name = graph[1]
- if class_name in TransitionKey.__class_bounds:
+ if encoding.class_range(class_name):
+ r = encoding.class_range(class_name)
if class_name in key_map:
- assert (key_map[class_name] ==
- TransitionKey([TransitionKey.__class_bounds[class_name]]))
- ranges.append(TransitionKey.__class_bounds[class_name])
- elif class_name in TransitionKey.__predefined_ranges:
+ assert key_map[class_name] == TransitionKey(encoding, [r])
+ ranges.append(r)
+ elif encoding.predefined_range_iter(class_name):
+ rs = list(encoding.predefined_range_iter(class_name))
if class_name in key_map:
- assert (key_map[class_name] ==
- TransitionKey(TransitionKey.__predefined_ranges[class_name]))
- ranges += TransitionKey.__predefined_ranges[class_name]
+ assert key_map[class_name] == TransitionKey(encoding, rs)
+ ranges += rs
elif class_name in key_map:
ranges += key_map[class_name].__ranges
else:
@@ -177,7 +210,7 @@ class TransitionKey:
raise Exception('bad key [%s]' % key)
@staticmethod
- def character_class(graph, key_map):
+ def character_class(encoding, graph, key_map):
'''Processes 'graph' (a representation of a character class in the rule
file), and constructs a TransitionKey based on it. 'key_map' contains
already constructed aliases for character classes (they can be used in the
@@ -188,12 +221,12 @@ class TransitionKey:
ranges = []
assert graph[0] == 'CLASS' or graph[0] == 'NOT_CLASS'
invert = graph[0] == 'NOT_CLASS'
- TransitionKey.__process_graph(graph[1], ranges, key_map)
- return TransitionKey.__key_from_ranges(invert, ranges)
+ TransitionKey.__process_graph(encoding, graph[1], ranges, key_map)
+ return TransitionKey.__key_from_ranges(encoding, invert, ranges)
def matches_char(self, char):
char = ord(char)
- # TODO class checks
+ assert char < 128
for r in self.__ranges:
if r[0] <= char and char <= r[1]: return True
return False
@@ -233,25 +266,23 @@ class TransitionKey:
def __hash__(self):
if self.__cached_hash == None:
- initial_hash = hash((-1, TransitionKey.__upper_bound + 1))
- f = lambda acc, r: acc ^ hash(r)
- self.__cached_hash = reduce(f, self.__ranges, initial_hash)
+ self.__cached_hash = hash(self.__ranges)
return self.__cached_hash
def __eq__(self, other):
return isinstance(other, self.__class__) and self.__ranges == other.__ranges
@staticmethod
- def __class_name(r):
- for name, v in TransitionKey.__class_bounds.items():
+ def __class_name(encoding, r):
+ for name, v in encoding.class_range_iter():
if r == v: return name
assert False
- def range_iter(self):
+ def range_iter(self, encoding):
assert not self == TransitionKey.epsilon() and not self.__is_unique()
for r in self.__ranges:
- if self.__is_class_range(r):
- yield ('CLASS', TransitionKey.__class_name(r))
+ if encoding.is_class_range(r):
+ yield ('CLASS', TransitionKey.__class_name(encoding, r))
else:
yield ('LATIN_1', r)
@@ -262,11 +293,13 @@ class TransitionKey:
}
@staticmethod
- def __range_str(r):
- if TransitionKey.__is_class_range(r):
- return TransitionKey.__class_name(r)
+ def __range_str(encoding, r):
+ if encoding and encoding.is_class_range(r):
+ return TransitionKey.__class_name(encoding, r)
def to_str(x):
- assert TransitionKey.__in_latin_1(x)
+ assert not encoding or encoding.in_primary_range(x)
+ if x > 127:
+ return str(x)
if not x in TransitionKey.__printable_cache:
res = "'%s'" % chr(x) if chr(x) in printable else str(x)
TransitionKey.__printable_cache[x] = res
@@ -276,30 +309,34 @@ class TransitionKey:
else:
return '[%s-%s]' % (to_str(r[0]), to_str(r[1]))
- def __init__(self, ranges, name = None):
+ def __init__(self, encoding, ranges, name = None):
if not ranges:
assert name == 'epsilon'
- assert not name in TransitionKey.__cached_keys
+ assert not name in TransitionKey.__cached_keys['no_encoding']
else:
- TransitionKey.__verify_ranges(ranges, True)
+ TransitionKey.__verify_ranges(encoding, ranges, True)
self.__name = name
self.__ranges = tuple(ranges) # immutable
self.__cached_hash = None
- def __str__(self):
+ def to_string(self, encoding):
if self.__name:
return self.__name
- return ', '.join(TransitionKey.__range_str(x) for x in self.__ranges)
+ strings = [TransitionKey.__range_str(encoding, x) for x in self.__ranges]
+ return ', '.join(strings)
+
+ def __str__(self):
+ self.to_string(None)
@staticmethod
- def __disjoint_keys(range_map):
+ def __disjoint_keys(encoding, range_map):
'''Takes a set of possibly overlapping ranges, returns a list of ranges
which don't overlap and which cover the same points as the original
set. range_map is a map from lower bounds to a list of upper bounds.'''
sort = lambda x : sorted(set(x))
range_map = sorted(map(lambda (k, v): (k, sort(v)), range_map.items()))
ranges = []
- upper_bound = TransitionKey.__upper_bound + 1
+ upper_bound = encoding.upper_bound() + 1
for i in range(len(range_map)):
(left, left_values) = range_map[i]
next = range_map[i + 1][0] if i != len(range_map) - 1 else upper_bound
@@ -319,23 +356,23 @@ class TransitionKey:
return ranges
@staticmethod
- def __disjoint_ranges_from_key_set(key_set):
+ def __disjoint_ranges_from_key_set(encoding, key_set):
if not key_set:
return []
range_map = {}
for x in key_set:
assert not x.__is_unique()
- assert x != TransitionKey.epsilon
+ assert x != TransitionKey.epsilon()
for r in x.__ranges:
if not r[0] in range_map:
range_map[r[0]] = []
range_map[r[0]].append(r[1])
- ranges = TransitionKey.__disjoint_keys(range_map)
- TransitionKey.__verify_ranges(ranges, False)
+ ranges = TransitionKey.__disjoint_keys(encoding, range_map)
+ TransitionKey.__verify_ranges(encoding, ranges, False)
return ranges
@staticmethod
- def disjoint_keys(key_set):
+ def disjoint_keys(encoding, key_set):
'''Takes a set of possibly overlapping TransitionKeys, returns a list of
TransitionKeys which don't overlap and whose union is the same as the union
of the original key_set. In addition, TransitionKeys are not merged, only
@@ -344,44 +381,44 @@ class TransitionKey:
For example, if key_set contains two TransitionKeys for ranges [1-10] and
[5-15], disjoint_keys returns a set of three TransitionKeys: [1-4], [5-10],
[11-16].'''
- ranges = TransitionKey.__disjoint_ranges_from_key_set(key_set)
- return map(lambda x : TransitionKey([x]), ranges)
+ ranges = TransitionKey.__disjoint_ranges_from_key_set(encoding, key_set)
+ return map(lambda x : TransitionKey(encoding, [x]), ranges)
@staticmethod
- def inverse_key(key_set):
+ def inverse_key(encoding, key_set):
'''Returns a TransitionKey which matches represents the inverse of the union
of 'key_set'. The TransitionKeys contain a set of character ranges and a set
of classes. The character ranges are inverted in relation to the latin_1
character range, and the character classes are inverted in relation to all
character classes in __class_bounds.'''
- ranges = TransitionKey.__disjoint_ranges_from_key_set(key_set)
- inverse = TransitionKey.__invert_ranges(ranges)
+ ranges = TransitionKey.__disjoint_ranges_from_key_set(encoding, key_set)
+ inverse = TransitionKey.__invert_ranges(encoding, ranges)
if not inverse:
return None
- return TransitionKey(inverse)
+ return TransitionKey(encoding, inverse)
@staticmethod
- def __key_from_ranges(invert, ranges):
+ def __key_from_ranges(encoding, invert, ranges):
range_map = {}
for r in ranges:
if not r[0] in range_map:
range_map[r[0]] = []
range_map[r[0]].append(r[1])
- ranges = TransitionKey.__disjoint_keys(range_map)
- ranges = TransitionKey.__merge_ranges(ranges)
+ ranges = TransitionKey.__disjoint_keys(encoding, range_map)
+ ranges = TransitionKey.__merge_ranges(encoding, ranges)
if invert:
- ranges = TransitionKey.__invert_ranges(ranges)
- return TransitionKey(ranges)
+ ranges = TransitionKey.__invert_ranges(encoding, ranges)
+ return TransitionKey(encoding, ranges)
@staticmethod
- def __merge_ranges(ranges):
+ def __merge_ranges(encoding, ranges):
merged = []
last = None
for r in ranges:
assert not TransitionKey.__is_unique_range(r)
if last == None:
last = r
- elif (last[1] + 1 == r[0] and not TransitionKey.__is_class_range(r)):
+ elif (last[1] + 1 == r[0] and not encoding.is_class_range(r)):
last = (last[0], r[1])
else:
merged.append(last)
@@ -391,35 +428,76 @@ class TransitionKey:
return merged
@staticmethod
- def merged_key(keys):
+ def merged_key(encoding, keys):
f = lambda acc, key: acc + list(key.__ranges)
- return TransitionKey.__key_from_ranges(False, reduce(f, keys, []))
+ return TransitionKey.__key_from_ranges(encoding, False, reduce(f, keys, []))
@staticmethod
- def __invert_ranges(ranges):
+ def __invert_ranges(encoding, ranges):
inverted = []
last = None
- # Extract character classes (as opposed to character ranges) from
- # __class_bounds. Since latin_1 is the only real character range, we can do
- # this.
- classes = set(TransitionKey.__class_bounds.values())
- latin_1 = TransitionKey.__class_bounds['latin_1']
- classes.remove(latin_1)
+ classes = set(encoding.class_value_iter())
for r in ranges:
assert not TransitionKey.__is_unique_range(r)
- if TransitionKey.__is_class_range(r):
+ if encoding.is_class_range(r):
classes.remove(r)
continue
if last == None:
- if r[0] != TransitionKey.__lower_bound:
- inverted.append((TransitionKey.__lower_bound, r[0] - 1))
+ if r[0] != encoding.lower_bound():
+ inverted.append((encoding.lower_bound(), r[0] - 1))
elif last[1] + 1 < r[0]:
inverted.append((last[1] + 1, r[0] - 1))
last = r
- upper_bound = latin_1[1]
+ upper_bound = encoding.primary_range()[1]
if last == None:
- inverted.append(latin_1)
+ inverted.append(encoding.primary_range())
elif last[1] < upper_bound:
inverted.append((last[1] + 1, upper_bound))
inverted += list(classes)
return inverted
+
+class Latin1Encoding(KeyEncoding):
+
+ def __init__(self):
+ super(Latin1Encoding, self).__init__(
+ 'latin1',
+ (1, 255),
+ ['eos', 'zero', 'byte_order_mark'])
+ self.add_predefined_range(
+ 'whitespace', [(9, 9), (11, 12), (32, 32), (133, 133), (160, 160)])
+ self.add_predefined_range(
+ 'letter', [
+ (65, 90), (97, 122), (170, 170), (181, 181),
+ (186, 186), (192, 214), (216, 246), (248, 255)])
+ self.add_predefined_range('line_terminator', [(10, 10), (13, 13)])
+ self.add_predefined_range(
+ 'identifier_part_not_letter', [(48, 57), (95, 95)])
+
+class Utf16Encoding(KeyEncoding):
+
+ def __init__(self):
+ super(Utf16Encoding, self).__init__(
+ 'utf16',
+ (1, 255),
+ ['eos', 'zero', 'byte_order_mark',
+ 'non_latin_1_whitespace',
+ 'non_latin_1_letter',
+ 'non_latin_1_identifier_part_not_letter',
+ 'non_latin_1_line_terminator',
+ 'non_latin_1_everything_else'])
+ self.add_predefined_range(
+ 'whitespace',
+ [(9, 9), (11, 12), (32, 32), (133, 133), (160, 160),
+ self.class_range('non_latin_1_whitespace')])
+ self.add_predefined_range(
+ 'letter', [
+ (65, 90), (97, 122), (170, 170), (181, 181),
+ (186, 186), (192, 214), (216, 246), (248, 255),
+ self.class_range('non_latin_1_letter')])
+ self.add_predefined_range(
+ 'line_terminator',
+ [(10, 10), (13, 13), self.class_range('non_latin_1_line_terminator')])
+ self.add_predefined_range(
+ 'identifier_part_not_letter',
+ [(48, 57), (95, 95),
+ self.class_range('non_latin_1_identifier_part_not_letter')])
« no previous file with comments | « tools/lexer_generator/transition_key_test.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698