Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Text.PrettyPrint: Starting indentation from left margin

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

?

like image 700
Cactus Avatar asked Mar 15 '12 02:03

Cactus


2 Answers

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;
}
like image 85
Alec Avatar answered Oct 14 '22 23:10

Alec


offset = 1 + length (render $ var <+> equals)
hang empty (negate offset) test
like image 39
ja. Avatar answered Oct 14 '22 22:10

ja.