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

Reply via email to