def test_conditional_a_la_pypy():
    from xotl.ql import qst
    # >>> dis.dis(compile('x and a or y', '', 'eval'))
    #   1           0 LOAD_NAME                0 (x)
    #               3 JUMP_IF_FALSE_OR_POP     9
    #               6 LOAD_NAME                1 (a)
    #         >>    9 JUMP_IF_TRUE_OR_POP     15
    #              12 LOAD_NAME                2 (y)
    #         >>   15 RETURN_VALUE
    import types
    from xotl.ql.revenge import Uncompyled
    from xotl.ql.revenge.scanners import InstructionSetBuilder, label
    builder = InstructionSetBuilder()
    with builder() as Instruction:
        Instruction(opname='LOAD_NAME', arg=0, argval='x', starts_line=1)
        Instruction(opname='JUMP_IF_FALSE_OR_POP', arg=label('else'))
        Instruction(opname='LOAD_NAME', arg=1, argval='a')
        Instruction(label='else',
                    opname='JUMP_IF_TRUE_OR_POP', arg=label('out'))
        Instruction(opname='LOAD_NAME', arg=2, argval='y')
        Instruction(label='out',
                    opname='RETURN_VALUE')
    code = builder.code
    code = types.CodeType(0, 0, 0, 3, 0, code, (), ('x', 'a', 'y'),
                          (), '', '<module>', 1, b'')
    u = Uncompyled(code)
    assert u.safe_ast
    expected = qst.parse('x and a or y')
    assert u.qst == expected
def test_real_pypy_normalization():
    from xotl.ql.revenge.scanners import InstructionSetBuilder, label
    from xotl.ql.revenge.scanners import getscanner, without_nops
    from xotl.ql.revenge.scanners import normalize_pypy_conditional

    # The PyPy byte code for `a if x else y`:
    builder = InstructionSetBuilder()
    with builder() as Instruction:
        Instruction(opname='LOAD_NAME',
                    arg=0, argval='x', argrepr='x',
                    starts_line=1, is_jump_target=False),
        Instruction(opname='POP_JUMP_IF_FALSE',
                    arg=label('else y'), starts_line=None)
        Instruction(opname='LOAD_NAME', arg=1,
                    argval='a', argrepr='a', starts_line=None)
        Instruction(opname='RETURN_VALUE',
                    arg=None, argval=None, argrepr='', starts_line=None)
        Instruction(label='else y',
                    opname='LOAD_NAME',
                    arg=2, argval='y', argrepr='y', starts_line=None),
        Instruction(opname='RETURN_VALUE',
                    arg=None, argval=None, argrepr='',
                    starts_line=None)
    expected_program = list(builder)
    scanner = getscanner()
    tokens, customize = scanner.disassemble(
        compile('a if x else y', '', 'eval'),
        normalize=(normalize_pypy_conditional, without_nops)
    )
    instructions = [token.instruction for token in tokens if token.instruction]
    assert instructions == expected_program
def test_scanner_normalization_single_return():
    from xotl.ql.revenge.scanners import InstructionSetBuilder, label
    from xotl.ql.revenge.scanners import keep_single_return

    builder = InstructionSetBuilder()
    with builder() as Instruction:
        Instruction(label='start',
                    opname='LOAD_NAME', arg=0,
                    argval='x', argrepr='x',
                    starts_line=1),
        Instruction(opname='POP_JUMP_IF_FALSE', arg=label('back'),
                    starts_line=None),
        Instruction(opname='LOAD_NAME', arg=1,
                    argval='a', argrepr='a',
                    starts_line=None),
        Instruction(opname='RETURN_VALUE',
                    arg=None, argval=None, argrepr='',
                    starts_line=None),
        Instruction(label='back',
                    opname='LOAD_NAME', arg=2,
                    argval='y', argrepr='y',
                    starts_line=None),
        # Let's make some jumps here but to see other jumps are not affected.
        # Since the return value above takes a single byte but the
        # JUMP_FORWARD takes 3 bytes, all offsets below that point are to be
        # shifted whereas those above will remain.
        Instruction(opname='JUMP_ABSOLUTE', arg=label('back'),
                    starts_line=None)
        Instruction(opname='JUMP_FORWARD', arg=label('next'),
                    starts_line=None),
        Instruction(label='next',
                    opname='FOR_ITER', arg=label('start'), starts_line=None)
        Instruction(opname='RETURN_VALUE', arg=None,
                    argval=None, argrepr='', starts_line=None)
    original = list(builder)

    builder = InstructionSetBuilder()
    with builder() as Instruction:
        Instruction(label='start',
                    opname='LOAD_NAME', arg=0,
                    argval='x', argrepr='x',
                    starts_line=1),
        Instruction(opname='POP_JUMP_IF_FALSE', arg=label('back'),
                    starts_line=None),
        Instruction(opname='LOAD_NAME', arg=1, argval='a', argrepr='a',
                    starts_line=None),
        Instruction(opname='JUMP_FORWARD', arg=label('retval'),
                    starts_line=None),
        Instruction(label='back',
                    opname='LOAD_NAME', arg=2,
                    argval='y', argrepr='y',
                    starts_line=None),
        # Let's make loop here to the instruction just above but to see other
        # jumps are not affected.
        Instruction(opname='JUMP_ABSOLUTE', arg=label('back'),
                    starts_line=None)
        Instruction(opname='JUMP_FORWARD', arg=label('next'),
                    starts_line=None),
        Instruction(label='next',
                    opname='FOR_ITER', arg=label('start'), starts_line=None)
        Instruction(label='retval',
                    opname='RETURN_VALUE', arg=None,
                    argval=None, argrepr='', starts_line=None)
    expected = list(builder)
    assert list(keep_single_return(original)) == expected