Wednesday, January 4, 2017

Text.PrettyPrint: Starting indentation from left margin

Leave a Comment

I'm trying to generate Javascript using Text.PrettyPrint. The problem is that nest produces huge indentation when put next to another prettyprinted element. For example, in this code:

import Text.PrettyPrint  fun :: Doc fun = vcat [ text "function" <+> lbrace            , nest 4 $ vcat $ replicate 5 $ text "// foo"            , rbrace            ]  var :: Doc var = text "var" <+> text "x"  test :: Doc test = var <+> equals <+> fun <> semi 

fun starts on column 9 in test (because of var <+> equals <> empty to the left of it), and thus its subsequent lines are indented by 9+4=13 columns:

var x = function {             // foo             // foo             // foo             // foo             // foo         }; 

Is there a way to render indentations from the left margin, so that the above would be rendered instead as

var x = function {     // foo     // foo     // foo     // foo     // foo }; 

?

3 Answers

Answers 1

The solution is indeed to use wl-pprint (and replace nest with indent). Then, the code given yields

var x = function {     // foo     // foo     // foo     // foo     // foo }; 

as desired. For anyone still intent on working something with trying to hack on pretty, note that although the constructors for Doc aren't exposed, you can still get at them via Generic with -XPatternSynonyms:

-- | Means of exposing the data constructors of `Doc` from `pretty` pattern GEmpty              = M1 (L1 (L1 (L1 (M1 U1)))) pattern GNilAbove doc       = M1 (L1 (L1 (R1 (M1 (M1 (K1 doc)))))) pattern GTextBeside d doc   = M1 (L1 (R1 (L1 (M1 (M1 (K1 d) :*: M1 (K1 doc)))))) pattern GNest n doc         = M1 (L1 (R1 (R1 (M1 (M1 (K1 n) :*: M1 (K1 doc)))))) pattern GUnion ldoc rdoc    = M1 (R1 (L1 (L1 (M1 (M1 (K1 ldoc) :*: M1 (K1 rdoc)))))) pattern GNoDoc              = M1 (R1 (L1 (R1 (M1 U1)))) pattern GBeside ldoc s rdoc = M1 (R1 (R1 (L1 (M1 (M1 (K1 ldoc) :*: M1 (K1 s) :*: M1 (K1 rdoc)))))) pattern GAbove ldoc b rdoc  = M1 (R1 (R1 (R1 (M1 (M1 (K1 ldoc) :*: M1 (K1 b) :*: M1 (K1 rdoc)))))) 

The problem is mostly not violating any of the many invariants the library has under the hood.


As a side note, I also found wl-pprint-annotated, a modern re-write of wl-pprint, with which one has access to underlying data constructors (at the cost of needing to keep in mind the invariants involved). This is actually the package I will end up using.

In particular, it lets me make this sort of brace block such that if it is small enough it will go on only one line:

-- | Asserts a 'Doc a' cannot render on multiple lines. oneLine :: Doc a -> Bool oneLine (WL.FlatAlt d _) = oneLine d oneLine (WL.Cat a b) = oneLine a && oneLine b oneLine (WL.Union a b) = oneLine a && oneLine b oneLine (WL.Annotate _ d) = oneLine d oneLine WL.Line = False oneLine _ = True  -- | Make a curly-brace delimited block. When possible, permit fitting everything on one line block :: Doc a -> Doc a block b | oneLine b = hsep ["{", b, "}"] `WL.Union` vsep [ "{", indent 2 b, "}" ]         | otherwise = vsep [ "{", indent 2 b, "}" ] 

Then I get nice results that automatically do or don't span multiple lines:

ghci> "function" <> parens "x" <+> block ("return" <+> "x" <> semi) function(x) { return x; } ghci> "function" <> parens "x" <+> block ("x" <> "++" <> semi <#> "return" <+> "x" <> semi) function(x) {   x++;   return x; } 

Answers 2

 offset = 1 + length (render $ var <+> equals) hang empty (negate offset) test 

Answers 3

You could achieve the desired result by applying vcat to a list where the first item includes also the variable definition and assignment.

Example:

fun :: Doc fun = vcat [ var <+> equals <+> text "function" <+> lbrace            , nest 4 $ vcat $ replicate 5 $ text "// foo"            , rbrace            ]  var :: Doc var = text "var" <+> text "x"  test :: Doc test = fun <> semi 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment