Appearance
| 1 | namespace Syntax.Process.Printer is | |
| 2 | use Collections; | |
| 3 | ||
| 4 | // Document intermediate representation for the layout engine. | |
| 5 | // | |
| 6 | // A formatter visitor builds a tree of these; DOC_RENDERER turns the tree | |
| 7 | // into text against a column budget. A GROUP renders flat (its LINEs become | |
| 8 | // spaces) when it fits the budget, otherwise broken (its LINEs become | |
| 9 | // newlines). This is what trades horizontal width against vertical sprawl. | |
| 10 | union DOC is | |
| 11 | // Empty document. | |
| 12 | NIL; | |
| 13 | // Literal text. Must not contain newlines. | |
| 14 | TEXT(text: string); | |
| 15 | // Soft break: a space when the enclosing group is flat, a newline when | |
| 16 | // broken. | |
| 17 | LINE; | |
| 18 | // Soft break with no flat-mode content: nothing when flat, a newline | |
| 19 | // when broken. | |
| 20 | SOFT_LINE; | |
| 21 | // Unconditional newline. Forces every enclosing group to break. | |
| 22 | HARD_LINE; | |
| 23 | // Sequence of documents. | |
| 24 | CONCAT(items: LIST[DOC]); | |
| 25 | // Indent the contained document by `indent` extra columns when it | |
| 26 | // breaks. | |
| 27 | NEST(indent: int, doc: DOC); | |
| 28 | // A breaking unit: rendered flat if it fits the budget, else broken. | |
| 29 | GROUP(doc: DOC); | |
| 30 | si | |
| 31 | ||
| 32 | // One open group/nest frame in DOC_BUILDER's stack. | |
| 33 | class DOC_FRAME is | |
| 34 | items: LIST[DOC]; | |
| 35 | is_group: bool; | |
| 36 | nest: int; | |
| 37 | ||
| 38 | init(is_group: bool, nest: int) is | |
| 39 | self.items = LIST[DOC](); | |
| 40 | self.is_group = is_group; | |
| 41 | self.nest = nest; | |
| 42 | si | |
| 43 | si | |
| 44 | ||
| 45 | // Imperative builder for a DOC tree. Mirrors the write/indent/outdent shape | |
| 46 | // of the old string-based printer so the visitor stays readable: emit text | |
| 47 | // and breaks into the current frame, open a GROUP or NEST with begin_*, and | |
| 48 | // close it with the matching end_*. | |
| 49 | class DOC_BUILDER is | |
| 50 | _stack: LIST[DOC_FRAME]; | |
| 51 | ||
| 52 | init() is | |
| 53 | _stack = LIST[DOC_FRAME](); | |
| 54 | _stack.add(DOC_FRAME(false, 0)); | |
| 55 | si | |
| 56 | ||
| 57 | _top: DOC_FRAME => _stack[_stack.count - 1]; | |
| 58 | ||
| 59 | emit(d: DOC) is | |
| 60 | _top.items.add(d); | |
| 61 | si | |
| 62 | ||
| 63 | text(s: string) is | |
| 64 | emit(DOC.TEXT(s)); | |
| 65 | si | |
| 66 | ||
| 67 | line() is | |
| 68 | emit(DOC.LINE()); | |
| 69 | si | |
| 70 | ||
| 71 | soft_line() is | |
| 72 | emit(DOC.SOFT_LINE()); | |
| 73 | si | |
| 74 | ||
| 75 | hard_line() is | |
| 76 | emit(DOC.HARD_LINE()); | |
| 77 | si | |
| 78 | ||
| 79 | begin_group() is | |
| 80 | _stack.add(DOC_FRAME(true, 0)); | |
| 81 | si | |
| 82 | ||
| 83 | // Open a nest. The newline that introduces an indented block is | |
| 84 | // emitted (by the printer) just before the nest opens; absorb it so | |
| 85 | // it lands inside the nest and is therefore indented — matching the | |
| 86 | // depth-at-write-time behaviour of the old string printer. | |
| 87 | begin_nest(amount: int) is | |
| 88 | let frame = DOC_FRAME(false, amount); | |
| 89 | let parent = _top; | |
| 90 | ||
| 91 | if parent.items.count > 0 then | |
| 92 | let last = parent.items[parent.items.count - 1]; | |
| 93 | if isa DOC.HARD_LINE(last) then | |
| 94 | parent.items.remove_at(parent.items.count - 1); | |
| 95 | frame.items.add(last); | |
| 96 | fi | |
| 97 | fi | |
| 98 | ||
| 99 | _stack.add(frame); | |
| 100 | si | |
| 101 | ||
| 102 | end_group() is | |
| 103 | let frame = _top; | |
| 104 | _stack.remove_at(_stack.count - 1); | |
| 105 | emit(DOC.GROUP(DOC.CONCAT(frame.items))); | |
| 106 | si | |
| 107 | ||
| 108 | // Close a nest. The block's last statement leaves a trailing newline | |
| 109 | // inside the nest; eject it so the closing keyword that follows sits | |
| 110 | // at the outer indent. | |
| 111 | end_nest() is | |
| 112 | let frame = _top; | |
| 113 | _stack.remove_at(_stack.count - 1); | |
| 114 | ||
| 115 | let trailing: DOC? mut = null; | |
| 116 | if | |
| 117 | frame.items.count > 0 /\ | |
| 118 | isa DOC.HARD_LINE(frame.items[frame.items.count - 1]) | |
| 119 | then | |
| 120 | trailing = frame.items[frame.items.count - 1]; | |
| 121 | frame.items.remove_at(frame.items.count - 1); | |
| 122 | fi | |
| 123 | ||
| 124 | let result: DOC? mut = DOC.CONCAT(frame.items); | |
| 125 | if frame.nest != 0 then | |
| 126 | result = DOC.NEST(frame.nest, result); | |
| 127 | fi | |
| 128 | emit(result); | |
| 129 | ||
| 130 | if trailing? then | |
| 131 | emit(trailing); | |
| 132 | fi | |
| 133 | si | |
| 134 | ||
| 135 | build() -> DOC => DOC.CONCAT(_stack[0].items); | |
| 136 | si | |
| 137 | si |