1 // Copyright Brian Schott 2015. 2 // Distributed under the Boost Software License, Version 1.0. 3 // (See accompanying file LICENSE.txt or copy at 4 // http://www.boost.org/LICENSE_1_0.txt) 5 6 module dfmt.wrapping; 7 8 import dparse.lexer; 9 import dfmt.tokens; 10 import dfmt.config; 11 12 struct State 13 { 14 this(uint breaks, const Token[] tokens, immutable short[] depths, 15 const Config* config, int currentLineLength, int indentLevel) pure @safe 16 { 17 import std.math : abs; 18 import core.bitop : popcnt, bsf; 19 import std.algorithm : min, map, sum; 20 21 immutable int remainingCharsMultiplier = 25; 22 immutable int newlinePenalty = 480; 23 24 this.breaks = breaks; 25 this._cost = 0; 26 this._solved = true; 27 28 if (breaks == 0) 29 { 30 immutable int l = currentLineLength + tokens.map!(a => tokenLength(a)).sum(); 31 if (l > config.dfmt_soft_max_line_length) 32 { 33 immutable int longPenalty = (l - config.dfmt_soft_max_line_length) 34 * remainingCharsMultiplier; 35 this._cost += longPenalty; 36 this._solved = longPenalty < newlinePenalty; 37 } 38 } 39 else 40 { 41 foreach (size_t i; 0 .. (uint.sizeof * 8)) 42 { 43 if (((1 << i) & breaks) == 0) 44 continue; 45 immutable prevType = i > 0 ? tokens[i - 1].type : tok!""; 46 immutable currentType = tokens[i].type; 47 immutable p = abs(depths[i]); 48 immutable bc = breakCost(prevType, currentType) * (p == 0 ? 1 : p * 2); 49 this._cost += bc + newlinePenalty; 50 } 51 52 int ll = currentLineLength; 53 size_t i = 0; 54 foreach (_; 0 .. uint.sizeof * 8) 55 { 56 immutable uint k = breaks >>> i; 57 immutable bool b = k == 0; 58 immutable uint bits = b ? ALGORITHMIC_COMPLEXITY_SUCKS : bsf(k); 59 immutable size_t j = min(i + bits + 1, tokens.length); 60 ll += tokens[i .. j].map!(a => tokenLength(a)).sum(); 61 if (ll > config.dfmt_soft_max_line_length) 62 { 63 immutable int longPenalty = (ll - config.dfmt_soft_max_line_length) 64 * remainingCharsMultiplier; 65 this._cost += longPenalty; 66 } 67 if (ll > config.max_line_length) 68 { 69 this._solved = false; 70 break; 71 } 72 i = j; 73 if (indentLevel < 0) 74 ll = (abs(indentLevel) + 1) * config.indent_size; 75 else 76 ll = (indentLevel + (i == 0 ? 0 : 1)) * config.indent_size; 77 if (b) 78 break; 79 } 80 } 81 } 82 83 int cost() const pure nothrow @safe @property 84 { 85 return _cost; 86 } 87 88 int solved() const pure nothrow @safe @property 89 { 90 return _solved; 91 } 92 93 int opCmp(ref const State other) const pure nothrow @safe 94 { 95 import core.bitop : bsf, popcnt; 96 97 if (_cost < other._cost) 98 return -1; 99 if (_cost == other._cost && (breaks != 0 && other.breaks != 0 100 && bsf(breaks) > bsf(other.breaks))) 101 return -1; 102 return _cost > other._cost; 103 } 104 105 bool opEquals(ref const State other) const pure nothrow @safe 106 { 107 return other.breaks == breaks; 108 } 109 110 size_t toHash() const pure nothrow @safe 111 { 112 return breaks; 113 } 114 115 uint breaks; 116 117 private: 118 int _cost; 119 bool _solved; 120 } 121 122 private enum ALGORITHMIC_COMPLEXITY_SUCKS = uint.sizeof * 8; 123 124 /** 125 * Note: Negative values for `indentLevel` are treated specially: costs for 126 * continuation indents are reduced. This is used for array literals. 127 */ 128 size_t[] chooseLineBreakTokens(size_t index, const Token[] tokens, 129 immutable short[] depths, const Config* config, int currentLineLength, int indentLevel) 130 { 131 import std.container.rbtree : RedBlackTree; 132 import std.algorithm : filter, min; 133 import core.bitop : popcnt; 134 135 static size_t[] genRetVal(uint breaks, size_t index) pure nothrow @safe 136 { 137 auto retVal = new size_t[](popcnt(breaks)); 138 size_t j = 0; 139 foreach (uint i; 0 .. uint.sizeof * 8) 140 if ((1 << i) & breaks) 141 retVal[j++] = index + i; 142 return retVal; 143 } 144 145 immutable size_t tokensEnd = min(tokens.length, ALGORITHMIC_COMPLEXITY_SUCKS); 146 auto open = new RedBlackTree!State; 147 open.insert(State(0, tokens[0 .. tokensEnd], depths[0 .. tokensEnd], config, 148 currentLineLength, indentLevel)); 149 State lowest; 150 lowest._solved = false; 151 int tries = 0; 152 while (!open.empty && tries < 10_00) 153 { 154 tries++; 155 State current = open.front(); 156 open.removeFront(); 157 if (current.solved) 158 return genRetVal(current.breaks, index); 159 if (current < lowest) 160 lowest = current; 161 validMoves!(typeof(open))(open, tokens[0 .. tokensEnd], depths[0 .. tokensEnd], 162 current.breaks, config, currentLineLength, indentLevel); 163 } 164 foreach (r; open[].filter!(a => a.solved)) 165 return genRetVal(r.breaks, index); 166 if (open[].front < lowest) 167 return genRetVal(open[].front.breaks, index); 168 else 169 return genRetVal(lowest.breaks, index); 170 } 171 172 void validMoves(OR)(auto ref OR output, const Token[] tokens, immutable short[] depths, 173 uint current, const Config* config, int currentLineLength, int indentLevel) 174 { 175 import std.algorithm : sort, canFind, min; 176 import std.array : insertInPlace; 177 178 foreach (i, token; tokens) 179 { 180 if (!isBreakToken(token.type) || (((1 << i) & current) != 0)) 181 continue; 182 immutable uint breaks = current | (1 << i); 183 output.insert(State(breaks, tokens, depths, config, currentLineLength, indentLevel)); 184 } 185 }