Merge pull request #270 from ahaensler/for-loops-v3.8
3.8: parse for loops and fix ternary ifs
This commit is contained in:
@@ -550,13 +550,14 @@ private:
|
||||
|
||||
class ASTIterBlock : public ASTBlock {
|
||||
public:
|
||||
ASTIterBlock(ASTBlock::BlkType blktype, int end, PycRef<ASTNode> iter)
|
||||
: ASTBlock(blktype, end), m_iter(std::move(iter)), m_idx(), m_comp() { }
|
||||
ASTIterBlock(ASTBlock::BlkType blktype, int start, int end, PycRef<ASTNode> iter)
|
||||
: ASTBlock(blktype, end), m_iter(std::move(iter)), m_idx(), m_comp(), m_start(start) { }
|
||||
|
||||
PycRef<ASTNode> iter() const { return m_iter; }
|
||||
PycRef<ASTNode> index() const { return m_idx; }
|
||||
PycRef<ASTNode> condition() const { return m_cond; }
|
||||
bool isComprehension() const { return m_comp; }
|
||||
int start() const { return m_start; }
|
||||
|
||||
void setIndex(PycRef<ASTNode> idx) { m_idx = std::move(idx); init(); }
|
||||
void setCondition(PycRef<ASTNode> cond) { m_cond = std::move(cond); }
|
||||
@@ -567,6 +568,7 @@ private:
|
||||
PycRef<ASTNode> m_idx;
|
||||
PycRef<ASTNode> m_cond;
|
||||
bool m_comp;
|
||||
int m_start;
|
||||
};
|
||||
|
||||
class ASTContainerBlock : public ASTBlock {
|
||||
|
64
ASTree.cpp
64
ASTree.cpp
@@ -54,7 +54,11 @@ static void CheckIfExpr(FastStack& stack, PycRef<ASTBlock> curblock)
|
||||
if (curblock->nodes().size() < 2)
|
||||
return;
|
||||
auto rit = curblock->nodes().crbegin();
|
||||
++rit; // the last is "else" block, the one before should be "if" (could be "for", ...)
|
||||
// the last is "else" block, the one before should be "if" (could be "for", ...)
|
||||
if ((*rit)->type() != ASTNode::NODE_BLOCK ||
|
||||
(*rit).cast<ASTBlock>()->blktype() != ASTBlock::BLK_ELSE)
|
||||
return;
|
||||
++rit;
|
||||
if ((*rit)->type() != ASTNode::NODE_BLOCK ||
|
||||
(*rit).cast<ASTBlock>()->blktype() != ASTBlock::BLK_IF)
|
||||
return;
|
||||
@@ -906,14 +910,29 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
stack.pop();
|
||||
/* Pop it? Don't pop it? */
|
||||
|
||||
int end;
|
||||
bool comprehension = false;
|
||||
PycRef<ASTBlock> top = blocks.top();
|
||||
if (top->blktype() == ASTBlock::BLK_WHILE) {
|
||||
blocks.pop();
|
||||
|
||||
// before 3.8, there is a SETUP_LOOP instruction with block start and end position,
|
||||
// the operand is usually a jump to a POP_BLOCK instruction
|
||||
// after 3.8, block extent has to be inferred implicitly; the operand is a jump to a position after the for block
|
||||
if (mod->majorVer() == 3 && mod->minorVer() >= 8) {
|
||||
end = operand;
|
||||
if (mod->verCompare(3, 10) >= 0)
|
||||
end *= sizeof(uint16_t); // // BPO-27129
|
||||
end += pos;
|
||||
comprehension = strcmp(code->name()->value(), "<listcomp>") == 0;
|
||||
} else {
|
||||
comprehension = true;
|
||||
PycRef<ASTBlock> top = blocks.top();
|
||||
end = top->end(); // block end position from SETUP_LOOP
|
||||
if (top->blktype() == ASTBlock::BLK_WHILE) {
|
||||
blocks.pop();
|
||||
} else {
|
||||
comprehension = true;
|
||||
}
|
||||
}
|
||||
PycRef<ASTIterBlock> forblk = new ASTIterBlock(ASTBlock::BLK_FOR, top->end(), iter);
|
||||
|
||||
PycRef<ASTIterBlock> forblk = new ASTIterBlock(ASTBlock::BLK_FOR, curpos, end, iter);
|
||||
forblk->setComprehension(comprehension);
|
||||
blocks.push(forblk.cast<ASTBlock>());
|
||||
curblock = blocks.top();
|
||||
@@ -935,7 +954,7 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
} else {
|
||||
comprehension = true;
|
||||
}
|
||||
PycRef<ASTIterBlock> forblk = new ASTIterBlock(ASTBlock::BLK_FOR, top->end(), iter);
|
||||
PycRef<ASTIterBlock> forblk = new ASTIterBlock(ASTBlock::BLK_FOR, curpos, top->end(), iter);
|
||||
forblk->setComprehension(comprehension);
|
||||
blocks.push(forblk.cast<ASTBlock>());
|
||||
curblock = blocks.top();
|
||||
@@ -957,7 +976,7 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
PycRef<ASTBlock> top = blocks.top();
|
||||
if (top->blktype() == ASTBlock::BLK_WHILE) {
|
||||
blocks.pop();
|
||||
PycRef<ASTIterBlock> forblk = new ASTIterBlock(ASTBlock::BLK_ASYNCFOR, top->end(), iter);
|
||||
PycRef<ASTIterBlock> forblk = new ASTIterBlock(ASTBlock::BLK_ASYNCFOR, curpos, top->end(), iter);
|
||||
blocks.push(forblk.cast<ASTBlock>());
|
||||
curblock = blocks.top();
|
||||
stack.push(nullptr);
|
||||
@@ -1214,6 +1233,7 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
|
||||
stack_hist.pop();
|
||||
}
|
||||
|
||||
ifblk = new ASTCondBlock(ASTBlock::BLK_EXCEPT, offs, cond.cast<ASTCompare>()->right(), false);
|
||||
} else if (curblock->blktype() == ASTBlock::BLK_ELSE
|
||||
&& curblock->size() == 0) {
|
||||
@@ -1286,18 +1306,28 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
offs *= sizeof(uint16_t); // // BPO-27129
|
||||
|
||||
if (offs < pos) {
|
||||
if (curblock->blktype() == ASTBlock::BLK_FOR
|
||||
&& curblock.cast<ASTIterBlock>()->isComprehension()) {
|
||||
PycRef<ASTNode> top = stack.top();
|
||||
if (curblock->blktype() == ASTBlock::BLK_FOR) {
|
||||
bool is_jump_to_start = offs == curblock.cast<ASTIterBlock>()->start();
|
||||
bool should_pop_for_block = curblock.cast<ASTIterBlock>()->isComprehension();
|
||||
// in v3.8, SETUP_LOOP is deprecated and for blocks aren't terminated by POP_BLOCK, so we add them here
|
||||
bool should_add_for_block = mod->majorVer() == 3 && mod->minorVer() >= 8 && is_jump_to_start && !curblock.cast<ASTIterBlock>()->isComprehension();
|
||||
|
||||
if (top.type() == ASTNode::NODE_COMPREHENSION) {
|
||||
PycRef<ASTComprehension> comp = top.cast<ASTComprehension>();
|
||||
if (should_pop_for_block || should_add_for_block) {
|
||||
PycRef<ASTNode> top = stack.top();
|
||||
|
||||
comp->addGenerator(curblock.cast<ASTIterBlock>());
|
||||
if (top.type() == ASTNode::NODE_COMPREHENSION) {
|
||||
PycRef<ASTComprehension> comp = top.cast<ASTComprehension>();
|
||||
|
||||
comp->addGenerator(curblock.cast<ASTIterBlock>());
|
||||
}
|
||||
|
||||
PycRef<ASTBlock> tmp = curblock;
|
||||
blocks.pop();
|
||||
curblock = blocks.top();
|
||||
if (should_add_for_block) {
|
||||
curblock->append(tmp.cast<ASTNode>());
|
||||
}
|
||||
}
|
||||
|
||||
blocks.pop();
|
||||
curblock = blocks.top();
|
||||
} else if (curblock->blktype() == ASTBlock::BLK_ELSE) {
|
||||
stack = stack_hist.top();
|
||||
stack_hist.pop();
|
||||
|
@@ -140,7 +140,8 @@ bool Pyc::IsCellArg(int opcode)
|
||||
bool Pyc::IsJumpArg(int opcode)
|
||||
{
|
||||
return (opcode == Pyc::POP_JUMP_IF_FALSE_A) || (opcode == Pyc::POP_JUMP_IF_TRUE_A) ||
|
||||
(opcode == Pyc::JUMP_IF_FALSE_OR_POP_A) || (opcode == JUMP_IF_TRUE_OR_POP_A);
|
||||
(opcode == Pyc::JUMP_IF_FALSE_OR_POP_A) || (opcode == JUMP_IF_TRUE_OR_POP_A) ||
|
||||
(opcode == Pyc::JUMP_ABSOLUTE_A) || (opcode == Pyc::JUMP_IF_NOT_EXC_MATCH_A);
|
||||
}
|
||||
|
||||
bool Pyc::IsJumpOffsetArg(int opcode)
|
||||
|
BIN
tests/compiled/test_for_loop_py3.8.3.10.pyc
Normal file
BIN
tests/compiled/test_for_loop_py3.8.3.10.pyc
Normal file
Binary file not shown.
@@ -19,8 +19,8 @@ if [[ -z "$outdir" ]]; then
|
||||
fi
|
||||
|
||||
shopt -s nullglob
|
||||
compfiles=( "$testdir/compiled/$testname".?.?.pyc )
|
||||
xfcfiles=( "$testdir/xfail/$testname".?.?.pyc )
|
||||
compfiles=( "$testdir/compiled/$testname".?.?*.pyc )
|
||||
xfcfiles=( "$testdir/xfail/$testname".?.?*.pyc )
|
||||
shopt -u nullglob
|
||||
|
||||
if (( ${#compfiles[@]} + ${#xfcfiles[@]} == 0 )); then
|
||||
|
19
tests/input/test_for_loop_py3.8.py
Normal file
19
tests/input/test_for_loop_py3.8.py
Normal file
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
test_for_loop_3.8.py -- source test
|
||||
For-loops have changed in python 3.8. The test and PR
|
||||
(https://github.com/zrax/pycdc/pull/270) add some support.
|
||||
It also fixes a problem with nested if- and for blocks.
|
||||
|
||||
This source is part of the decompyle test suite.
|
||||
|
||||
decompyle is a Python byte-code decompiler
|
||||
See http://www.goebel-consult.de/decompyle/ for download and
|
||||
for further information
|
||||
"""
|
||||
|
||||
if 1 == 1:
|
||||
print('block1')
|
||||
if 1 == 1:
|
||||
for a in 'foo':
|
||||
print('block2')
|
||||
return None
|
13
tests/tokenized/test_for_loop_py3.8.txt
Normal file
13
tests/tokenized/test_for_loop_py3.8.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
'\ntest_for_loop_3.8.py -- source test\nFor-loops have changed in python 3.8. The test and PR\n(https://github.com/zrax/pycdc/pull/270) add some support.\nIt also fixes a problem with nested if- and for blocks.\n\nThis source is part of the decompyle test suite.\n\ndecompyle is a Python byte-code decompiler\nSee http://www.goebel-consult.de/decompyle/ for download and\nfor further information\n' <EOL>
|
||||
if 1 == 1 : <EOL>
|
||||
<INDENT>
|
||||
print ( 'block1' ) <EOL>
|
||||
<OUTDENT>
|
||||
if 1 == 1 : <EOL>
|
||||
<INDENT>
|
||||
for a in 'foo' : <EOL>
|
||||
<INDENT>
|
||||
print ( 'block2' ) <EOL>
|
||||
<OUTDENT>
|
||||
<OUTDENT>
|
||||
return None <EOL>
|
Reference in New Issue
Block a user