src/hmisc/other/blockfmt

    Dark Mode
Search:
Group by:
  Source   Edit

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 `$`(blc: LytBlock): string {....raises: [ValueError], tags: [].}
  Source   Edit
proc `$`(le: LayoutElement): string {....raises: [ValueError], tags: [].}
  Source   Edit
proc `$`(lt: Layout): string {....raises: [ValueError], tags: [].}
  Source   Edit
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 `??`(bl: LytBlock; condOk: bool): 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]; bl: LytBlock; args: varargs[LytBlock]): LytBlock
  Source   Edit
proc `[]`(b: static[LytBuilderKind]; i: int; bl: LytBlock): LytBlock
  Source   Edit
proc `[]`(b: static[LytBuilderKind]; s: seq[LytBlock]; sep: string = ", "): LytBlock
  Source   Edit
proc `[]`(b: static[LytBuilderKind]; tlen: int = 1): LytBlock
  Source   Edit
func `[]`(blc: LytBlock; idx: int): LytBlock {....raises: [], tags: [].}
  Source   Edit
func `[]`(blc: var LytBlock; idx: int): var LytBlock {....raises: [], tags: [].}
  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 debugOn(self: Layout; buf: var string): void {....raises: [ValueError],
    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 isEmpty(bl: LytBlock): bool {.inline, ...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
func len(blc: LytBlock): int {....raises: [], tags: [].}
  Source   Edit
func len(s: LytStr): int {....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
func textLen(b: LytBlock): int {....raises: [], 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
func treeRepr(inBl: LytBlock): string {....raises: [ValueError], tags: [].}
  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
proc write(stream: Stream | File; self: Layout; indent: int = 0)
  Source   Edit

Iterators

iterator items(blc: LytBlock): LytBlock {....raises: [], tags: [].}
  Source   Edit
iterator mitems(blc: var LytBlock): var LytBlock {....raises: [], tags: [].}
  Source   Edit
iterator mpairs(blc: var LytBlock): (int, var LytBlock) {....raises: [], tags: [].}
  Source   Edit
iterator pairs(blc: LytBlock): (int, LytBlock) {....raises: [], tags: [].}
  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