Text.PrettyPrint: Starting indentation from left margin
Asked Answered
H

3

8

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
};

?

Hypochondriasis answered 15/3, 2012 at 2:43 Comment(3)
Daan Leijen's pretty printer wl-pprint has more flexible handling for indenting than the Hughes Peyton-Jones pretty printer. You might want to consider using it instead. See the manual for documentation, it is much more detailed than the Haddock docs.Impede
I think wl-pprint will turn out to be the right solution -- I am ready to accept this as an answer if you post it as such.Hypochondriasis
@Hypochondriasis did you ever get this working with wl-pprint? If so, why don't you add an answer for it?Stereogram
S
2

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;
}
Stereogram answered 2/1, 2017 at 10:22 Comment(0)
S
2
offset = 1 + length (render $ var <+> equals)
hang empty (negate offset) test
Swoop answered 15/3, 2012 at 5:42 Comment(4)
Doesn't seem to work; in fact, it looks just like without that extra hang. Plus, wouldn't it have a terrible performance impact?Hypochondriasis
No it's not the lack of <+>, I figured that part out myself. But I get the same output.Hypochondriasis
Works for me. pretty-1.1.1.0.Underthrust
This might work in some cases, but it will break in more nested cases (rendering a Doc can give different results depending on whether the Doc is part of something bigger). And the performance hit for nested cases would also be pretty hefty.Stereogram
S
2

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;
}
Stereogram answered 2/1, 2017 at 10:22 Comment(0)
C
1

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
Caretaker answered 30/12, 2016 at 21:38 Comment(2)
Of course I can do this - the problem is that I want the whole function { ... } to still be one Doc (not a list of lines). This way, pretty can still choose a nice layout for me (i.e. whether function { .. } even needs to span multiple lines).Stereogram
Right, but I couldn't find another (more elegant) way of doing it. The main issue here is that you're putting the var Doc beside the fun Doc, this will set fun's indentation offset according to the length of the doc that has been concatenated to. On the contrary concatenating two docs above one another will keep the indentation offset unaltered within the doc unless one of the two docs is a Nest.Caretaker

© 2022 - 2024 — McMap. All rights reserved.