如何使用 HughesPJ 的漂亮打印库正确缩进块?

How to properly indent blocks using HughesPJ's pretty printing library?

我有以下编程语言语法:

data Expr = ...

data Stmt = SExpr Expr | SBlock Block | SLet Fundef | ...

data Block = Block [Stmt]

data Fundef = Fundef String [String] Block

data TopDef = TopFun Fundef

使用以下示例语法:

function long_function_name () = {
  let g() = {
    {
       h()
    };
    3
  } 
}

我正在尝试使用 HughesPJ pretty 库为这种语言创建一个漂亮的打印机。到目前为止我的尝试看起来像:

instance Pretty Stmt where
  pPrint = \case
    SExpr e -> pPrint e
    SBlock b -> pPrint b
    SLet f -> text "let" <+> pPrint f

instance Pretty Block where
  pPrint (Block stmts) = lbrace $+$
    nest 2 (vcat (punctuate semi (map pPrint stmts))) $+$ 
    rbrace

instance Pretty Fundef where
  pPrint (Fundef name args body) = pPrint name  <> parens (...) <+> text "=" <+> pPrint body

instance Prettty TopDef where
  pPrint (TopFun f) = text "function" <+> pPrint f

问题是,我想 { 与函数声明在同一行,但它总是使以下行的缩进相对于括号的列而不是绝对的。应该在上面示例的漂亮打印中可见;

function long_function_name () = {
                                   let g() = {
                                               {
                                                 h()
                                               };
                                               3
                                             } 
}

为什么会发生这种情况,我应该如何解决这个问题?我想尽可能避免代码重复。

你在正文之前写 <+>,所以 $+$ 垂直连接完全在 function 行的水平连接内,因此它都是缩进的。我相信用 pretty 做你想做的事情的方法是在块上显式匹配,因为它是垂直布局的一部分,即:

pPrint (Fundef name args (Block stmts)) = vcat
  [ pPrint name <> parens (...) <+> text "=" <+> lbrace
  , nest 2 (vcat (punctuate semi (map pPrint stmts)))
  , rbrace
  ]

更现代的 pretty-printing 库,如 prettyprinter 使这更容易一些:nest(或 indent,或 hang)处理缩进在垂直布局中第一行 之后 行,因此您可以将 nest 放在左大括号和正文周围,以及嵌套外的右大括号,如下所示:

"prefix" <+> vcat
  [ nest 4 $ vcat
    [ "{"
    , "body"
    ]
  , "}"
  ]

prefix {
  body
}

(注意。您可以像这样使用 OverloadedStrings 而不是将文字包装在 text 中。)

但这不适用于 pretty,它似乎旨在让一切都保持一致。

我还推荐prettyprinter,因为它的其他优点,例如group一个函数,可以表达“如果合适就放在一行”,这对制作非常有帮助格式稳健且响应不同的渲染上下文。