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 }