Programmatic Building of Models Just for Pretty Printing DSM October 2006 Tero Hasu Helsinki Institute for Information Technology
Outline How to generate nicely laid out source code easily? Constructive pretty printing. qretty – a tool to support constructive pretty printing.
Constructive Pretty Printing Programmatically construct a model defining what is to be printed – then do a pretty-printing traversal over it. –Models constructed imperatively from objects. –Models concern the domain of prettily printed programs – no extraneous information required. (Yes, one might want programmatic model building in other domains, too.)
Some Approaches to Pretty Printing Source Code Have an abstract syntax tree (AST)? –Traverse that. Don't have one? –Use a template engine. –Or build an AST-like model just so you can traverse it?
Why Build Models Just for Pretty Printing? To separate the specification of the content from the formatting rules. –when specifying what to print, one can focus on actual content, without needing to worry about indentation, line breaking, delimiters –one gets all formatting rules in one place (in the printing routine), not spread around in templates
Approaches to Constructing Models Declaratively: Just list model contents in some syntax, with variable value expressions accounting for variability. – model = parse(“... gc.DrawRect( );...”) – (setq! model (... (call-meth (ident “gc”) “DrawRect” args)...)) Imperatively: Write code that builds a model incrementally by adding objects to a tree structure. – meth.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression(“gc”), “DrawRect”, args));
Possible Reasons for Building Models Imperatively Many currently popular languages lack sufficiently nice list syntax, but have concise syntax for manipulating objects. Named object properties and their accessors make it easy to manipulate models, allowing one to –build a model in any convenient order –build a model incrementally, in multiple passes [:person, [:name, "Jim"], [:phone, " "]] p = Person.new p.name = "Jim" p.phone = " "
What Information to Include in a Model? Less information to include generally means less work in building a model – best to have as little as possible Must know enough about an object to print it correctly......but not necessarily full target language semantics (If an open-ended set of target languages must be supported, full language semantics required.) Any two objects that can be printed in the same way can be added to the model in the same way; no need to specify that different treatment is required (say by using a different setter method)
What Kind of API to Use for Model Building? General-purpose vs. domain- specific API? –General purpose APIs are tedious to use –Specializing the API by hand for a domain is one solution (e.g. Ruby's Builder for Markup for generating XML) –Better way: Generate the API for each domain from a grammar xm.div { xm.br }
Introducing: qretty A Ruby library Supports constructive pretty printing by automating some of the implementation –takes an annotated grammar as input –produces a grammar-specific class hierarchy (for model building) and associated pretty-printing routines
qretty Grammar Specifications Must explicitly specify which non-terminals require a class Hints influencing API generation can be included Hints influencing pretty printing can be included
Example Grammar Specification class ExampleGrammar include Qretty::Dsl def initialize crule(:program, :exprs) arule(:exprs, opt(seplist(:expr, nl))) # all exprs go to same property to preserve order mfield(:expr, :pname => arule(:expr, choice(:call_expr, :if_expr)) crule(:call_expr, seq(ident(:func), "()")) # CallExpr ctor's first argument is function name cfield(:func) crule(:if_expr, seq("if ", :cond, " then", nl, indent(:exprs), nl, "end")) # condition is an instance of CallExpr mfield(:cond, :cname => :call_expr) finalize_grammar end end
Model Building Example model = ast::Program.new model.call_expr "OpenWindow" model.call_expr "DisplayGreeting" if0 = model.if_expr do |if0| if0.cond "IsNewUser" if0.call_expr "DisplayHelp" end model.call_expr "MakeVisible" unless $no_database if0.call_expr "CreateAccount" end OpenWindow() DisplayGreeting() if IsNewUser() then DisplayHelp() CreateAccount() end MakeVisible()
qretty Evaluation Implementation works, but could use redesign and more speed Large existing grammars hard to adapt (e.g. C++) –hard to come up with memorable naming –a lot of work to make class hierarchy shallow enough –grammar specification language imperfections Suggested feature: allow more declarative model building where convenient – program(call_expr(“OpenWindow”), call_expr(“DisplayGreeting”),...)
Summary One approach to pretty printing in program generation: Construct a model, then print it Benefits include: separation of concerns (what and how to print); model build order may differ from printing order qretty helps by generating a model building API and a pretty printer based on an annotated grammar Usable model building API is key, but API design is not easy – even with qretty