Revision: 17990
Author: [email protected]
Date: Fri Nov 22 08:40:59 2013 UTC
Log: Experimental parser: KeyEncoding class added
[email protected]
BUG=
Review URL: https://codereview.chromium.org/82983002
http://code.google.com/p/v8/source/detail?r=17990
Modified:
/branches/experimental/parser/tools/lexer_generator/automaton.py
/branches/experimental/parser/tools/lexer_generator/code_generator.py
/branches/experimental/parser/tools/lexer_generator/code_generator_test.py
/branches/experimental/parser/tools/lexer_generator/dfa.py
/branches/experimental/parser/tools/lexer_generator/generator.py
/branches/experimental/parser/tools/lexer_generator/lexer_test.py
/branches/experimental/parser/tools/lexer_generator/nfa.py
/branches/experimental/parser/tools/lexer_generator/nfa_builder.py
/branches/experimental/parser/tools/lexer_generator/rule_parser.py
/branches/experimental/parser/tools/lexer_generator/rule_parser_test.py
/branches/experimental/parser/tools/lexer_generator/transition_key_test.py
/branches/experimental/parser/tools/lexer_generator/transition_keys.py
=======================================
--- /branches/experimental/parser/tools/lexer_generator/automaton.py Wed
Nov 20 10:06:26 2013 UTC
+++ /branches/experimental/parser/tools/lexer_generator/automaton.py Fri
Nov 22 08:40:59 2013 UTC
@@ -132,6 +132,12 @@
class Automaton(object):
+ def __init__(self, encoding):
+ self.__encoding = encoding
+
+ def encoding(self):
+ return self.__encoding
+
@staticmethod
def visit_states(edge, visitor, visit_state = None, state_iter = None):
if not state_iter:
=======================================
--- /branches/experimental/parser/tools/lexer_generator/code_generator.py
Thu Nov 21 12:47:51 2013 UTC
+++ /branches/experimental/parser/tools/lexer_generator/code_generator.py
Fri Nov 22 08:40:59 2013 UTC
@@ -35,7 +35,6 @@
def __init__(self,
rule_processor,
- encoding = 'latin1',
minimize_default = True,
inline = True,
switching = True,
@@ -52,7 +51,6 @@
self.__log = log
self.__inline = inline
self.__switching = switching
- self.__encoding = encoding
def __state_cmp(self, left, right):
if left['original_node_number'] == self.__start_node_number:
@@ -96,7 +94,7 @@
return cmp(left[1], right[1])
@staticmethod
- def __transform_state(state):
+ def __transform_state(encoding, state):
# action data
action = state.action()
entry_action = None if not action else action.entry_action()
@@ -110,7 +108,7 @@
# map transition keys to disjoint ranges
disjoint_keys = {'value' : []}
def f((key, state)):
- ranges = list(key.range_iter())
+ ranges = list(key.range_iter(encoding))
disjoint_keys['value'] += ranges
return (ranges, state)
transitions = map(f, transitions)
@@ -203,7 +201,9 @@
def __canonicalize_traversal(self):
dfa_states = []
self.__dfa.visit_all_states(lambda state, acc:
dfa_states.append(state))
- dfa_states = map(CodeGenerator.__transform_state, dfa_states)
+ encoding = self.__dfa.encoding()
+ f = lambda state : CodeGenerator.__transform_state(encoding, state)
+ dfa_states = map(f, dfa_states)
id_map = {x['original_node_number'] : x for x in dfa_states}
CodeGenerator.__compute_depths(self.__start_node_number, 1, id_map)
dfa_states = sorted(dfa_states, cmp=self.__state_cmp)
@@ -246,9 +246,10 @@
undefined = jinja2.StrictUndefined)
template = template_env.get_template('code_generator.jinja')
- if self.__encoding == 'latin1':
+ encoding = self.__dfa.encoding().name()
+ if encoding == 'latin1':
char_type = 'uint8_t'
- elif self.__encoding == 'utf16':
+ elif encoding == 'utf16':
char_type = 'uint16_t'
else:
raise Exception('Unsupported encoding %s' % encoding)
@@ -257,5 +258,5 @@
debug_print = self.__debug_print,
default_action = default_action,
dfa_states = dfa_states,
- encoding = self.__encoding,
+ encoding = encoding,
char_type = char_type)
=======================================
---
/branches/experimental/parser/tools/lexer_generator/code_generator_test.py
Tue Nov 19 12:28:18 2013 UTC
+++
/branches/experimental/parser/tools/lexer_generator/code_generator_test.py
Fri Nov 22 08:40:59 2013 UTC
@@ -41,4 +41,4 @@
"foo" <|{FOO}|>
eos <|terminate|>
default_action <{DEFAULT}>'''
- CodeGenerator(RuleProcessor.parse(rules))
+ CodeGenerator(RuleProcessor.parse(rules, 'latin1'))
=======================================
--- /branches/experimental/parser/tools/lexer_generator/dfa.py Wed Nov 20
10:42:41 2013 UTC
+++ /branches/experimental/parser/tools/lexer_generator/dfa.py Fri Nov 22
08:40:59 2013 UTC
@@ -78,8 +78,8 @@
class Dfa(Automaton):
- def __init__(self, start_name, mapping):
- super(Dfa, self).__init__()
+ def __init__(self, encoding, start_name, mapping):
+ super(Dfa, self).__init__(encoding)
self.__terminal_set = set()
name_map = {}
for name, node_data in mapping.items():
@@ -95,7 +95,7 @@
inversion[state] = []
inversion[state].append(key)
for state, keys in inversion.items():
- merged_key = TransitionKey.merged_key(keys)
+ merged_key = TransitionKey.merged_key(encoding, keys)
node.add_transition(merged_key, name_map[state])
self.__start = name_map[start_name]
self.__node_count = len(mapping)
@@ -223,7 +223,9 @@
# TransitionKey [c-h], S1 and S3 cannot be in the same partition. This
will
# become clear when we check the transition for TransitionKey [c-d]
(S1 has
# a transition to S2, S3 has a transition to S4).
- self.__alphabet = list(TransitionKey.disjoint_keys(chain(*all_keys)))
+ encoding = self.__dfa.encoding()
+ self.__alphabet = list(
+ TransitionKey.disjoint_keys(encoding, chain(*all_keys)))
# For each state and each TransitionKey in the alphabet, find out which
# transition we take from the state with the TransitionKey.
@@ -388,4 +390,4 @@
if new_partitions:
partitions |= new_partitions
(start_name, mapping) =
self.__create_states_from_partitions(partitions)
- return Dfa(start_name, mapping)
+ return Dfa(self.__dfa.encoding(), start_name, mapping)
=======================================
--- /branches/experimental/parser/tools/lexer_generator/generator.py Wed
Nov 20 16:10:09 2013 UTC
+++ /branches/experimental/parser/tools/lexer_generator/generator.py Fri
Nov 22 08:40:59 2013 UTC
@@ -112,7 +112,7 @@
if verbose:
print "parsing %s" % re_file
with open(re_file, 'r') as f:
- rule_processor = RuleProcessor.parse(f.read())
+ rule_processor = RuleProcessor.parse(f.read(), args.encoding)
if minimize_default:
if args.no_verify_default:
@@ -135,7 +135,6 @@
code_file = args.code
if code_file:
code_generator = CodeGenerator(rule_processor,
- encoding = args.encoding,
minimize_default = minimize_default,
log = verbose,
inline = not args.no_inline,
=======================================
--- /branches/experimental/parser/tools/lexer_generator/lexer_test.py Wed
Nov 20 10:06:26 2013 UTC
+++ /branches/experimental/parser/tools/lexer_generator/lexer_test.py Fri
Nov 22 08:40:59 2013 UTC
@@ -33,7 +33,7 @@
def __verify_action_stream(self, rules, string, expected):
expected = map(lambda (action, s) : (Action(None, (action, None)), s),
expected)
- automata = RuleProcessor.parse(rules).default_automata()
+ automata = RuleProcessor.parse(rules, 'latin1').default_automata()
for automaton in [automata.nfa(), automata.dfa(),
automata.minimal_dfa()]:
for i, (action, start, stop) in enumerate(automaton.lex(string)):
self.assertEquals(expected[i][0], action)
=======================================
--- /branches/experimental/parser/tools/lexer_generator/nfa.py Wed Nov 20
10:53:56 2013 UTC
+++ /branches/experimental/parser/tools/lexer_generator/nfa.py Fri Nov 22
08:40:59 2013 UTC
@@ -106,8 +106,8 @@
class Nfa(Automaton):
- def __init__(self, start, end, nodes_created):
- super(Nfa, self).__init__()
+ def __init__(self, encoding, start, end, nodes_created):
+ super(Nfa, self).__init__(encoding)
self.__start = start
self.__end = end
self.__verify(nodes_created)
@@ -126,13 +126,12 @@
assert count == nodes_created
@staticmethod
- def __gather_transition_keys(state_set):
+ def __gather_transition_keys(encoding, state_set):
keys = set(chain(*map(lambda state: state.key_iter(), state_set)))
keys.discard(TransitionKey.epsilon())
- return TransitionKey.disjoint_keys(keys)
+ return TransitionKey.disjoint_keys(encoding, keys)
- @staticmethod
- def __to_dfa(nfa_state_set, dfa_nodes, end_node):
+ def __to_dfa(self, nfa_state_set, dfa_nodes, end_node):
nfa_state_set = Automaton.epsilon_closure(nfa_state_set)
assert nfa_state_set
name = ".".join(str(x.node_number()) for x in sorted(nfa_state_set))
@@ -142,11 +141,11 @@
'transitions': {},
'terminal': end_node in nfa_state_set,
'action' : Action.dominant_action(nfa_state_set)}
- for key in Nfa.__gather_transition_keys(nfa_state_set):
+ for key in Nfa.__gather_transition_keys(self.encoding(),
nfa_state_set):
match_states = set()
f = lambda state: state.transition_state_iter_for_key(key)
match_states |= set(chain(*map(f, nfa_state_set)))
- transition_state = Nfa.__to_dfa(match_states, dfa_nodes, end_node)
+ transition_state = self.__to_dfa(match_states, dfa_nodes, end_node)
dfa_nodes[name]['transitions'][key] = transition_state
return name
=======================================
--- /branches/experimental/parser/tools/lexer_generator/nfa_builder.py Wed
Nov 20 10:53:56 2013 UTC
+++ /branches/experimental/parser/tools/lexer_generator/nfa_builder.py Fri
Nov 22 08:40:59 2013 UTC
@@ -31,16 +31,14 @@
class NfaBuilder(object):
- def __init__(self):
+ def __init__(self, encoding, character_classes = {}):
self.__node_number = 0
self.__operation_map = {}
self.__members = getmembers(self)
- self.__character_classes = {}
+ self.__encoding = encoding
+ self.__character_classes = character_classes
self.__states = []
- def set_character_classes(self, classes):
- self.__character_classes = classes
-
def __new_state(self):
self.__node_number += 1
return NfaState()
@@ -107,18 +105,19 @@
return (state, [state])
def __literal(self, graph):
- return self.__key_state(TransitionKey.single_char(graph[1]))
+ return self.__key_state(
+ TransitionKey.single_char(self.__encoding, graph[1]))
def __class(self, graph):
- return self.__key_state(
- TransitionKey.character_class(graph, self.__character_classes))
+ return self.__key_state(TransitionKey.character_class(
+ self.__encoding, graph, self.__character_classes))
def __not_class(self, graph):
- return self.__key_state(
- TransitionKey.character_class(graph, self.__character_classes))
+ return self.__key_state(TransitionKey.character_class(
+ self.__encoding, graph, self.__character_classes))
def __any(self, graph):
- return self.__key_state(TransitionKey.any())
+ return self.__key_state(TransitionKey.any(self.__encoding))
def __epsilon(self, graph):
start = self.__new_state()
@@ -216,7 +215,7 @@
Automaton.visit_states(set([start_state]), outer)
@staticmethod
- def __replace_catch_all(state):
+ def __replace_catch_all(encoding, state):
catch_all = TransitionKey.unique('catch_all')
transitions = state.transitions()
if not catch_all in transitions:
@@ -227,7 +226,7 @@
keys = reduce(f, reachable_states, set())
keys.discard(TransitionKey.epsilon())
keys.discard(catch_all)
- inverse_key = TransitionKey.inverse_key(keys)
+ inverse_key = TransitionKey.inverse_key(encoding, keys)
if inverse_key:
transitions[inverse_key] = transitions[catch_all]
del transitions[catch_all]
@@ -236,9 +235,9 @@
(start, end, nodes_created) = self.__nfa(graph)
end.close(None)
self.__compute_epsilon_closures(start)
- f = lambda node, state: self.__replace_catch_all(node)
+ f = lambda node, state: self.__replace_catch_all(self.__encoding, node)
Automaton.visit_states(set([start]), f)
- return Nfa(start, end, nodes_created)
+ return Nfa(self.__encoding, start, end, nodes_created)
@staticmethod
def add_action(graph, action):
=======================================
--- /branches/experimental/parser/tools/lexer_generator/rule_parser.py Thu
Nov 21 17:52:02 2013 UTC
+++ /branches/experimental/parser/tools/lexer_generator/rule_parser.py Fri
Nov 22 08:40:59 2013 UTC
@@ -31,16 +31,17 @@
from regex_parser import RegexParser
from nfa_builder import NfaBuilder
from dfa import Dfa
-from transition_keys import TransitionKey
+from transition_keys import TransitionKey, KeyEncoding
class RuleParserState:
- def __init__(self):
+ def __init__(self, encoding):
self.aliases = {}
self.character_classes = {}
self.current_state = None
self.rules = {}
self.transitions = set()
+ self.encoding = encoding
def parse(self, string):
return RuleParser.parse(string, self)
@@ -70,7 +71,8 @@
if graph[0] == 'CLASS' or graph[0] == 'NOT_CLASS':
classes = state.character_classes
assert not p[1] in classes
- classes[p[1]] = TransitionKey.character_class(graph, classes)
+ encoding = state.encoding
+ classes[p[1]] = TransitionKey.character_class(encoding, graph,
classes)
def p_rules(self, p):
'''rules : state_change transition_rules rules
@@ -260,8 +262,8 @@
self.__process_parser_state(parser_state)
@staticmethod
- def parse(string):
- parser_state = RuleParserState()
+ def parse(string, encoding_name):
+ parser_state = RuleParserState(KeyEncoding.get(encoding_name))
RuleParser.parse(string, parser_state)
return RuleProcessor(parser_state)
@@ -288,7 +290,7 @@
def dfa(self):
if not self.__dfa:
(start, dfa_nodes) = self.nfa().compute_dfa()
- self.__dfa = Dfa(start, dfa_nodes)
+ self.__dfa = Dfa(self.nfa().encoding(), start, dfa_nodes)
return self.__dfa
def minimal_dfa(self):
@@ -298,8 +300,7 @@
def __process_parser_state(self, parser_state):
rule_map = {}
- builder = NfaBuilder()
- builder.set_character_classes(parser_state.character_classes)
+ builder = NfaBuilder(parser_state.encoding,
parser_state.character_classes)
assert 'default' in parser_state.rules
def process(subgraph, v):
graphs = []
=======================================
--- /branches/experimental/parser/tools/lexer_generator/rule_parser_test.py
Thu Nov 14 20:25:22 2013 UTC
+++ /branches/experimental/parser/tools/lexer_generator/rule_parser_test.py
Fri Nov 22 08:40:59 2013 UTC
@@ -26,13 +26,14 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import unittest
+from transition_keys import KeyEncoding
from rule_parser import RuleParserState
from regex_parser import RegexParser
class RuleParserTestCase(unittest.TestCase):
def setUp(self):
- self.state = RuleParserState()
+ self.state = RuleParserState(KeyEncoding.get('latin1'))
def parse(self, string):
return self.state.parse(string)
=======================================
---
/branches/experimental/parser/tools/lexer_generator/transition_key_test.py
Wed Nov 20 16:10:09 2013 UTC
+++
/branches/experimental/parser/tools/lexer_generator/transition_key_test.py
Fri Nov 22 08:40:59 2013 UTC
@@ -26,15 +26,18 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import unittest
-from transition_keys import TransitionKey
+from transition_keys import TransitionKey, KeyEncoding
from regex_parser import RegexParser
class TransitionKeyTestCase(unittest.TestCase):
+ __encoding = KeyEncoding.get('latin1')
+
__equal_pairs = [
(TransitionKey.epsilon(), TransitionKey.epsilon()),
- (TransitionKey.any(), TransitionKey.any()),
- (TransitionKey.single_char('a'), TransitionKey.single_char('a')),
+ (TransitionKey.any(__encoding), TransitionKey.any(__encoding)),
+ (TransitionKey.single_char(__encoding, 'a'),
+ TransitionKey.single_char(__encoding, 'a')),
]
def test_eq(self):
@@ -54,6 +57,7 @@
("a-z:whitespace::letter:" , "abc" , "123"),
]
classes = {}
+ encoding = self.__encoding
for (string, match, no_match) in data:
for invert in [False, True]:
if invert:
@@ -64,25 +68,26 @@
token = "CLASS"
graph = RegexParser.parse(regex)
assert graph[0] == token
- key = TransitionKey.character_class(graph, classes)
+ key = TransitionKey.character_class(encoding, graph, classes)
for c in match:
self.assertEqual(invert, not key.matches_char(c))
for c in no_match:
self.assertEqual(invert, key.matches_char(c))
def test_self_defined_classes(self):
+ encoding = self.__encoding
graph = RegexParser.parse("[a-z]")
classes = {
- 'self_defined' : TransitionKey.character_class(graph, {})}
+ 'self_defined' : TransitionKey.character_class(encoding, graph, {})}
graph = RegexParser.parse("[^:self_defined:]")
- key = TransitionKey.character_class(graph, classes)
+ key = TransitionKey.character_class(encoding, graph, classes)
self.assertTrue(key.matches_char('A'))
-
def test_disjoint_keys(self):
- key1 = TransitionKey([(1, 10)])
- key2 = TransitionKey([(5, 15)])
- disjoint_set = TransitionKey.disjoint_keys(set([key1, key2]))
- self.assertTrue(TransitionKey([(1, 4)]) in disjoint_set)
- self.assertTrue(TransitionKey([(5, 10)]) in disjoint_set)
- self.assertTrue(TransitionKey([(11, 15)]) in disjoint_set)
+ encoding = self.__encoding
+ key1 = TransitionKey(encoding, [(1, 10)])
+ key2 = TransitionKey(encoding, [(5, 15)])
+ disjoint_set = TransitionKey.disjoint_keys(encoding, set([key1, key2]))
+ self.assertTrue(TransitionKey(encoding, [(1, 4)]) in disjoint_set)
+ self.assertTrue(TransitionKey(encoding, [(5, 10)]) in disjoint_set)
+ self.assertTrue(TransitionKey(encoding, [(11, 15)]) in disjoint_set)
=======================================
--- /branches/experimental/parser/tools/lexer_generator/transition_keys.py
Thu Nov 21 12:47:51 2013 UTC
+++ /branches/experimental/parser/tools/lexer_generator/transition_keys.py
Fri Nov 22 08:40:59 2013 UTC
@@ -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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
@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 @@
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 @@
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 @@
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 @@
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')])
--
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev
---
You received this message because you are subscribed to the Google Groups "v8-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/groups/opt_out.