paiges icon indicating copy to clipboard operation
paiges copied to clipboard

How to print curly brace if it doesn't fit?

Open steinybot opened this issue 1 year ago • 5 comments

I would like to do:

def foo = bar

if it fits, otherwise do:

def foo = {
  bar
}

Is this possible?

Seems like I need FlatAlt("{" +: Doc.hardLine, doc) but there is no way to create a custom FlatAlt.

steinybot avatar Jun 06 '24 10:06 steinybot

Ohhh I can use Doc.fill.

Doc.fill("{" +: Doc.line, Seq(Doc.text("def foo ="), Doc.text("bar")))

Man this stuff takes me ages to get my head around. I've literally been staring at it for hours.

steinybot avatar Jun 06 '24 10:06 steinybot

Wait no I'm being dumb. That will always add the {. Damn it.

steinybot avatar Jun 06 '24 10:06 steinybot

Well this does it although it uses private stuff. Is there a better way?

package org.typelevel.paiges

import org.typelevel.paiges.Doc.FlatAlt

object Docx {

    implicit class DocOps(val doc: Doc) extends AnyVal {

        def orEmpty: Doc =
            flatAlt(doc, Doc.empty)

        def bracketIfLong(left: Doc, leftSep: Doc, rightSep: Doc, right: Doc, indent: Int = 2): Doc =
            left + (((leftSep + Doc.hardLine).orEmpty + doc).nested(indent) + (Doc.hardLine + rightSep).orEmpty + right).grouped
    }

    def bracketIfLong(left: Doc, leftSep: Doc, doc: Doc, rightSep: Doc, right: Doc, indent: Int = 2): Doc =
        doc.bracketIfLong(left, leftSep, rightSep, right, indent)

    def flatAlt(default: Doc, whenFlat: Doc): Doc =
        if (default == whenFlat) default
        else FlatAlt(default, whenFlat)
}
import org.scalatest.freespec.AnyFreeSpec
import org.typelevel.paiges.Doc
import org.typelevel.paiges.Docx

class MethodBuilderTest extends AnyFreeSpec {

    "MethodBuilder" - {
        "should add {} when the body is too long" in {
            val sig = Doc.text("def foo = ")
            val body = Doc.text("howlongcanyougo")
            val doc = Docx.bracketIfLong(sig, Doc.char('{'), body, Doc.char('}'), Doc.empty)
            val result = doc.render(24)
            val expected =
                """def foo = {
                  |  howlongcanyougo
                  |}""".stripMargin
            assert(result == expected)
        }
        "should not add {} when the body fits" in {
            val sig = Doc.text("def foo = ")
            val body = Doc.text("howlongcanyougo")
            val doc = Docx.bracketIfLong(sig, Doc.char('{'), body, Doc.char('}'), Doc.empty)
            val result = doc.render(25)
            val expected = """def foo = howlongcanyougo""".stripMargin
            assert(result == expected)
        }
    }
}

steinybot avatar Jun 06 '24 11:06 steinybot

This is a good solution.

Flatly was not directly exposed because it can break the invariants. It only makes sense for certain pairs of Docs. For instance if you flatten the resulting Doc should have lines as long or longer, not shorter.

That said, I think you .orEmpty combinator is safe and doesn't break invariants.

However, composing two such as you have done should only be done with a .grouped (the whole thing flattens or not at all) so a comment should be made around the method.

If you would make a PR I'd be happy to review and I think we could merge it.

johnynek avatar Jun 06 '24 16:06 johnynek

Flatly was not directly exposed because it can break the invariants. It only makes sense for certain pairs of Docs. For instance if you flatten the resulting Doc should have lines as long or longer, not shorter.

orEmpty in general would break the invariant width(default) <= width(whenFlat) wouldn't it?

So then I think bracketIfLong would be safe to use orEmpty if and only if width(leftSep) <= width(doc).

However, composing two such as you have done should only be done with a .grouped (the whole thing flattens or not at all) so a comment should be made around the method. Sorry, what should be grouped?

Are you suggesting a PR for just orEmpty or bracketIfLong too?

steinybot avatar Jun 07 '24 01:06 steinybot