Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
873bdc18
Commit
873bdc18
authored
Feb 17, 2000
by
Jeremy Hylton
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
satisfy the tabnanny
fix broken references to filename var in generateXXX methods
parent
1e862e8a
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
398 additions
and
394 deletions
+398
-394
Lib/compiler/pycodegen.py
Lib/compiler/pycodegen.py
+199
-197
Tools/compiler/compiler/pycodegen.py
Tools/compiler/compiler/pycodegen.py
+199
-197
No files found.
Lib/compiler/pycodegen.py
View file @
873bdc18
...
...
@@ -80,7 +80,7 @@ class ASTVisitor:
VERBOSE
=
0
def
__init__
(
self
):
self
.
node
=
None
self
.
node
=
None
def
preorder
(
self
,
tree
,
visitor
):
"""Do preorder walk of tree using visitor"""
...
...
@@ -163,27 +163,26 @@ class CodeGenerator:
# XXX should clean up initialization and generateXXX funcs
def
__init__
(
self
,
filename
=
"<?>"
):
self
.
filename
=
filename
self
.
code
=
PyAssembler
()
self
.
code
=
PyAssembler
()
self
.
code
.
setFlags
(
0
)
self
.
locals
=
misc
.
Stack
()
self
.
locals
=
misc
.
Stack
()
self
.
loops
=
misc
.
Stack
()
self
.
namespace
=
0
self
.
curStack
=
0
self
.
maxStack
=
0
def
emit
(
self
,
*
args
):
# XXX could just use self.emit = self.code.emit
apply
(
self
.
code
.
emit
,
args
)
# XXX could just use self.emit = self.code.emit
apply
(
self
.
code
.
emit
,
args
)
def
_generateFunctionOrLambdaCode
(
self
,
func
):
self
.
name
=
func
.
name
self
.
filename
=
filename
# keep a lookout for 'def foo((x,y)):'
args
,
hasTupleArg
=
self
.
generateArglist
(
func
.
argnames
)
self
.
code
=
PyAssembler
(
args
=
args
,
name
=
func
.
name
,
filename
=
filename
)
self
.
code
=
PyAssembler
(
args
=
args
,
name
=
func
.
name
,
filename
=
self
.
filename
)
self
.
namespace
=
self
.
OPTIMIZED
if
func
.
varargs
:
self
.
code
.
setVarArgs
()
...
...
@@ -191,8 +190,8 @@ class CodeGenerator:
self
.
code
.
setKWArgs
()
lnf
=
walk
(
func
.
code
,
LocalNameFinder
(
args
),
0
)
self
.
locals
.
push
(
lnf
.
getLocals
())
self
.
emit
(
'SET_LINENO'
,
func
.
lineno
)
if
hasTupleArg
:
self
.
emit
(
'SET_LINENO'
,
func
.
lineno
)
if
hasTupleArg
:
self
.
generateArgUnpack
(
func
.
argnames
)
walk
(
func
.
code
,
self
)
...
...
@@ -239,7 +238,7 @@ class CodeGenerator:
def
generateClassCode
(
self
,
klass
):
self
.
code
=
PyAssembler
(
name
=
klass
.
name
,
filename
=
filename
)
filename
=
self
.
filename
)
self
.
emit
(
'SET_LINENO'
,
klass
.
lineno
)
lnf
=
walk
(
klass
.
code
,
LocalNameFinder
(),
0
)
self
.
locals
.
push
(
lnf
.
getLocals
())
...
...
@@ -254,7 +253,7 @@ class CodeGenerator:
return
self
.
code
.
makeCodeObject
()
def
isLocalName
(
self
,
name
):
return
self
.
locals
.
top
().
has_elt
(
name
)
return
self
.
locals
.
top
().
has_elt
(
name
)
def
_nameOp
(
self
,
prefix
,
name
):
if
self
.
isLocalName
(
name
):
...
...
@@ -290,8 +289,8 @@ class CodeGenerator:
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
def
visitModule
(
self
,
node
):
lnf
=
walk
(
node
.
node
,
LocalNameFinder
(),
0
)
self
.
locals
.
push
(
lnf
.
getLocals
())
lnf
=
walk
(
node
.
node
,
LocalNameFinder
(),
0
)
self
.
locals
.
push
(
lnf
.
getLocals
())
self
.
visit
(
node
.
node
)
self
.
emit
(
'LOAD_CONST'
,
None
)
self
.
emit
(
'RETURN_VALUE'
)
...
...
@@ -353,15 +352,15 @@ class CodeGenerator:
kw
=
0
if
hasattr
(
node
,
'lineno'
):
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
self
.
visit
(
node
.
node
)
for
arg
in
node
.
args
:
self
.
visit
(
arg
)
self
.
visit
(
node
.
node
)
for
arg
in
node
.
args
:
self
.
visit
(
arg
)
if
isinstance
(
arg
,
ast
.
Keyword
):
kw
=
kw
+
1
else
:
pos
=
pos
+
1
self
.
emit
(
'CALL_FUNCTION'
,
kw
<<
8
|
pos
)
return
1
self
.
emit
(
'CALL_FUNCTION'
,
kw
<<
8
|
pos
)
return
1
def
visitKeyword
(
self
,
node
):
self
.
emit
(
'LOAD_CONST'
,
node
.
name
)
...
...
@@ -369,24 +368,24 @@ class CodeGenerator:
return
1
def
visitIf
(
self
,
node
):
after
=
StackRef
()
for
test
,
suite
in
node
.
tests
:
after
=
StackRef
()
for
test
,
suite
in
node
.
tests
:
if
hasattr
(
test
,
'lineno'
):
self
.
emit
(
'SET_LINENO'
,
test
.
lineno
)
else
:
print
"warning"
,
"no line number"
self
.
visit
(
test
)
dest
=
StackRef
()
self
.
emit
(
'JUMP_IF_FALSE'
,
dest
)
self
.
emit
(
'POP_TOP'
)
self
.
visit
(
suite
)
self
.
emit
(
'JUMP_FORWARD'
,
after
)
dest
.
bind
(
self
.
code
.
getCurInst
())
self
.
emit
(
'POP_TOP'
)
if
node
.
else_
:
self
.
visit
(
node
.
else_
)
after
.
bind
(
self
.
code
.
getCurInst
())
return
1
self
.
visit
(
test
)
dest
=
StackRef
()
self
.
emit
(
'JUMP_IF_FALSE'
,
dest
)
self
.
emit
(
'POP_TOP'
)
self
.
visit
(
suite
)
self
.
emit
(
'JUMP_FORWARD'
,
after
)
dest
.
bind
(
self
.
code
.
getCurInst
())
self
.
emit
(
'POP_TOP'
)
if
node
.
else_
:
self
.
visit
(
node
.
else_
)
after
.
bind
(
self
.
code
.
getCurInst
())
return
1
def
startLoop
(
self
):
l
=
Loop
()
...
...
@@ -516,64 +515,64 @@ class CodeGenerator:
return
1
def
visitCompare
(
self
,
node
):
"""Comment from compile.c follows:
The following code is generated for all but the last
comparison in a chain:
label: on stack: opcode:
jump to:
a
<code to load b>
a, b
DUP_TOP
a, b, b
ROT_THREE
b, a, b
COMPARE_OP
b, 0-or-1 JUMP_IF_FALSE
L1
b, 1
POP_TOP
b
We are now ready to repeat this sequence for the next
comparison in the chain.
For the last we generate:
b
<code to load c>
b, c
COMPARE_OP
0-or-1
If there were any jumps to L1 (i.e., there was more than one
comparison), we generate:
0-or-1 JUMP_FORWARD
L2
L1: b, 0
ROT_TWO
0, b
POP_TOP
0
L2:
0-or-1
"""
self
.
visit
(
node
.
expr
)
# if refs are never emitted, subsequent bind call has no effect
l1
=
StackRef
()
l2
=
StackRef
()
for
op
,
code
in
node
.
ops
[:
-
1
]:
# emit every comparison except the last
self
.
visit
(
code
)
self
.
emit
(
'DUP_TOP'
)
self
.
emit
(
'ROT_THREE'
)
self
.
emit
(
'COMPARE_OP'
,
op
)
"""Comment from compile.c follows:
The following code is generated for all but the last
comparison in a chain:
label: on stack: opcode:
jump to:
a
<code to load b>
a, b
DUP_TOP
a, b, b
ROT_THREE
b, a, b
COMPARE_OP
b, 0-or-1 JUMP_IF_FALSE
L1
b, 1
POP_TOP
b
We are now ready to repeat this sequence for the next
comparison in the chain.
For the last we generate:
b
<code to load c>
b, c
COMPARE_OP
0-or-1
If there were any jumps to L1 (i.e., there was more than one
comparison), we generate:
0-or-1 JUMP_FORWARD
L2
L1: b, 0
ROT_TWO
0, b
POP_TOP
0
L2:
0-or-1
"""
self
.
visit
(
node
.
expr
)
# if refs are never emitted, subsequent bind call has no effect
l1
=
StackRef
()
l2
=
StackRef
()
for
op
,
code
in
node
.
ops
[:
-
1
]:
# emit every comparison except the last
self
.
visit
(
code
)
self
.
emit
(
'DUP_TOP'
)
self
.
emit
(
'ROT_THREE'
)
self
.
emit
(
'COMPARE_OP'
,
op
)
# dupTop and compareOp cancel stack effect
self
.
emit
(
'JUMP_IF_FALSE'
,
l1
)
self
.
emit
(
'POP_TOP'
)
if
node
.
ops
:
# emit the last comparison
op
,
code
=
node
.
ops
[
-
1
]
self
.
visit
(
code
)
self
.
emit
(
'COMPARE_OP'
,
op
)
if
len
(
node
.
ops
)
>
1
:
self
.
emit
(
'JUMP_FORWARD'
,
l2
)
l1
.
bind
(
self
.
code
.
getCurInst
())
self
.
emit
(
'ROT_TWO'
)
self
.
emit
(
'POP_TOP'
)
l2
.
bind
(
self
.
code
.
getCurInst
())
return
1
self
.
emit
(
'JUMP_IF_FALSE'
,
l1
)
self
.
emit
(
'POP_TOP'
)
if
node
.
ops
:
# emit the last comparison
op
,
code
=
node
.
ops
[
-
1
]
self
.
visit
(
code
)
self
.
emit
(
'COMPARE_OP'
,
op
)
if
len
(
node
.
ops
)
>
1
:
self
.
emit
(
'JUMP_FORWARD'
,
l2
)
l1
.
bind
(
self
.
code
.
getCurInst
())
self
.
emit
(
'ROT_TWO'
)
self
.
emit
(
'POP_TOP'
)
l2
.
bind
(
self
.
code
.
getCurInst
())
return
1
def
visitGetattr
(
self
,
node
):
self
.
visit
(
node
.
expr
)
...
...
@@ -590,7 +589,7 @@ class CodeGenerator:
self
.
emit
(
'BINARY_SUBSCR'
)
elif
node
.
flags
==
'OP_ASSIGN'
:
self
.
emit
(
'STORE_SUBSCR'
)
elif
node
.
flags
==
'OP_DELETE'
:
elif
node
.
flags
==
'OP_DELETE'
:
self
.
emit
(
'DELETE_SUBSCR'
)
return
1
...
...
@@ -624,11 +623,11 @@ class CodeGenerator:
def
visitAssign
(
self
,
node
):
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
self
.
visit
(
node
.
expr
)
dups
=
len
(
node
.
nodes
)
-
1
dups
=
len
(
node
.
nodes
)
-
1
for
i
in
range
(
len
(
node
.
nodes
)):
elt
=
node
.
nodes
[
i
]
if
i
<
dups
:
self
.
emit
(
'DUP_TOP'
)
elt
=
node
.
nodes
[
i
]
if
i
<
dups
:
self
.
emit
(
'DUP_TOP'
)
if
isinstance
(
elt
,
ast
.
Node
):
self
.
visit
(
elt
)
return
1
...
...
@@ -659,10 +658,10 @@ class CodeGenerator:
visitAssList
=
visitAssTuple
def
binaryOp
(
self
,
node
,
op
):
self
.
visit
(
node
.
left
)
self
.
visit
(
node
.
right
)
self
.
emit
(
op
)
return
1
self
.
visit
(
node
.
left
)
self
.
visit
(
node
.
right
)
self
.
emit
(
op
)
return
1
def
unaryOp
(
self
,
node
,
op
):
self
.
visit
(
node
.
expr
)
...
...
@@ -670,28 +669,28 @@ class CodeGenerator:
return
1
def
visitAdd
(
self
,
node
):
return
self
.
binaryOp
(
node
,
'BINARY_ADD'
)
return
self
.
binaryOp
(
node
,
'BINARY_ADD'
)
def
visitSub
(
self
,
node
):
return
self
.
binaryOp
(
node
,
'BINARY_SUBTRACT'
)
return
self
.
binaryOp
(
node
,
'BINARY_SUBTRACT'
)
def
visitMul
(
self
,
node
):
return
self
.
binaryOp
(
node
,
'BINARY_MULTIPLY'
)
return
self
.
binaryOp
(
node
,
'BINARY_MULTIPLY'
)
def
visitDiv
(
self
,
node
):
return
self
.
binaryOp
(
node
,
'BINARY_DIVIDE'
)
return
self
.
binaryOp
(
node
,
'BINARY_DIVIDE'
)
def
visitMod
(
self
,
node
):
return
self
.
binaryOp
(
node
,
'BINARY_MODULO'
)
return
self
.
binaryOp
(
node
,
'BINARY_MODULO'
)
def
visitPower
(
self
,
node
):
return
self
.
binaryOp
(
node
,
'BINARY_POWER'
)
return
self
.
binaryOp
(
node
,
'BINARY_POWER'
)
def
visitLeftShift
(
self
,
node
):
return
self
.
binaryOp
(
node
,
'BINARY_LSHIFT'
)
return
self
.
binaryOp
(
node
,
'BINARY_LSHIFT'
)
def
visitRightShift
(
self
,
node
):
return
self
.
binaryOp
(
node
,
'BINARY_RSHIFT'
)
return
self
.
binaryOp
(
node
,
'BINARY_RSHIFT'
)
def
visitInvert
(
self
,
node
):
return
self
.
unaryOp
(
node
,
'UNARY_INVERT'
)
...
...
@@ -712,20 +711,20 @@ class CodeGenerator:
return
self
.
unaryOp
(
node
,
'UNARY_CONVERT'
)
def
bitOp
(
self
,
nodes
,
op
):
self
.
visit
(
nodes
[
0
])
for
node
in
nodes
[
1
:]:
self
.
visit
(
node
)
self
.
emit
(
op
)
return
1
self
.
visit
(
nodes
[
0
])
for
node
in
nodes
[
1
:]:
self
.
visit
(
node
)
self
.
emit
(
op
)
return
1
def
visitBitand
(
self
,
node
):
return
self
.
bitOp
(
node
.
nodes
,
'BINARY_AND'
)
return
self
.
bitOp
(
node
.
nodes
,
'BINARY_AND'
)
def
visitBitor
(
self
,
node
):
return
self
.
bitOp
(
node
.
nodes
,
'BINARY_OR'
)
return
self
.
bitOp
(
node
.
nodes
,
'BINARY_OR'
)
def
visitBitxor
(
self
,
node
):
return
self
.
bitOp
(
node
.
nodes
,
'BINARY_XOR'
)
return
self
.
bitOp
(
node
.
nodes
,
'BINARY_XOR'
)
def
visitTest
(
self
,
node
,
jump
):
end
=
StackRef
()
...
...
@@ -738,22 +737,22 @@ class CodeGenerator:
return
1
def
visitAssert
(
self
,
node
):
# XXX __debug__ and AssertionError appear to be special cases
# -- they are always loaded as globals even if there are local
# names. I guess this is a sort of renaming op.
skip
=
StackRef
()
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
self
.
emit
(
'LOAD_GLOBAL'
,
'__debug__'
)
self
.
emit
(
'JUMP_IF_FALSE'
,
skip
)
self
.
emit
(
'POP_TOP'
)
self
.
visit
(
node
.
test
)
self
.
emit
(
'JUMP_IF_TRUE'
,
skip
)
self
.
emit
(
'LOAD_GLOBAL'
,
'AssertionError'
)
self
.
visit
(
node
.
fail
)
self
.
emit
(
'RAISE_VARARGS'
,
2
)
skip
.
bind
(
self
.
code
.
getCurInst
())
self
.
emit
(
'POP_TOP'
)
return
1
# XXX __debug__ and AssertionError appear to be special cases
# -- they are always loaded as globals even if there are local
# names. I guess this is a sort of renaming op.
skip
=
StackRef
()
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
self
.
emit
(
'LOAD_GLOBAL'
,
'__debug__'
)
self
.
emit
(
'JUMP_IF_FALSE'
,
skip
)
self
.
emit
(
'POP_TOP'
)
self
.
visit
(
node
.
test
)
self
.
emit
(
'JUMP_IF_TRUE'
,
skip
)
self
.
emit
(
'LOAD_GLOBAL'
,
'AssertionError'
)
self
.
visit
(
node
.
fail
)
self
.
emit
(
'RAISE_VARARGS'
,
2
)
skip
.
bind
(
self
.
code
.
getCurInst
())
self
.
emit
(
'POP_TOP'
)
return
1
def
visitAnd
(
self
,
node
):
return
self
.
visitTest
(
node
,
'JUMP_IF_FALSE'
)
...
...
@@ -765,12 +764,12 @@ class CodeGenerator:
self
.
loadName
(
node
.
name
)
def
visitConst
(
self
,
node
):
self
.
emit
(
'LOAD_CONST'
,
node
.
value
)
self
.
emit
(
'LOAD_CONST'
,
node
.
value
)
return
1
def
visitEllipsis
(
self
,
node
):
self
.
emit
(
'LOAD_CONST'
,
Ellipsis
)
return
1
self
.
emit
(
'LOAD_CONST'
,
Ellipsis
)
return
1
def
visitTuple
(
self
,
node
):
for
elt
in
node
.
nodes
:
...
...
@@ -785,76 +784,76 @@ class CodeGenerator:
return
1
def
visitDict
(
self
,
node
):
self
.
emit
(
'BUILD_MAP'
,
0
)
for
k
,
v
in
node
.
items
:
# XXX need to add set lineno when there aren't constants
self
.
emit
(
'DUP_TOP'
)
self
.
visit
(
v
)
self
.
emit
(
'ROT_TWO'
)
self
.
visit
(
k
)
self
.
emit
(
'STORE_SUBSCR'
)
return
1
self
.
emit
(
'BUILD_MAP'
,
0
)
for
k
,
v
in
node
.
items
:
# XXX need to add set lineno when there aren't constants
self
.
emit
(
'DUP_TOP'
)
self
.
visit
(
v
)
self
.
emit
(
'ROT_TWO'
)
self
.
visit
(
k
)
self
.
emit
(
'STORE_SUBSCR'
)
return
1
def
visitReturn
(
self
,
node
):
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
self
.
visit
(
node
.
value
)
self
.
emit
(
'RETURN_VALUE'
)
return
1
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
self
.
visit
(
node
.
value
)
self
.
emit
(
'RETURN_VALUE'
)
return
1
def
visitRaise
(
self
,
node
):
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
n
=
0
if
node
.
expr1
:
self
.
visit
(
node
.
expr1
)
n
=
n
+
1
if
node
.
expr2
:
self
.
visit
(
node
.
expr2
)
n
=
n
+
1
if
node
.
expr3
:
self
.
visit
(
node
.
expr3
)
n
=
n
+
1
self
.
emit
(
'RAISE_VARARGS'
,
n
)
return
1
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
n
=
0
if
node
.
expr1
:
self
.
visit
(
node
.
expr1
)
n
=
n
+
1
if
node
.
expr2
:
self
.
visit
(
node
.
expr2
)
n
=
n
+
1
if
node
.
expr3
:
self
.
visit
(
node
.
expr3
)
n
=
n
+
1
self
.
emit
(
'RAISE_VARARGS'
,
n
)
return
1
def
visitPrint
(
self
,
node
):
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
for
child
in
node
.
nodes
:
self
.
visit
(
child
)
self
.
emit
(
'PRINT_ITEM'
)
return
1
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
for
child
in
node
.
nodes
:
self
.
visit
(
child
)
self
.
emit
(
'PRINT_ITEM'
)
return
1
def
visitPrintnl
(
self
,
node
):
self
.
visitPrint
(
node
)
self
.
emit
(
'PRINT_NEWLINE'
)
return
1
self
.
visitPrint
(
node
)
self
.
emit
(
'PRINT_NEWLINE'
)
return
1
def
visitExec
(
self
,
node
):
self
.
visit
(
node
.
expr
)
if
node
.
locals
is
None
:
self
.
emit
(
'LOAD_CONST'
,
None
)
else
:
self
.
visit
(
node
.
locals
)
if
node
.
globals
is
None
:
self
.
emit
(
'DUP_TOP'
)
else
:
self
.
visit
(
node
.
globals
)
self
.
emit
(
'EXEC_STMT'
)
self
.
visit
(
node
.
expr
)
if
node
.
locals
is
None
:
self
.
emit
(
'LOAD_CONST'
,
None
)
else
:
self
.
visit
(
node
.
locals
)
if
node
.
globals
is
None
:
self
.
emit
(
'DUP_TOP'
)
else
:
self
.
visit
(
node
.
globals
)
self
.
emit
(
'EXEC_STMT'
)
class
LocalNameFinder
:
def
__init__
(
self
,
names
=
()):
self
.
names
=
misc
.
Set
()
self
.
names
=
misc
.
Set
()
self
.
globals
=
misc
.
Set
()
for
name
in
names
:
self
.
names
.
add
(
name
)
for
name
in
names
:
self
.
names
.
add
(
name
)
def
getLocals
(
self
):
for
elt
in
self
.
globals
.
items
():
if
self
.
names
.
has_elt
(
elt
):
self
.
names
.
remove
(
elt
)
return
self
.
names
return
self
.
names
def
visitDict
(
self
,
node
):
return
1
return
1
def
visitGlobal
(
self
,
node
):
for
name
in
node
.
names
:
...
...
@@ -863,25 +862,25 @@ class LocalNameFinder:
def
visitFunction
(
self
,
node
):
self
.
names
.
add
(
node
.
name
)
return
1
return
1
def
visitLambda
(
self
,
node
):
return
1
def
visitImport
(
self
,
node
):
for
name
in
node
.
names
:
self
.
names
.
add
(
name
)
for
name
in
node
.
names
:
self
.
names
.
add
(
name
)
def
visitFrom
(
self
,
node
):
for
name
in
node
.
names
:
self
.
names
.
add
(
name
)
for
name
in
node
.
names
:
self
.
names
.
add
(
name
)
def
visitClassdef
(
self
,
node
):
self
.
names
.
add
(
node
.
name
)
return
1
self
.
names
.
add
(
node
.
name
)
return
1
def
visitAssName
(
self
,
node
):
self
.
names
.
add
(
node
.
name
)
self
.
names
.
add
(
node
.
name
)
class
Loop
:
def
__init__
(
self
):
...
...
@@ -926,7 +925,13 @@ class CompiledModule:
mtime
=
os
.
stat
(
self
.
filename
)[
stat
.
ST_MTIME
]
mtime
=
struct
.
pack
(
'i'
,
mtime
)
return
magic
+
mtime
def
compile
(
filename
):
buf
=
open
(
filename
).
read
()
mod
=
CompiledModule
(
buf
,
filename
)
mod
.
compile
()
mod
.
dump
(
filename
+
'c'
)
if
__name__
==
"__main__"
:
import
getopt
...
...
@@ -945,7 +950,4 @@ if __name__ == "__main__":
for
filename
in
args
:
if
VERBOSE
:
print
filename
buf
=
open
(
filename
).
read
()
mod
=
CompiledModule
(
buf
,
filename
)
mod
.
compile
()
mod
.
dump
(
filename
+
'c'
)
compile
(
filename
)
Tools/compiler/compiler/pycodegen.py
View file @
873bdc18
...
...
@@ -80,7 +80,7 @@ class ASTVisitor:
VERBOSE
=
0
def
__init__
(
self
):
self
.
node
=
None
self
.
node
=
None
def
preorder
(
self
,
tree
,
visitor
):
"""Do preorder walk of tree using visitor"""
...
...
@@ -163,27 +163,26 @@ class CodeGenerator:
# XXX should clean up initialization and generateXXX funcs
def
__init__
(
self
,
filename
=
"<?>"
):
self
.
filename
=
filename
self
.
code
=
PyAssembler
()
self
.
code
=
PyAssembler
()
self
.
code
.
setFlags
(
0
)
self
.
locals
=
misc
.
Stack
()
self
.
locals
=
misc
.
Stack
()
self
.
loops
=
misc
.
Stack
()
self
.
namespace
=
0
self
.
curStack
=
0
self
.
maxStack
=
0
def
emit
(
self
,
*
args
):
# XXX could just use self.emit = self.code.emit
apply
(
self
.
code
.
emit
,
args
)
# XXX could just use self.emit = self.code.emit
apply
(
self
.
code
.
emit
,
args
)
def
_generateFunctionOrLambdaCode
(
self
,
func
):
self
.
name
=
func
.
name
self
.
filename
=
filename
# keep a lookout for 'def foo((x,y)):'
args
,
hasTupleArg
=
self
.
generateArglist
(
func
.
argnames
)
self
.
code
=
PyAssembler
(
args
=
args
,
name
=
func
.
name
,
filename
=
filename
)
self
.
code
=
PyAssembler
(
args
=
args
,
name
=
func
.
name
,
filename
=
self
.
filename
)
self
.
namespace
=
self
.
OPTIMIZED
if
func
.
varargs
:
self
.
code
.
setVarArgs
()
...
...
@@ -191,8 +190,8 @@ class CodeGenerator:
self
.
code
.
setKWArgs
()
lnf
=
walk
(
func
.
code
,
LocalNameFinder
(
args
),
0
)
self
.
locals
.
push
(
lnf
.
getLocals
())
self
.
emit
(
'SET_LINENO'
,
func
.
lineno
)
if
hasTupleArg
:
self
.
emit
(
'SET_LINENO'
,
func
.
lineno
)
if
hasTupleArg
:
self
.
generateArgUnpack
(
func
.
argnames
)
walk
(
func
.
code
,
self
)
...
...
@@ -239,7 +238,7 @@ class CodeGenerator:
def
generateClassCode
(
self
,
klass
):
self
.
code
=
PyAssembler
(
name
=
klass
.
name
,
filename
=
filename
)
filename
=
self
.
filename
)
self
.
emit
(
'SET_LINENO'
,
klass
.
lineno
)
lnf
=
walk
(
klass
.
code
,
LocalNameFinder
(),
0
)
self
.
locals
.
push
(
lnf
.
getLocals
())
...
...
@@ -254,7 +253,7 @@ class CodeGenerator:
return
self
.
code
.
makeCodeObject
()
def
isLocalName
(
self
,
name
):
return
self
.
locals
.
top
().
has_elt
(
name
)
return
self
.
locals
.
top
().
has_elt
(
name
)
def
_nameOp
(
self
,
prefix
,
name
):
if
self
.
isLocalName
(
name
):
...
...
@@ -290,8 +289,8 @@ class CodeGenerator:
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
def
visitModule
(
self
,
node
):
lnf
=
walk
(
node
.
node
,
LocalNameFinder
(),
0
)
self
.
locals
.
push
(
lnf
.
getLocals
())
lnf
=
walk
(
node
.
node
,
LocalNameFinder
(),
0
)
self
.
locals
.
push
(
lnf
.
getLocals
())
self
.
visit
(
node
.
node
)
self
.
emit
(
'LOAD_CONST'
,
None
)
self
.
emit
(
'RETURN_VALUE'
)
...
...
@@ -353,15 +352,15 @@ class CodeGenerator:
kw
=
0
if
hasattr
(
node
,
'lineno'
):
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
self
.
visit
(
node
.
node
)
for
arg
in
node
.
args
:
self
.
visit
(
arg
)
self
.
visit
(
node
.
node
)
for
arg
in
node
.
args
:
self
.
visit
(
arg
)
if
isinstance
(
arg
,
ast
.
Keyword
):
kw
=
kw
+
1
else
:
pos
=
pos
+
1
self
.
emit
(
'CALL_FUNCTION'
,
kw
<<
8
|
pos
)
return
1
self
.
emit
(
'CALL_FUNCTION'
,
kw
<<
8
|
pos
)
return
1
def
visitKeyword
(
self
,
node
):
self
.
emit
(
'LOAD_CONST'
,
node
.
name
)
...
...
@@ -369,24 +368,24 @@ class CodeGenerator:
return
1
def
visitIf
(
self
,
node
):
after
=
StackRef
()
for
test
,
suite
in
node
.
tests
:
after
=
StackRef
()
for
test
,
suite
in
node
.
tests
:
if
hasattr
(
test
,
'lineno'
):
self
.
emit
(
'SET_LINENO'
,
test
.
lineno
)
else
:
print
"warning"
,
"no line number"
self
.
visit
(
test
)
dest
=
StackRef
()
self
.
emit
(
'JUMP_IF_FALSE'
,
dest
)
self
.
emit
(
'POP_TOP'
)
self
.
visit
(
suite
)
self
.
emit
(
'JUMP_FORWARD'
,
after
)
dest
.
bind
(
self
.
code
.
getCurInst
())
self
.
emit
(
'POP_TOP'
)
if
node
.
else_
:
self
.
visit
(
node
.
else_
)
after
.
bind
(
self
.
code
.
getCurInst
())
return
1
self
.
visit
(
test
)
dest
=
StackRef
()
self
.
emit
(
'JUMP_IF_FALSE'
,
dest
)
self
.
emit
(
'POP_TOP'
)
self
.
visit
(
suite
)
self
.
emit
(
'JUMP_FORWARD'
,
after
)
dest
.
bind
(
self
.
code
.
getCurInst
())
self
.
emit
(
'POP_TOP'
)
if
node
.
else_
:
self
.
visit
(
node
.
else_
)
after
.
bind
(
self
.
code
.
getCurInst
())
return
1
def
startLoop
(
self
):
l
=
Loop
()
...
...
@@ -516,64 +515,64 @@ class CodeGenerator:
return
1
def
visitCompare
(
self
,
node
):
"""Comment from compile.c follows:
The following code is generated for all but the last
comparison in a chain:
label: on stack: opcode:
jump to:
a
<code to load b>
a, b
DUP_TOP
a, b, b
ROT_THREE
b, a, b
COMPARE_OP
b, 0-or-1 JUMP_IF_FALSE
L1
b, 1
POP_TOP
b
We are now ready to repeat this sequence for the next
comparison in the chain.
For the last we generate:
b
<code to load c>
b, c
COMPARE_OP
0-or-1
If there were any jumps to L1 (i.e., there was more than one
comparison), we generate:
0-or-1 JUMP_FORWARD
L2
L1: b, 0
ROT_TWO
0, b
POP_TOP
0
L2:
0-or-1
"""
self
.
visit
(
node
.
expr
)
# if refs are never emitted, subsequent bind call has no effect
l1
=
StackRef
()
l2
=
StackRef
()
for
op
,
code
in
node
.
ops
[:
-
1
]:
# emit every comparison except the last
self
.
visit
(
code
)
self
.
emit
(
'DUP_TOP'
)
self
.
emit
(
'ROT_THREE'
)
self
.
emit
(
'COMPARE_OP'
,
op
)
"""Comment from compile.c follows:
The following code is generated for all but the last
comparison in a chain:
label: on stack: opcode:
jump to:
a
<code to load b>
a, b
DUP_TOP
a, b, b
ROT_THREE
b, a, b
COMPARE_OP
b, 0-or-1 JUMP_IF_FALSE
L1
b, 1
POP_TOP
b
We are now ready to repeat this sequence for the next
comparison in the chain.
For the last we generate:
b
<code to load c>
b, c
COMPARE_OP
0-or-1
If there were any jumps to L1 (i.e., there was more than one
comparison), we generate:
0-or-1 JUMP_FORWARD
L2
L1: b, 0
ROT_TWO
0, b
POP_TOP
0
L2:
0-or-1
"""
self
.
visit
(
node
.
expr
)
# if refs are never emitted, subsequent bind call has no effect
l1
=
StackRef
()
l2
=
StackRef
()
for
op
,
code
in
node
.
ops
[:
-
1
]:
# emit every comparison except the last
self
.
visit
(
code
)
self
.
emit
(
'DUP_TOP'
)
self
.
emit
(
'ROT_THREE'
)
self
.
emit
(
'COMPARE_OP'
,
op
)
# dupTop and compareOp cancel stack effect
self
.
emit
(
'JUMP_IF_FALSE'
,
l1
)
self
.
emit
(
'POP_TOP'
)
if
node
.
ops
:
# emit the last comparison
op
,
code
=
node
.
ops
[
-
1
]
self
.
visit
(
code
)
self
.
emit
(
'COMPARE_OP'
,
op
)
if
len
(
node
.
ops
)
>
1
:
self
.
emit
(
'JUMP_FORWARD'
,
l2
)
l1
.
bind
(
self
.
code
.
getCurInst
())
self
.
emit
(
'ROT_TWO'
)
self
.
emit
(
'POP_TOP'
)
l2
.
bind
(
self
.
code
.
getCurInst
())
return
1
self
.
emit
(
'JUMP_IF_FALSE'
,
l1
)
self
.
emit
(
'POP_TOP'
)
if
node
.
ops
:
# emit the last comparison
op
,
code
=
node
.
ops
[
-
1
]
self
.
visit
(
code
)
self
.
emit
(
'COMPARE_OP'
,
op
)
if
len
(
node
.
ops
)
>
1
:
self
.
emit
(
'JUMP_FORWARD'
,
l2
)
l1
.
bind
(
self
.
code
.
getCurInst
())
self
.
emit
(
'ROT_TWO'
)
self
.
emit
(
'POP_TOP'
)
l2
.
bind
(
self
.
code
.
getCurInst
())
return
1
def
visitGetattr
(
self
,
node
):
self
.
visit
(
node
.
expr
)
...
...
@@ -590,7 +589,7 @@ class CodeGenerator:
self
.
emit
(
'BINARY_SUBSCR'
)
elif
node
.
flags
==
'OP_ASSIGN'
:
self
.
emit
(
'STORE_SUBSCR'
)
elif
node
.
flags
==
'OP_DELETE'
:
elif
node
.
flags
==
'OP_DELETE'
:
self
.
emit
(
'DELETE_SUBSCR'
)
return
1
...
...
@@ -624,11 +623,11 @@ class CodeGenerator:
def
visitAssign
(
self
,
node
):
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
self
.
visit
(
node
.
expr
)
dups
=
len
(
node
.
nodes
)
-
1
dups
=
len
(
node
.
nodes
)
-
1
for
i
in
range
(
len
(
node
.
nodes
)):
elt
=
node
.
nodes
[
i
]
if
i
<
dups
:
self
.
emit
(
'DUP_TOP'
)
elt
=
node
.
nodes
[
i
]
if
i
<
dups
:
self
.
emit
(
'DUP_TOP'
)
if
isinstance
(
elt
,
ast
.
Node
):
self
.
visit
(
elt
)
return
1
...
...
@@ -659,10 +658,10 @@ class CodeGenerator:
visitAssList
=
visitAssTuple
def
binaryOp
(
self
,
node
,
op
):
self
.
visit
(
node
.
left
)
self
.
visit
(
node
.
right
)
self
.
emit
(
op
)
return
1
self
.
visit
(
node
.
left
)
self
.
visit
(
node
.
right
)
self
.
emit
(
op
)
return
1
def
unaryOp
(
self
,
node
,
op
):
self
.
visit
(
node
.
expr
)
...
...
@@ -670,28 +669,28 @@ class CodeGenerator:
return
1
def
visitAdd
(
self
,
node
):
return
self
.
binaryOp
(
node
,
'BINARY_ADD'
)
return
self
.
binaryOp
(
node
,
'BINARY_ADD'
)
def
visitSub
(
self
,
node
):
return
self
.
binaryOp
(
node
,
'BINARY_SUBTRACT'
)
return
self
.
binaryOp
(
node
,
'BINARY_SUBTRACT'
)
def
visitMul
(
self
,
node
):
return
self
.
binaryOp
(
node
,
'BINARY_MULTIPLY'
)
return
self
.
binaryOp
(
node
,
'BINARY_MULTIPLY'
)
def
visitDiv
(
self
,
node
):
return
self
.
binaryOp
(
node
,
'BINARY_DIVIDE'
)
return
self
.
binaryOp
(
node
,
'BINARY_DIVIDE'
)
def
visitMod
(
self
,
node
):
return
self
.
binaryOp
(
node
,
'BINARY_MODULO'
)
return
self
.
binaryOp
(
node
,
'BINARY_MODULO'
)
def
visitPower
(
self
,
node
):
return
self
.
binaryOp
(
node
,
'BINARY_POWER'
)
return
self
.
binaryOp
(
node
,
'BINARY_POWER'
)
def
visitLeftShift
(
self
,
node
):
return
self
.
binaryOp
(
node
,
'BINARY_LSHIFT'
)
return
self
.
binaryOp
(
node
,
'BINARY_LSHIFT'
)
def
visitRightShift
(
self
,
node
):
return
self
.
binaryOp
(
node
,
'BINARY_RSHIFT'
)
return
self
.
binaryOp
(
node
,
'BINARY_RSHIFT'
)
def
visitInvert
(
self
,
node
):
return
self
.
unaryOp
(
node
,
'UNARY_INVERT'
)
...
...
@@ -712,20 +711,20 @@ class CodeGenerator:
return
self
.
unaryOp
(
node
,
'UNARY_CONVERT'
)
def
bitOp
(
self
,
nodes
,
op
):
self
.
visit
(
nodes
[
0
])
for
node
in
nodes
[
1
:]:
self
.
visit
(
node
)
self
.
emit
(
op
)
return
1
self
.
visit
(
nodes
[
0
])
for
node
in
nodes
[
1
:]:
self
.
visit
(
node
)
self
.
emit
(
op
)
return
1
def
visitBitand
(
self
,
node
):
return
self
.
bitOp
(
node
.
nodes
,
'BINARY_AND'
)
return
self
.
bitOp
(
node
.
nodes
,
'BINARY_AND'
)
def
visitBitor
(
self
,
node
):
return
self
.
bitOp
(
node
.
nodes
,
'BINARY_OR'
)
return
self
.
bitOp
(
node
.
nodes
,
'BINARY_OR'
)
def
visitBitxor
(
self
,
node
):
return
self
.
bitOp
(
node
.
nodes
,
'BINARY_XOR'
)
return
self
.
bitOp
(
node
.
nodes
,
'BINARY_XOR'
)
def
visitTest
(
self
,
node
,
jump
):
end
=
StackRef
()
...
...
@@ -738,22 +737,22 @@ class CodeGenerator:
return
1
def
visitAssert
(
self
,
node
):
# XXX __debug__ and AssertionError appear to be special cases
# -- they are always loaded as globals even if there are local
# names. I guess this is a sort of renaming op.
skip
=
StackRef
()
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
self
.
emit
(
'LOAD_GLOBAL'
,
'__debug__'
)
self
.
emit
(
'JUMP_IF_FALSE'
,
skip
)
self
.
emit
(
'POP_TOP'
)
self
.
visit
(
node
.
test
)
self
.
emit
(
'JUMP_IF_TRUE'
,
skip
)
self
.
emit
(
'LOAD_GLOBAL'
,
'AssertionError'
)
self
.
visit
(
node
.
fail
)
self
.
emit
(
'RAISE_VARARGS'
,
2
)
skip
.
bind
(
self
.
code
.
getCurInst
())
self
.
emit
(
'POP_TOP'
)
return
1
# XXX __debug__ and AssertionError appear to be special cases
# -- they are always loaded as globals even if there are local
# names. I guess this is a sort of renaming op.
skip
=
StackRef
()
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
self
.
emit
(
'LOAD_GLOBAL'
,
'__debug__'
)
self
.
emit
(
'JUMP_IF_FALSE'
,
skip
)
self
.
emit
(
'POP_TOP'
)
self
.
visit
(
node
.
test
)
self
.
emit
(
'JUMP_IF_TRUE'
,
skip
)
self
.
emit
(
'LOAD_GLOBAL'
,
'AssertionError'
)
self
.
visit
(
node
.
fail
)
self
.
emit
(
'RAISE_VARARGS'
,
2
)
skip
.
bind
(
self
.
code
.
getCurInst
())
self
.
emit
(
'POP_TOP'
)
return
1
def
visitAnd
(
self
,
node
):
return
self
.
visitTest
(
node
,
'JUMP_IF_FALSE'
)
...
...
@@ -765,12 +764,12 @@ class CodeGenerator:
self
.
loadName
(
node
.
name
)
def
visitConst
(
self
,
node
):
self
.
emit
(
'LOAD_CONST'
,
node
.
value
)
self
.
emit
(
'LOAD_CONST'
,
node
.
value
)
return
1
def
visitEllipsis
(
self
,
node
):
self
.
emit
(
'LOAD_CONST'
,
Ellipsis
)
return
1
self
.
emit
(
'LOAD_CONST'
,
Ellipsis
)
return
1
def
visitTuple
(
self
,
node
):
for
elt
in
node
.
nodes
:
...
...
@@ -785,76 +784,76 @@ class CodeGenerator:
return
1
def
visitDict
(
self
,
node
):
self
.
emit
(
'BUILD_MAP'
,
0
)
for
k
,
v
in
node
.
items
:
# XXX need to add set lineno when there aren't constants
self
.
emit
(
'DUP_TOP'
)
self
.
visit
(
v
)
self
.
emit
(
'ROT_TWO'
)
self
.
visit
(
k
)
self
.
emit
(
'STORE_SUBSCR'
)
return
1
self
.
emit
(
'BUILD_MAP'
,
0
)
for
k
,
v
in
node
.
items
:
# XXX need to add set lineno when there aren't constants
self
.
emit
(
'DUP_TOP'
)
self
.
visit
(
v
)
self
.
emit
(
'ROT_TWO'
)
self
.
visit
(
k
)
self
.
emit
(
'STORE_SUBSCR'
)
return
1
def
visitReturn
(
self
,
node
):
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
self
.
visit
(
node
.
value
)
self
.
emit
(
'RETURN_VALUE'
)
return
1
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
self
.
visit
(
node
.
value
)
self
.
emit
(
'RETURN_VALUE'
)
return
1
def
visitRaise
(
self
,
node
):
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
n
=
0
if
node
.
expr1
:
self
.
visit
(
node
.
expr1
)
n
=
n
+
1
if
node
.
expr2
:
self
.
visit
(
node
.
expr2
)
n
=
n
+
1
if
node
.
expr3
:
self
.
visit
(
node
.
expr3
)
n
=
n
+
1
self
.
emit
(
'RAISE_VARARGS'
,
n
)
return
1
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
n
=
0
if
node
.
expr1
:
self
.
visit
(
node
.
expr1
)
n
=
n
+
1
if
node
.
expr2
:
self
.
visit
(
node
.
expr2
)
n
=
n
+
1
if
node
.
expr3
:
self
.
visit
(
node
.
expr3
)
n
=
n
+
1
self
.
emit
(
'RAISE_VARARGS'
,
n
)
return
1
def
visitPrint
(
self
,
node
):
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
for
child
in
node
.
nodes
:
self
.
visit
(
child
)
self
.
emit
(
'PRINT_ITEM'
)
return
1
self
.
emit
(
'SET_LINENO'
,
node
.
lineno
)
for
child
in
node
.
nodes
:
self
.
visit
(
child
)
self
.
emit
(
'PRINT_ITEM'
)
return
1
def
visitPrintnl
(
self
,
node
):
self
.
visitPrint
(
node
)
self
.
emit
(
'PRINT_NEWLINE'
)
return
1
self
.
visitPrint
(
node
)
self
.
emit
(
'PRINT_NEWLINE'
)
return
1
def
visitExec
(
self
,
node
):
self
.
visit
(
node
.
expr
)
if
node
.
locals
is
None
:
self
.
emit
(
'LOAD_CONST'
,
None
)
else
:
self
.
visit
(
node
.
locals
)
if
node
.
globals
is
None
:
self
.
emit
(
'DUP_TOP'
)
else
:
self
.
visit
(
node
.
globals
)
self
.
emit
(
'EXEC_STMT'
)
self
.
visit
(
node
.
expr
)
if
node
.
locals
is
None
:
self
.
emit
(
'LOAD_CONST'
,
None
)
else
:
self
.
visit
(
node
.
locals
)
if
node
.
globals
is
None
:
self
.
emit
(
'DUP_TOP'
)
else
:
self
.
visit
(
node
.
globals
)
self
.
emit
(
'EXEC_STMT'
)
class
LocalNameFinder
:
def
__init__
(
self
,
names
=
()):
self
.
names
=
misc
.
Set
()
self
.
names
=
misc
.
Set
()
self
.
globals
=
misc
.
Set
()
for
name
in
names
:
self
.
names
.
add
(
name
)
for
name
in
names
:
self
.
names
.
add
(
name
)
def
getLocals
(
self
):
for
elt
in
self
.
globals
.
items
():
if
self
.
names
.
has_elt
(
elt
):
self
.
names
.
remove
(
elt
)
return
self
.
names
return
self
.
names
def
visitDict
(
self
,
node
):
return
1
return
1
def
visitGlobal
(
self
,
node
):
for
name
in
node
.
names
:
...
...
@@ -863,25 +862,25 @@ class LocalNameFinder:
def
visitFunction
(
self
,
node
):
self
.
names
.
add
(
node
.
name
)
return
1
return
1
def
visitLambda
(
self
,
node
):
return
1
def
visitImport
(
self
,
node
):
for
name
in
node
.
names
:
self
.
names
.
add
(
name
)
for
name
in
node
.
names
:
self
.
names
.
add
(
name
)
def
visitFrom
(
self
,
node
):
for
name
in
node
.
names
:
self
.
names
.
add
(
name
)
for
name
in
node
.
names
:
self
.
names
.
add
(
name
)
def
visitClassdef
(
self
,
node
):
self
.
names
.
add
(
node
.
name
)
return
1
self
.
names
.
add
(
node
.
name
)
return
1
def
visitAssName
(
self
,
node
):
self
.
names
.
add
(
node
.
name
)
self
.
names
.
add
(
node
.
name
)
class
Loop
:
def
__init__
(
self
):
...
...
@@ -926,7 +925,13 @@ class CompiledModule:
mtime
=
os
.
stat
(
self
.
filename
)[
stat
.
ST_MTIME
]
mtime
=
struct
.
pack
(
'i'
,
mtime
)
return
magic
+
mtime
def
compile
(
filename
):
buf
=
open
(
filename
).
read
()
mod
=
CompiledModule
(
buf
,
filename
)
mod
.
compile
()
mod
.
dump
(
filename
+
'c'
)
if
__name__
==
"__main__"
:
import
getopt
...
...
@@ -945,7 +950,4 @@ if __name__ == "__main__":
for
filename
in
args
:
if
VERBOSE
:
print
filename
buf
=
open
(
filename
).
read
()
mod
=
CompiledModule
(
buf
,
filename
)
mod
.
compile
()
mod
.
dump
(
filename
+
'c'
)
compile
(
filename
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment