Permalink
Browse files

added support for getter/setter @Property decorator

  • Loading branch information...
hartsantler committed Oct 3, 2013
1 parent 6d7af79 commit bdbd0fb0d01d104ab2637828834462a22a4b5629
Showing with 149 additions and 11 deletions.
  1. +70 −11 pythonscript/python_to_pythonjs.py
  2. +30 −0 tests/first-class_function.html
  3. +49 −0 tests/property_decorator.html
@@ -74,6 +74,8 @@ def __init__(self):
self._catch_attributes = None
self._names = set()
self._instances = dict() ## instance name : class name
self._decorator_properties = dict()
self._decorator_class_props = dict()
def visit_Assert(self, node):
## hijacking "assert isinstance(a,A)" as a type system ##
@@ -128,6 +130,8 @@ def visit_ClassDef(self, node):
name = node.name
self._classes[ name ] = list() ## method names
self._catch_attributes = None
self._decorator_properties = dict() ## property names : {'get':func, 'set':func}
for dec in node.decorator_list:
if isinstance(dec, Name) and dec.id == 'inline':
self._catch_attributes = set()
@@ -142,10 +146,15 @@ def visit_ClassDef(self, node):
if isinstance(item, FunctionDef):
self._classes[ name ].append( item.name )
item_name = item.name
item.original_name = item.name
item.name = '__%s_%s' % (name, item_name)
self.visit(item) # this will output the code for the function
#writer.write('__%s_attrs.%s = %s' % (name, item_name, item.name)) ## not ClosureCompiler compatible
writer.write('__%s_attrs["%s"] = %s' % (name, item_name, item.name))
if item_name in self._decorator_properties:
pass
else:
writer.write('__%s_attrs["%s"] = %s' % (name, item_name, item.name))
if item_name == '__getattr__':
writer.write( self._gen_getattr_helper(name, item.name) )
@@ -154,11 +163,16 @@ def visit_ClassDef(self, node):
item_name = item.targets[0].id
item.targets[0].id = '__%s_%s' % (name.id, item_name)
self.visit(item) # this will output the code for the assign
#writer.write('%s_attrs.%s = %s' % (name, item_name, item.targets[0].id)) ## not ClosureCompiler compatible
writer.write('%s_attrs["%s"] = %s' % (name, item_name, item.targets[0].id))
if self._catch_attributes: self._inline_classes[ name ] = self._catch_attributes
if self._catch_attributes:
self._inline_classes[ name ] = self._catch_attributes
if self._decorator_properties:
self._decorator_class_props[ name ] = self._decorator_properties
writer.write('#@props: %s'%self._decorator_properties)
self._catch_attributes = None
self._decorator_properties = None
writer.write('%s = create_class("%s", __%s_parents, __%s_attrs)' % (name, name, name, name))
@@ -264,6 +278,9 @@ def visit_Attribute(self, node):
return '''JS('%s["__dict__"]["%s"]')''' %(name, node.attr)
elif node.attr in self._classes[ klass ]: ## method
return '''JS('__%s_attrs["%s"]')''' %(klass, node.attr)
elif klass in self._decorator_class_props and node.attr in self._decorator_class_props[klass]:
getter = self._decorator_class_props[klass][node.attr]['get']
return '''JS('%s( [%s] )')''' %(getter, name)
else:
return '''JS('__%s___getattr__( [%s, "%s"] )')''' %(klass, name, node.attr)
@@ -275,8 +292,16 @@ def visit_Attribute(self, node):
return '''JS('%s["__dict__"]["%s"]')''' %(name, node.attr)
elif node.attr in self._classes[ klass ]: ## method
return '''JS('__%s_attrs["%s"]')''' %(klass, node.attr)
elif klass in self._decorator_class_props and node.attr in self._decorator_class_props[klass]:
getter = self._decorator_class_props[klass][node.attr]['get']
return '''JS('%s( [%s] )')''' %(getter, name)
else:
return '''JS('__%s___getattr__( [%s, "%s"] )')''' %(klass, name, node.attr)
elif klass in self._decorator_class_props and node.attr in self._decorator_class_props[klass]:
getter = self._decorator_class_props[klass][node.attr]['get']
return '''JS('%s( [%s] )')''' %(getter, name)
else:
return 'get_attribute(%s, "%s")' % (name, node.attr)
else:
@@ -317,12 +342,23 @@ def visit_Assign(self, node):
if name == 'self' and isinstance(self._catch_attributes, set):
self._catch_attributes.add( target.attr )
code = 'set_attribute(%s, "%s", %s)' % (
name,
target.attr,
self.visit(node.value)
)
writer.write(code)
fallback = True
if name in self._instances: ## support '.' operator overloading
klass = self._instances[ name ]
if klass in self._decorator_class_props and target.attr in self._decorator_class_props[klass]:
setter = self._decorator_class_props[klass][target.attr].get( 'set', None )
if setter:
writer.write( '''JS('%s( [%s, %s] )')''' %(setter, name, self.visit(node.value)) )
fallback = False
if fallback:
code = 'set_attribute(%s, "%s", %s)' % (
name,
target.attr,
self.visit(node.value)
)
writer.write(code)
elif isinstance(target, Name):
if isinstance(node.value, Call) and hasattr(node.value.func, 'id') and node.value.func.id in self._classes:
@@ -401,6 +437,29 @@ def visit_Call(self, node):
return '%s()' %name
def visit_FunctionDef(self, node):
property_decorator = None
decorators = []
for decorator in reversed(node.decorator_list):
if isinstance(decorator, Name) and decorator.id == 'property':
property_decorator = decorator
n = node.name + '__getprop__'
self._decorator_properties[ node.original_name ] = dict( get=n )
node.name = n
elif isinstance(decorator, Attribute) and isinstance(decorator.value, Name) and decorator.value.id in self._decorator_properties:
if decorator.attr == 'setter':
n = node.name + '__setprop__'
self._decorator_properties[ decorator.value.id ]['set'] = n
node.name = n
elif decorator.attr == 'deleter':
raise NotImplementedError
else:
raise RuntimeError
else:
decorators.append( decorator )
writer.write('def %s(args, kwargs):' % node.name)
writer.push()
@@ -470,7 +529,7 @@ def visit_FunctionDef(self, node):
writer.pull()
# apply decorators
for decorator in reversed(node.decorator_list):
for decorator in decorators:
writer.write('%s = %s(create_array(%s))' % (node.name, self.visit(decorator), node.name))
def visit_For(self, node):
@@ -0,0 +1,30 @@
<html>
<head>
<script src="pythonscript.js"></script>
<script type="text/python" closure="true">
def myfunc():
return 1
myfunc.a = "i am first class"
myfunc.b = 100
myfunc.c = 3.14
def other( f ):
print( f.a )
def test():
print( myfunc.a )
print( myfunc.b )
print( myfunc.c )
other( myfunc ) ## test function to another function
</script>
</head>
<body>
<button onclick="test()">click me</button>
<br/>
<a href="http://en.wikipedia.org/wiki/First-class_function">http://en.wikipedia.org/wiki/First-class_function</a>
</body>
</html>
@@ -0,0 +1,49 @@
<html>
<head>
<script src="pythonscript.js"></script>
<script type="text/python" closure="true">
class A:
def __init__(self):
self._x = 1
self._y = 2
self._z = 3
@property
def x(self):
return self._x
@x.setter
def x(self,value):
self._x = value
@property
def y(self):
return self._y
@y.setter
def y(self,value):
self._y = value
@property
def z(self):
return self._z
@z.setter
def z(self,value):
self._z = value
def test():
a = A()
print( a.x )
print( a.y )
print( a.z )
a.x = 100
print( a.x )
</script>
</head>
<body>
<button onclick="test()">click me</button>
</body>
</html>

0 comments on commit bdbd0fb

Please sign in to comment.