#!/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']()['()'] )['()'], )['()'], ]), ), ), ])