import Math from "Math" import String from "String" import List from "List" import {} from "Char" // inspired from: https://hackage.haskell.org/package/wl-pprint-1.2.1/docs/src/Text.PrettyPrint.Leijen.html // Check: https://github.com/kfl/wpp // https://github.com/the-sett/elm-pretty-printer/tree/3.0.0 export type Doc = EmptyDoc | CharDoc(Char) | TextDoc(Integer, String) | LineDoc(Boolean) | CatDoc({} -> Doc, {} -> Doc) | NestDoc(Integer, {} -> Doc) | UnionDoc(Doc, Doc) | ColumnDoc(Integer -> Doc) | NestingDoc(Integer -> Doc) export type SimpleDoc = SEmpty | SChar(Char, SimpleDoc) | SText(Integer, String, {} -> SimpleDoc) | SLine(Integer, SimpleDoc) instance Semigroup Doc { assoc = beside } empty :: Doc export empty = EmptyDoc line :: Doc export line = LineDoc(false) linebreak :: Doc export linebreak = LineDoc(true) char :: Char -> Doc export char = (c) => where(c) { '\n' => line or => CharDoc(or) } space :: Doc export space = char(' ') colon :: Doc export colon = char(':') comma :: Doc export comma = char(',') dot :: Doc export dot = char('.') quote :: Doc export quote = char('"') lbracket :: Doc export lbracket = char('[') rbracket :: Doc export rbracket = char(']') lbrace :: Doc export lbrace = char('{') rbrace :: Doc export rbrace = char('}') quotes :: Doc -> Doc export quotes = (doc) => enclose(quote, quote, doc) brackets :: Doc -> Doc export brackets = (doc) => enclose(lbracket, rbracket, doc) braces :: Doc -> Doc export braces = (doc) => enclose(lbrace, rbrace, doc) text :: String -> Doc export text = (s) => where(s) { "" => EmptyDoc or => TextDoc(String.length(or), or) } /** * Same as text but forces a specific length to be considered for the layout. * This is useful when a string containing non rendered characters is given * such as ANSI escape sequences. * * @since 0.21.0 */ textWithLength :: Integer -> String -> Doc export textWithLength = (i, s) => where(s) { "" => EmptyDoc or => TextDoc(i, or) } group :: Doc -> Doc export group = (x) => UnionDoc(flatten(x), x) flatten :: Doc -> Doc export flatten = (doc) => where(doc) { CatDoc(x, y) => CatDoc(() => flatten(x()), () => flatten(y())) NestDoc(i, x) => NestDoc(i, () => flatten(x())) LineDoc(break) => break ? EmptyDoc : TextDoc(1, " ") UnionDoc(x, y) => flatten(x) ColumnDoc(f) => ColumnDoc(pipe(f, flatten)) NestingDoc(f) => NestingDoc(pipe(f, flatten)) or => or } softline :: Doc export softline = group(line) softbreak :: Doc export softbreak = group(linebreak) enclose :: Doc -> Doc -> Doc -> Doc export enclose = (l, r, x) => pipe( beside(l), beside($, r) )(x) beside :: Doc -> Doc -> Doc export beside = (x, y) => CatDoc(() => x, () => y) hcat :: List Doc -> Doc export hcat = List.reduceRight(beside, empty) sepBy :: Doc -> List Doc -> Doc export sepBy = (separator, docs) => pipe( List.intersperse(separator), hcat )(docs) nest :: Integer -> Doc -> Doc export nest = (i, x) => NestDoc(i, () => x) column :: (Integer -> Doc) -> Doc export column = (f) => ColumnDoc(f) nesting :: (Integer -> Doc) -> Doc export nesting = (f) => NestingDoc(f) renderPretty :: Float -> Integer -> Doc -> SimpleDoc export renderPretty = (_, w, doc) => { // r = Math.max(0, min(w, round(fromIntegral(w) * rfrac))) r = w nicest :: Integer -> Integer -> SimpleDoc -> ({} -> SimpleDoc) -> SimpleDoc nicest = (n, k, x, y) => { width = Math.min(w - k, r - k + n) return fits(width, x) ? x : y() } best :: Integer -> Integer -> List #[Integer, Doc] -> SimpleDoc best = (n, k, docs) => where(docs) { [] => SEmpty [#[i, d], ...ds] => where(d) { EmptyDoc => best(n, k, ds) CharDoc(c) => SChar(c, best(n, k + 1, ds)) TextDoc(l, s) => SText(l, s, () => best(n, k + l, ds)) LineDoc(_) => SLine(i, best(i, i, ds)) CatDoc(x, y) => best(n, k, [#[i, x()], #[i, y()], ...ds]) NestDoc(j, x) => best(n, k, [#[i + j, x()], ...ds]) UnionDoc(x, y) => nicest(n, k, best(n, k, [#[i, x], ...ds]), () => best(n, k, [#[i, y], ...ds])) ColumnDoc(f) => best(n, k, [#[i, f(k)], ...ds]) NestingDoc(f) => best(n, k, [#[i, f(i)], ...ds]) } } return best(0, 0, [#[0, doc]]) } fits :: Integer -> SimpleDoc -> Boolean fits = (w, simpleDoc) => if (w < 0) { false } else { where(simpleDoc) { SEmpty => true SChar(_, x) => fits(w - 1, x) SText(l, _, x) => fits(w - l, x()) SLine(_, _) => true } } indentation :: Integer -> String indentation = (n) => String.repeat(' ', n) prettyPrint :: Integer -> Doc -> String export prettyPrint = (width, doc) => { helper :: String -> SimpleDoc -> String helper = (pretty, d) => where(d) { SEmpty => pretty SChar(c, next) => helper(String.appendChar(c, pretty), next) SText(_, s, next) => helper(pretty ++ s, next()) SLine(i, next) => helper(pretty ++ "\n" ++ indentation(i), next) } return helper("", renderPretty(0, width, doc)) }