urbit/pkg/arvo/lib/plume.hoon
2020-11-20 15:23:56 -08:00

421 lines
13 KiB
Plaintext

/- *plum
::
:: This library includes `plume`, the actual pretty printing logic,
:: and a handful of utilities for constructing plums.
::
:: Generally, you'll just use `plume` like this:
::
:: ~(tall plume plum) :: Pretty print `plum` in tall mode.
:: ~(flat plume plum) :: Pretty print `plum` in wide mode.
::
:: There is probably no reason to look at the utility routines unless
:: you are writing something to generate `plum`s.
::
:: This is the pretty-printer. Use the `flat` arm to render a plum
:: into a single line and use the `tall` arm to get a nice multi-line
:: rendering that switches to wide mode if there's enough space.
::
:: For details about how this works and what exactly it does in various
:: cases, take a look at the docs for `plum`, `plumfmt`, and at the
:: docs on the arms of this door.
::
^? |%
++ plume
|_ =plum
::
:: An line, indented by `indent` spaces.
::
+$ line [indent=@ud text=tape]
::
:: An sequence of indented lines.
::
+$ block (list line)
::
:: +flat: print as a single line
::
++ flat
^- wain
text:linear
::
:: +tall: print as multiple lines
::
++ tall
^- wain
%+ turn window
|= line
(crip (runt [indent ' '] text))
::
:: +adjust: adjust lines to right
::
++ adjust
|= [tab=@ud =block] ^- ^block
(turn block |=([@ud tape] [(add tab +<-) +<+]))
::
:: Prepend `n` spaces to a tape.
::
++ prepend-spaces
|= [n=@ t=tape] ^- tape
(runt [n ' '] t)
::
:: +window: print as list of tabbed lines
::
++ window
^- block
~+ :: for random access
?@ plum [0 (trip plum)]~ :: trivial text
?- -.plum
::
:: %para: Line-wrappable paragraph. This is a stub; it should
:: wrap text to 40 characters.
::
%para
[0 +:linear]~
::
:: %sbrk: nested subexpression
::
:: This is an opportunity to switch to wide mode. First, try
:: rendered in wide mode. If that's possible and the result
:: isn't too big, use that. Otherwise recurse into the subplum
:: without switching to wide mode.
::
%sbrk
=/ sub kid.plum
?+ sub
window(plum sub)
[%tree *]
=/ wideresult
?~(wide.fmt.sub ~ [~ u=linear])
?: ?&(?=(^ wideresult) (lte length.u.wideresult 40))
[0 text.u.wideresult]~
window(plum sub)
==
::
:: %tree: Try to render a text tree in tall mode.
::
:: We want to render this in tall mode. First, verify that there
:: the plum has a tall render (if not, fall back to `linear`
:: formatting), then render all the subplums, and then render
:: them in one of three ways:
::
:: - If the `plumfmt` contains an `indef` and that indef has
:: no prefix, then this is a variable-arity rune with a
:: terminator: Use vertical formatting.
::
:: - If the `plumfmt` contains an `indef` and that indef DOES have
:: a prefix, then this is something that looks like a core: Use
:: `core-like` formatting.
::
:: - Otherwise, this is a rune with a fixed number of arguments
:: Render the subplums using backstep indentation.
::
:: There's also a special case where something has exactly one sub-plum.
:: where something has exactly one sub-block. For example, we
:: want this output:
::
:: |-
:: foo
::
%tree
?~ tall.fmt.plum [0 text:linear]~
=/ prelude (trip intro.u.tall.fmt.plum)
|^ =/ blocks (turn kids.plum |=(=^plum window(plum plum)))
=/ prelude (trip intro.u.tall.fmt.plum)
?~ indef.u.tall.fmt.plum
?: =(1 (lent blocks))
[[0 prelude] (zing blocks)]
(backstep prelude blocks)
=/ prefix (trip sigil.u.indef.u.tall.fmt.plum)
=/ finale (trip final.u.indef.u.tall.fmt.plum)
?~ blocks %+ weld
?~(prelude ~ [0 prelude]~)
?~(finale ~ [0 finale]~)
?~ prefix (running prelude blocks finale)
(core-like prelude prefix blocks finale)
--
==
::
:: Render a plum in tall-mode using backstep indentation. Here,
:: we are rendering things that look something like this:
::
:: :+ foo
:: bar
:: baz
::
++ backstep
|= [prelude=tape blocks=(list block)]
^- block
%- zing
=/ nkids (lent blocks)
=/ idx 1
|- ^- (list block)
?~ blocks ~
:_ $(blocks t.blocks, idx +(idx))
^- block
=/ indent (mul 2 (sub nkids idx))
?. =(1 idx) (adjust indent i.blocks)
(rune-inline-with-block prelude indent i.blocks)
::
:: To make things look a bit nicer, we want to put the first
:: sub-block on the same line as the rune. We want this:
::
:: :- foo
:: baz
::
:: Instead of this:
::
:: :-
:: foo
:: baz
::
:: This handles the "foo" case.
::
++ rune-inline-with-block
|= [rune=tape indent=@ blk=block]
^- block
=. indent (max indent (add 2 (lent rune)))
=. blk (adjust indent blk)
?~ rune blk
?~ blk [0 rune]~
:_ t.blk
:- 0
%+ weld rune
=/ spaces-btwn (sub indent.i.blk (lent rune))
(prepend-spaces spaces-btwn text.i.blk)
::
:: Render a tall hoon with running indentation. Here, we are
:: rendering things that look sopmething like:
::
:: :~ foo
:: bar
:: baz
:: ==
::
:: So, there's basically three cases here: Either the prelude
:: is a rune, the prelude is empty, or prelude is some other
:: random-ass thing.
::
:: - If there is no prelude, then just combine all of the
:: sub-blocks together unaltered.
:: - If it's a rune (two-characters wide), then combine the
:: rune and the first line into one line (separated by two
:: spaces) and indent the rest of the lines by four spaces.
:: - If the rune is some other random-ass thing (has a length
:: that isn't 0 or 2), then render the prelude alone on the
:: first line and then combine the sub-blocks together,
:: all indented by another two spaces.
::
:: Regardless, if there's a finale, stick it on the end without
:: any indentation.
::
++ running
|= [prelude=tape blocks=(list block) finale=tape]
^- block
=/ result=block (zing blocks)
=. result
?+ (lent prelude)
[[0 prelude] (adjust 2 result)] :: unusual prelude
%0 :: empty prelude
result
%2 :: rune prelude
(rune-inline-with-block prelude 4 result)
==
?~ finale result
(snoc result [0 finale])
::
:: This renders sub-blocks where each sub-block needs to be
:: prefixed by some tape. For example:
::
:: |%
:: ++ foo
:: bar
:: ++ baz
:: qux
:: --
::
++ core-like
|= [prelude=tape prefix=tape blocks=(list block) finale=tape]
^- block
=/ clear (add 2 (lent prefix))
=/ result
^- block
%- zing
^- (list block)
%+ turn blocks
|= blk=block
^- block
^+ +<
=* tab ?~(blk 0 (sub clear (min clear indent.i.blk)))
=. blk (adjust tab blk)
?~ blk ~
:_ t.blk
:- 0
%+ weld prefix
(runt [(sub indent.i.blk (lent prefix)) ' '] text.i.blk)
=. result
?~ finale result
(snoc result [0 finale])
?~ prelude result
[[0 prelude] result]
::
:: +linear: Render a plum onto a single line, even if it only has a
:: wide form.
::
++ linear
^- [length=@ud text=tape]
~+ :: ~+ for random access
?@ plum [(met 3 plum) (trip plum)] :: Just a cord.
?- -.plum
::
:: This is already in wide mode, so %sbrk nodes don't matter here.
::
%sbrk
linear(plum kid.plum)
::
:: %para: To write a wrappable text paragraph to a single line,
:: we just combine all the lines into one, interspersing single
:: spaces chars.
::
%para
|- ^- [length=@ud text=tape]
?~ lines.plum [0 ~]
=/ next $(lines.plum t.lines.plum)
=/ this [length=(met 3 i.lines.plum) text=(trip i.lines.plum)]
:- (add +(length.this) length.next)
(weld text.this `tape`[' ' text.next])
::
:: Render a text tree to a single line.
::
%tree
|^ ^- [length=@ud text=tape]
?~ wide.fmt.plum (force-wide window)
=/ body (render-body delimit.u.wide.fmt.plum kids.plum)
?~ enclose.u.wide.fmt.plum body
(wrap-with-enclose u.enclose.u.wide.fmt.plum body)
::
:: Given a list of subplums and a delimiter, render all the
:: subplums onto a single line, and combine them into a single
:: string by interspersing the delimiter.
::
++ render-body
|= [delimit=cord kids=(list ^plum)]
=/ stop (trip delimit)
|- ^- [length=@ud text=tape]
?~ kids [0 ~]
=/ next $(kids t.kids)
=/ this linear(plum i.kids)
?~ text.next this
:- :(add length.this (lent stop) length.next)
:(weld text.this stop text.next)
::
:: Wrap a wide-form-rendered result with the `enclose` cords
:: from its `plumefmt`.
::
++ wrap-with-enclose
|= [clamps=(pair cord cord) body=[length=@ text=tape]]
^- [length=@ud text=tape]
::
=/ close [(trip -.clamps) (trip +.clamps)]
:- :(add length.body (lent -.close) (lent +.close))
:(weld -.close text.body +.close)
::
:: Given the result of rendering a plum in tall form, combine
:: all the lines into one by separating each by two spaces.
::
++ force-wide
|= render=(list [@ud text=tape])
^- [length=@ud text=tape]
::
?~ render [0 ~]
=/ next (force-wide t.render)
:- :(add (lent text.i.render) 2 length.next)
?~(text.next text.i.render :(weld text.i.render " " text.next))
--
==
--
::
:: Convenience function to build a `plumfmt` for a rune with a fixed
:: number of parameters.
::
++ fixed
|= rune=@ta
^- plumfmt
[wide=`[' ' `[(cat 3 +< '(') ')']] tall=`[+< ~]]
::
:: Same as `fixed` but only outputs in `tall` mode.
::
++ tall-fixed
|= rune=cord
^- (unit [cord (unit [cord cord])])
`[rune ~]
::
:: Convenience function to build a the `tall` part of a `plumfmt` for
:: a running-style rune (one that takes a variable number of parameters
:: and has a terminator).
::
++ tall-running
|= [rune=cord sigil=cord term=cord]
^- (unit [cord (unit [cord cord])])
`[rune `[sigil term]]
::
:: Convenience function for rendering a rune into a plum. This takes
:: a rune, an optional tall-form terminator, optionally a short-form (if
:: you don't supply a short-form, it'll just construct the standard
:: wide-form (e.g. "?~(x x ~)") for you, and a list of sub-plums.
::
++ rune
|= $: rune=cord
term=(unit cord)
short=(unit [cord cord cord])
kids=(list plum)
==
^. plum
|^ :- %sbrk
:+ %tree
:- (rune-wide-form rune short)
?~ term (tall-fixed rune)
(tall-running rune '' u.term)
kids
::
:: If you just give this a rune, it'll build the standard wide-form.
:: Otherwise, it'll just use the one that you gave it.
::
++ rune-wide-form
|= [rune=cord short=(unit [fst=cord mid=cord lst=cord])]
^- (unit (pair cord (unit [cord cord])))
=* fst (cat 3 rune '(')
=* std `[' ' `[fst ')']]
?~ short std
`[mid.u.short `[fst.u.short lst.u.short]]
--
::
:: Just a helper function for constructing a wide-form %tree plum.
::
++ simple-wide
|= [init=cord sep=cord end=cord kids=(list plum)]
^- plum
=/ fmt=plumfmt [wide=[~ sep [~ init end]] tall=~]
[%tree fmt kids]
::
:: Convenience function that builds a plum for a subexpression. The
:: `%sbrk` tells the pretty-printer that this is a valid place to
:: switch from tall mode to wide mode.
::
++ subtree
|= [p=plumfmt q=(list plum)]
^- plum
[%sbrk [%tree p q]]
::
:: Convenience for generating plums that look like s-expressions. Useful
:: for quickly getting decent-looking debug output.
::
++ sexp
|= [sym=cord kids=(list plum)]
^- plum
=/ head=cord (cat 3 '(' sym)
=/ headspc=cord (cat 3 head ' ')
=/ symcol=cord (cat 3 sym ':')
=/ fmt=plumfmt [[~ ' ' [~ headspc ')']] [~ symcol [~ '' '']]]
?~ kids (cat 3 '(' (cat 3 sym ')'))
[%sbrk [%tree fmt kids]]
--