150 likes | 311 Views
DSM 2006 22 October 2006 Tero Hasu tero.hasu@hiit.fi Helsinki Institute for Information Technology. Programmatic Building of Models Just for Pretty Printing. Outline. How to generate nicely laid out source code easily? Constructive pretty printing.
E N D
DSM 200622 October 2006 Tero Hasutero.hasu@hiit.fiHelsinki Institute for Information Technology Programmatic Building of Models Just for Pretty Printing
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(<%= args %>); ...”) • (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));
[:person, [:name, "Jim"], [:phone, "5551234"]] p = Person.newp.name = "Jim"p.phone = "5551234" 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
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)
xm.div { xm.br } <div> <br/></div> 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
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 • http://pdis.hiit.fi/s4all/download/qretty/
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 => :@list) 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 endend
OpenWindow()DisplayGreeting()if IsNewUser() then DisplayHelp() CreateAccount()endMakeVisible() Model Building Example • model = ast::Program.newmodel.call_expr "OpenWindow"model.call_expr "DisplayGreeting"if0 = model.if_expr do |if0| if0.cond "IsNewUser" if0.call_expr "DisplayHelp"endmodel.call_expr "MakeVisible"unless $no_database if0.call_expr "CreateAccount"end
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