module lang::box::util::Box2Text
Two-dimensional text layout algorithm
Usage
import lang::box::util::Box2Text;
Dependencies
import util::Math;
import List;
import String;
import lang::box::\syntax::Box;
import IO;
Description
The input to Box2Text is a hierarchy of "Boxes" represented by the ((lang::box::\syntax::Box)) algebraic data-type. These boxes put hard and soft relative positioning constraints on the embedded text fragments, and there is the global soft constraints of the width of the screen (or the paper).
This implementation is a port from ASF+SDF to Rascal. The ASF+SDF implementation was published as "From Box to Tex:An algebraic approach to the construction of documentation tools" by Mark van den Brand and Eelco Visser (June 30, 1994). The original Box concept was introduced by Joel Coutaz as this technical report: "The Box, A Layout Abstraction for User Interface Toolkits" (1984) Pittsburgh, PA: Carnegie Mellon University.
The main function format maps a Box tree to a str:
- To obtain Box terms people typically transform ASTs or Parse Trees to Box using pattern matching in Rascal.
- Options encode global default options for constraint parameters that only override local parameters if they were elided.
Examples
This demonstrates the semantics of the main hard constraints:
Hfor horizontal;Vfor vertical;Ifor indentation.
rascal>import lang::box::util::Box2Text;
ok
rascal>import lang::box::\syntax::Box;
ok
rascal>format(H(L("A"), L("B"), L("C"), hs=2))
str: "A B C\n"
───
A B C
───
rascal>format(H(L("A"), L("B"), L("C"), hs=1))
str: "A B C\n"
───
A B C
───
rascal>format(H(L("A"), L("B"), L("C"), hs=0))
str: "ABC\n"
───
ABC
───
rascal>format(V(L("A"), L("B"), L("C"), vs=2))
str: "A\n\n\nB\n\n\nC\n"
───
A
B
C
───
rascal>format(V(L("A"), L("B"), L("C"), vs=1))
str: "A\n\nB\n\nC\n"
───
A
B
C
───
rascal>format(V(L("A"), L("B"), L("C"), vs=0))
str: "A\nB\nC\n"
───
A
B
C
───
rascal>format(H(L("A"), V(L("B"), L("C"))))
str: "A B\n C\n"
───
A B
C
───
rascal>format(H(L("A"), I(L("B")), L("C")))
str: "A B C\n"
───
A B C
───
rascal>format(H(L("A"), V(L("B"), H(L("C"), L("D")))))
str: "A B\n C D\n"
───
A B
C D
───
The "soft" constraints change their behavior based on available horizontal room:
rascal>format(HV([L("W<i>") | i <- [0..10]]));
str: "W0 W1 W2 W3 W4 W5 W6 W7 W8 W9\n"
───
W0 W1 W2 W3 W4 W5 W6 W7 W8 W9
───
rascal>format(HV([L("W<i>") | i <- [0..20]]));
str: "W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 W19\n"
───
W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 W19
───
rascal>format(HV([L("W<i>") | i <- [0..40]]));
str: "W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 W19 W20 W21 W22 W23 W24 W25 W26 W27 W28 W29 W30 W31\nW32 W33 W34 W35 W36 W37 W38 W39\n"
───
W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 W19 W20 W21 W22 W23 W24 W25 W26 W27 W28 W29 W30 W31
W32 W33 W34 W35 W36 W37 W38 W39
───
rascal>format(HV([L("W<i>") | i <- [0..80]]));
str: "W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 W19 W20 W21 W22 W23 W24 W25 W26 W27 W28 W29 W30 W31\nW32 W33 W34 W35 W36 W37 W38 W39 W40 W41 W42 W43 W44 W45 W46 W47 W48 W49 W50 W51 W52 W53 W54 W55 W56 W57 W58 W59 W60\nW61 W62 W63 W64 W65 W66 W67 W68 W69 W70 W71 W72 W73 W74 W75 W76 W77 W78 W79\n"
───
W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 W19 W20 W21 W22 W23 W24 W25 W26 W27 W28 W29 W30 W31
W32 W33 W34 W35 W36 W37 W38 W39 W40 W41 W42 W43 W44 W45 W46 W47 W48 W49 W50 W51 W52 W53 W54 W55 W56 W57 W58 W59 W60
W61 W62 W63 W64 W65 W66 W67 W68 W69 W70 W71 W72 W73 W74 W75 W76 W77 W78 W79
───
rascal>format(HV([L("W<i>") | i <- [0..100]]));
str: "W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 W19 W20 W21 W22 W23 W24 W25 W26 W27 W28 W29 W30 W31\nW32 W33 W34 W35 W36 W37 W38 W39 W40 W41 W42 W43 W44 W45 W46 W47 W48 W49 W50 W51 W52 W53 W54 W55 W56 W57 W58 W59 W60\nW61 W62 W63 W64 W65 W66 W67 W68 W69 W70 W71 W72 W73 W74 W75 W76 W77 W78 W79 W80 W81 W82 W83 W84 W85 W86 W87 W88 W89\nW90 W91 W92 W93 W94 W95 W96 W97 W98 W99\n"
───
W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 W19 W20 W21 W22 W23 W24 W25 W26 W27 W28 W29 W30 W31
W32 W33 W34 W35 W36 W37 W38 W39 W40 W41 W42 W43 W44 W45 W46 W47 W48 W49 W50 W51 W52 W53 W54 W55 W56 W57 W58 W59 W60
W61 W62 W63 W64 W65 W66 W67 W68 W69 W70 W71 W72 W73 W74 W75 W76 W77 W78 W79 W80 W81 W82 W83 W84 W85 W86 W87 W88 W89
W90 W91 W92 W93 W94 W95 W96 W97 W98 W99
───
rascal>format(HOV([L("W<i>") | i <- [0..10]]));
str: "W0 W1 W2 W3 W4 W5 W6 W7 W8 W9\n"
───
W0 W1 W2 W3 W4 W5 W6 W7 W8 W9
───
rascal>format(HOV([L("W<i>") | i <- [0..20]]));
str: "W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 W19\n"
───
W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 W19
───
rascal>format(HOV([L("W<i>") | i <- [0..30]]));
str: "W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 W19 W20 W21 W22 W23 W24 W25 W26 W27 W28 W29\n"
───
W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 W19 W20 W21 W22 W23 W24 W25 W26 W27 W28 W29
───
By cleverly combining constraints, a specifically desired behavior is easy to achieve:
rascal>format(H(L("if"), H(L("("), L("true"), L(")"), hs=0), HOV(L("doSomething"))))
str: "if (true) doSomething\n"
───
if (true) doSomething
───
rascal>format(H(L("if"), H(L("("), L("true"), L(")"), hs=0), HOV([L("W<i>") | i <- [0..30]])))
str: "if (true) W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 W19 W20 W21 W22 W23 W24 W25 W26 W27 W28 W29\n"
───
if (true) W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 W19 W20 W21 W22 W23 W24 W25 W26 W27 W28 W29
───
rascal>format(H(L("if"), H(L("("), L("true"), L(")"), hs=0), HV([L("W<i>") | i <- [0..30]])))
str: "if (true) W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 W19 W20 W21 W22 W23 W24 W25 W26 W27 W28 W29\n"
───
if (true) W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 W19 W20 W21 W22 W23 W24 W25 W26 W27 W28 W29
───
Pitfalls
- Box2text does not have highlighting features anymore; you can use Highlight for this instead.
data FormattingOptions
formatting options for ((Box2Text))
data FormattingOptions (
int maxWidth=120,
int wrapAfter=90,
int tabSize = 4,
bool insertSpaces = true,
bool trimTrailingWhitespace = true,
bool insertFinalNewline = true,
bool trimFinalNewlines = true
)
= formattingOptions()
;
maxWidthis the constraint that makes HV and HOV boxes switch to vertical modewrapAfteris the lowerbound that makes HV and HOV stay horizontaltabSizeis the default indentation used when anIbox does not have an explicitisparameterinsertSpaces, when set to true it prefers spaces over tabs, when set to false we use tabs for indentation (seetabSize)trimTrailingWhitespacewhentruethe formatter can not leave spaces or tabs after the last non-whitespace character, when false it does not matter.insertFinalNewline, insert a newline character at the end of the file if one does not exist.trimFinalNewlines, trim all newlines after the final newline at the end of the file.
Note that there may be more FormattingOptions due to other elements of a formatting pipeline, such as Layout Diff.
data Alignment
Specialized alignments for the final column to implement trimTrailingWhitespace
data Alignment
= fl()
| fc()
;
function format
Converts boxes into a string by finding an "optimal" two-dimensional layout
str format(Box b, FormattingOptions opts = formattingOptions())
- This algorithm never changes the left-to-right order of the Boxes constituents, such that syntactical correctness is maintained
- This algorithm tries to never over-run the
maxWidthparameter, but if it must to maintain text order, and the specified nesting of boxes, it will anyway. For example, if a table column doesn't fit it will still be printed. We saymaxWidthis a soft constraint. - Separator options like
i,handvoptions are hard constraints, they may lead to overridingmaxWidth. - H, V and I boxes represent hard constraints too.
- HV and HOV are the soft constraints that allow for better solutions, so use them where you can to allow for flexible layout that can handle deeply nested expressions and statements.
function finalNewlineOptions
str finalNewlineOptions(str lines, bool insertFinalNewline, bool trimFinalNewlines)
alias Text
Box2text uses list[str] as intermediate representation of the output during formatting
list[str]
Benefits
- Helps with fast concatenation
- Allows for measuring (max) width and height of intermediate results very quickly
Pitfalls
- Because of this representation, box2text does not recognize that unprintable characters have width 0. So,
ANSI escape codes, and characters like \r and \n in
Lboxes will break the accuracy of the algorithm.
function box2text
Converts boxes into list of lines (Unicode)
Text box2text(Box b, FormattingOptions opts = formattingOptions())
data Options
Configuration options for a single formatting run.
data Options
= options(
int hs = 1,
int vs = 0,
int is = 4,
int maxWidth = 80,
int wrapAfter = 70,
bool trimTrailingWhitespace = true,
bool insertSpaces = true
)
;
This is used during the algorithm, not for external usage.
hsis the current separation between every horizontal element in H, HV and HOV boxesvsis the current separation between vertical elements in V, HV and HOV boxesisis the default (additional) indentation for indented boxesmaxWidthis the number of columns (characters) of a single line on screen or on paperwrapAfteris the threshold criterion for line fullness, to go to the next line in a HV box and to switching between horizontal and vertical for HOV boxes.
function u
Quickly splice in any nested U boxes, and empty H, V, HV, I or HOV boxes
list[Box] u(list[Box] boxes)
function isDegenerate
Empty H, V, HOV, HV, I boxes should not lead to accidental extra separators in their context
bool isDegenerate(Box b)
function vv
simple vertical concatenation (every list element is a line)
Text vv(Text a, Text b)
function blank
Create a string of spaces just as wide as the parameter a
str blank(str a, Options opts)
function wd
Computes a white line with the length of the last line of a
Text wd([], Options _)
Text wd([*_, str x], Options opts)
function twidth
Computes the maximum width of text t
int twidth([])
default int twidth(Text t)
function hwidth
Computes the length of the last line of t
int hwidth([])
int hwidth([*_, str last])
function bar
Prepends str a before text b, all lines of b will be shifted
Text bar(str a, [], Options _)
Text bar(str a, [str bh, *str bt], Options opts)
function hskip
Produce text consisting of a white line of length n
Text hskip(int n, Options opts)
function vskip
Produces text consisting of n white lines at length 0
Text vskip(int n)
function prepend
Prepend Every line in b with a
Text prepend(str a, Text b)
function hh
Implements horizontal concatenation, also for multiple lines
Text hh([], Text b, Options _)
Text hh(Text a, [], Options _)
Text hh([a], Text b, Options opts)
default Text hh(Text a, Text b, Options opts)
function lhh
Horizontal concatenation, but if the left text is empty return nothing.
Text lhh([], Text _, Options _)
default Text lhh(a, b, Options opts)
function rhh
Horizontal concatenation, but if the right text is empty return nothing.
Text rhh(Text _, [], Options _)
Text rhh(Text a, Text b, Options opts)
function rvv
Vertical concatenation, but if the right text is empty return nothing.
Text rvv(Text _, [])
default Text rvv(Text a, Text b)
function LL
Text LL(str s)
function HH
Text HH([], Box _, Options _opts, int _m)
Text HH(list[Box] b:[_, *_], Box _, Options opts, int m)
function GG
Text GG(list[Box] boxes, Box c, Options opts, int m, int gs, Box op, bool backwards)
function groupBy
list[Box] groupBy(list[Box] boxes, int gs, Box op, false)
function groupBy
simulates grouping as-if done from the back, by starting to peel off the rest instead of grouping the rest at the end
list[Box] groupBy(list[Box] boxes, int gs, Box op, true)
list[Box] groupBy([], int _gs, Box _op)
list[Box] groupBy(list[Box] boxes:[Box _, *_], int gs, Box op)
function VV
Text VV([], Box _c, Options _opts, int _m)
Text VV(list[Box] b:[_, *_], Box c, Options opts, int m)
function II
Text II([], Box _c, Options _opts, int _m)
Text II(list[Box] b:[_, *_] , c:H_(list[Box] _), Options opts, int m)
Text II(list[Box] b:[Box _, *Box _], c:V_(list[Box] _), Options opts, int m)
function WDWD
Text WDWD([], Box _c , Options _opts, int _m)
Text WDWD([Box head, *Box tail], Box c , Options opts, int m)
function ifHOV
Text ifHOV([], Box b, Box c, Options opts, int m)
Text ifHOV(Text t:[str head], Box b, Box c, Options opts, int m)
Text ifHOV(Text t:[str head, str _, *str_], Box b, Box c, Options opts, int m)
function HOVHOV
Text HOVHOV(list[Box] b, Box c, Options opts, int m)
function HVHV
Text HVHV(Text T, int s, Text a, Box A, list[Box] B, Options opts, int m)
Text HVHV(Text T, int _s, [], Options _opts, int _m, Box _c)
Text HVHV(Text T, int s, [Box head, *Box tail], Options opts, int m, Box c)
Text HVHV([], Box _, Options opts, int m)
Text HVHV(list[Box] b:[Box head], Box _, Options opts, int m)
Text HVHV(list[Box] b:[Box head, Box next, *Box tail], Box _, Options opts, int m)
function continueWith
Text continueWith(Box b:L(str s) , Box c, Options opts, int m)
Text continueWith(Box b:H_(list[Box] bl) , Box c, Options opts, int m)
Text continueWith(Box b:V_(list[Box] bl) , Box c, Options opts, int m)
Text continueWith(Box b:I_(list[Box] bl) , Box c, Options opts, int m)
Text continueWith(Box b:WD_(list[Box] bl) , Box c, Options opts, int m)
Text continueWith(Box b:HOV_(list[Box] bl), Box c, Options opts, int m)
Text continueWith(Box b:HV_(list[Box] bl) , Box c, Options opts, int m)
Text continueWith(Box b:SPACE(int n) , Box c, Options opts, int m)
Text continueWith(Box b:U_(list[Box] bl) , Box c, Options opts, int m)
Text continueWith(Box b:G_(list[Box] bl), Box c, Options opts, int m)
Text continueWith(Box b:A_(list[Row] rows), Box c, Options opts, int m)
Text continueWith(Box b:AG_(list[Box] boxes), Box c, Options opts, int m)
function \continue
Option inheritance layer; then continue with the next box.
Text \continue(Box b, Box c, Options opts, int m)
The next box is either configured by itself. Options are transferred from the box to the opts parameter for easy passing on to recursive calls.
data Box
This is to store the result of the first pass of the algorithm over all the cells in an array/table
data Box (int width=0, int height=1)
function boxSize
Completely layout a box and then measure its width and height, and annotate the result into the Box
Box boxSize(Box b, Box c, Options opts, int m)
function RR
list[list[Box]] RR(list[Row] bl, Box c, Options opts, int m)
function Acolumns
Compute the maximum number of columns of the rows in a table
int Acolumns(list[Row] rows)
function Awidth
Compute the maximum cell width for each column in an array
list[int] Awidth(list[list[Box]] rows)
function AcompleteRows
Adds empty cells to every row until every row has the same amount of columns.
list[Row] AcompleteRows(list[Row] rows, int columns=Acolumns(rows), Box rs=NULL())
function align
Helper function for aligning Text inside an array cell
Box align(l(), Box cell, int maxWidth)
function align
Helper function for aligning Text inside an array cell
Box align(r(), Box cell, int maxWidth)
Box align(c(), Box cell, int maxWidth)
Box align(fl(), Box cell, int maxWidth)
Box align(fc(), Box cell, int maxWidth)
function AA
Text AA(list[Row] table, Box c, list[Alignment] alignments, Box rs, Options opts, int m)
function AAG
Text AAG([], int _gs, list[Alignment] _columns, Box _rs, Box _c, Options _opts, int _m)
Text AAG(list[Box] boxes:[Box _, *_], int gs, list[Alignment] columns, Box rs, Box c, Options opts, int m)
function groupRows
list[Row] groupRows([], int _gs)
list[Row] groupRows(list[Box] boxes:[Box _, *_], int gs)
function AcompleteAlignments
Cuts off and extends the alignment spec to the width of the table
list[Alignment] AcompleteAlignments(list[Alignment] alignments, int maxColumns)
- if too few columns are specified:
l()'s are added accordingly - if too many columns are specified: they are cut off from the right
function AfinalColumnSpecials
Translate l() and c() to fl() and fc() for the final columns to help implement trimTrailingWhitespace
list[Alignment] AfinalColumnSpecials([*Alignment pre, l()])
list[Alignment] AfinalColumnSpecials([*Alignment pre, c()])
default list[Alignment] AfinalColumnSpecials(list[Alignment] as)
function noWidthOverflow
Check soft limit for HV and HOV boxes
bool noWidthOverflow(list[Box] hv, Options opts)
function applyHVconstraints
Changes all HV boxes that do fit horizontally into hard H boxes.
Box applyHVconstraints(Box b, Options opts)
function applyHOVconstraints
Changes all HOV boxes that do fit horizontally into hard H boxes, and the others into hard V boxes.
Box applyHOVconstraints(Box b, Options opts)
function box2data
Workhorse, that first applies hard HV and HOV limits and then starts the general algorithm
Text box2data(Box b, Options opts)
Tests
test horizontalPlacement2
test bool horizontalPlacement2()
= format(H(L("A"), L("B"), L("C"), hs=2))
== "A B C
'";
test horizontalPlacement3
test bool horizontalPlacement3()
= format(H(L("A"), L("B"), L("C"), hs=3))
== "A B C
'";
test horizontalIndentIsNoop1
test bool horizontalIndentIsNoop1()
= format(H(L("A"), I(L("B"))))
== "A B
'";
test horizontalIndentIsNoop2
test bool horizontalIndentIsNoop2()
= format(HV(L("A"), I(L("B"))))
== "A B
'";
test horizontalIndentIsNoop3
test bool horizontalIndentIsNoop3()
= format(HOV(L("A"), I(L("B"))))
== "A B
'";
test emptyBoxesNoExtraSpacing1
test bool emptyBoxesNoExtraSpacing1()
= format(H(L("A"), H(), L("B")))
== "A B
'";
test emptyBoxesNoExtraSpacing2
test bool emptyBoxesNoExtraSpacing2()
= format(H(L("A"), V(), L("B")))
== "A B
'";
test emptyBoxesNoExtraSpacing3
test bool emptyBoxesNoExtraSpacing3()
= format(H(L("A"), I(), L("B")))
== "A B
'";
test emptyBoxesNoExtraSpacing4
test bool emptyBoxesNoExtraSpacing4()
= format(V(L("A"), H(), L("B")))
== "A
'B
'";
test emptyBoxesNoExtraSpacing5
test bool emptyBoxesNoExtraSpacing5()
= format(V(L("A"), V(), L("B")))
== "A
'B
'";
test verticalPlacement0
test bool verticalPlacement0()
= format(V(L("A"), L("B"), L("C"), vs=0))
== "A
'B
'C
'";
test verticalPlacement1
test bool verticalPlacement1()
= format(V(L("A"), L("B"), L("C"), vs=1))
== "A
'
'B
'
'C
'";
test verticalIndentation2
test bool verticalIndentation2()
= format(V(L("A"), I(L("B")), L("C")))
== "A
' B
'C
'";
test blockIndent
test bool blockIndent()
= format(V(L("A"), I(V(L("B"), L("C"))), L("D")))
== "A
' B
' C
'D
'";
test wrappingIgnoreIndent
test bool wrappingIgnoreIndent()
= format(HV(L("A"), I(L("B")), L("C"), hs=0), opts=formattingOptions(maxWidth=2, wrapAfter=2))
== "AB
'C
'";
test wrappingWithIndent
test bool wrappingWithIndent()
= format(HV(L("A"), I(L("B")), I(L("C")), hs=0),opts=formattingOptions( maxWidth=2, wrapAfter=2))
== "AB
' C
'";
test multiBoxIndentIsVertical
test bool multiBoxIndentIsVertical()
= format(I(L("A"), L("B")))
== " A
' B
'";
test flipping1NoIndent
test bool flipping1NoIndent()
= format(HOV(L("A"), L("B"), L("C"), hs=0, vs=0), opts=formattingOptions(maxWidth=2, wrapAfter=2))
== "A
'B
'C
'";
test horizontalOfOneVertical
test bool horizontalOfOneVertical()
= format(H(L("A"), V(L("B"), L("C"))))
== "A B
' C
'";
test stairCase
test bool stairCase()
= format(H(L("A"), V(L("B"), H(L("C"), V(L("D"), H(L("E"), L("F")))))))
== "A B
' C D
' E F
'";
test simpleTable
test bool simpleTable()
= format(A(R([L("1"),L("2"),L("3")]),R([L("4"), L("5"), L("6")]),R([L("7"), L("8"), L("9")])))
== "1 2 3
'4 5 6
'7 8 9
'";
test simpleAlignedTable
test bool simpleAlignedTable()
= format(A(R([L("1"),L("2"),L("3")]),R([L("44"), L("55"), L("66")]),R([L("777"), L("888"), L("999")]),
columns=[l(),c(),r()]))
== "1 2 3
'44 55 66
'777 888 999
'";
test simpleAlignedTableDifferentAlignment
test bool simpleAlignedTableDifferentAlignment()
= format(A(R([L("1"),L("2"),L("3")]),R([L("44"), L("55"), L("66")]),R([L("777"), L("888"), L("999")]),
columns=[r(),c(),l()]))
== " 1 2 3
' 44 55 66
'777 888 999
'";
test WDtest
test bool WDtest() {
L1 = H(L("aap") , hs=0);
L2 = H(WD(L1), L("noot"), hs=0);
L3 = H(WD(L2), L("mies"), hs=0);
return format(V(L1, L2, L3))
== "aap
' noot
' mies
'";
}
test groupByTest
test bool groupByTest() {
lst = [L("<i>") | i <- [0..10]];
g1 = G(lst, op=H(), gs=3);
lst2 = [H(L("<i>"), L("<i+1>"), L("<i+2>")) | i <- [0,3..7]] + [H(L("9"))];
return format(V(g1)) == format(V(lst2));
}
test groupByBackwardsTest
test bool groupByBackwardsTest() {
lst = [L("<i>") | i <- [0..10]];
g1 = G(lst, op=H(), gs=3, backwards=true);
lst2 = [H(L("0"))] + [H(L("<i>"), L("<i+1>"), L("<i+2>")) | i <- [1, 4..10]];
return format(V([g1])) == format(V(lst2));
}
test noDegenerateHVSeparators1
test bool noDegenerateHVSeparators1()
= format(HV(L("a"),V(),L("b")))
== "a b
'";
test noDegenerateHVSeparators2
test bool noDegenerateHVSeparators2()
= format(HV(L("a"),V(),L("b")), opts=formattingOptions(maxWidth=1, wrapAfter=1))
== "a
'b
'";
test noDegenerateHOVSeparators1
test bool noDegenerateHOVSeparators1()
= format(HOV(L("a"),V(),L("b")))
== "a b
'";
test noDegenerateHOVSeparators2
test bool noDegenerateHOVSeparators2()
= format(HOV(L("a"),V(),L("b")), opts=formattingOptions(maxWidth=1, wrapAfter=1))
== "a
'b
'";