#!/usr/bin/python
"""Pretty-print object-calculus expressions with various bits of shorthand.
In particular, elide 'self' names on method definitions that don't
need them; use () instead of {}.'()' in ObjectDerivation expressions
enclosed in a MethodCall(x, '()'); and elide 'arg1', 'arg2', etc., in
ObjectDerivation expressions that contain all of them.
It seemed to me that objcalc.py was getting too large, and I wanted to
pull out the pretty-printing functionality into another file,
particularly since it involves some substantial tree rewriting.
Unfortunately there doesn't seem to be a good way to do this in
Python.
What I'd really like to do is create an object 'objcalcpp' derived
from the 'objcalc' module, add an 'occurs_free' method to several of
the classes 'objcalcpp' inherits from 'objcalc', and override
'objcalcpp.SyntaxNode.stringify_methods',
'objcalcpp.MethodDefinition.__str__', and
'objcalcpp.ObjectDerivation.__str__', but while you can do that kind
of thing in the object-calculus or in Ohmu, you can't do it in Python
or any other current deployed OO language.
Approach #1: create two big functions full of 'isinstance' tests and
lots of tightly-coupled attribute access. Works OK for 'occurs_free',
which only has to compute a boolean over the tree it gets (although I
did leave out a case here, fortunately one I tested for); but for
pretty_print, it needs to either modify or duplicate some of the code
presently found within the objects themselves, namely the __str__
methods of MethodDefinition, ObjectLiteral, ObjectDerivation, and
MethodCall, in order to redirect their recursive calls to itself. Got
this working.
Drawbacks: not very OO (tight coupling, isinstance), error-prone (due
to case analysis), code duplication.
Approach #2: parallel class hierarchy. A code smell in itself, but
could avoid reimplementing various __str__ methods. Probably would
have to use multiple inheritance to redefine __getitem__ and __call__
in all the classes so that the normal syntactic sugar would work.
Approach #3: monkeypatching (known in other languages as 'reopening
classes from another module'). Replace methods of original classes
with 'better' versions. This approach is, in some sense, the closest
approach to AOP.
Approach #4: rewrite in AspectJ.
Approach #5: rewrite in Haskell.
Approach #6: rewrite in the object-calculus.
"""
import objcalc
# approach #1
def template_function_to_copy_and_paste(var, ocexpr):
"""Not very object-oriented!"""
if isinstance(ocexpr, objcalc.Constant):
pass
if isinstance(ocexpr, objcalc.Variable):
pass
elif isinstance(ocexpr, objcalc.ObjectLiteral):
pass
elif isinstance(ocexpr, objcalc.ObjectDerivation):
pass
elif isinstance(ocexpr, objcalc.MethodCall):
pass
raise CantHappen
def occurs_free_in_methods(var, methods):
for method in methods:
if var == method.selfname: continue
if occurs_free(var, method.methodbody): return True
return False
class CantHappen(Exception): pass
def occurs_free(var, ocexpr):
if isinstance(ocexpr, objcalc.Constant):
return False
if isinstance(ocexpr, objcalc.Variable):
return var == ocexpr
elif isinstance(ocexpr, objcalc.ObjectLiteral):
return occurs_free_in_methods(var, ocexpr.methods)
elif isinstance(ocexpr, objcalc.ObjectDerivation):
return (occurs_free(var, ocexpr.baseobj) or
occurs_free_in_methods(var, ocexpr.methods))
elif isinstance(ocexpr, objcalc.MethodCall):
return occurs_free(var, ocexpr.objexpr)
raise CantHappen
def prettyprint_method(method):
name = objcalc.namequoter.quote(method.methodname)
body = prettyprint(method.methodbody)
if occurs_free(method.selfname, method.methodbody):
return '%s.%s = %s' % (prettyprint(method.selfname), name, body)
else:
return '%s = %s' % (name, body)
def prettyprint_methods_of(objexpr):
return objexpr.indent('\n'.join(map(prettyprint_method, objexpr.methods)))
def prettyprint_derivation(ocexpr, delims = '{}'):
left, right = delims
return '%s %s\n%s%s' % (prettyprint(ocexpr.baseobj),
left, prettyprint_methods_of(ocexpr), right)
def prettyprint(ocexpr):
if (isinstance(ocexpr, objcalc.Constant) or
isinstance(ocexpr, objcalc.Variable)):
return str(ocexpr)
elif isinstance(ocexpr, objcalc.ObjectLiteral):
return '{\n%s}' % prettyprint_methods_of(ocexpr)
elif isinstance(ocexpr, objcalc.ObjectDerivation):
return prettyprint_derivation(ocexpr)
elif isinstance(ocexpr, objcalc.MethodCall):
if ocexpr.methodname == '()' and isinstance(ocexpr.objexpr,
objcalc.ObjectDerivation):
return prettyprint_derivation(ocexpr.objexpr, delims = '()')
return '%s.%s' % (prettyprint(ocexpr.objexpr),
objcalc.namequoter.quote(ocexpr.methodname))
raise CantHappen
# approach #2 (unfinished)
class Constant(objcalc.Constant): pass
class Variable(objcalc.Variable): pass
class ObjectLiteral(objcalc.ObjectLiteral): pass
class ObjectDerivation(objcalc.ObjectDerivation): pass
class MethodCall(objcalc.MethodCall): pass
class MethodDefinition(objcalc.MethodDefinition): pass
def ok(a, b): assert a == b, (a, b)
def test():
self = objcalc.Variable('self', 'ocpp')
empty = objcalc.ObjectLiteral([])
_37 = objcalc.Constant(37)
callme = empty(self['()'] >> _37)
called = callme['()']
ok(called.evaluate({}), 37)
# omit 'self' where it doesn't occur free in the definition
ok(occurs_free(self, self), True)
ok(occurs_free(self, _37), False)
# XXX need more tests for occurs_free
ok(prettyprint(self), 'self')
ok(prettyprint(_37), '37')
ok(prettyprint(empty), "{\n}")
ok(prettyprint(callme), "{\n} {\n" +
" '()' = 37\n" +
"}")
ok(prettyprint(callme['cat']), prettyprint(callme) + '.cat')
ok(prettyprint(called), "{\n} (\n" +
" '()' = 37\n" +
")")
test()
def sample_code():
"""Returns an object-calculus expression with a little bit of sample code.
This particular code is intended to be the ultimate parent of all
objects.
The code includes working definitions for 'true' and 'false', and
a possibly-working implementation of Lisp-style lists with ':' for
the cons operator, '++' for the list append operator, a 'filter'
method on unary functions (using the booleans) to extract items
from a list for which that function returns true, and a 'qsort()'
method that uses 'filter', ':', and '++'.
So far there's no support for infix operators, positional
arguments, or not splitting short expressions onto many lines, in
the pretty-printer, so code that should look like this:
'++'.'()' = cons.head : (cons.tail ++ '++'.arg1)
is rendered by the pretty-printer as follows instead:
'++'.'()' = cons.head.':' (
arg1 = cons.tail.'++' (
arg1 = '++'.arg1
)
)
"""
obj = Variable('object', 'ocpp.sample_code')
self = Variable('self', 'ocpp.sample_code')
my = Variable('my', 'ocpp.sample_code')
function = Variable('function', 'ocpp.sample_code')
cons = Variable('cons', 'ocpp.sample_code')
pp = Variable('++', 'ocpp.sample_code')
colon = Variable(':', 'ocpp.sample_code')
x = Variable('x', 'ocpp.sample_code')
filt = Variable('filter', 'ocpp.sample_code')
booleans = Variable('booleans', 'ocpp.sample_code')
return ObjectLiteral([
self['booleans'] >> ObjectLiteral([
booleans['true'] >> ObjectLiteral([
self['negated'] >> booleans['false'],
self['ifTrue'] >> ObjectLiteral([
x['then'] >> Constant(1),
x['else'] >> Constant(0),
x['()'] >> x['then'],
]),
self['ifFalse'] >> self['negated']['ifTrue'],
]),
booleans['false'] >> booleans['true'](
self['negated'] >> booleans['true'],
self['ifTrue'] >> booleans['true']['ifTrue'](
x['()'] >> x['else'],
),
),
]),
# nil is the empty list, and the root of the list-node hierarchy
obj['nil'] >> ObjectLiteral([
self['isNil'] >> obj['booleans']['true'],
self['++'] >> obj['unary_function'] (
pp['arg1'] >> self,
pp['()'] >> pp['arg1'],
),
self['qsort'] >> ObjectLiteral([ my['()'] >> self ]),
]),
# Comparison functions: you only have to define '<' and '==' in
# your classes
obj['>='] >> obj['<']['negated_function'],
obj['<='] >> obj['>']['negated_function'],
obj['!='] >> obj['==']['negated_function'],
# There's no way to do 'flip' without 'getattr'.
obj['>'] >> obj['unary_function'] (
my['()'] >> my['arg1']['<'](self['arg1'] >> obj)['()'],
),
# Unary functions have a method for filtering lists
obj['unary_function'] >> ObjectLiteral([
self['arg1'] >> Constant("Uninitialized unary function argument"),
self['()'] >> Constant("Undefined function result"),
function['filter'] >> obj['unary_function'] (
filt['()'] >> filt['arg1']['isNil']['ifTrue'](
self['then'] >> filt['arg1'],
self['else'] >> function(x['arg1'] >>
filt['arg1']['head'])['()']['ifTrue'](
self['then'] >> filt['arg1'][':'](self['arg1'] >>
filt(x['arg1'] >> filt['arg1']['tail'])
)['()'],
self['else'] >> filt(
x['arg1'] >> filt['arg1']['tail'])['()'],
)['()'],
)['()'],
),
function['negated_function'] >> obj['unary_function'] (
my['()'] >> function(self['arg1'] >>
my['arg1'])['()']['negated'],
),
]),
# Define an infix operator that constructs list nodes
self[':'] >> self['unary_function'] (
colon['()'] >> self['nil'] (
x['isNil'] >> self['booleans']['false'],
x['head'] >> self,
x['tail'] >> colon['arg1'],
cons['++'] >> self['nil']['++'] (
pp['()'] >> cons['head'][':'](
my['arg1'] >> cons['tail']['++'](x['arg1'] >>
pp['arg1'])['()'],
)['()'],
),
self['qsort'] >> ObjectLiteral ([
my['low'] >> self['head']['<']['filter'](x['arg1'] >>
self['tail'])['()'],
my['high'] >> self['head']['>=']['filter'](x['arg1'] >>
self['tail'])['()'],
my['()'] >> my['low']['qsort']()['()']['++'] (
x['arg1'] >> self['head'][':'](
x['arg1'] >> my['high']['qsort']()['()']
)['()'],
)['()'],
]),
),
),
])