plyj
plyj copied to clipboard
Pretty printing?
Are there any plans to add pretty printing?
Are there! Yes, plans are there. Unfortunately work on the project stalled a bit after I unsuccessfully wrestled with Java 8 support. I'll keep this as a todo. Maybe I or someone else will find some time.
Awesome
I just made the basis of a primitive pretty printer, after I tidy it up I'll make a pull request to get your thoughts on it.
So to save time, before refactoring the mess of a pretty printer I wrote, I figured I should ask you how you picture this being implemented. Here's the implementation I'm using at the moment which was never intended to be reused (plus it's only partially implemented).
Current implementation
I originally wrote this before hearing your initial response in the event there was no interest of adding a pretty printer to begin with, so instead of initially incorporating it in the library I just did a bunch if statements to check the type of the AST instead of dynamic dispatch on a common method of each AST.
Suggested Implementation
Printer class
- Handles configuration of output
- indentation
- line breaking
- Implemented in terms of the file/buffer like interface (not sure if correct name), currently using string concatenation in the previous implementation
- IOString
- File
Printer would have an api like so
class Printer:
#
# any configuration, like indentation amount, and whatever makes sense
#
def __init__(self, **config):
#
# would invoke `write_ast` on the `ast`, and reset internal
# pretty printer state, like current indentation, and all output
# will be written to `output` which is a buffer of some sorts
#
def pretty_print(self, ast, output):
#
# tell the pretty printer to bump after the next call to `line_break`
#
def bump_indentation(self, amount):
#
# would handle anything like applying indentation for line
#
def line_break(self):
#
# just abstracting the write action so it's not tied to any implementation,
# which would make the printer class easier to subtype
#
def write(self, string_being_written):
#
# various other methods for pretty printing
#
So use would look like something like this
import plyj.parser as parser
import plyj.printer as printer
import StringIO
parser = parser.Parser()
printer = printer.Printer(indent="\s\s")
ast = parser.parse_file(file('Foo.java'))
out = StringIO.StringIO()
printer.pretty_print(ast, out)
SourceElement class adjustments
Each SourceElement
would have a method like, write_ast
, that only the Printer
class should call and it would handle any AST specific printing behaviour like so.
class ClassDeclaration(SourceElement):
# ...
def write_ast(self, printer):
printer.line_break()
printer.seperate_by(self.modifiers, ' ')
printer.write('class ' + self.name)
if self.type_parameters:
printer.write('<')
printer.seperate_by(self.modifiers, ' ')
printer.write('>')
if self.extends:
# ...
if self.implements:
# ...
self.write(' {')
printer.bump_indentation(1)
printer.line_break()
for declaration in tree.body:
#
# recursively calling the write_ast method
#
declaration.write_ast(printer)
printer.bump_indentation(-1)
printer.line_break()
printer.write('}')
And this would be implemented for each AST
Possible Caveats?
My main reservation about this approach is SourceElement
is a model of some sort and adding this functionality to it may not be desired? But still, it seems like it's the approach that would be the most maintainable, and most sensible approach that I can immediately think of.
Thoughts?
Sorry for the essay btw
Thanks for your input.
Your caveat hits the nail on the head. You're not the first to suggest it this way though. Anything that is not part of the model will stay out of the model. Printing Java is special behavior, same as an XML or JSON representation of the AST would be.
Conditionals over the AST is one way to do it, another would be to implement a visitor which is my current favorite. I haven't done anything in that regard yet but my gut feeling says this will be the most elegant way of doing things. The visitor API is not tested all that well yet and there are still some issues to be solved, particularly when it comes to subclasses (cf. #34). I'm not sure if there is a need for another printer interface. The printer visitor could get all configuration regarding indentation, line breaks and the like and write it out directly. I don't know how well that works in Python, in Java I'd just use a java.io.Writer
. I'm sure Python has something similar that abstracts over character output.
I was unaware of the visitor api, but that sounds like the way to go especially on you start considering alternative output targets.
I agree, Visitor is the way to go for almost everything when working on an AST.
Yeah I agree, I wrote my initial solution when I had no idea what the Visitor pattern was, since then I've done some compiler work and I can see why it would be an ideal approach for this.
Since there doesn't seem have been much movement on this issue (I could be wrong) I may as well refactor my original approach and make a PR when I get a chance, and modify it based on feedback.
You're not wrong. I kept this issue open because it is a sensible feature request that definitely needs attention. I think a simple pretty printer would be tedious because there are lots of AST elements but relatively straight forward to write.