More PyPy grammar rules

* assert one and two-arg form
* trystmt

Simplify adding multiple grammar rules
This commit is contained in:
rocky
2016-07-26 10:21:12 -04:00
parent 9f0b0809b1
commit 6c5bd6289f
10 changed files with 98 additions and 40 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,17 @@
# From PyPy argparse, dedent.
# PyPY adds opcode JUMP_IF_NOT_DEBUG.
# This is the two argument form.
assert __name__ != '__main"', 'Indent decreased below 0.'
# From PyPy simple_interact.py
# PyPy uses POP_JUMP_IF_FALSE as well as POP_JUMP_IF_TRUE
# CPython only uses POP_JUMP_IF_TRUE
while 1:
try:
more = 10
except EOFError:
break
more = len(__file__)
assert not more, "FOO"
assert not more

View File

@@ -0,0 +1,9 @@
# From PyPy 2.7 argparse.py
# PyPY reduces branches as a result of the return statement
# So we need a new rules for trystmt and try_middle which we
# suffix with _pypy, e.g. trystmt_pypy, and try_middle_pypy
def call(self, string):
try:
return open(string, self, self._bufsize)
except IOError:
pass

View File

@@ -30,6 +30,8 @@ class PythonParser(GenericASTBuilder):
def add_unique_rule(self, rule, opname, count, customize): def add_unique_rule(self, rule, opname, count, customize):
"""Add rule to grammar, but only if it hasn't been added previously """Add rule to grammar, but only if it hasn't been added previously
opname and count are used in the customize() semantic the actions
to add the semantic action rule. Often, count is not used.
""" """
if rule not in self.new_rules: if rule not in self.new_rules:
# print("XXX ", rule) # debug # print("XXX ", rule) # debug
@@ -39,6 +41,14 @@ class PythonParser(GenericASTBuilder):
pass pass
return return
def add_unique_rules(self, rules, customize):
"""Add rules to grammar
"""
for rule in rules:
opname = rule.split('::=')[0].strip()
self.add_unique_rule(rule, opname, 0, customize)
return
def cleanup(self): def cleanup(self):
""" """
Remove recursive references to allow garbage Remove recursive references to allow garbage

View File

@@ -247,9 +247,7 @@ class Python2Parser(PythonParser):
expr ::= expr {expr}^n CALL_FUNCTION_VAR_KW_n POP_TOP expr ::= expr {expr}^n CALL_FUNCTION_VAR_KW_n POP_TOP
expr ::= expr {expr}^n CALL_FUNCTION_KW_n POP_TOP expr ::= expr {expr}^n CALL_FUNCTION_KW_n POP_TOP
For PYPY: PyPy adds custom rules here as well
load_attr ::= expr LOOKUP_METHOD
call_function ::= expr CALL_METHOD
''' '''
for opname, v in list(customize.items()): for opname, v in list(customize.items()):
opname_base = opname[:opname.rfind('_')] opname_base = opname[:opname.rfind('_')]
@@ -272,37 +270,44 @@ class Python2Parser(PythonParser):
opname, v, customize) opname, v, customize)
continue continue
elif opname == 'JUMP_IF_NOT_DEBUG': elif opname == 'JUMP_IF_NOT_DEBUG':
self.add_unique_rule( self.add_unique_rules([
"stmt ::= assert_pypy", opname_base, v, customize) 'jmp_true_false ::= POP_JUMP_IF_TRUE',
self.add_unique_rule( 'jmp_true_false ::= POP_JUMP_IF_FALSE',
"stmt ::= assert2_pypy", opname_base, v, customize) "stmt ::= assert_pypy",
self.add_unique_rule( "stmt ::= assert2_pypy",
"assert_pypy ::= JUMP_IF_NOT_DEBUG assert_expr jmp_true " "assert_pypy ::= JUMP_IF_NOT_DEBUG assert_expr jmp_true_false "
"LOAD_ASSERT RAISE_VARARGS_1 COME_FROM", "LOAD_ASSERT RAISE_VARARGS_1 COME_FROM",
opname_base, v, customize) "assert2_pypy ::= JUMP_IF_NOT_DEBUG assert_expr jmp_true_false "
self.add_unique_rule( "LOAD_ASSERT expr CALL_FUNCTION_1 RAISE_VARARGS_1 COME_FROM",
"assert2_pypy ::= JUMP_IF_NOT_DEBUG assert_expr jmp_true " ], customize)
"LOAD_ASSERT expr CALL_FUNCTION_1 RAISE_VARARGS_1 COME_FROM",
opname_base, v, customize)
continue continue
elif opname_base == 'BUILD_MAP': elif opname_base == 'BUILD_MAP':
if opname == 'BUILD_MAP_n': if opname == 'BUILD_MAP_n':
# PyPy sometimes has no count. Sigh. # PyPy sometimes has no count. Sigh.
rule = ('dictcomp_func ::= BUILD_MAP_n LOAD_FAST FOR_ITER designator ' self.add_unique_rules([
'comp_iter JUMP_BACK RETURN_VALUE RETURN_LAST') 'dictcomp_func ::= BUILD_MAP_n LOAD_FAST FOR_ITER designator '
self.add_unique_rule(rule, 'dictomp_func', 1, customize) 'comp_iter JUMP_BACK RETURN_VALUE RETURN_LAST',
'kvlist_n ::= kvlist_n kv3',
kvlist_n = 'kvlist_n' 'kvlist_n ::=',
rule = 'kvlist_n ::= kvlist_n kv3' 'mapexpr ::= BUILD_MAP_n kvlist_n',
self.add_unique_rule(rule, 'kvlist_n', 0, customize) ], customize)
rule = 'kvlist_n ::='
self.add_unique_rule(rule, 'kvlist_n', 1, customize)
else: else:
kvlist_n = "kvlist_%s" % v kvlist_n = "kvlist_%s" % v
rule = kvlist_n + ' ::= ' + ' kv3' * v self.add_unique_rules([
self.add_unique_rule(rule, opname_base, v, customize) (kvlist_n + " ::=" + ' kv3' * v),
rule = "mapexpr ::= %s %s" % (opname, kvlist_n) "mapexpr ::= %s %s" % (opname, kvlist_n)
self.add_unique_rule(rule, opname_base, v, customize) ], customize)
continue
elif opname == 'SETUP_EXCEPT':
# FIXME: have a way here to detect PyPy. Right now we
# only have SETUP_EXCEPT customization for PyPy, but that might not
# always be the case.
self.add_unique_rules([
"stmt ::= trystmt_pypy",
"trystmt_pypy ::= SETUP_EXCEPT suite_stmts_opt try_middle_pypy",
"try_middle_pypy ::= COME_FROM except_stmts END_FINALLY COME_FROM"
], customize)
continue
elif opname_base in ('UNPACK_TUPLE', 'UNPACK_SEQUENCE'): elif opname_base in ('UNPACK_TUPLE', 'UNPACK_SEQUENCE'):
rule = 'unpack ::= ' + opname + ' designator'*v rule = 'unpack ::= ' + opname + ' designator'*v
elif opname_base == 'UNPACK_LIST': elif opname_base == 'UNPACK_LIST':
@@ -316,6 +321,7 @@ class Python2Parser(PythonParser):
('pos_arg '*v, opname), nop_func) ('pos_arg '*v, opname), nop_func)
rule = 'mkfunc ::= %s LOAD_CONST %s' % ('expr '*v, opname) rule = 'mkfunc ::= %s LOAD_CONST %s' % ('expr '*v, opname)
elif opname_base == 'MAKE_CLOSURE': elif opname_base == 'MAKE_CLOSURE':
# FIXME: use add_uniqe_rules to tidy this up.
self.addRule('mklambda ::= %s load_closure LOAD_LAMBDA %s' % self.addRule('mklambda ::= %s load_closure LOAD_LAMBDA %s' %
('expr '*v, opname), nop_func) ('expr '*v, opname), nop_func)
self.addRule('genexpr ::= %s load_closure LOAD_GENEXPR %s expr GET_ITER CALL_FUNCTION_1' % self.addRule('genexpr ::= %s load_closure LOAD_GENEXPR %s expr GET_ITER CALL_FUNCTION_1' %

View File

@@ -66,7 +66,6 @@ class Python27Parser(Python2Parser):
while1stmt ::= SETUP_LOOP return_stmts COME_FROM while1stmt ::= SETUP_LOOP return_stmts COME_FROM
""" """
class Python27ParserSingle(Python27Parser, PythonParserSingle): class Python27ParserSingle(Python27Parser, PythonParserSingle):
pass pass

View File

@@ -97,10 +97,21 @@ class Scanner2(scan.Scanner):
# 'LOAD_ASSERT' is used in assert statements. # 'LOAD_ASSERT' is used in assert statements.
self.load_asserts = set() self.load_asserts = set()
for i in self.op_range(0, n): for i in self.op_range(0, n):
# We need to detect the difference between # We need to detect the difference between:
# "raise AssertionError" and # raise AssertionError
# "assert" # and
if self.code[i] == self.opc.PJIT and self.code[i+3] == self.opc.LOAD_GLOBAL: # assert ...
# Below we use the heuristic that it is preceded by a POP_JUMP.
# however we could also use followed by RAISE_VARARGS
# or for PyPy there may be a JUMP_IF_NOT_DEBUG before.
# FIXME: remove uses of PJIF, and PJIT
if self.is_pypy:
have_pop_jump = self.code[i] in (self.opc.PJIF,
self.opc.PJIT)
else:
have_pop_jump = self.code[i] == self.opc.PJIT
if have_pop_jump and self.code[i+3] == self.opc.LOAD_GLOBAL:
if names[self.get_argument(i+3)] == 'AssertionError': if names[self.get_argument(i+3)] == 'AssertionError':
self.load_asserts.add(i+3) self.load_asserts.add(i+3)
@@ -195,10 +206,12 @@ class Scanner2(scan.Scanner):
opname = '%s_%d' % (opname, oparg) opname = '%s_%d' % (opname, oparg)
if op != self.opc.BUILD_SLICE: if op != self.opc.BUILD_SLICE:
customize[opname] = oparg customize[opname] = oparg
elif self.is_pypy and opname in ('LOOKUP_METHOD', 'JUMP_IF_NOT_DEBUG'): elif self.is_pypy and opname in ('LOOKUP_METHOD',
# The value in the dict is used in rule uniquness key. 'JUMP_IF_NOT_DEBUG',
# These ops need only be done once. Hence we use arbitrary constant 'SETUP_EXCEPT'):
# 0. # The value in the dict is in special cases in semantic actions, such
# as CALL_FUNCTION. The value is not used in these cases, so we put
# in arbitrary value 0.
customize[opname] = 0 customize[opname] = 0
elif op == self.opc.JUMP_ABSOLUTE: elif op == self.opc.JUMP_ABSOLUTE:
target = self.get_target(offset) target = self.get_target(offset)

View File

@@ -243,7 +243,10 @@ class Scanner3(scan.Scanner):
else: else:
opname = '%s_%d' % (opname, pos_args) opname = '%s_%d' % (opname, pos_args)
elif self.is_pypy and opname in ('CALL_METHOD', 'JUMP_IF_NOT_DEBUG'): elif self.is_pypy and opname in ('CALL_METHOD', 'JUMP_IF_NOT_DEBUG'):
customize['CALL_METHOD'] = argval # The value in the dict is in special cases in semantic actions, such
# as CALL_FUNCTION. The value is not used in these cases, so we put
# in arbitrary value 0.
customize[opname] = 0
elif opname == 'UNPACK_EX': elif opname == 'UNPACK_EX':
# FIXME: try with scanner and parser by # FIXME: try with scanner and parser by
# changing inst.argval # changing inst.argval

View File

@@ -385,7 +385,8 @@ TABLE_DIRECT = {
# PyPy Additions # PyPy Additions
####################### #######################
'assert_pypy': ( '%|assert %c\n' , 1 ), 'assert_pypy': ( '%|assert %c\n' , 1 ),
'assert2_pypy': ( '%|assert %c, %c\n' , 1, 4 ) 'assert2_pypy': ( '%|assert %c, %c\n' , 1, 4 ),
'trystmt_pypy': ( '%|try:\n%+%c%-%c\n\n', 1, 2 ),
} }
@@ -1743,7 +1744,7 @@ class SourceWalker(GenericASTTraversal, object):
# Is there some sort of invalid bounds access going on? # Is there some sort of invalid bounds access going on?
if isinstance(entry[arg], int): if isinstance(entry[arg], int):
self.preorder(node[entry[arg]]) self.preorder(node[entry[arg]])
arg += 1 arg += 1
elif typ == 'p': elif typ == 'p':
p = self.prec p = self.prec
(index, self.prec) = entry[arg] (index, self.prec) = entry[arg]