Nim reimplementation of layout algorithm from https://github.com/google/rfmt.
This module implements combinator-based text layout algorithm, adapted from google's rfmt implementation. It provides convenient primitves for building custom pretty-printers without worrying about choosing optimal layout - you describe all possible layouts and then get the most optimal one, given constraints on right and left margins, line break and several others.
To construct block tree for layoyt you can either use makeLineBlock, makeStackBlock and other functions or DSL.
DSL for tree construction is implemented in form of operator overloading, allowing for easy splicing of custom logic. LytBuilderKind describes different forms of layout and has overloaded [] operator. initBlockFmtDSL template creates one-letter shortcuts for different layout kinds -
- H for H orizontal
- V for V ertical
- T for T ext
- I for I ndent
- S for S pace
- C for C hoice
- W for W rap
- E for E mpty
And should be used like this:
H[ T["proc ("], C[ # Choice combinator # Put arguments horizontally H[@[T["arg1: int"], T["arg2: int"],]].join(T[", "]), # Put arguments vertically V[@[T["arg1: int"], T["arg2: int"],]].join(T[", "]), ], T[")"] ]
This describes layut for nim proc declarations, and provides to alternatives - put arguments either horizontally or veritcally.
Note
Horizontal layout combinator attaches topmost line in the block to the lowest part of the preceding block, so arrangement H[V[T["[#]"], T["[#]"]], V[T["[#]"], T["[#]"]]] would result in
Types
Layout = ref object elements: seq[LayoutElement]
- An object containing a sequence of directives to the console. Source Edit
LytBlock = ref object layoutCache: Table[Option[LytSolution], Option[LytSolution]] isBreaking* {.requiresinit.}: bool ## Whether or not this block should end the line breakMult* {.requiresinit.}: int ## Local line break cost change minWidth* {.requiresinit.}: int hasInnerChoice*: bool id {.requiresinit.}: int case kind*: LytBlockKind of bkVerb: textLines*: seq[LytStr] ## Multiple lines of text firstNl*: bool ## Insert newline at the block start of bkText: text*: LytStr ## A single line of text, free of carriage ## returs etc. of bkWrap: prefix*: Option[string] sep*: string ## Separator for block wraps wrapElements*: seq[LytBlock] of bkStack, bkChoice, bkLine: elements*: seq[LytBlock] of bkEmpty: nil
- Source Edit
LytBlockKind = enum bkText, ## Single line text block bkLine, ## Horizontally stacked lines bkChoice, ## Several alternating layouts bkStack, ## Vertically stacked layouts bkWrap, ## Mulitple blocks wrapped to create lowerst-cost layout bkVerb, ## Multiple lines verbatim bkEmpty ## Empty layout block - ignored by `add` etc.
- Source Edit
LytBuilderKind = enum blkLine, blkStack, blkText, blkIndent, blkSpace, blkChoice, blkEmpty, blkWrap
- Source Edit
LytOptions = object leftMargin*: int ## position of the first right margin. Expected `0` rightMargin*: int ## position of the second right margin. Set for `80` ## to wrap on default column limit. leftMarginCost*: float ## cost (per character) beyond margin 0. ## Expected value `~0.05` rightMarginCost*: float ## cost (per character) beyond margin 1. Should ## be much higher than `c0`. Expected value ## `~100` linebreakCost*: int ## cost per line-break indentSpaces*: int ## spaces per indent cpack*: float ## cost (per element) for packing justified layouts. ## Expected value `~0.001` format_policy*: LytFormatPolicy
- Source Edit
LytSolution = ref object id {.requiresinit.}: int ## A Solution object effectively maps an integer (the left margin at ## ## which the solution is placed) to a layout notionally optimal for ## ## that margin, together with cost information used to evaluate the ## ## layout. For compactness, the map takes the form of a ## ## piecewise-linear cost function, with associated layouts. knots: seq[int] ## a list of ints, specifying the margin settings at ## which the layout changes. Note that the first knot is required to be ## ## 0. spans: seq[int] ## a list of ints, giving for each knot, the width of ## ## the corresponding layout in characters. intercepts: seq[float] ## constant cost associated with each knot. gradients: seq[float] ## at each knot, the rate with which the layout ## cost increases with an additional margin ## indent of 1 character. layouts*: seq[Layout] ## the Layout objects expressing the optimal ## layout between each knot. index: int
- Source Edit
LytStr = object text*: ColoredText
- Source Edit
OutConsole = object leftMargin: int rightMargin: int hPos: int margins: seq[int] outStr: ColoredText
- Source Edit
Consts
defaultFormatOpts = (leftMargin: 0, rightMargin: 80, leftMarginCost: 0.05, rightMarginCost: 100.0, linebreakCost: 5, indentSpaces: 2, cpack: 0.001, format_policy: (breakElementLines: (:anonymous, nil)))
- Source Edit
Procs
proc `$`(sln: LytSolution): string {....raises: [ValueError], tags: [].}
- Source Edit
proc `$`(sln: Option[LytSolution]): string {....raises: [ValueError], tags: [].}
- Source Edit
func `&?`(bl: LytBlock; added: tuple[condOk: bool, bl: LytBlock]): LytBlock {. ...raises: [], tags: [].}
- Source Edit
func `??`(blocks: tuple[ok, fail: LytBlock]; condOk: bool): LytBlock {. ...raises: [], tags: [].}
- Source Edit
proc `[]`(b: static[LytBuilderKind]; a: string | ColoredString | ColoredLine | seq[ColoredLine] | ColoredText; breaking: bool = false): LytBlock
- Source Edit
proc `[]`(b: static[LytBuilderKind]; tlen: int = 1): LytBlock
- Source Edit
func add(target: var LytBlock; other: varargs[LytBlock]; compact: bool = defaultCompact) {....raises: [], tags: [].}
- Source Edit
func codegenRepr(inBl: LytBlock; indent: int = 0): string {. ...raises: [ValueError], tags: [].}
- Source Edit
func condOr(cond: bool; ok: LytBlock; fail: LytBlock = makeEmptyBlock()): LytBlock {. ...raises: [], tags: [].}
- Source Edit
func convertBlock(bk: LytBlock; newKind: LytBlockKind): LytBlock {....raises: [], tags: [].}
- Source Edit
proc doOptLayout(self: var LytBlock; rest: var Option[LytSolution]; opts: LytOptions): Option[LytSolution] {. ...raises: [ValueError, Exception, KeyError], tags: [RootEffect].}
- Source Edit
func filterEmpty(blocks: openArray[LytBlock]): seq[LytBlock] {....raises: [], tags: [].}
- Source Edit
func flatten(bl: LytBlock; kind: set[LytBlockKind]): LytBlock {....raises: [], tags: [].}
- Source Edit
func join(blocks: LytBlock; sep: LytBlock; vertLines: bool = true): LytBlock {. ...raises: [NilArgumentError], tags: [].}
- Source Edit
func join(blocks: seq[LytBlock]; sep: LytBlock; direction: LytBlockKind): LytBlock {. ...raises: [], tags: [].}
- Source Edit
proc makeAlignedGrid(blocks: seq[seq[LytBlock]]; aligns: openArray[StringAlignDirection]): LytBlock {. ...raises: [ArgumentError, NilArgumentError], tags: [].}
- Source Edit
proc makeAlignedGrid(blocks: seq[seq[LytBlock]]; aligns: openArray[ tuple[leftPad, rightPad: int, direction: StringAlignDirection]]): LytBlock {. ...raises: [ArgumentError, NilArgumentError], tags: [].}
- Source Edit
func makeBlock(kind: LytBlockKind; breakMult: int = 1): LytBlock {....raises: [], tags: [].}
- Source Edit
func makeChoiceBlock(elems: openArray[LytBlock]; breakMult: int = 1; compact: bool = defaultCompact): LytBlock {....raises: [], tags: [].}
- Source Edit
func makeEmptyBlock(): LytBlock {....raises: [], tags: [].}
- Source Edit
func makeForceLinebreak(text: string = ""): LytBlock {....raises: [], tags: [].}
- Source Edit
func makeIndentBlock(blc: LytBlock; indent: int; breakMult: int = 1): LytBlock {. ...raises: [NilArgumentError], tags: [].}
- Source Edit
func makeLineBlock(elems: openArray[LytBlock]; breakMult: int = 1; compact: bool = defaultCompact): LytBlock {. ...raises: [NilArgumentError], tags: [].}
- Source Edit
func makeLineCommentBlock(text: string; prefix: string = "# "): LytBlock {. ...raises: [], tags: [].}
- Source Edit
proc makeStackBlock(elems: openArray[LytBlock]; breakMult: int = 1; compact: bool = defaultCompact): LytBlock {....raises: [], tags: [].}
- Source Edit
func makeTextBlock(text: ColoredString | ColoredLine | ColoredRuneLine | string; breakMult: int = 1; breaking: bool = false): LytBlock
- Source Edit
func makeTextBlock(text: string; breakMult: int = 1): LytBlock {....raises: [], tags: [].}
- Source Edit
proc makeTextBlocks(text: openArray[string]): seq[LytBlock] {....raises: [], tags: [].}
- Source Edit
func makeTextOrStackTextBlock(text: string | ColoredString | ColoredLine | seq[ColoredLine] | ColoredText; breaking: bool = false; firstNl: bool = false; breakMult: int = 1): LytBlock
- Source Edit
func makeVerbBlock[S: string | ColoredString | ColoredLine | ColoredRuneLine]( textLines: openArray[S]; breaking: bool = true; firstNl: bool = false; breakMult: int = 1): LytBlock
- Source Edit
func makeWrapBlock(elems: openArray[LytBlock]; breakMult: int = 1; sep: string = ", "): LytBlock {....raises: [], tags: [].}
- Source Edit
proc printOn(self: Layout; buf: var OutConsole) {....raises: [], tags: [].}
- Source Edit
func pyCodegenRepr(inBl: LytBlock; indent: int = 0; nimpref: string = ""; prelude: bool = false; colortext: bool = false; colored: bool = false; makeTextOrVerb: bool = false): string {. ...raises: [ImplementError, ValueError], tags: [].}
- Source Edit
proc toLayouts(bl: LytBlock; opts: LytOptions = defaultFormatOpts): seq[Layout] {....raises: [ ArgumentError, ValueError, Exception, KeyError, NoneArgumentError], tags: [RootEffect].}
- Source Edit
proc toString(bl: LytBlock; rightMargin: int = 80; opts: LytOptions = defaultFormatOpts): ColoredText {....raises: [ ArgumentError, ValueError, Exception, KeyError, NoneArgumentError], tags: [RootEffect].}
- Source Edit
proc treeRepr(self: Layout; level: int = 0): ColoredText {. ...raises: [ValueError, Exception], tags: [RootEffect].}
- Source Edit
proc treeRepr(self: LytSolution; level: int = 0): ColoredText {. ...raises: [ValueError, Exception], tags: [RootEffect].}
- Source Edit
Templates
template addItBlock(res: LytBlock; item: typed; expr: untyped; join: LytBlock): untyped
- Source Edit
template findSingle(elems: typed; targetKind: typed): untyped
- Source Edit
template initBlockFmtDSL() {.dirty.}
- Source Edit
template joinItBlock(direction: LytBlockKind; item: typed; expr: untyped; join: LytBlock): untyped
- Source Edit
template joinItLine(item: typed; expr: untyped; join: LytBlock): untyped
- Source Edit