Skip to content

Commit bdbd0fb

Browse files
author
hartsantler
committed
added support for getter/setter @Property decorator
1 parent 6d7af79 commit bdbd0fb

3 files changed

Lines changed: 149 additions & 11 deletions

File tree

pythonscript/python_to_pythonjs.py

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ def __init__(self):
7474
self._catch_attributes = None
7575
self._names = set()
7676
self._instances = dict() ## instance name : class name
77+
self._decorator_properties = dict()
78+
self._decorator_class_props = dict()
7779

7880
def visit_Assert(self, node):
7981
## hijacking "assert isinstance(a,A)" as a type system ##
@@ -128,6 +130,8 @@ def visit_ClassDef(self, node):
128130
name = node.name
129131
self._classes[ name ] = list() ## method names
130132
self._catch_attributes = None
133+
self._decorator_properties = dict() ## property names : {'get':func, 'set':func}
134+
131135
for dec in node.decorator_list:
132136
if isinstance(dec, Name) and dec.id == 'inline':
133137
self._catch_attributes = set()
@@ -142,10 +146,15 @@ def visit_ClassDef(self, node):
142146
if isinstance(item, FunctionDef):
143147
self._classes[ name ].append( item.name )
144148
item_name = item.name
149+
item.original_name = item.name
145150
item.name = '__%s_%s' % (name, item_name)
151+
146152
self.visit(item) # this will output the code for the function
147-
#writer.write('__%s_attrs.%s = %s' % (name, item_name, item.name)) ## not ClosureCompiler compatible
148-
writer.write('__%s_attrs["%s"] = %s' % (name, item_name, item.name))
153+
154+
if item_name in self._decorator_properties:
155+
pass
156+
else:
157+
writer.write('__%s_attrs["%s"] = %s' % (name, item_name, item.name))
149158

150159
if item_name == '__getattr__':
151160
writer.write( self._gen_getattr_helper(name, item.name) )
@@ -154,11 +163,16 @@ def visit_ClassDef(self, node):
154163
item_name = item.targets[0].id
155164
item.targets[0].id = '__%s_%s' % (name.id, item_name)
156165
self.visit(item) # this will output the code for the assign
157-
#writer.write('%s_attrs.%s = %s' % (name, item_name, item.targets[0].id)) ## not ClosureCompiler compatible
158166
writer.write('%s_attrs["%s"] = %s' % (name, item_name, item.targets[0].id))
159167

160-
if self._catch_attributes: self._inline_classes[ name ] = self._catch_attributes
168+
if self._catch_attributes:
169+
self._inline_classes[ name ] = self._catch_attributes
170+
if self._decorator_properties:
171+
self._decorator_class_props[ name ] = self._decorator_properties
172+
writer.write('#@props: %s'%self._decorator_properties)
173+
161174
self._catch_attributes = None
175+
self._decorator_properties = None
162176

163177
writer.write('%s = create_class("%s", __%s_parents, __%s_attrs)' % (name, name, name, name))
164178

@@ -264,6 +278,9 @@ def visit_Attribute(self, node):
264278
return '''JS('%s["__dict__"]["%s"]')''' %(name, node.attr)
265279
elif node.attr in self._classes[ klass ]: ## method
266280
return '''JS('__%s_attrs["%s"]')''' %(klass, node.attr)
281+
elif klass in self._decorator_class_props and node.attr in self._decorator_class_props[klass]:
282+
getter = self._decorator_class_props[klass][node.attr]['get']
283+
return '''JS('%s( [%s] )')''' %(getter, name)
267284
else:
268285
return '''JS('__%s___getattr__( [%s, "%s"] )')''' %(klass, name, node.attr)
269286

@@ -275,8 +292,16 @@ def visit_Attribute(self, node):
275292
return '''JS('%s["__dict__"]["%s"]')''' %(name, node.attr)
276293
elif node.attr in self._classes[ klass ]: ## method
277294
return '''JS('__%s_attrs["%s"]')''' %(klass, node.attr)
295+
elif klass in self._decorator_class_props and node.attr in self._decorator_class_props[klass]:
296+
getter = self._decorator_class_props[klass][node.attr]['get']
297+
return '''JS('%s( [%s] )')''' %(getter, name)
278298
else:
279299
return '''JS('__%s___getattr__( [%s, "%s"] )')''' %(klass, name, node.attr)
300+
301+
elif klass in self._decorator_class_props and node.attr in self._decorator_class_props[klass]:
302+
getter = self._decorator_class_props[klass][node.attr]['get']
303+
return '''JS('%s( [%s] )')''' %(getter, name)
304+
280305
else:
281306
return 'get_attribute(%s, "%s")' % (name, node.attr)
282307
else:
@@ -317,12 +342,23 @@ def visit_Assign(self, node):
317342
if name == 'self' and isinstance(self._catch_attributes, set):
318343
self._catch_attributes.add( target.attr )
319344

320-
code = 'set_attribute(%s, "%s", %s)' % (
321-
name,
322-
target.attr,
323-
self.visit(node.value)
324-
)
325-
writer.write(code)
345+
fallback = True
346+
if name in self._instances: ## support '.' operator overloading
347+
klass = self._instances[ name ]
348+
if klass in self._decorator_class_props and target.attr in self._decorator_class_props[klass]:
349+
setter = self._decorator_class_props[klass][target.attr].get( 'set', None )
350+
if setter:
351+
writer.write( '''JS('%s( [%s, %s] )')''' %(setter, name, self.visit(node.value)) )
352+
fallback = False
353+
354+
355+
if fallback:
356+
code = 'set_attribute(%s, "%s", %s)' % (
357+
name,
358+
target.attr,
359+
self.visit(node.value)
360+
)
361+
writer.write(code)
326362
elif isinstance(target, Name):
327363

328364
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):
401437
return '%s()' %name
402438

403439
def visit_FunctionDef(self, node):
440+
property_decorator = None
441+
decorators = []
442+
for decorator in reversed(node.decorator_list):
443+
if isinstance(decorator, Name) and decorator.id == 'property':
444+
property_decorator = decorator
445+
n = node.name + '__getprop__'
446+
self._decorator_properties[ node.original_name ] = dict( get=n )
447+
node.name = n
448+
449+
elif isinstance(decorator, Attribute) and isinstance(decorator.value, Name) and decorator.value.id in self._decorator_properties:
450+
if decorator.attr == 'setter':
451+
n = node.name + '__setprop__'
452+
self._decorator_properties[ decorator.value.id ]['set'] = n
453+
node.name = n
454+
elif decorator.attr == 'deleter':
455+
raise NotImplementedError
456+
else:
457+
raise RuntimeError
458+
459+
else:
460+
decorators.append( decorator )
461+
462+
404463
writer.write('def %s(args, kwargs):' % node.name)
405464
writer.push()
406465

@@ -470,7 +529,7 @@ def visit_FunctionDef(self, node):
470529
writer.pull()
471530

472531
# apply decorators
473-
for decorator in reversed(node.decorator_list):
532+
for decorator in decorators:
474533
writer.write('%s = %s(create_array(%s))' % (node.name, self.visit(decorator), node.name))
475534

476535
def visit_For(self, node):

tests/first-class_function.html

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<html>
2+
<head>
3+
<script src="pythonscript.js"></script>
4+
5+
<script type="text/python" closure="true">
6+
def myfunc():
7+
return 1
8+
9+
myfunc.a = "i am first class"
10+
myfunc.b = 100
11+
myfunc.c = 3.14
12+
13+
def other( f ):
14+
print( f.a )
15+
16+
def test():
17+
print( myfunc.a )
18+
print( myfunc.b )
19+
print( myfunc.c )
20+
other( myfunc ) ## test function to another function
21+
22+
</script>
23+
</head>
24+
25+
<body>
26+
<button onclick="test()">click me</button>
27+
<br/>
28+
<a href="http://en.wikipedia.org/wiki/First-class_function">http://en.wikipedia.org/wiki/First-class_function</a>
29+
</body>
30+
</html>

tests/property_decorator.html

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<html>
2+
<head>
3+
<script src="pythonscript.js"></script>
4+
5+
<script type="text/python" closure="true">
6+
7+
class A:
8+
def __init__(self):
9+
self._x = 1
10+
self._y = 2
11+
self._z = 3
12+
13+
@property
14+
def x(self):
15+
return self._x
16+
@x.setter
17+
def x(self,value):
18+
self._x = value
19+
20+
@property
21+
def y(self):
22+
return self._y
23+
@y.setter
24+
def y(self,value):
25+
self._y = value
26+
27+
@property
28+
def z(self):
29+
return self._z
30+
@z.setter
31+
def z(self,value):
32+
self._z = value
33+
34+
def test():
35+
a = A()
36+
print( a.x )
37+
print( a.y )
38+
print( a.z )
39+
40+
a.x = 100
41+
print( a.x )
42+
43+
</script>
44+
</head>
45+
46+
<body>
47+
<button onclick="test()">click me</button>
48+
</body>
49+
</html>

0 commit comments

Comments
 (0)