Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions ASTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2584,6 +2584,19 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
stack.push(value);
}
break;
case Pyc::MAKE_CELL_A:
// no op
break;
case Pyc::COPY_FREE_VARS_A:
// nonlocal should be inserted
// some nonlocals may be redundant but we're not optimizing currently
if (operand != code->freeVars()->size()) {
throw std::runtime_error("Different number of free variables copied");
}
for (int i = 0; i < code->freeVars()->size(); i++) {
code->markNonLocal(code->freeVars()->get(i).cast<PycString>());
}
break;
default:
fprintf(stderr, "Unsupported opcode: %s (%d)\n", Pyc::OpcodeName(opcode), opcode);
cleanBuild = false;
Expand Down Expand Up @@ -3567,6 +3580,21 @@ void decompyle(PycRef<PycCode> code, PycModule* mod, std::ostream& pyc_output)
}
pyc_output << "\n";
}

PycCode::nonlocals_t non_locals = code->getNonLocals();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Easier to duplicate from globals instead of handling both together. Can be changed if need be.

if (non_locals.size()) {
start_line(cur_indent + 1, pyc_output);
pyc_output << "nonlocal ";
bool first = true;
for (const auto& non_local : non_locals) {
if (!first)
pyc_output << ", ";
pyc_output << non_local->value();
first = false;
}
pyc_output << "\n";
}

printDocstringAndGlobals = false;
}

Expand Down
29 changes: 28 additions & 1 deletion pyc_code.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ lntable Obj Obj Obj Obj Obj Obj
exceptiontable Obj
*/

// TODO : Simplify this function
void PycCode::load(PycData* stream, PycModule* mod)
{
if (mod->verCompare(1, 3) >= 0 && mod->verCompare(2, 3) < 0)
Expand Down Expand Up @@ -75,23 +76,49 @@ void PycCode::load(PycData* stream, PycModule* mod)
m_consts = LoadObject(stream, mod).cast<PycSequence>();
m_names = LoadObject(stream, mod).cast<PycSequence>();

// Represents localsplusnames for py 3.11+
if (mod->verCompare(1, 3) >= 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment is misleading

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

umm so this variable is being used for two different purposes - corresponds to locals before 3.11 and localsplusnames after 3.11. More like locals has been repurposed in cpython to include non local and global variables as well.

m_localNames = LoadObject(stream, mod).cast<PycSequence>();
else
m_localNames = new PycTuple;

if (mod->verCompare(3, 11) >= 0)
PycSimpleSequence::value_t free_vars_extra, cell_vars_extra;

if (mod->verCompare(3, 11) >= 0) {
m_localKinds = LoadObject(stream, mod).cast<PycString>();

if (m_localKinds->length() != m_localNames->size()) {
throw std::runtime_error("All variables kinds are not available");
}

// Starting py3.11, all variables are now part of localsplusnames

for (int i = 0; i < m_localKinds->length(); i++) {
const char kind = m_localKinds->value()[i];
auto name = m_localNames->get(i);

if (kind & Kinds::CO_FAST_CELL) {
cell_vars_extra.push_back(name);
}
else if (kind & Kinds::CO_FAST_FREE) {
free_vars_extra.push_back(name);
}
}
}
else
m_localKinds = new PycString;

if (mod->verCompare(2, 1) >= 0 && mod->verCompare(3, 11) < 0)
m_freeVars = LoadObject(stream, mod).cast<PycSequence>();
else if (mod->verCompare(3, 11) >= 0)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think given the complicated structure this whole function should be rewritten appropriately.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly seems quite hard to rewrite this function. I think default initializations would be quite useful here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default initializations themselves are not trivial :| It's annoying as well. I shall skip simplifying this logic at the moment.

m_freeVars = new PycTuple(free_vars_extra);
else
m_freeVars = new PycTuple;

if (mod->verCompare(2, 1) >= 0 && mod->verCompare(3, 11) < 0)
m_cellVars = LoadObject(stream, mod).cast<PycSequence>();
else if (mod->verCompare(3, 11) >= 0)
m_cellVars = new PycTuple(cell_vars_extra);
else
m_cellVars = new PycTuple;

Expand Down
21 changes: 20 additions & 1 deletion pyc_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class PycExceptionTableEntry {

class PycCode : public PycObject {
public:
typedef std::vector<PycRef<PycString>> globals_t;
typedef std::vector<PycRef<PycString>> globals_t, nonlocals_t;

enum CodeFlags {
CO_OPTIMIZED = 0x1, // 1.3 ->
CO_NEWLOCALS = 0x2, // 1.3 ->
Expand All @@ -49,6 +50,17 @@ class PycCode : public PycObject {
CO_NO_MONITORING_EVENTS = 0x2000000, // 3.13 ->
};

enum Kinds { // 3.11 ->
CO_FAST_ARG_POS = 0x2,
CO_FAST_ARG_KW = 0x4,
CO_FAST_ARG_VAR = 0x8,
CO_FAST_ARG = CO_FAST_ARG_POS | CO_FAST_ARG_KW | CO_FAST_ARG_VAR ,
CO_FAST_HIDDEN = 0x10,
CO_FAST_LOCAL = 0x20,
CO_FAST_CELL = 0x40,
CO_FAST_FREE = 0x80,
};

PycCode(int type = TYPE_CODE)
: PycObject(type), m_argCount(), m_posOnlyArgCount(), m_kwOnlyArgCount(),
m_numLocals(), m_stackSize(), m_flags(), m_firstLine() { }
Expand Down Expand Up @@ -99,6 +111,12 @@ class PycCode : public PycObject {
m_globalsUsed.emplace_back(std::move(varname));
}

const nonlocals_t& getNonLocals() const { return m_nonlocalsUsed; }

void markNonLocal(PycRef<PycString> varname) {
m_nonlocalsUsed.emplace_back(std::move(varname));
}

std::vector<PycExceptionTableEntry> exceptionTableEntries() const;

private:
Expand All @@ -118,6 +136,7 @@ class PycCode : public PycObject {
PycRef<PycString> m_lnTable;
PycRef<PycString> m_exceptTable;
globals_t m_globalsUsed; /* Global vars used in this code */
nonlocals_t m_nonlocalsUsed; // Nonlocal vars used in this code
};

#endif
5 changes: 5 additions & 0 deletions pyc_sequence.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class PycTuple : public PycSimpleSequence {
typedef PycSimpleSequence::value_t value_t;
PycTuple(int type = TYPE_TUPLE) : PycSimpleSequence(type) { }

PycTuple(value_t values, int type = TYPE_TUPLE): PycSimpleSequence(type) {
m_values = values;
m_size = values.size();
}

void load(class PycData* stream, class PycModule* mod) override;
};

Expand Down
Binary file added tests/compiled/non_local.3.12.pyc
Binary file not shown.
7 changes: 7 additions & 0 deletions tests/input/non_local.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
def outer():
x = 0
def inner():
nonlocal x
x += 1
print(x)
return inner
2 changes: 1 addition & 1 deletion tests/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def run_test(test_file):
if not compiled_files:
status_line += '\033[33mXFAIL ({})\033[0m\n'.format(xfails)
else:
status_line += '\033[32mPASS ({})\033[33m + XFAIL ()\033[0m\n' \
status_line += '\033[32mPASS ({})\033[33m + XFAIL ({})\033[0m\n' \
.format(len(compiled_files), xfails)
else:
status_line += '\033[32mPASS ({})\033[0m\n'.format(len(compiled_files))
Expand Down
10 changes: 10 additions & 0 deletions tests/tokenized/non_local.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
def outer ( ) : <EOL>
<INDENT>
x = 0 <EOL>
def inner ( ) : <EOL>
<INDENT>
nonlocal x <EOL>
x += 1 <EOL>
print ( x ) <EOL>
<OUTDENT>
return inner <EOL>
Loading