Commit fae2663b authored by Jeremy Hylton's avatar Jeremy Hylton

Preliminary support for nested scopes

XXX Still doesn't work right for classes
XXX Still doesn't do sufficient error checking
parent 44645861
...@@ -99,12 +99,6 @@ class FlowGraph: ...@@ -99,12 +99,6 @@ class FlowGraph:
if not self.exit in order: if not self.exit in order:
order.append(self.exit) order.append(self.exit)
## for b in order:
## print repr(b)
## print "\t", b.get_children()
## print b
## print
return order return order
def getBlocks(self): def getBlocks(self):
...@@ -222,6 +216,7 @@ CO_OPTIMIZED = 0x0001 ...@@ -222,6 +216,7 @@ CO_OPTIMIZED = 0x0001
CO_NEWLOCALS = 0x0002 CO_NEWLOCALS = 0x0002
CO_VARARGS = 0x0004 CO_VARARGS = 0x0004
CO_VARKEYWORDS = 0x0008 CO_VARKEYWORDS = 0x0008
CO_NESTED = 0x0010
# the FlowGraph is transformed in place; it exists in one of these states # the FlowGraph is transformed in place; it exists in one of these states
RAW = "RAW" RAW = "RAW"
...@@ -245,6 +240,15 @@ class PyFlowGraph(FlowGraph): ...@@ -245,6 +240,15 @@ class PyFlowGraph(FlowGraph):
self.flags = 0 self.flags = 0
self.consts = [] self.consts = []
self.names = [] self.names = []
# Free variables found by the symbol table scan, including
# variables used only in nested scopes, are included here.
self.freevars = []
self.cellvars = []
# The closure list is used to track the order of cell
# variables and free variables in the resulting code object.
# The offsets used by LOAD_CLOSURE/LOAD_DEREF refer to both
# kinds of variables.
self.closure = []
self.varnames = list(args) or [] self.varnames = list(args) or []
for i in range(len(self.varnames)): for i in range(len(self.varnames)):
var = self.varnames[i] var = self.varnames[i]
...@@ -260,6 +264,12 @@ class PyFlowGraph(FlowGraph): ...@@ -260,6 +264,12 @@ class PyFlowGraph(FlowGraph):
if flag == CO_VARARGS: if flag == CO_VARARGS:
self.argcount = self.argcount - 1 self.argcount = self.argcount - 1
def setFreeVars(self, names):
self.freevars = list(names)
def setCellVars(self, names):
self.cellvars = names
def getCode(self): def getCode(self):
"""Get a Python code object""" """Get a Python code object"""
if self.stage == RAW: if self.stage == RAW:
...@@ -335,6 +345,7 @@ class PyFlowGraph(FlowGraph): ...@@ -335,6 +345,7 @@ class PyFlowGraph(FlowGraph):
"""Convert arguments from symbolic to concrete form""" """Convert arguments from symbolic to concrete form"""
assert self.stage == FLAT assert self.stage == FLAT
self.consts.insert(0, self.docstring) self.consts.insert(0, self.docstring)
self.sort_cellvars()
for i in range(len(self.insts)): for i in range(len(self.insts)):
t = self.insts[i] t = self.insts[i]
if len(t) == 2: if len(t) == 2:
...@@ -345,6 +356,19 @@ class PyFlowGraph(FlowGraph): ...@@ -345,6 +356,19 @@ class PyFlowGraph(FlowGraph):
self.insts[i] = opname, conv(self, oparg) self.insts[i] = opname, conv(self, oparg)
self.stage = CONV self.stage = CONV
def sort_cellvars(self):
"""Sort cellvars in the order of varnames and prune from freevars.
"""
cells = {}
for name in self.cellvars:
cells[name] = 1
self.cellvars = [name for name in self.varnames
if cells.has_key(name)]
for name in self.cellvars:
del cells[name]
self.cellvars = self.cellvars + cells.keys()
self.closure = self.cellvars + self.freevars
def _lookupName(self, name, list): def _lookupName(self, name, list):
"""Return index of name in list, appending if necessary""" """Return index of name in list, appending if necessary"""
t = type(name) t = type(name)
...@@ -382,6 +406,17 @@ class PyFlowGraph(FlowGraph): ...@@ -382,6 +406,17 @@ class PyFlowGraph(FlowGraph):
_convert_STORE_GLOBAL = _convert_NAME _convert_STORE_GLOBAL = _convert_NAME
_convert_DELETE_GLOBAL = _convert_NAME _convert_DELETE_GLOBAL = _convert_NAME
def _convert_DEREF(self, arg):
self._lookupName(arg, self.names)
self._lookupName(arg, self.varnames)
return self._lookupName(arg, self.closure)
_convert_LOAD_DEREF = _convert_DEREF
_convert_STORE_DEREF = _convert_DEREF
def _convert_LOAD_CLOSURE(self, arg):
self._lookupName(arg, self.varnames)
return self._lookupName(arg, self.closure)
_cmp = list(dis.cmp_op) _cmp = list(dis.cmp_op)
def _convert_COMPARE_OP(self, arg): def _convert_COMPARE_OP(self, arg):
return self._cmp.index(arg) return self._cmp.index(arg)
...@@ -432,7 +467,8 @@ class PyFlowGraph(FlowGraph): ...@@ -432,7 +467,8 @@ class PyFlowGraph(FlowGraph):
self.lnotab.getCode(), self.getConsts(), self.lnotab.getCode(), self.getConsts(),
tuple(self.names), tuple(self.varnames), tuple(self.names), tuple(self.varnames),
self.filename, self.name, self.lnotab.firstline, self.filename, self.name, self.lnotab.firstline,
self.lnotab.getTable()) self.lnotab.getTable(), tuple(self.freevars),
tuple(self.cellvars))
def getConsts(self): def getConsts(self):
"""Return a tuple for the const slot of the code object """Return a tuple for the const slot of the code object
......
This diff is collapsed.
"""Module symbol-table generator""" """Module symbol-table generator"""
from compiler import ast from compiler import ast
from compiler.consts import SC_LOCAL, SC_GLOBAL, SC_FREE, SC_CELL, SC_UNKNOWN
import types import types
import sys
MANGLE_LEN = 256 MANGLE_LEN = 256
class Scope: class Scope:
...@@ -14,7 +17,12 @@ class Scope: ...@@ -14,7 +17,12 @@ class Scope:
self.uses = {} self.uses = {}
self.globals = {} self.globals = {}
self.params = {} self.params = {}
self.frees = {}
self.cells = {}
self.children = [] self.children = []
# nested is true if the class could contain free variables,
# i.e. if it is nested within another function.
self.nested = None
self.klass = None self.klass = None
if klass is not None: if klass is not None:
for i in range(len(klass)): for i in range(len(klass)):
...@@ -70,13 +78,112 @@ class Scope: ...@@ -70,13 +78,112 @@ class Scope:
def get_children(self): def get_children(self):
return self.children return self.children
def DEBUG(self):
return
print >> sys.stderr, self.name, self.nested and "nested" or ""
print >> sys.stderr, "\tglobals: ", self.globals
print >> sys.stderr, "\tcells: ", self.cells
print >> sys.stderr, "\tdefs: ", self.defs
print >> sys.stderr, "\tuses: ", self.uses
print >> sys.stderr, "\tfrees:", self.frees
def check_name(self, name):
"""Return scope of name.
The scope of a name could be LOCAL, GLOBAL, FREE, or CELL.
"""
if self.globals.has_key(name):
return SC_GLOBAL
if self.cells.has_key(name):
return SC_CELL
if self.defs.has_key(name):
return SC_LOCAL
if self.nested and (self.frees.has_key(name) or
self.uses.has_key(name)):
return SC_FREE
if self.nested:
return SC_UNKNOWN
else:
return SC_GLOBAL
def get_free_vars(self):
if not self.nested:
return ()
free = {}
free.update(self.frees)
for name in self.uses.keys():
if not (self.defs.has_key(name) or
self.globals.has_key(name)):
free[name] = 1
return free.keys()
def handle_children(self):
for child in self.children:
frees = child.get_free_vars()
globals = self.add_frees(frees)
for name in globals:
child.force_global(name)
def force_global(self, name):
"""Force name to be global in scope.
Some child of the current node had a free reference to name.
When the child was processed, it was labelled a free
variable. Now that all its enclosing scope have been
processed, the name is known to be a global or builtin. So
walk back down the child chain and set the name to be global
rather than free.
Be careful to stop if a child does not think the name is
free.
"""
self.globals[name] = 1
if self.frees.has_key(name):
del self.frees[name]
for child in self.children:
if child.check_name(name) == SC_FREE:
child.force_global(name)
def add_frees(self, names):
"""Process list of free vars from nested scope.
Returns a list of names that are either 1) declared global in the
parent or 2) undefined in a top-level parent. In either case,
the nested scope should treat them as globals.
"""
child_globals = []
for name in names:
sc = self.check_name(name)
if self.nested:
if sc == SC_UNKNOWN or sc == SC_FREE \
or isinstance(self, ClassScope):
self.frees[name] = 1
elif sc == SC_GLOBAL:
child_globals.append(name)
elif isinstance(self, FunctionScope) and sc == SC_LOCAL:
self.cells[name] = 1
else:
child_globals.append(name)
else:
if sc == SC_LOCAL:
self.cells[name] = 1
else:
child_globals.append(name)
return child_globals
def get_cell_vars(self):
return self.cells.keys()
class ModuleScope(Scope): class ModuleScope(Scope):
__super_init = Scope.__init__ __super_init = Scope.__init__
def __init__(self): def __init__(self):
self.__super_init("global", self) self.__super_init("global", self)
class LambdaScope(Scope): class FunctionScope(Scope):
pass
class LambdaScope(FunctionScope):
__super_init = Scope.__init__ __super_init = Scope.__init__
__counter = 1 __counter = 1
...@@ -86,9 +193,6 @@ class LambdaScope(Scope): ...@@ -86,9 +193,6 @@ class LambdaScope(Scope):
self.__counter += 1 self.__counter += 1
self.__super_init("lambda.%d" % i, module, klass) self.__super_init("lambda.%d" % i, module, klass)
class FunctionScope(Scope):
pass
class ClassScope(Scope): class ClassScope(Scope):
__super_init = Scope.__init__ __super_init = Scope.__init__
...@@ -111,17 +215,24 @@ class SymbolVisitor: ...@@ -111,17 +215,24 @@ class SymbolVisitor:
for n in node.defaults: for n in node.defaults:
self.visit(n, parent) self.visit(n, parent)
scope = FunctionScope(node.name, self.module, self.klass) scope = FunctionScope(node.name, self.module, self.klass)
if parent.nested or isinstance(parent, FunctionScope):
scope.nested = 1
self.scopes[node] = scope self.scopes[node] = scope
self._do_args(scope, node.argnames) self._do_args(scope, node.argnames)
self.visit(node.code, scope) self.visit(node.code, scope)
self.handle_free_vars(scope, parent)
scope.DEBUG()
def visitLambda(self, node, parent): def visitLambda(self, node, parent):
for n in node.defaults: for n in node.defaults:
self.visit(n, parent) self.visit(n, parent)
scope = LambdaScope(self.module, self.klass) scope = LambdaScope(self.module, self.klass)
if parent.nested or isinstance(parent, FunctionScope):
scope.nested = 1
self.scopes[node] = scope self.scopes[node] = scope
self._do_args(scope, node.argnames) self._do_args(scope, node.argnames)
self.visit(node.code, scope) self.visit(node.code, scope)
self.handle_free_vars(scope, parent)
def _do_args(self, scope, args): def _do_args(self, scope, args):
for name in args: for name in args:
...@@ -130,16 +241,25 @@ class SymbolVisitor: ...@@ -130,16 +241,25 @@ class SymbolVisitor:
else: else:
scope.add_param(name) scope.add_param(name)
def handle_free_vars(self, scope, parent):
parent.add_child(scope)
if scope.children:
scope.DEBUG()
scope.handle_children()
def visitClass(self, node, parent): def visitClass(self, node, parent):
parent.add_def(node.name) parent.add_def(node.name)
for n in node.bases: for n in node.bases:
self.visit(n, parent) self.visit(n, parent)
scope = ClassScope(node.name, self.module) scope = ClassScope(node.name, self.module)
if parent.nested or isinstance(parent, FunctionScope):
scope.nested = 1
self.scopes[node] = scope self.scopes[node] = scope
prev = self.klass prev = self.klass
self.klass = node.name self.klass = node.name
self.visit(node.code, scope) self.visit(node.code, scope)
self.klass = prev self.klass = prev
self.handle_free_vars(scope, parent)
# name can be a def or a use # name can be a def or a use
......
...@@ -99,12 +99,6 @@ class FlowGraph: ...@@ -99,12 +99,6 @@ class FlowGraph:
if not self.exit in order: if not self.exit in order:
order.append(self.exit) order.append(self.exit)
## for b in order:
## print repr(b)
## print "\t", b.get_children()
## print b
## print
return order return order
def getBlocks(self): def getBlocks(self):
...@@ -222,6 +216,7 @@ CO_OPTIMIZED = 0x0001 ...@@ -222,6 +216,7 @@ CO_OPTIMIZED = 0x0001
CO_NEWLOCALS = 0x0002 CO_NEWLOCALS = 0x0002
CO_VARARGS = 0x0004 CO_VARARGS = 0x0004
CO_VARKEYWORDS = 0x0008 CO_VARKEYWORDS = 0x0008
CO_NESTED = 0x0010
# the FlowGraph is transformed in place; it exists in one of these states # the FlowGraph is transformed in place; it exists in one of these states
RAW = "RAW" RAW = "RAW"
...@@ -245,6 +240,15 @@ class PyFlowGraph(FlowGraph): ...@@ -245,6 +240,15 @@ class PyFlowGraph(FlowGraph):
self.flags = 0 self.flags = 0
self.consts = [] self.consts = []
self.names = [] self.names = []
# Free variables found by the symbol table scan, including
# variables used only in nested scopes, are included here.
self.freevars = []
self.cellvars = []
# The closure list is used to track the order of cell
# variables and free variables in the resulting code object.
# The offsets used by LOAD_CLOSURE/LOAD_DEREF refer to both
# kinds of variables.
self.closure = []
self.varnames = list(args) or [] self.varnames = list(args) or []
for i in range(len(self.varnames)): for i in range(len(self.varnames)):
var = self.varnames[i] var = self.varnames[i]
...@@ -260,6 +264,12 @@ class PyFlowGraph(FlowGraph): ...@@ -260,6 +264,12 @@ class PyFlowGraph(FlowGraph):
if flag == CO_VARARGS: if flag == CO_VARARGS:
self.argcount = self.argcount - 1 self.argcount = self.argcount - 1
def setFreeVars(self, names):
self.freevars = list(names)
def setCellVars(self, names):
self.cellvars = names
def getCode(self): def getCode(self):
"""Get a Python code object""" """Get a Python code object"""
if self.stage == RAW: if self.stage == RAW:
...@@ -335,6 +345,7 @@ class PyFlowGraph(FlowGraph): ...@@ -335,6 +345,7 @@ class PyFlowGraph(FlowGraph):
"""Convert arguments from symbolic to concrete form""" """Convert arguments from symbolic to concrete form"""
assert self.stage == FLAT assert self.stage == FLAT
self.consts.insert(0, self.docstring) self.consts.insert(0, self.docstring)
self.sort_cellvars()
for i in range(len(self.insts)): for i in range(len(self.insts)):
t = self.insts[i] t = self.insts[i]
if len(t) == 2: if len(t) == 2:
...@@ -345,6 +356,19 @@ class PyFlowGraph(FlowGraph): ...@@ -345,6 +356,19 @@ class PyFlowGraph(FlowGraph):
self.insts[i] = opname, conv(self, oparg) self.insts[i] = opname, conv(self, oparg)
self.stage = CONV self.stage = CONV
def sort_cellvars(self):
"""Sort cellvars in the order of varnames and prune from freevars.
"""
cells = {}
for name in self.cellvars:
cells[name] = 1
self.cellvars = [name for name in self.varnames
if cells.has_key(name)]
for name in self.cellvars:
del cells[name]
self.cellvars = self.cellvars + cells.keys()
self.closure = self.cellvars + self.freevars
def _lookupName(self, name, list): def _lookupName(self, name, list):
"""Return index of name in list, appending if necessary""" """Return index of name in list, appending if necessary"""
t = type(name) t = type(name)
...@@ -382,6 +406,17 @@ class PyFlowGraph(FlowGraph): ...@@ -382,6 +406,17 @@ class PyFlowGraph(FlowGraph):
_convert_STORE_GLOBAL = _convert_NAME _convert_STORE_GLOBAL = _convert_NAME
_convert_DELETE_GLOBAL = _convert_NAME _convert_DELETE_GLOBAL = _convert_NAME
def _convert_DEREF(self, arg):
self._lookupName(arg, self.names)
self._lookupName(arg, self.varnames)
return self._lookupName(arg, self.closure)
_convert_LOAD_DEREF = _convert_DEREF
_convert_STORE_DEREF = _convert_DEREF
def _convert_LOAD_CLOSURE(self, arg):
self._lookupName(arg, self.varnames)
return self._lookupName(arg, self.closure)
_cmp = list(dis.cmp_op) _cmp = list(dis.cmp_op)
def _convert_COMPARE_OP(self, arg): def _convert_COMPARE_OP(self, arg):
return self._cmp.index(arg) return self._cmp.index(arg)
...@@ -432,7 +467,8 @@ class PyFlowGraph(FlowGraph): ...@@ -432,7 +467,8 @@ class PyFlowGraph(FlowGraph):
self.lnotab.getCode(), self.getConsts(), self.lnotab.getCode(), self.getConsts(),
tuple(self.names), tuple(self.varnames), tuple(self.names), tuple(self.varnames),
self.filename, self.name, self.lnotab.firstline, self.filename, self.name, self.lnotab.firstline,
self.lnotab.getTable()) self.lnotab.getTable(), tuple(self.freevars),
tuple(self.cellvars))
def getConsts(self): def getConsts(self):
"""Return a tuple for the const slot of the code object """Return a tuple for the const slot of the code object
......
This diff is collapsed.
"""Module symbol-table generator""" """Module symbol-table generator"""
from compiler import ast from compiler import ast
from compiler.consts import SC_LOCAL, SC_GLOBAL, SC_FREE, SC_CELL, SC_UNKNOWN
import types import types
import sys
MANGLE_LEN = 256 MANGLE_LEN = 256
class Scope: class Scope:
...@@ -14,7 +17,12 @@ class Scope: ...@@ -14,7 +17,12 @@ class Scope:
self.uses = {} self.uses = {}
self.globals = {} self.globals = {}
self.params = {} self.params = {}
self.frees = {}
self.cells = {}
self.children = [] self.children = []
# nested is true if the class could contain free variables,
# i.e. if it is nested within another function.
self.nested = None
self.klass = None self.klass = None
if klass is not None: if klass is not None:
for i in range(len(klass)): for i in range(len(klass)):
...@@ -70,13 +78,112 @@ class Scope: ...@@ -70,13 +78,112 @@ class Scope:
def get_children(self): def get_children(self):
return self.children return self.children
def DEBUG(self):
return
print >> sys.stderr, self.name, self.nested and "nested" or ""
print >> sys.stderr, "\tglobals: ", self.globals
print >> sys.stderr, "\tcells: ", self.cells
print >> sys.stderr, "\tdefs: ", self.defs
print >> sys.stderr, "\tuses: ", self.uses
print >> sys.stderr, "\tfrees:", self.frees
def check_name(self, name):
"""Return scope of name.
The scope of a name could be LOCAL, GLOBAL, FREE, or CELL.
"""
if self.globals.has_key(name):
return SC_GLOBAL
if self.cells.has_key(name):
return SC_CELL
if self.defs.has_key(name):
return SC_LOCAL
if self.nested and (self.frees.has_key(name) or
self.uses.has_key(name)):
return SC_FREE
if self.nested:
return SC_UNKNOWN
else:
return SC_GLOBAL
def get_free_vars(self):
if not self.nested:
return ()
free = {}
free.update(self.frees)
for name in self.uses.keys():
if not (self.defs.has_key(name) or
self.globals.has_key(name)):
free[name] = 1
return free.keys()
def handle_children(self):
for child in self.children:
frees = child.get_free_vars()
globals = self.add_frees(frees)
for name in globals:
child.force_global(name)
def force_global(self, name):
"""Force name to be global in scope.
Some child of the current node had a free reference to name.
When the child was processed, it was labelled a free
variable. Now that all its enclosing scope have been
processed, the name is known to be a global or builtin. So
walk back down the child chain and set the name to be global
rather than free.
Be careful to stop if a child does not think the name is
free.
"""
self.globals[name] = 1
if self.frees.has_key(name):
del self.frees[name]
for child in self.children:
if child.check_name(name) == SC_FREE:
child.force_global(name)
def add_frees(self, names):
"""Process list of free vars from nested scope.
Returns a list of names that are either 1) declared global in the
parent or 2) undefined in a top-level parent. In either case,
the nested scope should treat them as globals.
"""
child_globals = []
for name in names:
sc = self.check_name(name)
if self.nested:
if sc == SC_UNKNOWN or sc == SC_FREE \
or isinstance(self, ClassScope):
self.frees[name] = 1
elif sc == SC_GLOBAL:
child_globals.append(name)
elif isinstance(self, FunctionScope) and sc == SC_LOCAL:
self.cells[name] = 1
else:
child_globals.append(name)
else:
if sc == SC_LOCAL:
self.cells[name] = 1
else:
child_globals.append(name)
return child_globals
def get_cell_vars(self):
return self.cells.keys()
class ModuleScope(Scope): class ModuleScope(Scope):
__super_init = Scope.__init__ __super_init = Scope.__init__
def __init__(self): def __init__(self):
self.__super_init("global", self) self.__super_init("global", self)
class LambdaScope(Scope): class FunctionScope(Scope):
pass
class LambdaScope(FunctionScope):
__super_init = Scope.__init__ __super_init = Scope.__init__
__counter = 1 __counter = 1
...@@ -86,9 +193,6 @@ class LambdaScope(Scope): ...@@ -86,9 +193,6 @@ class LambdaScope(Scope):
self.__counter += 1 self.__counter += 1
self.__super_init("lambda.%d" % i, module, klass) self.__super_init("lambda.%d" % i, module, klass)
class FunctionScope(Scope):
pass
class ClassScope(Scope): class ClassScope(Scope):
__super_init = Scope.__init__ __super_init = Scope.__init__
...@@ -111,17 +215,24 @@ class SymbolVisitor: ...@@ -111,17 +215,24 @@ class SymbolVisitor:
for n in node.defaults: for n in node.defaults:
self.visit(n, parent) self.visit(n, parent)
scope = FunctionScope(node.name, self.module, self.klass) scope = FunctionScope(node.name, self.module, self.klass)
if parent.nested or isinstance(parent, FunctionScope):
scope.nested = 1
self.scopes[node] = scope self.scopes[node] = scope
self._do_args(scope, node.argnames) self._do_args(scope, node.argnames)
self.visit(node.code, scope) self.visit(node.code, scope)
self.handle_free_vars(scope, parent)
scope.DEBUG()
def visitLambda(self, node, parent): def visitLambda(self, node, parent):
for n in node.defaults: for n in node.defaults:
self.visit(n, parent) self.visit(n, parent)
scope = LambdaScope(self.module, self.klass) scope = LambdaScope(self.module, self.klass)
if parent.nested or isinstance(parent, FunctionScope):
scope.nested = 1
self.scopes[node] = scope self.scopes[node] = scope
self._do_args(scope, node.argnames) self._do_args(scope, node.argnames)
self.visit(node.code, scope) self.visit(node.code, scope)
self.handle_free_vars(scope, parent)
def _do_args(self, scope, args): def _do_args(self, scope, args):
for name in args: for name in args:
...@@ -130,16 +241,25 @@ class SymbolVisitor: ...@@ -130,16 +241,25 @@ class SymbolVisitor:
else: else:
scope.add_param(name) scope.add_param(name)
def handle_free_vars(self, scope, parent):
parent.add_child(scope)
if scope.children:
scope.DEBUG()
scope.handle_children()
def visitClass(self, node, parent): def visitClass(self, node, parent):
parent.add_def(node.name) parent.add_def(node.name)
for n in node.bases: for n in node.bases:
self.visit(n, parent) self.visit(n, parent)
scope = ClassScope(node.name, self.module) scope = ClassScope(node.name, self.module)
if parent.nested or isinstance(parent, FunctionScope):
scope.nested = 1
self.scopes[node] = scope self.scopes[node] = scope
prev = self.klass prev = self.klass
self.klass = node.name self.klass = node.name
self.visit(node.code, scope) self.visit(node.code, scope)
self.klass = prev self.klass = prev
self.handle_free_vars(scope, parent)
# name can be a def or a use # name can be a def or a use
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment