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
164 changes: 156 additions & 8 deletions ASTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1559,13 +1559,14 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
case Pyc::LOAD_NAME_A:
stack.push(new ASTName(code->getName(operand)));
break;

case Pyc::MAKE_CLOSURE_A:
case Pyc::MAKE_FUNCTION:
case Pyc::MAKE_FUNCTION_A:
{
PycRef<ASTNode> fun_code = stack.top();
stack.pop();

/* Test for the qualified name of the function (at TOS) */
// Test for the qualified name of the function (at TOS)
int tos_type = fun_code.cast<ASTObject>()->object().type();
if (tos_type != PycObject::TYPE_CODE &&
tos_type != PycObject::TYPE_CODE2) {
Expand All @@ -1576,17 +1577,164 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
ASTFunction::defarg_t defArgs, kwDefArgs;
const int defCount = operand & 0xFF;
const int kwDefCount = (operand >> 8) & 0xFF;
for (int i = 0; i < defCount; ++i) {
defArgs.push_front(stack.top());
stack.pop();
const int annotationCount = (operand >> 16) & 0x7FFF;

// Python 3.3 and below
if (mod->verCompare(3, 3) <= 0) {
for (int i = 0; i < defCount; ++i) {
defArgs.push_front(stack.top());
stack.pop();
}
// KW Defaults not mentioned in docs, but they come after the positional args
for (int i = 0; i < kwDefCount; ++i) {
kwDefArgs.push_front(stack.top());
stack.pop(); // KW Pair object
stack.pop(); // KW Pair name
}
}
for (int i = 0; i < kwDefCount; ++i) {
kwDefArgs.push_front(stack.top());
stack.pop();
// Python 3.4-3.5
else if (mod->verCompare(3, 5) <= 0) {
// From Py 3.4: annotations and order change (kw first)
if (annotationCount) {
stack.pop(); // Tuple of param names for annotations
for (int i = 0; i < annotationCount; ++i) {
stack.pop(); // Pop annotation objects and ignore
}
}
for (int i = 0; i < kwDefCount; ++i) {
kwDefArgs.push_front(stack.top());
stack.pop(); // KW Pair object
stack.pop(); // KW Pair name
}
for (int i = 0; i < defCount; ++i) {
defArgs.push_front(stack.top());
stack.pop();
}
}
// Python 3.6-3.9 (flag mask, annotation dict)
else if (mod->verCompare(3, 9) <= 0) {
if (operand & 0x08) { // Cells for free vars to create a closure
stack.pop(); // Ignore these for syntax generation
}
if (operand & 0x04) { // Annotation dict (3.6-9)
stack.pop(); // Ignore annotations
}
if (operand & 0x02) { // Kwarg Defaults
PycRef<ASTNode> kw_tuple = stack.top();
stack.pop();
std::vector<PycRef<ASTNode>> kw_values = kw_tuple.cast<ASTConstMap>()->values();
for (const PycRef<ASTNode>& kw : kw_values) {
kwDefArgs.push_front(kw);
}
}
if (operand & 0x01) { // Positional Defaults (including positional-or-KW args)
PycRef<ASTNode> pos_tuple = stack.top();
stack.pop();
std::vector<PycRef<PycObject>> pos_values = pos_tuple.cast<ASTObject>()->object().cast<PycTuple>()->values();
for (const PycRef<PycObject>& pos : pos_values) {
defArgs.push_back(new ASTObject(pos));
}
}
}
// Python 3.10-3.13 (annotation can be dict or string, otherwise same)
else if (mod->verCompare(3, 13) <= 0) {
if (operand & 0x08) { // Cells for free vars to create a closure
stack.pop(); // Ignore these for syntax generation
}
if (operand & 0x04) { // Annotation dict (3.10-13) or string (PEP 563)
stack.pop(); // Ignore annotations
}
if (operand & 0x02) { // Kwarg Defaults
PycRef<ASTNode> kw_tuple = stack.top();
stack.pop();
std::vector<PycRef<ASTNode>> kw_values = kw_tuple.cast<ASTConstMap>()->values();
for (const PycRef<ASTNode>& kw : kw_values) {
kwDefArgs.push_front(kw);
}
}
if (operand & 0x01) { // Positional Defaults (including positional-or-KW args)
PycRef<ASTNode> pos_tuple = stack.top();
stack.pop();
std::vector<PycRef<PycObject>> pos_values = pos_tuple.cast<ASTObject>()->object().cast<PycTuple>()->values();
for (const PycRef<PycObject>& pos : pos_values) {
defArgs.push_back(new ASTObject(pos));
}
}
}
// Python 3.14+ (assume new flag 0x10 for environment dict, otherwise same as 3.13)
else if (mod->verCompare(3, 14) <= 0) {
if (operand & 0x10) { // Hypothetical: function environment variables
stack.pop(); // Ignore environment dict
}
if (operand & 0x08) { // Cells for free vars to create a closure
stack.pop(); // Ignore these for syntax generation
}
if (operand & 0x04) { // Annotation dict or string
stack.pop(); // Ignore annotations
}
if (operand & 0x02) { // Kwarg Defaults
PycRef<ASTNode> kw_tuple = stack.top();
stack.pop();
std::vector<PycRef<ASTNode>> kw_values = kw_tuple.cast<ASTConstMap>()->values();
for (const PycRef<ASTNode>& kw : kw_values) {
kwDefArgs.push_front(kw);
}
}
if (operand & 0x01) { // Positional Defaults (including positional-or-KW args)
PycRef<ASTNode> pos_tuple = stack.top();
stack.pop();
std::vector<PycRef<PycObject>> pos_values = pos_tuple.cast<ASTObject>()->object().cast<PycTuple>()->values();
for (const PycRef<PycObject>& pos : pos_values) {
defArgs.push_back(new ASTObject(pos));
}
}
}
// Unknown future versions: fallback, try 3.14 logic
else {
if (operand & 0x10) {
stack.pop();
}
if (operand & 0x08) {
stack.pop();
}
if (operand & 0x04) {
stack.pop();
}
if (operand & 0x02) {
PycRef<ASTNode> kw_tuple = stack.top();
stack.pop();
std::vector<PycRef<ASTNode>> kw_values = kw_tuple.cast<ASTConstMap>()->values();
for (const PycRef<ASTNode>& kw : kw_values) {
kwDefArgs.push_front(kw);
}
}
if (operand & 0x01) {
PycRef<ASTNode> pos_tuple = stack.top();
stack.pop();
std::vector<PycRef<PycObject>> pos_values = pos_tuple.cast<ASTObject>()->object().cast<PycTuple>()->values();
for (const PycRef<PycObject>& pos : pos_values) {
defArgs.push_back(new ASTObject(pos));
}
}
}

stack.push(new ASTFunction(fun_code, defArgs, kwDefArgs));
}
break;
case Pyc::SET_FUNCTION_ATTRIBUTE_A:
{
PycRef<ASTNode> value = stack.top(); stack.pop();
PycRef<ASTNode> func = stack.top(); stack.pop();

// Currently we can't store the attribute (no AST support yet),
// so just discard 'value' and keep function on stack.
(void)value; // suppress unused warning

stack.push(func);
}
break;


case Pyc::NOP:
break;
case Pyc::POP_BLOCK:
Expand Down
Binary file added tests/compiled/test_functions_3_13.3.13.pyc
Binary file not shown.
4 changes: 4 additions & 0 deletions tests/input/test_functions_3_13.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def top():
def inner(c):
return c * 2
return inner
7 changes: 7 additions & 0 deletions tests/tokenized/test_functions_3_13.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
def top ( ) : <EOL>
<INDENT>
def inner ( c ) : <EOL>
<INDENT>
return c * 2 <EOL>
<OUTDENT>
return inner <EOL>